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