bokken_runtime/
sol_syscalls.rs

1use std::{sync::{Arc}, collections::{HashSet, HashMap}};
2
3use solana_program::{program_stubs::SyscallStubs, program_error::{UNSUPPORTED_SYSVAR, ProgramError}, entrypoint::ProgramResult, pubkey::Pubkey, instruction::Instruction, account_info::AccountInfo};
4use tokio::{sync::{Mutex, mpsc, RwLock}, task};
5use itertools::Itertools;
6
7use crate::{ipc_comm::IPCComm, debug_env::{BokkenRuntimeMessage, BokkenAccountData}, executor::{BokkenSolanaContext, execute_sol_program_thread, SolanaAccountsBlob}};
8
9#[derive(Debug)]
10pub(crate) enum BokkenSyscallMsg {
11	PushContext {
12		ctx: BokkenSolanaContext,
13		msg_sender_clone: mpsc::Sender<BokkenSyscallMsg>,
14	},
15	PopContext
16}
17
18/// Syscall replacements for the `solana_program` crate, with support for recursive invocations.
19/// 
20/// This does not support multiple instances of the same solana program executing in parallel. There should be only
21/// once instance undergoing active execution at a time.
22#[derive(Debug)]
23pub(crate) struct BokkenSyscalls {
24	ipc: Arc<Mutex<IPCComm>>,
25	program_id: Pubkey,
26	invoke_result_senders: Arc<Mutex<HashMap<u64, mpsc::Sender<(u64, HashMap<Pubkey, BokkenAccountData>)>>>>,
27	// Using a mutex is just the easiest way to make the property mutable while being Send + Sync that I know of
28	return_data: Arc<Mutex<Option<(Pubkey, Vec<u8>)>>>,
29	contexts: Arc<Mutex<Vec<BokkenSolanaContext>>>,
30}
31impl BokkenSyscalls {
32
33	/// Creates an instance of `BokkenSyscalls`
34	/// 
35	/// * `ipc` Used for sending log messages and CPI requests
36	/// * `program_id` Our program ID
37	/// * `invoke_result_senders` Where the main IPC Read loop can put its CPI results while we wait for them
38	/// * `msg_receiver` For receiving new execution contexts
39	pub fn new(
40		ipc: Arc<Mutex<IPCComm>>,
41		program_id: Pubkey,
42		invoke_result_senders: Arc<Mutex<HashMap<u64, mpsc::Sender<(u64, HashMap<Pubkey, BokkenAccountData>)>>>>,
43		mut msg_receiver: mpsc::Receiver<BokkenSyscallMsg>
44	) -> Self {
45		let contexts= Arc::new(Mutex::new(Vec::new()));
46		let contexts_clone = contexts.clone();
47		let ipc_clone = ipc.clone();
48		task::spawn(async move {
49			while let Some(msg) = msg_receiver.recv().await {
50				match msg {
51					BokkenSyscallMsg::PushContext { ctx, msg_sender_clone } => {
52						let blob = ctx.blob.clone();
53						let nonce = ctx.nonce();
54						contexts_clone.lock().await.push(ctx);
55						execute_sol_program_thread(nonce, blob, ipc_clone.clone(), msg_sender_clone).await;
56					},
57					BokkenSyscallMsg::PopContext => {
58						contexts_clone.lock().await.pop();
59					},
60				}
61			}
62		});
63		Self {
64			ipc,
65			program_id,
66			invoke_result_senders,
67			return_data: Arc::new(Mutex::new(None)),
68			contexts
69		}
70	}
71	fn stack_height(&self) -> u8 {
72		self.contexts.blocking_lock().last().expect("not be empty during program execution").cpi_height()
73	}
74	fn nonce(&self) -> u64 {
75		self.contexts.blocking_lock().last().expect("not be empty during program execution").nonce()
76	}
77	fn account_data_lock(&self) -> Arc<RwLock<SolanaAccountsBlob>> {
78		self.contexts.blocking_lock()
79			.last()
80			.expect("not be empty during program execution")
81			.blob
82			.clone()
83	}
84	fn is_valid_signer(&self, pubkey: &Pubkey) -> bool {
85		self.contexts
86			.blocking_lock()
87			.last()
88			.expect("not be empty during program execution")
89			.is_signer(pubkey)
90	}
91	fn is_valid_writable(&self, pubkey: &Pubkey) -> bool {
92		self.contexts
93			.blocking_lock()
94			.last()
95			.expect("not be empty during program execution")
96			.is_writable(pubkey)
97	}
98}
99
100impl SyscallStubs for BokkenSyscalls {
101	fn sol_log(&self, message: &str) {
102		let msg = format!("Program logged: {}", message);
103		println!("{}", msg);
104		let mut ipc = self.ipc.blocking_lock();
105		ipc.blocking_send_msg(
106			BokkenRuntimeMessage::Log {
107				nonce: self.nonce(),
108				message: msg
109			}
110		).expect("Message encoding not to fail");
111	}
112	fn sol_log_compute_units(&self) {
113		self.sol_log("WARNING: sol_log_compute_units() not available");
114	}
115	fn sol_invoke_signed(
116		&self,
117		instruction: &Instruction,
118		account_infos: &[AccountInfo],
119		signers_seeds: &[&[&[u8]]],
120	) -> ProgramResult {
121		let mut just_signed = HashSet::new();
122		for signing_seed in signers_seeds.iter() {
123			just_signed.insert(
124				Pubkey::create_program_address(signing_seed, &self.program_id)?
125			);
126		}
127		let mut outgoing_account_datas = HashMap::new();
128		let ctx_account_data_lock = self.account_data_lock();
129		{
130			let ctx_acocunt_datas = ctx_account_data_lock.blocking_read();
131			for (i, meta) in instruction.accounts.iter().enumerate() {
132				if *account_infos[i].key != meta.pubkey {
133					self.sol_log("Invoke: Accoune meta doesn't match account info");
134					return Err(ProgramError::InvalidAccountData);
135				}
136				if meta.is_writable && !self.is_valid_writable(&meta.pubkey) {
137					// TODO: Find out what error should be returned, or if this is even needed
138					self.sol_log("Invoke: Cannot instruction requres an non-writable account to be writable");
139					return Err(ProgramError::Custom(0));
140				}
141				if meta.is_signer && !self.is_valid_signer(&meta.pubkey) && !just_signed.contains(&meta.pubkey) {
142					self.sol_log(format!(
143						"Invoke: Account {} needs to be signed, but it isn't and doesn't match any given PDA seeds",
144						meta.pubkey
145					).as_str());
146					return Err(ProgramError::MissingRequiredSignature);
147				}
148				outgoing_account_datas.insert(
149					meta.pubkey.clone(),
150					ctx_acocunt_datas.get_account_data(&meta.pubkey).expect("To have the account info we were just passed")
151				);
152			}
153			// ctx_acocunt_datas drops unlocks
154		}
155		
156		let mut receiver = {
157			let (sender, receiver) = mpsc::channel(1);
158			self.invoke_result_senders.blocking_lock().insert(self.nonce(), sender);
159			receiver
160			// self.invoke_result_senders unlocks
161		};
162		{
163			self.ipc.blocking_lock().blocking_send_msg(
164				BokkenRuntimeMessage::CrossProgramInvoke {
165					nonce: self.nonce(),
166					program_id: self.program_id,
167					instruction: instruction.data.clone(),
168					account_metas: instruction.accounts.iter().map(|v|{v.into()}).collect(),
169					account_datas: HashMap::new(),
170					call_depth: self.stack_height()
171				}
172			).expect("encoding to not fail");
173			// self.ipc unlocks
174		}
175		let (return_code, account_datas) = receiver.blocking_recv().expect("get a response from CPI");
176		{
177			let mut ctx_acocunt_datas = ctx_account_data_lock.blocking_write();
178			// We update these before potentially panicking for extra debugging flexibility
179			for (pubkey, account_data) in account_datas.into_iter() {
180				ctx_acocunt_datas.set_account_data(&pubkey, account_data)?;
181			}
182			// ctx_acocunt_datas drops and unlocks
183		}
184		if return_code != 0 {
185			// SOL programs cannot catch a failed CPI, don't let 'em!
186			panic!("CPI failed wirth return code {}", return_code);
187		}
188		Ok(())
189	}
190	fn sol_get_clock_sysvar(&self, _var_addr: *mut u8) -> u64 {
191		UNSUPPORTED_SYSVAR
192	}
193	fn sol_get_epoch_schedule_sysvar(&self, _var_addr: *mut u8) -> u64 {
194		UNSUPPORTED_SYSVAR
195	}
196	fn sol_get_fees_sysvar(&self, _var_addr: *mut u8) -> u64 {
197		UNSUPPORTED_SYSVAR
198	}
199	fn sol_get_rent_sysvar(&self, _var_addr: *mut u8) -> u64 {
200		UNSUPPORTED_SYSVAR
201	}
202	fn sol_get_return_data(&self) -> Option<(Pubkey, Vec<u8>)> {
203		self.return_data.blocking_lock().clone()
204	}
205	fn sol_set_return_data(&self, data: &[u8]) {
206		let mut return_data = self.return_data.blocking_lock();
207		*return_data = Some((self.program_id, data.to_vec()));
208	}
209	fn sol_log_data(&self, fields: &[&[u8]]) {
210		self.sol_log(format!("data: {}", fields.iter().map(base64::encode).join(" ")).as_str());
211	}
212	fn sol_get_processed_sibling_instruction(&self, _index: usize) -> Option<Instruction> {
213		self.sol_log("WARNING: sol_get_processed_sibling_instruction() not available");
214		None
215	}
216	fn sol_get_stack_height(&self) -> u64 {
217		self.stack_height() as u64
218	}
219}