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