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