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