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