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