1use crate::{
8 ansible::provisioning::{PrivateNodeProvisionInventory, ProvisionOptions},
9 error::{Error, Result},
10 funding::get_address_from_sk,
11 get_anvil_node_data_hardcoded, get_bootstrap_cache_url, get_genesis_multiaddr,
12 write_environment_details, BinaryOption, DeploymentInventory, DeploymentType,
13 EnvironmentDetails, EnvironmentType, EvmDetails, EvmNetwork, InfraRunOptions, LogFormat,
14 NodeType, TestnetDeployer,
15};
16use alloy::{hex::ToHexExt, primitives::U256};
17use colored::Colorize;
18use log::error;
19use serde::{Deserialize, Serialize};
20use std::{path::PathBuf, time::Duration};
21
22#[derive(Clone, Serialize, Deserialize)]
23pub struct DeployOptions {
24 pub binary_option: BinaryOption,
25 pub chunk_size: Option<u64>,
26 pub chunk_tracker_data_addresses: Vec<String>,
27 pub chunk_tracker_services: u16,
28 pub client_env_variables: Option<Vec<(String, String)>>,
29 pub client_vm_count: Option<u16>,
30 pub client_vm_size: Option<String>,
31 pub current_inventory: DeploymentInventory,
32 pub enable_logging: bool,
33 pub enable_metrics: bool,
34 pub environment_type: EnvironmentType,
35 pub evm_data_payments_address: Option<String>,
36 pub evm_network: EvmNetwork,
37 pub evm_node_vm_size: Option<String>,
38 pub evm_payment_token_address: Option<String>,
39 pub evm_rpc_url: Option<String>,
40 pub full_cone_vm_size: Option<String>,
41 pub full_cone_private_node_count: u16,
42 pub full_cone_private_node_vm_count: Option<u16>,
43 pub full_cone_private_node_volume_size: Option<u16>,
44 pub funding_wallet_secret_key: Option<String>,
45 pub genesis_node_volume_size: Option<u16>,
46 pub initial_gas: Option<U256>,
47 pub initial_tokens: Option<U256>,
48 pub interval: Duration,
49 pub log_format: Option<LogFormat>,
50 pub max_archived_log_files: u16,
51 pub max_log_files: u16,
52 pub max_uploads: Option<u32>,
53 pub name: String,
54 pub network_id: u8,
55 pub network_dashboard_branch: Option<String>,
56 pub node_count: u16,
57 pub node_env_variables: Option<Vec<(String, String)>>,
58 pub node_vm_count: Option<u16>,
59 pub node_vm_size: Option<String>,
60 pub node_volume_size: Option<u16>,
61 pub output_inventory_dir_path: PathBuf,
62 pub peer_cache_node_count: u16,
63 pub peer_cache_node_vm_count: Option<u16>,
64 pub peer_cache_node_vm_size: Option<String>,
65 pub peer_cache_node_volume_size: Option<u16>,
66 pub port_restricted_cone_vm_size: Option<String>,
67 pub port_restricted_cone_private_node_count: u16,
68 pub port_restricted_cone_private_node_vm_count: u16,
69 pub port_restricted_cone_private_node_volume_size: Option<u16>,
70 pub single_node_payment: bool,
71 pub start_chunk_trackers: bool,
72 pub start_delayed_verifier: bool,
73 pub start_performance_verifier: bool,
74 pub start_random_verifier: bool,
75 pub symmetric_nat_gateway_vm_size: Option<String>,
76 pub symmetric_private_node_count: u16,
77 pub symmetric_private_node_vm_count: Option<u16>,
78 pub symmetric_private_node_volume_size: Option<u16>,
79 pub public_rpc: bool,
80 pub region: String,
81 pub rewards_address: String,
82 pub uploaders_count: u16,
83 pub upload_interval: u16,
84 pub upload_size: u16,
85 pub upnp_vm_size: Option<String>,
86 pub upnp_private_node_count: u16,
87 pub upnp_private_node_vm_count: Option<u16>,
88 pub upnp_private_node_volume_size: Option<u16>,
89}
90
91impl TestnetDeployer {
92 pub async fn deploy_to_genesis(
93 &self,
94 options: &DeployOptions,
95 ) -> Result<(ProvisionOptions, (String, String))> {
96 let build_custom_binaries = options.binary_option.should_provision_build_machine();
97
98 self.create_or_update_infra(&InfraRunOptions {
99 client_image_id: None,
100 client_vm_count: options.client_vm_count,
101 client_vm_size: options.client_vm_size.clone(),
102 enable_build_vm: build_custom_binaries,
103 evm_node_count: match options.evm_network {
104 EvmNetwork::Anvil => Some(1),
105 EvmNetwork::ArbitrumOne => Some(0),
106 EvmNetwork::ArbitrumSepoliaTest => Some(0),
107 EvmNetwork::Custom => Some(0),
108 },
109 evm_node_vm_size: options.evm_node_vm_size.clone(),
110 evm_node_image_id: None,
111 full_cone_vm_size: options.full_cone_vm_size.clone(),
112 full_cone_private_node_vm_count: options.full_cone_private_node_vm_count,
113 full_cone_private_node_volume_size: options.full_cone_private_node_volume_size,
114 genesis_vm_count: Some(1),
115 genesis_node_volume_size: options.genesis_node_volume_size,
116 name: options.name.clone(),
117 nat_gateway_image_id: None,
118 node_image_id: None,
119 node_vm_count: options.node_vm_count,
120 node_vm_size: options.node_vm_size.clone(),
121 node_volume_size: options.node_volume_size,
122 peer_cache_image_id: None,
123 peer_cache_node_vm_count: options.peer_cache_node_vm_count,
124 peer_cache_node_vm_size: options.peer_cache_node_vm_size.clone(),
125 peer_cache_node_volume_size: options.peer_cache_node_volume_size,
126 port_restricted_cone_vm_size: options.port_restricted_cone_vm_size.clone(),
127 port_restricted_private_node_vm_count: Some(
128 options.port_restricted_cone_private_node_vm_count,
129 ),
130 port_restricted_private_node_volume_size: options
131 .port_restricted_cone_private_node_volume_size,
132 region: options.region.clone(),
133 symmetric_nat_gateway_vm_size: options.symmetric_nat_gateway_vm_size.clone(),
134 symmetric_private_node_vm_count: options.symmetric_private_node_vm_count,
135 symmetric_private_node_volume_size: options.symmetric_private_node_volume_size,
136 tfvars_filenames: Some(
137 options
138 .environment_type
139 .get_tfvars_filenames(&options.name, &options.region),
140 ),
141 upnp_vm_size: options.upnp_vm_size.clone(),
142 upnp_private_node_vm_count: options.upnp_private_node_vm_count,
143 upnp_private_node_volume_size: options.upnp_private_node_volume_size,
144 })
145 .map_err(|err| {
146 println!("Failed to create infra {err:?}");
147 err
148 })?;
149
150 write_environment_details(
151 &self.s3_repository,
152 &options.name,
153 &EnvironmentDetails {
154 deployment_type: DeploymentType::New,
155 environment_type: options.environment_type.clone(),
156 evm_details: EvmDetails {
157 network: options.evm_network.clone(),
158 data_payments_address: options.evm_data_payments_address.clone(),
159 payment_token_address: options.evm_payment_token_address.clone(),
160 rpc_url: options.evm_rpc_url.clone(),
161 },
162 funding_wallet_address: None,
163 network_id: Some(options.network_id),
164 region: options.region.clone(),
165 rewards_address: Some(options.rewards_address.clone()),
166 },
167 )
168 .await?;
169
170 let mut provision_options = ProvisionOptions::from(options.clone());
171 let anvil_node_data = if options.evm_network == EvmNetwork::Anvil {
172 self.ansible_provisioner
173 .print_ansible_run_banner("Provision Anvil Node");
174 self.ansible_provisioner
175 .provision_evm_nodes(&provision_options)
176 .map_err(|err| {
177 println!("Failed to provision evm node {err:?}");
178 err
179 })?;
180
181 Some(
182 get_anvil_node_data_hardcoded(&self.ansible_provisioner.ansible_runner).map_err(
183 |err| {
184 println!("Failed to get evm testnet data {err:?}");
185 err
186 },
187 )?,
188 )
189 } else {
190 None
191 };
192
193 let funding_wallet_address = if let Some(secret_key) = &options.funding_wallet_secret_key {
194 let address = get_address_from_sk(secret_key)?;
195 Some(address.encode_hex())
196 } else if let Some(emv_data) = &anvil_node_data {
197 let address = get_address_from_sk(&emv_data.deployer_wallet_private_key)?;
198 Some(address.encode_hex())
199 } else {
200 error!("Funding wallet address not provided");
201 None
202 };
203
204 if let Some(custom_evm) = anvil_node_data {
205 provision_options.evm_data_payments_address =
206 Some(custom_evm.data_payments_address.clone());
207 provision_options.evm_payment_token_address =
208 Some(custom_evm.payment_token_address.clone());
209 provision_options.evm_rpc_url = Some(custom_evm.rpc_url.clone());
210 provision_options.funding_wallet_secret_key =
211 Some(custom_evm.deployer_wallet_private_key.clone());
212 };
213
214 write_environment_details(
215 &self.s3_repository,
216 &options.name,
217 &EnvironmentDetails {
218 deployment_type: DeploymentType::New,
219 environment_type: options.environment_type.clone(),
220 evm_details: EvmDetails {
221 network: options.evm_network.clone(),
222 data_payments_address: provision_options.evm_data_payments_address.clone(),
223 payment_token_address: provision_options.evm_payment_token_address.clone(),
224 rpc_url: provision_options.evm_rpc_url.clone(),
225 },
226 funding_wallet_address,
227 network_id: Some(options.network_id),
228 region: options.region.clone(),
229 rewards_address: Some(options.rewards_address.clone()),
230 },
231 )
232 .await?;
233
234 if build_custom_binaries {
235 self.ansible_provisioner
236 .print_ansible_run_banner("Build Custom Binaries");
237 self.ansible_provisioner
238 .build_autonomi_binaries(&provision_options, None)
239 .map_err(|err| {
240 println!("Failed to build safe network binaries {err:?}");
241 err
242 })?;
243 }
244
245 self.ansible_provisioner
246 .print_ansible_run_banner("Provision Genesis Node");
247 self.ansible_provisioner
248 .provision_genesis_node(&provision_options)
249 .map_err(|err| {
250 println!("Failed to provision genesis node {err:?}");
251 err
252 })?;
253
254 let (genesis_multiaddr, genesis_ip) =
255 get_genesis_multiaddr(&self.ansible_provisioner.ansible_runner, &self.ssh_client)?
256 .ok_or_else(|| Error::GenesisListenAddress)?;
257 Ok((
258 provision_options,
259 (genesis_multiaddr, get_bootstrap_cache_url(&genesis_ip)),
260 ))
261 }
262
263 pub async fn deploy(&self, options: &DeployOptions) -> Result<()> {
264 let (mut provision_options, (genesis_multiaddr, genesis_network_contacts)) =
265 self.deploy_to_genesis(options).await?;
266
267 println!("Obtained multiaddr for genesis node: {genesis_multiaddr}, network contact: {genesis_network_contacts}");
268
269 let mut node_provision_failed = false;
270 self.ansible_provisioner
271 .print_ansible_run_banner("Provision Peer Cache Nodes");
272 match self.ansible_provisioner.provision_nodes(
273 &provision_options,
274 Some(genesis_multiaddr.clone()),
275 Some(genesis_network_contacts.clone()),
276 NodeType::PeerCache,
277 ) {
278 Ok(()) => {
279 println!("Provisioned Peer Cache nodes");
280 }
281 Err(err) => {
282 error!("Failed to provision Peer Cache nodes: {err}");
283 node_provision_failed = true;
284 }
285 }
286
287 self.ansible_provisioner
288 .print_ansible_run_banner("Provision Public Nodes");
289 match self.ansible_provisioner.provision_nodes(
290 &provision_options,
291 Some(genesis_multiaddr.clone()),
292 Some(genesis_network_contacts.clone()),
293 NodeType::Generic,
294 ) {
295 Ok(()) => {
296 println!("Provisioned public nodes");
297 }
298 Err(err) => {
299 error!("Failed to provision public nodes: {err}");
300 node_provision_failed = true;
301 }
302 }
303
304 self.ansible_provisioner
305 .print_ansible_run_banner("Provision UPnP Nodes");
306 match self.ansible_provisioner.provision_nodes(
307 &provision_options,
308 Some(genesis_multiaddr.clone()),
309 Some(genesis_network_contacts.clone()),
310 NodeType::Upnp,
311 ) {
312 Ok(()) => {
313 println!("Provisioned UPnP nodes");
314 }
315 Err(err) => {
316 error!("Failed to provision UPnP nodes: {err}");
317 node_provision_failed = true;
318 }
319 }
320
321 let private_node_inventory = PrivateNodeProvisionInventory::new(
322 &self.ansible_provisioner,
323 options.full_cone_private_node_vm_count,
324 options.symmetric_private_node_vm_count,
325 Some(options.port_restricted_cone_private_node_vm_count),
326 )?;
327
328 if private_node_inventory.should_provision_full_cone_private_nodes() {
329 match self.ansible_provisioner.provision_full_cone(
330 &provision_options,
331 Some(genesis_multiaddr.clone()),
332 Some(genesis_network_contacts.clone()),
333 private_node_inventory.clone(),
334 None,
335 ) {
336 Ok(()) => {
337 println!("Provisioned Full Cone nodes and Gateway");
338 }
339 Err(err) => {
340 error!("Failed to provision Full Cone nodes and Gateway: {err}");
341 node_provision_failed = true;
342 }
343 }
344 }
345
346 if private_node_inventory.should_provision_port_restricted_cone_private_nodes() {
347 match self.ansible_provisioner.provision_port_restricted_cone(
348 &provision_options,
349 Some(genesis_multiaddr.clone()),
350 Some(genesis_network_contacts.clone()),
351 private_node_inventory.clone(),
352 None,
353 ) {
354 Ok(()) => {
355 println!("Provisioned Port Restricted Cone nodes and Gateway");
356 }
357 Err(err) => {
358 error!("Failed to provision Port Restricted Cone nodes and Gateway: {err}");
359 node_provision_failed = true;
360 }
361 }
362 }
363
364 if private_node_inventory.should_provision_symmetric_private_nodes() {
365 self.ansible_provisioner
366 .print_ansible_run_banner("Provision Symmetric NAT Gateway");
367 self.ansible_provisioner
368 .provision_symmetric_nat_gateway(&provision_options, &private_node_inventory)
369 .map_err(|err| {
370 println!("Failed to provision Symmetric NAT gateway {err:?}");
371 err
372 })?;
373
374 self.ansible_provisioner
375 .print_ansible_run_banner("Provision Symmetric Private Nodes");
376 match self.ansible_provisioner.provision_symmetric_private_nodes(
377 &mut provision_options,
378 Some(genesis_multiaddr.clone()),
379 Some(genesis_network_contacts.clone()),
380 &private_node_inventory,
381 ) {
382 Ok(()) => {
383 println!("Provisioned Symmetric private nodes");
384 }
385 Err(err) => {
386 error!("Failed to provision Symmetric Private nodes: {err}");
387 node_provision_failed = true;
388 }
389 }
390 }
391
392 self.ansible_provisioner
393 .print_ansible_run_banner("Provision Uploaders");
394 self.ansible_provisioner
395 .provision_uploaders(
396 &provision_options,
397 Some(genesis_multiaddr.clone()),
398 Some(genesis_network_contacts.clone()),
399 )
400 .await
401 .map_err(|err| {
402 println!("Failed to provision Clients {err:?}");
403 err
404 })?;
405 self.ansible_provisioner
406 .print_ansible_run_banner("Provision Downloaders");
407 self.ansible_provisioner
408 .provision_downloaders(
409 &provision_options,
410 Some(genesis_multiaddr.clone()),
411 Some(genesis_network_contacts.clone()),
412 )
413 .await
414 .map_err(|err| {
415 println!("Failed to provision downloaders {err:?}");
416 err
417 })?;
418 self.ansible_provisioner
419 .print_ansible_run_banner("Provision Chunk Trackers");
420 self.ansible_provisioner
421 .provision_chunk_trackers(
422 &provision_options,
423 Some(genesis_multiaddr.clone()),
424 Some(genesis_network_contacts.clone()),
425 )
426 .await
427 .map_err(|err| {
428 println!("Failed to provision chunk trackers {err:?}");
429 err
430 })?;
431
432 if node_provision_failed {
433 println!();
434 println!("{}", "WARNING!".yellow());
435 println!("Some nodes failed to provision without error.");
436 println!("This usually means a small number of nodes failed to start on a few VMs.");
437 println!("However, most of the time the deployment will still be usable.");
438 println!("See the output from Ansible to determine which VMs had failures.");
439 }
440
441 Ok(())
442 }
443}