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