Skip to content
Open
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
17 changes: 17 additions & 0 deletions .ansible-lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
skip_list:
# We intentionally use systemctl --user via command because
# ansible.builtin.systemd_service scope: user is broken under become
- command-instead-of-module
# Variable names are referenced in inventory and cross-role; renaming
# would break existing deployments
- var-naming[no-role-prefix]
# Podman module uses key=value format that ansible-lint can't parse
- args[module]

exclude_paths:
- old/

mock_roles:
- nginxinc.nginx
- nginxinc.nginx_config
16 changes: 16 additions & 0 deletions ansible.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[defaults]
localhost_warning = False
remote_tmp = /tmp
interpreter_python = auto_silent
pipelining = true
inventory = inventory/hosts.yml
collections_path = ./
roles_path = ./roles

[inventory]
inventory_unparsed_warning = False

[ssh_connection]
# Use gcloud compute ssh as the SSH transport
ssh_executable = ./bin/gcloud-ssh.sh
transfer_method = piped
74 changes: 74 additions & 0 deletions bin/gcloud-ssh.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/bash
# Ansible SSH wrapper for GCP instances
#
# Uses gcloud to set up a ControlMaster SSH connection on first use,
# then reuses it for subsequent calls (avoiding gcloud overhead per task).

GCP_PROJECT="${GCP_PROJECT:-zenith-development-489709}"
GCP_ZONE="${GCP_ZONE:-europe-west3-a}"

ssh_opts=()
hostname=""
remote_cmd=()
skip_next=false

# SSH options that consume the next argument
opts_with_param="b c D E e F I i J L l m O o p Q R S W w"

for arg in "$@"; do
if $skip_next; then
ssh_opts+=("$arg")
skip_next=false
continue
fi

case "$arg" in
--)
remote_cmd+=("$arg")
;;
-*)
ssh_opts+=("$arg")
flag="${arg#-}"
for opt in $opts_with_param; do
if [[ "$flag" == "$opt" ]]; then
skip_next=true
break
fi
done
;;
*)
if [ -z "$hostname" ]; then
hostname="$arg"
else
remote_cmd+=("$arg")
fi
;;
esac
done

if [ -z "$hostname" ]; then
echo "Error: could not determine hostname from SSH args" >&2
exit 1
fi

# Persistent ControlMaster socket per host
SOCKET_DIR="/tmp/gcloud-ssh-sockets"
mkdir -p "$SOCKET_DIR"
SOCKET="$SOCKET_DIR/$hostname"

# If a ControlMaster socket already exists and is alive, use plain ssh
if ssh -O check -S "$SOCKET" "$hostname" 2>/dev/null; then
exec ssh -S "$SOCKET" "${ssh_opts[@]}" "$hostname" "${remote_cmd[@]}"
fi

# No active socket — use gcloud to establish a ControlMaster connection
# First call: gcloud sets up the connection and leaves a persistent socket
gcloud compute ssh \
--zone "$GCP_ZONE" \
--project "$GCP_PROJECT" \
--quiet \
"$hostname" \
-- -o "ControlMaster=auto" \
-o "ControlPath=$SOCKET" \
-o "ControlPersist=600" \
"${ssh_opts[@]}" "${remote_cmd[@]}"
11 changes: 5 additions & 6 deletions galaxy.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#SPDX-License-Identifier: MIT-0
# SPDX-License-Identifier: MIT-0
### REQUIRED
# The namespace of the collection. This can be a company/brand/organization or product namespace under which all
# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with
Expand All @@ -17,8 +17,7 @@ readme: README.md
# A list of the collection's content authors. Can be just the name or in the format 'Full Name <email> (url)
# @nicks:irc/im.site#channel'
authors:
- your name <[email protected]>

- your name <[email protected]>

### OPTIONAL but strongly recommended
# A short summary description of the collection
Expand All @@ -27,15 +26,16 @@ description: your collection description
# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only
# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file'
license:
- GPL-2.0-or-later
- GPL-2.0-or-later

# The path to the license file for the collection. This path is relative to the root of the collection. This key is
# mutually exclusive with 'license'
license_file: ''

# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character
# requirements as 'namespace' and 'name'
tags: []
tags:
- infrastructure

# Collections that this collection requires to be installed for it to be usable. The key of the dict is the
# collection label 'namespace.name'. The value is a version range
Expand Down Expand Up @@ -67,4 +67,3 @@ build_ignore: []
# 'omit_default_directives' is a boolean that controls whether the default directives are used. Mutually exclusive
# with 'build_ignore'
# manifest: null

188 changes: 188 additions & 0 deletions inventory/group_vars/canton_nodes/canton.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# ─────────────────────────────────────────────────────────
# Canton shared defaults (applied to all canton_nodes)
# ─────────────────────────────────────────────────────────
# Container registry authentication
podman_auth_json:
auths:
ghcr.io:
auth: "="

github_pat: ""
cloudflared_tunnel_token: ""

zenith_evm_dist_dir: "{{ playbook_dir }}/../../zenith-evm/dist"
canton_version: "3.6.0"
canton_edition: "open-source"
canton_java_xmx: 2g
canton_nofile_limit: 65536
canton_nproc_limit: 4096

# Storage
canton_participant_storage_type: postgres
canton_sequencer_mediator_storage_type: postgres
canton_sequencer_type: BFT
canton_postgres_host: "127.0.0.1"
canton_postgres_port: "5432"
canton_postgres_user: canton
canton_postgres_password: canton

# Shared paths (used across all canton roles)
canton_user: canton
canton_group: canton
canton_user_home: "/home/{{ canton_user }}"
canton_base_dir: "{{ canton_user_home }}/canton"
canton_bin_dir: "{{ canton_base_dir }}/bin"
canton_config_dir: "{{ canton_base_dir }}/configs"
canton_log_dir: "{{ canton_base_dir }}/logs"
canton_lib_dir: "{{ canton_base_dir }}/lib"
canton_dars_dir: "{{ canton_base_dir }}/dars"
canton_scripts_dir: "{{ canton_base_dir }}/scripts"
canton_systemd_dir: "{{ canton_user_home }}/.config/systemd/user"
canton_jar: "{{ canton_lib_dir }}/canton.jar"

# Zenith paths
zenith_base_dir: "{{ canton_user_home }}/zenith"
zenith_bin_dir: "{{ zenith_base_dir }}/bin"
zenith_data_dir: "{{ zenith_base_dir }}/data"
zenith_etc_dir: "{{ zenith_base_dir }}/etc"

# ─────────────────────────────────────────────────────────
# PostgreSQL databases to create
# ─────────────────────────────────────────────────────────
canton_postgres_databases:
- sequencer1
- mediator1
- participant1
- participant2
- participant3
- canton_evm_explorer

# ─────────────────────────────────────────────────────────
# Canton participants to deploy
# ─────────────────────────────────────────────────────────
canton_participants:
- name: participant1
description: "Validator1 - Signatory with Zenith EVM"
config_file: participant1.conf
admin_api_port: 5012
ledger_api_port: 5011
http_ledger_api_port: 5013
extension:
name: zenith-evm
display_name: "Zenith EVM Execution Service"
host: "127.0.0.1"
port: 1606
declared_functions:
- { function_id: "build-block", config_hash: "" }
- { function_id: "get-header", config_hash: "" }
- { function_id: "get-receipt", config_hash: "" }

- name: participant2
description: "Validator2 - Signatory with Zenith EVM"
config_file: participant2.conf
admin_api_port: 5022
ledger_api_port: 5021
http_ledger_api_port: 5023
extension:
name: zenith-evm
display_name: "Zenith EVM Execution Service"
host: "127.0.0.1"
port: 1607
declared_functions:
- { function_id: "build-block", config_hash: "" }
- { function_id: "get-header", config_hash: "" }
- { function_id: "get-receipt", config_hash: "" }

- name: participant3
description: "Observer - Read-only without EVM"
config_file: participant3.conf
admin_api_port: 5032
ledger_api_port: 5031
http_ledger_api_port: 5033

