Skip to main content

agave_scheduling_utils/bridge/
test.rs

1use {
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        // Allocate in shared memory and copy the transaction bytes.
101        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        // Allocate pubkeys in shared memory if provided.
141        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        // Build the batch region and response region, then send.
171        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                // Read the batch contents from shared memory.
250                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                // Free the batch container (transactions are managed by the bridge).
260                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        // Allocate the batch container in worker's shared memory.
327        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        // Write the transaction region (offset is relative to the shared allocator,
333        // which is the same underlying file for both client and worker).
334        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        // Write the metadata.
345        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}