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