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