1use crate::arch_program::pubkey::Pubkey;
2use crate::arch_program::system_instruction;
3use crate::build_and_sign_transaction;
4use crate::client::ArchRpcClient;
5use crate::program_deployment::ProgramDeployerError;
6use crate::sign_message_bip322;
7use crate::Config;
8use crate::MAX_TX_BATCH_SIZE;
9use crate::RUNTIME_TX_SIZE_LIMIT;
10use crate::{
11 types::{RuntimeTransaction, Signature},
12 Status,
13};
14use arch_program::bpf_loader::{LoaderState, BPF_LOADER_ID};
15use arch_program::hash::Hash;
16use arch_program::loader_instruction;
17use arch_program::sanitized::ArchMessage;
18use bitcoin::key::Keypair;
19use indicatif::{ProgressBar, ProgressStyle};
20use std::fs;
21use tracing::{debug, info, warn};
22
23pub struct ProgramDeployer {
24 client: ArchRpcClient,
25}
26
27impl ProgramDeployer {
28 pub fn new(config: &Config) -> Self {
29 Self {
30 client: ArchRpcClient::new(config),
31 }
32 }
33
34 pub async fn try_deploy_program(
35 &self,
36 program_name: String,
37 program_keypair: Keypair,
38 authority_keypair: Keypair,
39 elf_path: &String,
40 ) -> Result<Pubkey, ProgramDeployerError> {
41 info!("Starting program deployment: {}", program_name);
42
43 let elf = fs::read(elf_path).map_err(|source| ProgramDeployerError::ElfReadError {
44 path: elf_path.clone(),
45 source,
46 })?;
47
48 let program_pubkey = Pubkey::from_slice(&program_keypair.x_only_public_key().0.serialize());
49 let authority_pubkey =
50 Pubkey::from_slice(&authority_keypair.x_only_public_key().0.serialize());
51
52 if let Some(pubkey) = self
53 .ensure_account_exists(
54 program_pubkey,
55 authority_pubkey,
56 program_keypair,
57 authority_keypair,
58 &elf,
59 )
60 .await?
61 {
62 return Ok(pubkey);
63 }
64
65 self.write_program_elf(program_keypair, authority_keypair, &elf)
66 .await?;
67
68 self.verify_elf_deployed(program_pubkey, &elf).await?;
69
70 info!(program = %program_pubkey, "Step 2/3: ELF file sent and verified");
71
72 self.ensure_executable(program_pubkey, authority_pubkey, authority_keypair)
73 .await?;
74
75 self.verify_executable(program_pubkey).await?;
76
77 info!(
78 program = %program_pubkey,
79 "Program deployment complete: {}",
80 program_name
81 );
82
83 Ok(program_pubkey)
84 }
85
86 async fn ensure_account_exists(
91 &self,
92 program_pubkey: Pubkey,
93 authority_pubkey: Pubkey,
94 program_keypair: Keypair,
95 authority_keypair: Keypair,
96 elf: &[u8],
97 ) -> Result<Option<Pubkey>, ProgramDeployerError> {
98 if let Ok(account_info) = self.client.read_account_info(program_pubkey).await {
99 info!(program = %program_pubkey, "Step 1/3: Account already exists, skipping creation");
100
101 if account_info.data.len() < LoaderState::program_data_offset() {
102 warn!(program = %program_pubkey, "Account is not initialized, redeploying");
103 } else if account_info.data[LoaderState::program_data_offset()..] == *elf {
104 info!(program = %program_pubkey, "Same program already deployed, skipping");
105
106 if !account_info.is_executable {
107 self.make_program_executable(
108 program_pubkey,
109 authority_pubkey,
110 authority_keypair,
111 )
112 .await?;
113 }
114
115 return Ok(Some(program_pubkey));
116 } else {
117 warn!(program = %program_pubkey, "ELF mismatch with on-chain content, redeploying");
118 }
119 } else {
120 self.create_program_account(
121 program_pubkey,
122 authority_pubkey,
123 program_keypair,
124 authority_keypair,
125 elf.len(),
126 )
127 .await?;
128 }
129
130 Ok(None)
131 }
132
133 async fn create_program_account(
134 &self,
135 program_pubkey: Pubkey,
136 authority_pubkey: Pubkey,
137 program_keypair: Keypair,
138 authority_keypair: Keypair,
139 elf_len: usize,
140 ) -> Result<(), ProgramDeployerError> {
141 let recent_blockhash = self.client.get_best_finalized_block_hash().await?;
142
143 let create_account_tx = build_and_sign_transaction(
144 ArchMessage::new(
145 &[system_instruction::create_account(
146 &authority_pubkey,
147 &program_pubkey,
148 arch_program::rent::minimum_rent(LoaderState::program_data_offset() + elf_len),
149 0,
150 &BPF_LOADER_ID,
151 )],
152 Some(authority_pubkey),
153 recent_blockhash,
154 ),
155 vec![authority_keypair, program_keypair],
156 self.client.config.network,
157 )?;
158
159 let txid = self.client.send_transaction(create_account_tx).await?;
160 let tx = self.client.wait_for_processed_transaction(&txid).await?;
161
162 if let Status::Failed(reason) = tx.status {
163 return Err(ProgramDeployerError::AccountCreationFailed { txid, reason });
164 }
165
166 info!(program = %program_pubkey, tx = %txid, "Step 1/3: Program account created");
167 Ok(())
168 }
169
170 async fn verify_elf_deployed(
171 &self,
172 program_pubkey: Pubkey,
173 elf: &[u8],
174 ) -> Result<(), ProgramDeployerError> {
175 let account_info = self.client.read_account_info(program_pubkey).await?;
176
177 if account_info.data[LoaderState::program_data_offset()..] != *elf {
178 return Err(ProgramDeployerError::ElfMismatch {
179 program: program_pubkey,
180 });
181 }
182
183 debug!(
184 program = %program_pubkey,
185 owner = %account_info.owner,
186 data_len = account_info.data.len(),
187 utxo = %account_info.utxo,
188 executable = account_info.is_executable,
189 "Program account state after ELF upload"
190 );
191
192 Ok(())
193 }
194
195 async fn ensure_executable(
196 &self,
197 program_pubkey: Pubkey,
198 authority_pubkey: Pubkey,
199 authority_keypair: Keypair,
200 ) -> Result<(), ProgramDeployerError> {
201 let account_info = self.client.read_account_info(program_pubkey).await?;
202
203 if account_info.is_executable {
204 info!(program = %program_pubkey, "Step 3/3: Program account is already executable");
205 } else {
206 self.make_program_executable(program_pubkey, authority_pubkey, authority_keypair)
207 .await?;
208 }
209
210 Ok(())
211 }
212
213 async fn verify_executable(&self, program_pubkey: Pubkey) -> Result<(), ProgramDeployerError> {
214 let account_info = self.client.read_account_info(program_pubkey).await?;
215
216 if !account_info.is_executable {
217 return Err(ProgramDeployerError::NotExecutable {
218 program: program_pubkey,
219 });
220 }
221
222 debug!(
223 program = %program_pubkey,
224 owner = %account_info.owner,
225 data_len = account_info.data.len(),
226 utxo = %account_info.utxo,
227 executable = account_info.is_executable,
228 "Final program account state"
229 );
230
231 Ok(())
232 }
233
234 async fn make_program_executable(
235 &self,
236 program_pubkey: Pubkey,
237 authority_pubkey: Pubkey,
238 authority_keypair: Keypair,
239 ) -> Result<(), ProgramDeployerError> {
240 let recent_blockhash = self.client.get_best_finalized_block_hash().await?;
241 let executability_tx = build_and_sign_transaction(
242 ArchMessage::new(
243 &[loader_instruction::deploy(program_pubkey, authority_pubkey)],
244 Some(authority_pubkey),
245 recent_blockhash,
246 ),
247 vec![authority_keypair],
248 self.client.config.network,
249 )?;
250
251 let txid = self.client.send_transaction(executability_tx).await?;
252 let tx = self.client.wait_for_processed_transaction(&txid).await?;
253
254 if let Status::Failed(reason) = tx.status {
255 return Err(ProgramDeployerError::MakeExecutableFailed { txid, reason });
256 }
257
258 info!(program = %program_pubkey, tx = %txid, "Step 3/3: Made program account executable");
259 Ok(())
260 }
261
262 async fn write_program_elf(
263 &self,
264 program_keypair: Keypair,
265 authority_keypair: Keypair,
266 elf: &[u8],
267 ) -> Result<(), ProgramDeployerError> {
268 let program_pubkey = Pubkey::from_slice(&program_keypair.x_only_public_key().0.serialize());
269 let authority_pubkey =
270 Pubkey::from_slice(&authority_keypair.x_only_public_key().0.serialize());
271
272 let account_info = self.client.read_account_info(program_pubkey).await?;
273
274 debug!(
275 program = %program_pubkey,
276 executable = account_info.is_executable,
277 data_len = account_info.data.len(),
278 utxo = %account_info.utxo,
279 owner = %account_info.owner,
280 "Account state before ELF write"
281 );
282
283 if account_info.is_executable {
284 let recent_blockhash = self.client.get_best_finalized_block_hash().await?;
285 let retract_tx = build_and_sign_transaction(
286 ArchMessage::new(
287 &[loader_instruction::retract(
288 program_pubkey,
289 authority_pubkey,
290 )],
291 Some(authority_pubkey),
292 recent_blockhash,
293 ),
294 vec![authority_keypair],
295 self.client.config.network,
296 )?;
297
298 let retract_txid = self.client.send_transaction(retract_tx).await?;
299 self.client
300 .wait_for_processed_transaction(&retract_txid)
301 .await?;
302 }
303
304 if account_info.data.len() != LoaderState::program_data_offset() + elf.len() {
305 self.resize_program_account(
306 program_pubkey,
307 authority_pubkey,
308 program_keypair,
309 authority_keypair,
310 &account_info,
311 elf.len(),
312 )
313 .await?;
314 }
315
316 self.send_elf_chunks(program_pubkey, authority_pubkey, authority_keypair, elf)
317 .await
318 }
319
320 async fn resize_program_account(
321 &self,
322 program_pubkey: Pubkey,
323 authority_pubkey: Pubkey,
324 program_keypair: Keypair,
325 authority_keypair: Keypair,
326 account_info: &crate::types::AccountInfo,
327 elf_len: usize,
328 ) -> Result<(), ProgramDeployerError> {
329 debug!(program = %program_pubkey, "Truncating program account to match ELF size");
330
331 let minimum_rent =
332 arch_program::rent::minimum_rent(LoaderState::program_data_offset() + elf_len);
333 let missing_lamports = minimum_rent.saturating_sub(account_info.lamports);
334
335 if missing_lamports > 0 {
336 let recent_blockhash = self.client.get_best_finalized_block_hash().await?;
337 let transfer_tx = build_and_sign_transaction(
338 ArchMessage::new(
339 &[system_instruction::transfer(
340 &authority_pubkey,
341 &program_pubkey,
342 missing_lamports,
343 )],
344 Some(authority_pubkey),
345 recent_blockhash,
346 ),
347 vec![authority_keypair],
348 self.client.config.network,
349 )?;
350
351 let transfer_txid = self.client.send_transaction(transfer_tx).await?;
352 self.client
353 .wait_for_processed_transaction(&transfer_txid)
354 .await?;
355 }
356
357 let recent_blockhash = self.client.get_best_finalized_block_hash().await?;
358 let truncate_tx = build_and_sign_transaction(
359 ArchMessage::new(
360 &[loader_instruction::truncate(
361 program_pubkey,
362 authority_pubkey,
363 elf_len as u32,
364 )],
365 Some(authority_pubkey),
366 recent_blockhash,
367 ),
368 vec![program_keypair, authority_keypair],
369 self.client.config.network,
370 )?;
371
372 let truncate_txid = self.client.send_transaction(truncate_tx).await?;
373 self.client
374 .wait_for_processed_transaction(&truncate_txid)
375 .await?;
376
377 Ok(())
378 }
379
380 async fn send_elf_chunks(
381 &self,
382 program_pubkey: Pubkey,
383 authority_pubkey: Pubkey,
384 authority_keypair: Keypair,
385 elf: &[u8],
386 ) -> Result<(), ProgramDeployerError> {
387 let recent_blockhash = self.client.get_best_finalized_block_hash().await?;
388 let chunk_size = extend_bytes_max_len();
389 let num_chunks = elf.chunks(chunk_size).len();
390
391 debug!(
392 program = %program_pubkey,
393 chunks = num_chunks,
394 blockhash = %recent_blockhash,
395 "Building ELF write transactions"
396 );
397
398 let txs = elf
399 .chunks(chunk_size)
400 .enumerate()
401 .map(|(i, chunk)| {
402 let offset: u32 = (i * chunk_size) as u32;
403 let message = ArchMessage::new(
404 &[loader_instruction::write(
405 program_pubkey,
406 authority_pubkey,
407 offset,
408 chunk.to_vec(),
409 )],
410 Some(authority_pubkey),
411 recent_blockhash,
412 );
413
414 let digest_slice = message.hash();
415
416 Ok(RuntimeTransaction {
417 version: 0,
418 signatures: vec![Signature(sign_message_bip322(
419 &authority_keypair,
420 &digest_slice,
421 self.client.config.network,
422 )?)],
423 message,
424 })
425 })
426 .collect::<Result<Vec<RuntimeTransaction>, ProgramDeployerError>>()?;
427
428 let pb = ProgressBar::new(txs.len() as u64);
429 pb.set_style(
430 ProgressStyle::default_bar()
431 .template(
432 "{spinner:.green} [{elapsed_precise}] Sending ELF [{bar:40.cyan/blue}] {pos}/{len} ({eta})",
433 )
434 .expect("Failed to set progress bar style")
435 .progress_chars("#>-"),
436 );
437
438 let batches = txs
439 .chunks(MAX_TX_BATCH_SIZE)
440 .map(|chunk| chunk.to_vec())
441 .collect::<Vec<Vec<RuntimeTransaction>>>();
442
443 let mut tx_ids = Vec::new();
444 for batch in batches {
445 let ids = self.client.send_transactions(batch).await?;
446 tx_ids.extend(ids);
447 }
448
449 debug!(
450 program = %program_pubkey,
451 sent = tx_ids.len(),
452 "Waiting for ELF write confirmations"
453 );
454
455 for (i, tx_id) in tx_ids.iter().enumerate() {
456 let processed_tx = self.client.wait_for_processed_transaction(tx_id).await?;
457 if let Status::Failed(reason) = processed_tx.status {
458 let offset = (i * chunk_size) as u32;
459 return Err(ProgramDeployerError::ElfWriteFailed {
460 txid: *tx_id,
461 offset,
462 reason,
463 });
464 }
465 pb.inc(1);
466 }
467
468 pb.finish_with_message("ELF write transactions confirmed");
469 Ok(())
470 }
471}
472
473pub fn extend_bytes_max_len() -> usize {
475 let message = ArchMessage::new(
476 &[loader_instruction::write(
477 Pubkey::system_program(),
478 Pubkey::system_program(),
479 0,
480 vec![0_u8; 256],
481 )],
482 None,
483 Hash::from([0; 32]),
484 );
485
486 RUNTIME_TX_SIZE_LIMIT
487 - RuntimeTransaction {
488 version: 0,
489 signatures: vec![Signature([0_u8; 64])],
490 message,
491 }
492 .serialize()
493 .len()
494}