Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_runtime_tests_fd_dump_pb_h
2 : #define HEADER_fd_src_flamenco_runtime_tests_fd_dump_pb_h
3 :
4 : /* fd_dump_pb.h provides APIs for dumping syscalls, instructions,
5 : transactions, and blocks into a digestable and replayable Protobuf
6 : message. This is useful for debugging ledger test mismatches,
7 : collecting seed corpora, and gathering real data to test new
8 : harnesses.
9 :
10 : The following arguments can be added to the [capture] section of
11 : the TOML configuration file when running backtest:
12 : COMMON:
13 : - dump_proto_dir <output_dir>
14 : * Defines the output directory to dump Protobuf messages to
15 : - capture_start_slot <slot_number>
16 : * If present, defines the starting slot to dump Protobuf
17 : messages from
18 :
19 : HARNESS-SPECIFIC FILTERS:
20 : Instructions:
21 : - dump_instr_to_pb <0/1>
22 : * If enabled, instructions will be dumped to the
23 : specified output directory
24 : * File name format is "instr-<base58_enc_sig>-<instruction_idx>.bin",
25 : where instruction_idx is 1-indexed
26 : * Each file represents a single instruction as a
27 : serialized InstrContext Protobuf message
28 : - dump_instr_program_id_filter <base58_enc_program_id>
29 : * If present, only instructions from the specified
30 : program will be dumped
31 :
32 : Transactions:
33 : - dump_txn_to_pb <0/1>
34 : * If enabled, transactions will be dumped to the
35 : specified output directory
36 : * By default, file name format is
37 : "txn-<base58_enc_sig>.txnctx" containing a
38 : serialized TxnContext Protobuf message
39 : * If dump_txn_as_fixture is also set, the file format
40 : is "txn-<base58_enc_sig>.fix" containing a
41 : serialized TxnFixture (TxnContext + TxnResult)
42 : - dump_txn_as_fixture <0/1>
43 : * If enabled (requires dump_txn_to_pb), transactions
44 : are dumped as TxnFixture messages containing both
45 : the input context (captured before execution) and
46 : the output result (captured after execution)
47 : * Useful for verifying transaction harness accuracy
48 : against backtest results
49 :
50 : Blocks
51 : - dump_block_to_pb <0/1>
52 : * If enabled, blocks will be dumped to the specified
53 : output directory
54 : * File name format is "block-<slot_number>.bin"
55 : * Each file represents a single block as a serialized
56 : BlockContext Protobuf message
57 :
58 : Syscalls:
59 : - dump_syscall_to_pb <0/1>
60 : * If enabled, syscalls will be dumped to the specified
61 : output directory
62 : * File name format is "syscall-<fn_name>-<base58_enc_sig>-<program_id_idx>-<instr_stack_sz>-<cus_remaining>.bin"
63 : - dump_syscall_name_filter <fn_name>
64 : * If present, only syscalls with the specified name will
65 : be dumped, e.g. sol_memcpy_, sol_invoke_signed_rust
66 :
67 : Other notes:
68 : solana-conformance (https://github.com/firedancer-io/solana-conformance)
69 : * Allows decoding / executing / debugging of above Protobuf
70 : messages in an isolated environment
71 : * Allows execution result(s) comparison between Firedancer
72 : and Solana / Agave
73 : * See solana-conformance/README.md for functionality and use
74 : cases */
75 :
76 : #include "../info/fd_instr_info.h"
77 : #include "../../vm/fd_vm.h"
78 : #include "generated/block.pb.h"
79 : #include "../../../disco/fd_txn_p.h"
80 :
81 : /* The amount of memory allocated towards dumping blocks from ledgers */
82 0 : #define FD_BLOCK_DUMP_CTX_SPAD_MEM_MAX (2UL<<30)
83 : #define FD_BLOCK_DUMP_CTX_MAX_TXN_CNT (10000UL)
84 :
85 : FD_PROTOTYPES_BEGIN
86 :
87 : /***** Dumping context *****/
88 :
89 : /* Generic struct to hold options for dumping protobuf messages */
90 : struct fd_dump_proto_ctx {
91 : /* General dumping options */
92 : char const * dump_proto_output_dir;
93 : ulong dump_proto_start_slot;
94 :
95 : /* Instruction Capture */
96 : uint dump_instr_to_pb : 1;
97 : uint has_dump_instr_program_id_filter : 1;
98 : uchar dump_instr_program_id_filter[ 32 ];
99 :
100 : /* Transaction Capture */
101 : uint dump_txn_to_pb : 1;
102 : uint dump_txn_as_fixture : 1;
103 :
104 : /* Block Capture */
105 : uint dump_block_to_pb : 1;
106 :
107 : /* Syscall Capture */
108 : uint dump_syscall_to_pb : 1;
109 : char const * dump_syscall_name_filter;
110 :
111 : };
112 :
113 : /* Persistent context for block dumping. Maintains state about
114 : in-progress block dumping, such as any dynamic memory allocations
115 : (which live in the spad) and the block context message. */
116 : struct fd_block_dump_ctx {
117 : /* Block context message */
118 : fd_exec_test_block_context_t block_context;
119 :
120 : /* Collected transactions to dump */
121 : fd_txn_p_t txns_to_dump[FD_BLOCK_DUMP_CTX_MAX_TXN_CNT];
122 : ulong txns_to_dump_cnt;
123 :
124 : /* Spad for dynamic memory allocations for the block context message*/
125 : fd_spad_t * spad;
126 : };
127 : typedef struct fd_block_dump_ctx fd_block_dump_ctx_t;
128 :
129 : static inline ulong
130 0 : fd_block_dump_context_align( void ) {
131 0 : return alignof(fd_block_dump_ctx_t);
132 0 : }
133 :
134 : static inline ulong
135 0 : fd_block_dump_context_footprint( void ) {
136 0 : ulong l = FD_LAYOUT_INIT;
137 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_block_dump_ctx_t), sizeof(fd_block_dump_ctx_t) );
138 0 : l = FD_LAYOUT_APPEND( l, fd_spad_align(), fd_spad_footprint( FD_BLOCK_DUMP_CTX_SPAD_MEM_MAX ) );
139 0 : l = FD_LAYOUT_FINI( l, fd_spad_align() );
140 0 : return l;
141 0 : }
142 :
143 : static inline void *
144 0 : fd_block_dump_context_new( void * mem ) {
145 0 : FD_SCRATCH_ALLOC_INIT( l, mem );
146 0 : fd_block_dump_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_block_dump_ctx_t), sizeof(fd_block_dump_ctx_t) );
147 0 : fd_spad_t * spad = FD_SCRATCH_ALLOC_APPEND( l, fd_spad_align(), fd_spad_footprint( FD_BLOCK_DUMP_CTX_SPAD_MEM_MAX ) );
148 :
149 0 : ctx->spad = fd_spad_new( spad, FD_BLOCK_DUMP_CTX_SPAD_MEM_MAX );
150 0 : ctx->txns_to_dump_cnt = 0UL;
151 0 : return ctx;
152 0 : }
153 :
154 : static inline fd_block_dump_ctx_t *
155 0 : fd_block_dump_context_join( void * mem ) {
156 0 : if( FD_UNLIKELY( !mem ) ) {
157 0 : FD_LOG_ERR(( "NULL mem" ));
158 0 : return NULL;
159 0 : }
160 :
161 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_block_dump_context_align() ) ) ) {
162 0 : FD_LOG_ERR(( "misaligned mem" ));
163 0 : return NULL;
164 0 : }
165 :
166 0 : fd_block_dump_ctx_t * ctx = (fd_block_dump_ctx_t *)mem;
167 0 : ctx->spad = fd_spad_join( ctx->spad );
168 0 : return ctx;
169 0 : }
170 :
171 : static inline void *
172 0 : fd_block_dump_context_delete( void * mem ) {
173 0 : if( FD_UNLIKELY( !mem ) ) {
174 0 : FD_LOG_WARNING(( "NULL mem" ));
175 0 : return NULL;
176 0 : }
177 0 : return mem;
178 0 : }
179 :
180 : static inline void *
181 0 : fd_block_dump_context_leave( fd_block_dump_ctx_t * ctx ) {
182 0 : if( FD_UNLIKELY( !ctx ) ) {
183 0 : FD_LOG_WARNING(( "NULL ctx" ));
184 0 : return NULL;
185 0 : }
186 0 : return (void *)ctx;
187 0 : }
188 :
189 : /* Resets the block dump context to prepare for the next block. */
190 : static inline void
191 0 : fd_block_dump_context_reset( fd_block_dump_ctx_t * ctx ) {
192 0 : fd_memset( &ctx->block_context, 0, sizeof(ctx->block_context) );
193 0 : fd_spad_reset( ctx->spad );
194 0 : ctx->txns_to_dump_cnt = 0UL;
195 0 : }
196 :
197 : /* Persistent context for transaction dumping. Holds the in-progress
198 : fixture message across the two dump phases (context before execution,
199 : effects after execution). The spad is used for dynamic memory
200 : allocations referenced by the protobuf message. */
201 :
202 0 : #define FD_TXN_DUMP_CTX_SPAD_MEM_MAX (1UL<<28) /* 256 MB */
203 :
204 : struct fd_txn_dump_ctx {
205 : fd_exec_test_txn_fixture_t fixture;
206 : fd_spad_t * spad;
207 : };
208 : typedef struct fd_txn_dump_ctx fd_txn_dump_ctx_t;
209 :
210 : static inline ulong
211 0 : fd_txn_dump_context_align( void ) {
212 0 : return alignof(fd_txn_dump_ctx_t);
213 0 : }
214 :
215 : static inline ulong
216 0 : fd_txn_dump_context_footprint( void ) {
217 0 : ulong l = FD_LAYOUT_INIT;
218 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_txn_dump_ctx_t), sizeof(fd_txn_dump_ctx_t) );
219 0 : l = FD_LAYOUT_APPEND( l, fd_spad_align(), fd_spad_footprint( FD_TXN_DUMP_CTX_SPAD_MEM_MAX ) );
220 0 : l = FD_LAYOUT_FINI( l, fd_spad_align() );
221 0 : return l;
222 0 : }
223 :
224 : static inline void *
225 0 : fd_txn_dump_context_new( void * mem ) {
226 0 : FD_SCRATCH_ALLOC_INIT( l, mem );
227 0 : fd_txn_dump_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txn_dump_ctx_t), sizeof(fd_txn_dump_ctx_t) );
228 0 : fd_spad_t * spad = FD_SCRATCH_ALLOC_APPEND( l, fd_spad_align(), fd_spad_footprint( FD_TXN_DUMP_CTX_SPAD_MEM_MAX ) );
229 :
230 0 : ctx->spad = fd_spad_new( spad, FD_TXN_DUMP_CTX_SPAD_MEM_MAX );
231 0 : fd_memset( &ctx->fixture, 0, sizeof(ctx->fixture) );
232 0 : return ctx;
233 0 : }
234 :
235 : static inline fd_txn_dump_ctx_t *
236 0 : fd_txn_dump_context_join( void * mem ) {
237 0 : if( FD_UNLIKELY( !mem ) ) {
238 0 : FD_LOG_ERR(( "NULL mem" ));
239 0 : return NULL;
240 0 : }
241 :
242 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_txn_dump_context_align() ) ) ) {
243 0 : FD_LOG_ERR(( "misaligned mem" ));
244 0 : return NULL;
245 0 : }
246 :
247 0 : fd_txn_dump_ctx_t * ctx = (fd_txn_dump_ctx_t *)mem;
248 0 : ctx->spad = fd_spad_join( ctx->spad );
249 0 : return ctx;
250 0 : }
251 :
252 : static inline void *
253 0 : fd_txn_dump_context_delete( void * mem ) {
254 0 : if( FD_UNLIKELY( !mem ) ) {
255 0 : FD_LOG_ERR(( "NULL mem" ));
256 0 : return NULL;
257 0 : }
258 0 : return mem;
259 0 : }
260 :
261 : static inline void *
262 0 : fd_txn_dump_context_leave( fd_txn_dump_ctx_t * ctx ) {
263 0 : if( FD_UNLIKELY( !ctx ) ) {
264 0 : FD_LOG_ERR(( "NULL ctx" ));
265 0 : return NULL;
266 0 : }
267 0 : return (void *)ctx;
268 0 : }
269 :
270 : static inline void
271 0 : fd_txn_dump_context_reset( fd_txn_dump_ctx_t * ctx ) {
272 0 : fd_memset( &ctx->fixture, 0, sizeof(ctx->fixture) );
273 0 : fd_spad_reset( ctx->spad );
274 0 : }
275 :
276 : /****** Actual dumping functions ******/
277 :
278 : void
279 : fd_dump_instr_to_protobuf( fd_runtime_t * runtime,
280 : fd_bank_t * bank,
281 : fd_txn_in_t const * txn_in,
282 : fd_txn_out_t * txn_out,
283 : fd_instr_info_t * instr,
284 : ushort instruction_idx );
285 :
286 : void
287 : fd_dump_txn_to_protobuf( fd_runtime_t * runtime,
288 : fd_bank_t * bank,
289 : fd_txn_in_t const * txn_in,
290 : fd_txn_out_t * txn_out );
291 :
292 : /* Builds a TxnResult protobuf message from the transaction execution
293 : results into a caller-provided buffer. The result struct and all
294 : sub-allocations (account data, return data) are bump-allocated
295 : within [out_buf, out_buf+out_bufsz). Returns the number of bytes
296 : consumed from out_buf. *txn_result_out is set to point to the
297 : result struct within out_buf.
298 :
299 : Shared between the transaction dumper and the fuzz harness. */
300 : ulong
301 : create_txn_result_protobuf_from_txn( fd_exec_test_txn_result_t ** txn_result_out,
302 : void * out_buf,
303 : ulong out_bufsz,
304 : fd_txn_in_t const * txn_in,
305 : fd_txn_out_t * txn_out,
306 : fd_bank_t * bank,
307 : int exec_res );
308 :
309 : /* Transaction dumping (two-phase approach):
310 :
311 : Phase 1: fd_dump_txn_context_to_protobuf() captures the TxnContext
312 : before transaction execution into the txn dump context's fixture.
313 :
314 : Phase 2: fd_dump_txn_result_to_protobuf() captures the TxnResult
315 : after transaction execution into the txn dump context's fixture.
316 :
317 : fd_dump_txn_fixture_to_file() serializes and writes the fixture
318 : (or just the context) to disk.
319 :
320 : How it works in fd_runtime_prepare_and_execute_txn:
321 :
322 : fd_dump_txn_context_to_protobuf() // before execution
323 : ... execute transaction ...
324 : fd_dump_txn_result_to_protobuf() // after execution
325 : fd_dump_txn_fixture_to_file() // write to disk */
326 : void
327 : fd_dump_txn_context_to_protobuf( fd_txn_dump_ctx_t * txn_dump_ctx,
328 : fd_runtime_t * runtime,
329 : fd_bank_t * bank,
330 : fd_txn_in_t const * txn_in,
331 : fd_txn_out_t * txn_out );
332 :
333 : void
334 : fd_dump_txn_result_to_protobuf( fd_txn_dump_ctx_t * txn_dump_ctx,
335 : fd_txn_in_t const * txn_in,
336 : fd_txn_out_t * txn_out,
337 : fd_bank_t * bank,
338 : int exec_res );
339 :
340 : void
341 : fd_dump_txn_fixture_to_file( fd_txn_dump_ctx_t * txn_dump_ctx,
342 : fd_dump_proto_ctx_t const * dump_proto_ctx,
343 : fd_txn_in_t const * txn_in );
344 :
345 : /* Block dumping is a little bit different than the other harnesses due
346 : to the architecture of our system. Unlike the other dumping
347 : functions, blocks are dumped in two separate stages - transaction
348 : execution and block finalization. Transactions are streamed into
349 : the exec tile as they come in from the dispatcher, so we maintain a
350 : running list of transaction descriptors to dump within the dumping
351 : context (using fd_dump_block_to_protobuf_collect_tx). When the block
352 : is finalized, we take the accumulated transaction descriptors and
353 : convert them into Protobuf messages using
354 : fd_dump_block_to_protobuf, along with other fields in the slot /
355 : epoch context and any stake, vote, and transaction accounts.
356 :
357 : How it works in the replay tile:
358 :
359 : ...boot up backtest...
360 : unprivledged_init() {
361 : fd_block_dump_context_new()
362 : }
363 :
364 : ...start executing transactions...
365 :
366 : while( txns_to_execute ) {
367 : fd_dump_block_to_protobuf_collect_tx()
368 : }
369 :
370 : ...finalize the block...
371 : fd_dump_block_to_protobuf()
372 : fd_block_dump_context_reset() */
373 : void
374 : fd_dump_block_to_protobuf_collect_tx( fd_block_dump_ctx_t * dump_block_ctx,
375 : fd_txn_p_t const * txn );
376 :
377 : void
378 : fd_dump_block_to_protobuf( fd_block_dump_ctx_t * dump_block_ctx,
379 : fd_banks_t * banks,
380 : fd_bank_t * bank,
381 : fd_accdb_user_t * accdb,
382 : fd_dump_proto_ctx_t const * dump_proto_ctx );
383 :
384 : void
385 : fd_dump_vm_syscall_to_protobuf( fd_vm_t const * vm,
386 : char const * fn_name );
387 :
388 : FD_PROTOTYPES_END
389 :
390 : #endif /* HEADER_fd_src_flamenco_runtime_tests_fd_dump_pb_h */
|