Skip to content

Commit e8c47c5

Browse files
committed
Merge branch 'master' into tim/collector-bootstrapping-merge
Resolve minor conflict in bootstrap transit README
2 parents cb47946 + 542eea6 commit e8c47c5

File tree

14 files changed

+580
-35
lines changed

14 files changed

+580
-35
lines changed

cmd/bootstrap/transit/README.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,50 @@ $ transit generate-cluster-block-vote -t ${server-token} -d ${bootstrap-dir}
9595
$ transit push-cluster-block-vote -t ${server-token} -d ${bootstrap-dir} -v ${vote-file}
9696
```
9797

98-
### Pull Clustering Assignment
98+
### Pull Cluster Assignment
9999

100100
Running `transit pull-clustering` will perform the following actions:
101101

102102
1. Fetch the assignment of collection nodes to clusters for the upcoming spork and write it to `<bootstrap-dir>/public-root-information/root-clustering.json`
103103

104104
### Sign Cluster Root Block
105105

106-
After the root block and random beacon key have been fetched, running `transit generate-cluster-block-vote` will:
106+
After the root clustering has been fetched, running `transit generate-cluster-block-vote` will:
107107

108-
1. Create a signature over the cluster root block, for the cluster the node is assigned to, using the node's private staking key.
108+
1. Create a signature over the root block of the collection node's assigned cluster, using the node's private staking key.
109109
2. Store the resulting vote to the file `<bootstrap-dir>/private-root-information/private-node-info_<node_id>/root-cluster-block-vote.json`
110110

111111
### Upload Vote
112112

113113
Once a vote has been generated, running `transit push-cluster-block-vote` will upload the vote file to the server.
114+
115+
## Root Block Signing Automation
116+
117+
The root block voting is a critical and time-sensitive step of a network upgrade (spork).
118+
During this process, Collection Node operators must download the cluster assignment, generate votes from each of their Collection Nodes, and upload those votes to the server (one vote per node).
119+
Then Consensus Node operators must download the root block, generate a vote for it using each of their Consensus Nodes, and upload those votes to the server (one vote per node).
120+
121+
To ensure this step is completed reliably and without delays, operators running multiple Collection Nodes or Consensus Nodes are strongly encouraged to automate the root block voting process.
122+
The provided `vote_on_cluster_block.yml` and `vote_on_root_block.yml` Ansible playbooks serve as an example for building such automation. They automate:
123+
124+
- Pulling the root information
125+
- For Collection Nodes: the collection cluster assignment
126+
- For Consensus Nodes: the root block and random beacon key
127+
- Generating the vote
128+
- Pushing the vote to the server
129+
130+
for Collection and Consensus nodes respectively.
131+
132+
Refer to this playbook as a reference for how to use the transit script in an automated environment.
133+
134+
Example usage:
135+
136+
```shell
137+
ansible-playbook -i inventories/mainnet/mainnet26.yml/ vote_on_root_block.yml \
138+
-e "boot_tools_tar=https://storage.googleapis.com/flow-genesis-bootstrap/boot-tools.tar" \
139+
-e "bootstrap_directory=/var/flow/bootstrap"
140+
-e "genesis_bucket=flow-genesis-bootstrap" \
141+
-e "network_version_token=mainnet-26" \
142+
-e "output_directory=/var/flow/bootstrap" \
143+
-e force_repull_transit=true \
144+
```
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
---
2+
- name: Prepare and sign collection cluster root block
3+
hosts: collection
4+
become: true
5+
gather_facts: false
6+
7+
vars:
8+
9+
# Required inputs (no defaults)
10+
# http(s) URL or absolute local path to boot-tools.tar (on controller)
11+
boot_tools_tar: "{{ boot_tools_tar }}"
12+
bootstrap_directory: "{{ bootstrap_directory }}"
13+
genesis_bucket: "{{ genesis_bucket }}"
14+
network_version_token: "{{ network_version_token }}"
15+
output_directory: "{{ output_directory }}"
16+
17+
# Derived
18+
transit_path: "{{ output_directory }}/transit"
19+
_is_url: "{{ (boot_tools_tar is string) and (boot_tools_tar is match('^https?://')) }}"
20+
21+
# Flag to force re-pull/install of transit
22+
force_repull_transit: false
23+
24+
pre_tasks:
25+
- name: Validate required inputs are provided
26+
assert:
27+
that:
28+
- boot_tools_tar is defined and (boot_tools_tar | string | length) > 0
29+
- output_directory is defined and (output_directory | string | length) > 0
30+
- network_version_token is defined and (network_version_token | string | length) > 0
31+
- genesis_bucket is defined and (genesis_bucket | string | length) > 0
32+
fail_msg: >-
33+
Missing one or more required inputs: boot_tools_tar, output_directory,
34+
network_version_token, genesis_bucket.
35+
tags: [validate]
36+
37+
- name: Validate output_directory is an absolute path
38+
assert:
39+
that:
40+
- output_directory is match('^/')
41+
fail_msg: "output_directory must be an absolute path (e.g., /opt/flow/transit-bin)."
42+
tags: [validate]
43+
44+
- name: Validate bootstrap_directory is an absolute path
45+
assert:
46+
that:
47+
- bootstrap_directory is match('^/')
48+
fail_msg: "bootstrap_directory must be an absolute path (e.g., /opt/flow/transit-bin)."
49+
tags: [validate]
50+
51+
- name: Validate boot_tools_tar looks like a URL or an absolute file path
52+
vars:
53+
_looks_like_path: "{{ (not _is_url) and (boot_tools_tar is match('^/')) }}"
54+
assert:
55+
that:
56+
- _is_url or _looks_like_path
57+
fail_msg: "boot_tools_tar must be an http(s) URL or an absolute local path on the controller."
58+
tags: [validate]
59+
60+
tasks:
61+
- name: Check if output directory exists
62+
stat:
63+
path: "{{ output_directory }}"
64+
register: dir_stat
65+
66+
- name: Create output directory (skip if exists)
67+
file:
68+
path: "{{ output_directory }}"
69+
state: directory
70+
mode: "0755"
71+
when: not dir_stat.stat.exists
72+
73+
- name: Check if transit already exists in output directory
74+
stat:
75+
path: "{{ transit_path }}"
76+
register: transit_stat
77+
when: not force_repull_transit
78+
79+
- name: Decide if we should fetch/reinstall transit
80+
set_fact:
81+
should_fetch_transit: "{{ true if force_repull_transit else (not transit_stat.stat.exists) }}"
82+
83+
- block:
84+
- name: Create temporary working directory
85+
tempfile:
86+
state: directory
87+
suffix: boottools
88+
register: tmpdir
89+
90+
- name: Download boot-tools.tar to temp directory (URL)
91+
get_url:
92+
url: "{{ boot_tools_tar }}"
93+
dest: "{{ tmpdir.path }}/boot-tools.tar"
94+
mode: "0644"
95+
force: true
96+
timeout: 120
97+
when: _is_url
98+
99+
- name: Copy boot-tools.tar to temp directory (local controller path)
100+
copy:
101+
src: "{{ boot_tools_tar }}"
102+
dest: "{{ tmpdir.path }}/boot-tools.tar"
103+
mode: "0644"
104+
when: not _is_url
105+
106+
- name: Extract boot-tools.tar into temp directory
107+
unarchive:
108+
src: "{{ tmpdir.path }}/boot-tools.tar"
109+
dest: "{{ tmpdir.path }}"
110+
remote_src: true
111+
112+
- name: Find the transit binary inside the extracted boot tools
113+
find:
114+
paths: "{{ tmpdir.path }}"
115+
patterns: "transit"
116+
file_type: file
117+
recurse: true
118+
register: transit_found
119+
120+
- name: Ensure transit binary was found in boot-tools.tar
121+
assert:
122+
that:
123+
- (transit_found.files | length) > 0
124+
fail_msg: "Could not locate 'transit' inside boot-tools.tar."
125+
126+
- name: Install transit into output directory
127+
copy:
128+
src: "{{ (transit_found.files | first).path }}"
129+
dest: "{{ transit_path }}"
130+
mode: "0755"
131+
remote_src: true
132+
133+
- name: Cleanup temporary working directory
134+
file:
135+
path: "{{ tmpdir.path }}"
136+
state: absent
137+
when: should_fetch_transit
138+
139+
- name: Ensure transit binary is executable
140+
file:
141+
path: "{{ transit_path }}"
142+
mode: "0755"
143+
state: file
144+
145+
# --- Transit commands ---
146+
- name: Pull root clustering assignment command
147+
command:
148+
argv:
149+
- "{{ transit_path }}"
150+
- pull-clustering
151+
- -t
152+
- "{{ network_version_token }}"
153+
- -b
154+
- "{{ bootstrap_directory }}"
155+
- -o
156+
- "{{ output_directory }}"
157+
- -g
158+
- "{{ genesis_bucket }}"
159+
register: pull_clustering_result
160+
ignore_errors: false
161+
162+
- name: Show output of pull clustering command
163+
debug:
164+
msg:
165+
- "stdout: {{ pull_clustering_result.stdout | default('') }}"
166+
- "stderr: {{ pull_clustering_result.stderr | default('') }}"
167+
168+
- name: Generate cluster block vote
169+
command:
170+
argv:
171+
- "{{ transit_path }}"
172+
- generate-cluster-block-vote
173+
- -b
174+
- "{{ bootstrap_directory }}"
175+
- -o
176+
- "{{ output_directory }}"
177+
register: gen_vote_result
178+
ignore_errors: false
179+
180+
- name: Show output of generate cluster block vote command
181+
debug:
182+
msg:
183+
- "stdout: {{ gen_vote_result.stdout | default('') }}"
184+
- "stderr: {{ gen_vote_result.stderr | default('') }}"
185+
186+
- name: Push cluster block vote
187+
command:
188+
argv:
189+
- "{{ transit_path }}"
190+
- push-cluster-block-vote
191+
- -t
192+
- "{{ network_version_token }}"
193+
- -b
194+
- "{{ bootstrap_directory }}"
195+
- -d
196+
- "{{ output_directory }}"
197+
- -g
198+
- "{{ genesis_bucket }}"
199+
register: push_vote_result
200+
ignore_errors: false
201+
202+
- name: Show output of push root block vote
203+
debug:
204+
msg:
205+
- "stdout: {{ push_vote_result.stdout | default('') }}"
206+
- "stderr: {{ push_vote_result.stderr | default('') }}"
207+
208+
- name: Get node id from file
209+
command: cat {{ boostrap_directory }}/public-root-information/node-id
210+
register: nodeid_result
211+
212+
- name: Strip whitespace from node id
213+
set_fact:
214+
node_id: "{{ nodeid_result.stdout | trim }}"
215+
216+
- name: Check if cluster block vote file exists in GCS
217+
command: >
218+
gsutil ls gs://{{ genesis_bucket }}/{{ network_version_token }}/root-cluster-block-vote.{{ node_id }}.json
219+
register: gsutil_result
220+
ignore_errors: true
221+
222+
- name: Assert cluster block vote file exists in bucket
223+
assert:
224+
that:
225+
- gsutil_result.rc == 0
226+
fail_msg: "Cluster block vote file not found in bucket"
227+

0 commit comments

Comments
 (0)