bokken_runtime/
lib.rs

1use std::{path::PathBuf, sync::{Arc}, collections::{HashMap}, time::Duration};
2
3use color_eyre::eyre;
4use debug_env::{BokkenValidatorMessage, BokkenAccountData};
5use executor::BokkenSolanaContext;
6use ipc_comm::IPCComm;
7use sol_syscalls::{BokkenSyscalls, BokkenSyscallMsg};
8use solana_program::{pubkey::Pubkey, program_stubs::set_syscall_stubs};
9use bpaf::Bpaf;
10use tokio::{net::UnixStream, sync::{Mutex, mpsc}, time::sleep};
11
12
13pub mod sol_syscalls;
14pub mod executor;
15pub mod debug_env;
16pub mod ipc_comm;
17
18
19#[derive(Clone, Debug, Bpaf)]
20#[bpaf(options, version)]
21/// A native-compiled Solana program to be used with Bokken
22struct CommandOptions {
23	/// The unix socket of the Bokken instance to link to
24	#[bpaf(short, long, argument::<PathBuf>("PATH"))]
25	socket_path: PathBuf,
26
27   	/// Program ID of this program
28	#[bpaf(short, long, argument::<Pubkey>("PUBKEY"))]
29	program_id: Pubkey,
30}
31
32async fn ipc_read_loop(
33	comm: Arc<Mutex<IPCComm>>,
34	syscall_sender: mpsc::Sender<BokkenSyscallMsg>,
35	invoke_result_senders: Arc<Mutex<HashMap<u64, mpsc::Sender<(u64, HashMap<Pubkey, BokkenAccountData>)>>>>
36) -> eyre::Result<()> {
37	loop {
38		// Solana program executions 
39		let msg = {
40			let mut comm = comm.lock().await;
41			if comm.stopped() {
42				break;
43			}
44			if let Some(msg) = comm.recv_msg().await? {
45				msg
46			}else{
47				sleep(Duration::from_millis(1)).await;
48				continue;
49			}
50		};
51		match msg {
52			BokkenValidatorMessage::Invoke {
53				nonce,
54				program_id,
55				instruction,
56				account_metas,
57				account_datas,
58				call_depth
59			} => {
60				let context = BokkenSolanaContext::new(
61					program_id,
62					instruction,
63					account_metas.into_iter().map(|v|{v.into()}).collect(),
64					account_datas,
65					nonce,
66					call_depth
67				);
68				syscall_sender.send(
69					BokkenSyscallMsg::PushContext{
70						ctx: context,
71						msg_sender_clone: syscall_sender.clone()
72					}
73				).await?;
74			},
75   			BokkenValidatorMessage::CrossProgramInvokeResult {
76				nonce,
77				return_code,
78				account_datas
79			} => {
80				if let Some(sender) = invoke_result_senders.lock().await.remove(&nonce) {
81					sender.send((return_code, account_datas)).await?;
82				}
83			},
84		}
85	}
86	Ok(())
87}
88
89pub async fn bokken_runtime_main() -> eyre::Result<()> {
90	let opts = command_options().run();
91	// The actual solana program execution happens in a different thread as all the syscall methods are blocking.
92	// Therefore, IPCComm is in a mutex so it can be shared with BokkenSyscalls for when a log or CPI happens.
93	let comm = Arc::new(Mutex::new(IPCComm::new(UnixStream::connect(opts.socket_path).await?)));
94	{
95		// Send our configured program ID to the main process in order to register it
96		comm.lock().await.send_msg(opts.program_id).await?;
97	}
98	let (syscall_sender, syscall_receiver) = mpsc::channel::<BokkenSyscallMsg>(1);
99	let invoke_result_senders = Arc::new(Mutex::new(HashMap::new()));
100	let syscall_mgr = Box::new(BokkenSyscalls::new(
101		comm.clone(),
102		opts.program_id,
103		invoke_result_senders.clone(),
104		syscall_receiver
105	));
106	// Override default `solana_program` syscalls with our `BokkenSyscalls`
107	set_syscall_stubs(syscall_mgr);
108	println!("bokken_runtime_main: Sent program ID, set syscalls, awaiting execution requests...");
109	// TODO: Listen for signals and exit gracefully
110	ipc_read_loop(comm, syscall_sender, invoke_result_senders).await?;
111	Ok(())
112}
113
114#[macro_export]
115macro_rules! bokken_program {
116    ($program_crate_name:ident) => {
117		extern crate $program_crate_name;
118
119		#[tokio::main]
120		async fn main() -> color_eyre::eyre::Result<()> {
121			color_eyre::install()?;
122			bokken_runtime::bokken_runtime_main().await
123		}
124    };
125}