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