agave_scheduling_utils/bridge/
test.rs1use {
2 crate::{
3 bridge::{KeyedTransactionMeta, ScheduleBatch, SchedulerBindingsBridge, TransactionKey},
4 handshake::{AgaveSession, ClientLogon, client, server::Server},
5 responses_region::{execution_responses_from_iter, resolve_responses_from_iter},
6 transaction_ptr::TransactionPtrBatch,
7 },
8 agave_scheduler_bindings::{
9 ProgressMessage, SharablePubkeys, SharableTransactionBatchRegion,
10 SharableTransactionRegion, TpuToPackMessage, TransactionResponseRegion,
11 WorkerToPackMessage, pack_message_flags, processed_codes,
12 worker_message_types::{
13 CheckResponse, ExecutionResponse, fee_payer_balance_flags, not_included_reasons,
14 resolve_flags, status_check_flags,
15 },
16 },
17 solana_pubkey::Pubkey,
18 solana_transaction::versioned::VersionedTransaction,
19 std::ops::{Deref, DerefMut},
20};
21
22pub struct TestBridge<M>
23where
24 M: Copy,
25{
26 bridge: SchedulerBindingsBridge<M>,
27 agave: AgaveSession,
28}
29
30impl<M> Deref for TestBridge<M>
31where
32 M: Copy,
33{
34 type Target = SchedulerBindingsBridge<M>;
35
36 fn deref(&self) -> &Self::Target {
37 &self.bridge
38 }
39}
40
41impl<M> DerefMut for TestBridge<M>
42where
43 M: Copy,
44{
45 fn deref_mut(&mut self) -> &mut Self::Target {
46 &mut self.bridge
47 }
48}
49
50impl<M> TestBridge<M>
51where
52 M: Copy,
53{
54 #[must_use]
55 pub fn new(worker_count: usize, worker_req_cap: usize) -> Self {
56 assert!(
57 worker_req_cap.is_power_of_two(),
58 "shaq requires power of 2 queue sizes"
59 );
60
61 let logon = ClientLogon {
62 worker_count,
63 allocator_size: 64 * 1024 * 1024,
64 allocator_handles: 1,
65 tpu_to_pack_capacity: 1024,
66 progress_tracker_capacity: 256,
67 pack_to_worker_capacity: worker_req_cap,
68 worker_to_pack_capacity: 1024,
69 flags: 0,
70 };
71
72 let (agave, files) = Server::setup_session(logon).unwrap();
73 let client_session = client::setup_session(&logon, files).unwrap();
74
75 Self {
76 bridge: SchedulerBindingsBridge::new(client_session),
77 agave,
78 }
79 }
80
81 #[must_use]
82 pub fn tx_count(&self) -> usize {
83 self.bridge.state().len()
84 }
85
86 #[must_use]
87 pub fn contains_tx(&self, key: TransactionKey) -> bool {
88 self.bridge.state().contains_key(key)
89 }
90
91 pub fn queue_progress(&mut self, progress: ProgressMessage) {
92 self.agave.progress_tracker.try_write(progress).unwrap();
93 self.agave.progress_tracker.commit();
94 }
95
96 pub fn queue_tpu(&mut self, tx: &VersionedTransaction) {
97 let serialized = wincode::serialize(tx).unwrap();
98 let allocator = &self.agave.tpu_to_pack.allocator;
99
100 let ptr = allocator
102 .allocate(serialized.len().try_into().unwrap())
103 .unwrap();
104 unsafe {
105 std::ptr::copy_nonoverlapping(serialized.as_ptr(), ptr.as_ptr(), serialized.len());
106 }
107 let offset = unsafe { allocator.offset(ptr) };
108
109 let msg = TpuToPackMessage {
110 transaction: SharableTransactionRegion {
111 offset,
112 length: serialized.len() as u32,
113 },
114 flags: 0,
115 src_addr: [0; 16],
116 };
117
118 self.agave.tpu_to_pack.producer.try_write(msg).unwrap();
119 self.agave.tpu_to_pack.producer.commit();
120 }
121
122 pub fn queue_check_response_ok(
123 &mut self,
124 batch: &ScheduleBatch<Vec<KeyedTransactionMeta<M>>>,
125 index: usize,
126 keys: Option<Vec<Pubkey>>,
127 ) {
128 self.queue_check_response(batch, index, keys, self.check_ok());
129 }
130
131 pub fn queue_check_response(
132 &mut self,
133 batch: &ScheduleBatch<Vec<KeyedTransactionMeta<M>>>,
134 index: usize,
135 keys: Option<Vec<Pubkey>>,
136 mut response: CheckResponse,
137 ) {
138 let worker_idx = batch.worker;
139
140 if let Some(keys) = keys {
142 let worker = &mut self.agave.workers[worker_idx];
143 let pubkeys_ptr = worker
144 .allocator
145 .allocate(
146 (keys
147 .len()
148 .checked_mul(std::mem::size_of::<Pubkey>())
149 .unwrap())
150 .try_into()
151 .unwrap(),
152 )
153 .unwrap();
154 unsafe {
155 std::ptr::copy_nonoverlapping(
156 keys.as_ptr().cast::<u8>(),
157 pubkeys_ptr.as_ptr(),
158 keys.len()
159 .checked_mul(std::mem::size_of::<Pubkey>())
160 .unwrap(),
161 );
162 }
163 let offset = unsafe { worker.allocator.offset(pubkeys_ptr) };
164 response.resolved_pubkeys = SharablePubkeys {
165 offset,
166 num_pubkeys: keys.len() as u32,
167 };
168 }
169
170 let batch_region = self.build_single_tx_batch_region(batch, index, worker_idx);
172 let worker = &mut self.agave.workers[worker_idx];
173 let responses_region =
174 resolve_responses_from_iter(&worker.allocator, [response].into_iter()).unwrap();
175
176 let msg = WorkerToPackMessage {
177 batch: batch_region,
178 processed_code: processed_codes::PROCESSED,
179 responses: responses_region,
180 };
181
182 worker.worker_to_pack.try_write(msg).unwrap();
183 worker.worker_to_pack.commit();
184 }
185
186 pub fn queue_all_checks_ok(&mut self) {
187 while let Some(batch) = self.pop_schedule() {
188 assert_eq!(batch.flags & 1, pack_message_flags::CHECK);
189
190 for i in 0..batch.transactions.len() {
191 self.queue_check_response_ok(&batch, i, None);
192 }
193 }
194 }
195
196 pub fn queue_execute_response(
197 &mut self,
198 batch: &ScheduleBatch<Vec<KeyedTransactionMeta<M>>>,
199 index: usize,
200 response: ExecutionResponse,
201 ) {
202 let worker_idx = batch.worker;
203 let batch_region = self.build_single_tx_batch_region(batch, index, worker_idx);
204 let worker = &mut self.agave.workers[worker_idx];
205
206 let responses_region =
207 execution_responses_from_iter(&worker.allocator, [response].into_iter()).unwrap();
208
209 let msg = WorkerToPackMessage {
210 batch: batch_region,
211 processed_code: processed_codes::PROCESSED,
212 responses: responses_region,
213 };
214
215 worker.worker_to_pack.try_write(msg).unwrap();
216 worker.worker_to_pack.commit();
217 }
218
219 pub fn queue_unprocessed_response(
220 &mut self,
221 batch: &ScheduleBatch<Vec<KeyedTransactionMeta<M>>>,
222 index: usize,
223 ) {
224 let worker_idx = batch.worker;
225 let batch_region = self.build_single_tx_batch_region(batch, index, worker_idx);
226 let worker = &mut self.agave.workers[worker_idx];
227
228 let msg = WorkerToPackMessage {
229 batch: batch_region,
230 processed_code: processed_codes::MAX_WORKING_SLOT_EXCEEDED,
231 responses: TransactionResponseRegion {
232 tag: 0,
233 num_transaction_responses: 0,
234 transaction_responses_offset: 0,
235 },
236 };
237
238 worker.worker_to_pack.try_write(msg).unwrap();
239 worker.worker_to_pack.commit();
240 }
241
242 pub fn pop_schedule(&mut self) -> Option<ScheduleBatch<Vec<KeyedTransactionMeta<M>>>> {
243 for (worker_idx, worker) in self.agave.workers.iter_mut().enumerate() {
244 worker.pack_to_worker.sync();
245 if let Some(msg) = worker.pack_to_worker.try_read() {
246 let msg = *msg;
247 worker.pack_to_worker.finalize();
248
249 let batch = unsafe {
251 TransactionPtrBatch::<KeyedTransactionMeta<M>>::from_sharable_transaction_batch_region(
252 &msg.batch,
253 self.bridge.allocator(),
254 )
255 };
256
257 let transactions: Vec<_> = batch.iter().map(|(_tx_ptr, meta)| meta).collect();
258
259 unsafe { batch.free() };
261
262 return Some(ScheduleBatch {
263 worker: worker_idx,
264 transactions,
265 max_working_slot: msg.max_working_slot,
266 flags: msg.flags,
267 });
268 }
269 }
270
271 None
272 }
273
274 pub fn check_ok(&self) -> CheckResponse {
275 let progress = self.bridge.progress();
276
277 CheckResponse {
278 parsing_and_sanitization_flags: 0,
279 status_check_flags: status_check_flags::REQUESTED | status_check_flags::PERFORMED,
280 fee_payer_balance_flags: fee_payer_balance_flags::REQUESTED
281 | fee_payer_balance_flags::PERFORMED,
282 resolve_flags: resolve_flags::REQUESTED | resolve_flags::PERFORMED,
283 included_slot: progress.current_slot,
284 balance_slot: progress.current_slot,
285 fee_payer_balance: u64::from(u32::MAX),
286 resolution_slot: progress.current_slot,
287 min_alt_deactivation_slot: u64::MAX,
288 resolved_pubkeys: SharablePubkeys {
289 offset: 0,
290 num_pubkeys: 0,
291 },
292 }
293 }
294
295 #[must_use]
296 pub fn execute_ok(&self) -> ExecutionResponse {
297 ExecutionResponse {
298 execution_slot: self.bridge.progress().current_slot,
299 not_included_reason: not_included_reasons::NONE,
300 cost_units: 0,
301 fee_payer_balance: u64::from(u32::MAX),
302 }
303 }
304
305 #[must_use]
306 pub fn execute_err(&self, reason: u8) -> ExecutionResponse {
307 ExecutionResponse {
308 execution_slot: self.bridge.progress().current_slot,
309 not_included_reason: reason,
310 cost_units: 0,
311 fee_payer_balance: u64::from(u32::MAX),
312 }
313 }
314
315 fn build_single_tx_batch_region(
316 &self,
317 batch: &ScheduleBatch<Vec<KeyedTransactionMeta<M>>>,
318 index: usize,
319 worker_idx: usize,
320 ) -> SharableTransactionBatchRegion {
321 type Batch<'a, M> = TransactionPtrBatch<'a, KeyedTransactionMeta<M>>;
322
323 let meta = batch.transactions[index];
324 let worker_allocator = &self.agave.workers[worker_idx].allocator;
325
326 let batch_ptr = worker_allocator
328 .allocate(Batch::<M>::TRANSACTION_META_END as u32)
329 .unwrap();
330 let batch_offset = unsafe { worker_allocator.offset(batch_ptr) };
331
332 let tx_state = self.bridge.transaction(meta.key);
335 let tx_region = unsafe {
336 tx_state
337 .data
338 .inner_data()
339 .to_sharable_transaction_region(self.bridge.allocator())
340 };
341 let tx_ptr = batch_ptr.cast::<SharableTransactionRegion>();
342 unsafe { tx_ptr.as_ptr().write(tx_region) };
343
344 let meta_ptr = unsafe {
346 batch_ptr
347 .as_ptr()
348 .byte_add(Batch::<M>::TRANSACTION_META_START)
349 .cast::<KeyedTransactionMeta<M>>()
350 };
351 unsafe { meta_ptr.write(meta) };
352
353 SharableTransactionBatchRegion {
354 num_transactions: 1,
355 transactions_offset: batch_offset,
356 }
357 }
358}