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