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