simulator_client/
injection.rs1use std::{collections::BTreeMap, time::Duration};
2
3use simulator_api::{AccountData, BinaryEncoding, EncodedBinary};
4use solana_address::Address;
5use solana_client::nonblocking::rpc_client::RpcClient;
6use thiserror::Error;
7use tokio_retry::{
8 Retry,
9 strategy::{ExponentialBackoff, jitter},
10};
11use tracing::warn;
12
13use crate::error::err_chain;
14
15const MODIFY_RPC_RETRIES: usize = 10;
16const MODIFY_RPC_BACKOFF_BASE: u64 = 2;
22const MODIFY_RPC_BACKOFF_FACTOR_MS: u64 = 500;
23const MODIFY_RPC_MAX_DELAY: Duration = Duration::from_secs(10);
24
25const BPF_LOADER_UPGRADEABLE: &str = "BPFLoaderUpgradeab1e11111111111111111111111";
26
27#[derive(Debug, Error)]
29pub enum ProgramModError {
30 #[error("session has no rpc_endpoint (was the session created?)")]
31 NoRpcEndpoint,
32
33 #[error("invalid program id `{id}`")]
34 InvalidProgramId { id: String },
35
36 #[error("RPC error: {source}")]
37 Rpc {
38 #[source]
39 source: Box<dyn std::error::Error + Send + Sync>,
40 },
41}
42
43pub fn build_program_injection(
84 programdata_address: Address,
85 elf: &[u8],
86 deploy_slot: u64,
87 upgrade_authority: Option<Address>,
88 lamports: u64,
89) -> BTreeMap<Address, AccountData> {
90 let data = build_programdata_bytes(elf, deploy_slot, upgrade_authority.as_ref());
91
92 let account = AccountData {
93 space: data.len() as u64,
94 data: EncodedBinary::from_bytes(&data, BinaryEncoding::Base64),
95 executable: false,
96 lamports,
97 owner: BPF_LOADER_UPGRADEABLE
98 .parse::<Address>()
99 .expect("valid BPF loader address"),
100 };
101
102 let mut map = BTreeMap::new();
103 map.insert(programdata_address, account);
104 map
105}
106
107pub fn build_programdata_bytes(
113 elf: &[u8],
114 deploy_slot: u64,
115 upgrade_authority: Option<&Address>,
116) -> Vec<u8> {
117 let header_len = if upgrade_authority.is_some() { 45 } else { 13 };
118 let mut data = Vec::with_capacity(header_len + elf.len());
119
120 data.extend_from_slice(&3u32.to_le_bytes());
122 data.extend_from_slice(&deploy_slot.to_le_bytes());
124
125 match upgrade_authority {
126 None => {
127 data.push(0); }
129 Some(authority) => {
130 data.push(1); data.extend_from_slice(authority.as_ref());
132 }
133 }
134
135 data.extend_from_slice(elf);
136 data
137}
138
139pub async fn modify_program_via_rpc(
146 rpc: &RpcClient,
147 program_id: &str,
148 elf: &[u8],
149) -> Result<BTreeMap<Address, AccountData>, ProgramModError> {
150 let program_addr: Address =
151 program_id
152 .parse()
153 .map_err(|_| ProgramModError::InvalidProgramId {
154 id: program_id.to_string(),
155 })?;
156 let programdata_addr = solana_loader_v3_interface::get_program_data_address(&program_addr);
157
158 let strategy = ExponentialBackoff::from_millis(MODIFY_RPC_BACKOFF_BASE)
159 .factor(MODIFY_RPC_BACKOFF_FACTOR_MS)
160 .max_delay(MODIFY_RPC_MAX_DELAY)
161 .map(jitter)
162 .take(MODIFY_RPC_RETRIES);
163
164 Retry::spawn(strategy, || async {
165 modify_program_via_rpc_once(rpc, programdata_addr, elf)
166 .await
167 .inspect_err(|e| {
168 warn!(
169 program_id,
170 error = %err_chain(e),
171 "modify_program_via_rpc attempt failed"
172 )
173 })
174 })
175 .await
176}
177
178async fn modify_program_via_rpc_once(
179 rpc: &RpcClient,
180 programdata_addr: Address,
181 elf: &[u8],
182) -> Result<BTreeMap<Address, AccountData>, ProgramModError> {
183 let slot = rpc.get_slot().await.map_err(|e| ProgramModError::Rpc {
184 source: Box::new(e),
185 })?;
186 let deploy_slot = slot.saturating_sub(1);
187
188 let existing = rpc
189 .get_account(&programdata_addr)
190 .await
191 .map_err(|e| ProgramModError::Rpc {
192 source: Box::new(e),
193 })?;
194
195 let upgrade_authority = if existing.data.get(12).copied() == Some(1) {
196 existing.data.get(13..45).and_then(|b| {
197 let bytes: [u8; 32] = b.try_into().ok()?;
198 Some(Address::from(bytes))
199 })
200 } else {
201 None
202 };
203
204 let data_len = upgrade_authority.map_or(13, |_| 45) + elf.len();
205 let lamports = rpc
206 .get_minimum_balance_for_rent_exemption(data_len)
207 .await
208 .map_err(|e| ProgramModError::Rpc {
209 source: Box::new(e),
210 })?;
211
212 Ok(build_program_injection(
213 programdata_addr,
214 elf,
215 deploy_slot,
216 upgrade_authority,
217 lamports,
218 ))
219}