# ─────────────────────────────────────────────────────────
# Canton network topology
# ─────────────────────────────────────────────────────────
canton_network:
sequencer_mediator:
host: canton-test-01
address: "127.0.0.1"
public_api_port: 5008
admin_api_port: 5009
mediator_admin_api_port: 5007

participants:
participant1:
host: canton-test-01
address: "127.0.0.1"
description: "Validator1 - Signatory"
admin_api_port: 5012
ledger_api_port: 5011
http_ledger_api_port: 5013

participant2:
host: canton-test-01
address: "127.0.0.1"
description: "Validator2 - Signatory"
admin_api_port: 5022
ledger_api_port: 5021
http_ledger_api_port: 5023

participant3:
host: canton-test-01
address: "127.0.0.1"
description: "Observer - Read-only"
admin_api_port: 5032
ledger_api_port: 5031
http_ledger_api_port: 5033

# ─────────────────────────────────────────────────────────
# Zenith services
# ─────────────────────────────────────────────────────────
zenith_block_builders:
- id: 1
http_port: 32998
authrpc_port: 9551
p2p_port: 30403

- id: 2
http_port: 32999
authrpc_port: 9552
p2p_port: 30404

zenith_ecs_servers:
- id: 1
port: 1606
block_builder_port: 32998

- id: 2
port: 1607
block_builder_port: 32999

zenith_headcasters:
- id: 1
rpc_port: 32998 # block-builder-1 HTTP RPC
listen_port: 9000 # libp2p listen
canton_endpoint: "http://127.0.0.1:5011" # participant1 ledger API
engine_url: "http://127.0.0.1:9551" # block-builder-1 authrpc (Engine API)
engine_jwt_file: "%h/zenith/data/relayer-1/jwt.hex"

- id: 2
rpc_port: 32998 # block-builder-1 HTTP RPC (fetch blocks from builder that has them)
listen_port: 9001 # libp2p listen
canton_endpoint: "http://127.0.0.1:5021" # participant2 ledger API
engine_url: "http://127.0.0.1:9552" # block-builder-2 authrpc (Engine API)
engine_jwt_file: "%h/zenith/data/relayer-2/jwt.hex"

zenith_sequencers:
- id: 1
http_port: 9200
validator_id: 1 # maps to ZENITH_VALIDATOR1_PARTY env var
canton_ledger_api_port: 5011 # participant1 ledger API

zenith_validators:
- id: 1
validator_id: 1 # maps to ZENITH_VALIDATOR1_PARTY env var
canton_ledger_api_port: 5011 # participant1 ledger API
block_time: 5
observers: [1] # sequencer (Validator1) needs to be observer
44 changes: 44 additions & 0 deletions inventory/host_vars/canton-test-01/canton.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# ─────────────────────────────────────────────────────────
# canton-test-01: All-in-one Zenith-Canton deployment
# ─────────────────────────────────────────────────────────

# Sequencer-Mediator ports
canton_sequencer_mediator_config: sequencer-mediator.conf
canton_sequencer_public_api_port: 5008
canton_sequencer_admin_api_port: 5009
canton_mediator_admin_api_port: 5007

# All services on this host (for Vector / monitoring)
canton_services:
- name: canton-postgres
type: postgres
- name: canton-sequencer-mediator
type: sequencer-mediator
- name: canton-participant1
type: participant
- name: canton-participant2
type: participant
- name: canton-participant3
type: participant
- name: canton-network-setup
type: setup
- name: zenith-deploy-contract
type: setup
- name: zenith-block-builder-1
type: block-builder
- name: zenith-block-builder-2
type: block-builder
- name: zenith-ecs-1
type: ecs
- name: zenith-ecs-2
type: ecs
- name: canton-evm-explorer-indexer
type: explorer
- name: canton-evm-explorer
type: explorer
- name: zenith-headcaster-1
type: headcaster
- name: zenith-sequencer-1
type: sequencer
- name: cloudflared
type: tunnel
11 changes: 11 additions & 0 deletions inventory/hosts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
all:
children:
canton_nodes:
children:
canton_sequencers:
hosts:
canton-test-01:

canton_participants:
hosts:
canton-test-01:
Loading