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 "../../../util/spad/fd_spad.h"
78 : #include "../../vm/fd_vm.h"
79 : #include "generated/block.pb.h"
80 : #include "../../../disco/fd_txn_p.h"
81 :
82 : /* The amount of memory allocated towards dumping blocks from ledgers */
83 0 : #define FD_BLOCK_DUMP_CTX_SPAD_MEM_MAX (2UL<<30)
84 : #define FD_BLOCK_DUMP_CTX_MAX_TXN_CNT (10000UL)
85 :
86 : FD_PROTOTYPES_BEGIN
87 :
88 : /***** Dumping context *****/
89 :
90 : /* Generic struct to hold options for dumping protobuf messages */
91 : struct fd_dump_proto_ctx {
92 : /* General dumping options */
93 : char const * dump_proto_output_dir;
94 : ulong dump_proto_start_slot;
95 :
96 : /* Instruction Capture */
97 : uint dump_instr_to_pb : 1;
98 : uint has_dump_instr_program_id_filter : 1;
99 : uchar dump_instr_program_id_filter[ 32 ];
100 :
101 : /* Transaction Capture */
102 : uint dump_txn_to_pb : 1;
103 : uint dump_txn_as_fixture : 1;
104 :
105 : /* Block Capture */
106 : uint dump_block_to_pb : 1;
107 :
108 : /* Syscall Capture */
109 : uint dump_syscall_to_pb : 1;
110 : char const * dump_syscall_name_filter;
111 :
112 : };
113 :
114 : /* Persistent context for block dumping. Maintains state about
115 : in-progress block dumping, such as any dynamic memory allocations
116 : (which live in the spad) and the block context message. */
117 : struct fd_block_dump_ctx {
118 : /* Block context message */
119 : fd_exec_test_block_context_t block_context;
120 :
121 : /* Collected transactions to dump */
122 : fd_txn_p_t txns_to_dump[FD_BLOCK_DUMP_CTX_MAX_TXN_CNT];
123 : ulong txns_to_dump_cnt;
124 :
125 : /* Spad for dynamic memory allocations for the block context message*/
126 : fd_spad_t * spad;
127 : };
128 : typedef struct fd_block_dump_ctx fd_block_dump_ctx_t;
129 :
130 : static inline ulong
131 0 : fd_block_dump_context_align( void ) {
132 0 : return alignof(fd_block_dump_ctx_t);
133 0 : }
134 :
135 : static inline ulong
136 0 : fd_block_dump_context_footprint( void ) {
137 0 : ulong l = FD_LAYOUT_INIT;
138 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_block_dump_ctx_t), sizeof(fd_block_dump_ctx_t) );
139 0 : l = FD_LAYOUT_APPEND( l, fd_spad_align(), fd_spad_footprint( FD_BLOCK_DUMP_CTX_SPAD_MEM_MAX ) );
140 0 : l = FD_LAYOUT_FINI( l, fd_spad_align() );
141 0 : return l;
142 0 : }
143 :
144 : static inline void *
145 0 : fd_block_dump_context_new( void * mem ) {
146 0 : FD_SCRATCH_ALLOC_INIT( l, mem );
147 0 : fd_block_dump_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_block_dump_ctx_t), sizeof(fd_block_dump_ctx_t) );
148 0 : fd_spad_t * spad = FD_SCRATCH_ALLOC_APPEND( l, fd_spad_align(), fd_spad_footprint( FD_BLOCK_DUMP_CTX_SPAD_MEM_MAX ) );
149 :
150 0 : ctx->spad = fd_spad_new( spad, FD_BLOCK_DUMP_CTX_SPAD_MEM_MAX );
151 0 : ctx->txns_to_dump_cnt = 0UL;
152 0 : return ctx;
153 0 : }
154 :
155 : static inline fd_block_dump_ctx_t *
156 0 : fd_block_dump_context_join( void * mem ) {
157 0 : if( FD_UNLIKELY( !mem ) ) {
158 0 : FD_LOG_ERR(( "NULL mem" ));
159 0 : return NULL;
160 0 : }
161 :
162 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_block_dump_context_align() ) ) ) {
163 0 : FD_LOG_ERR(( "misaligned mem" ));
164 0 : return NULL;
165 0 : }
166 :
167 0 : fd_block_dump_ctx_t * ctx = (fd_block_dump_ctx_t *)mem;
168 0 : ctx->spad = fd_spad_join( ctx->spad );
169 0 : return ctx;
170 0 : }
171 :
172 : static inline void *
173 0 : fd_block_dump_context_delete( void * mem ) {
174 0 : if( FD_UNLIKELY( !mem ) ) {
175 0 : FD_LOG_WARNING(( "NULL mem" ));
176 0 : return NULL;
177 0 : }
178 0 : return mem;
179 0 : }
180 :
181 : static inline void *
182 0 : fd_block_dump_context_leave( fd_block_dump_ctx_t * ctx ) {
183 0 : if( FD_UNLIKELY( !ctx ) ) {
184 0 : FD_LOG_WARNING(( "NULL ctx" ));
185 0 : return NULL;
186 0 : }
187 0 : return (void *)ctx;
188 0 : }
189 :
190 : /* Resets the block dump context to prepare for the next block. */
191 : static inline void
192 0 : fd_block_dump_context_reset( fd_block_dump_ctx_t * ctx ) {
193 0 : fd_memset( &ctx->block_context, 0, sizeof(ctx->block_context) );
194 0 : fd_spad_reset( ctx->spad );
195 0 : ctx->txns_to_dump_cnt = 0UL;
196 0 : }
197 :
198 : /* Persistent context for transaction dumping. Holds the in-progress
199 : fixture message across the two dump phases (context before execution,
200 : effects after execution). The spad is used for dynamic memory
201 : allocations referenced by the protobuf message. */
202 :
203 0 : #define FD_TXN_DUMP_CTX_SPAD_MEM_MAX (1UL<<28) /* 256 MB */
204 :
205 : struct fd_txn_dump_ctx {
206 : fd_exec_test_txn_fixture_t fixture;
207 : fd_spad_t * spad;
208 : };
209 : typedef struct fd_txn_dump_ctx fd_txn_dump_ctx_t;
210 :
211 : static inline ulong
212 0 : fd_txn_dump_context_align( void ) {
213 0 : return alignof(fd_txn_dump_ctx_t);
214 0 : }
215 :
216 : static inline ulong
217 0 : fd_txn_dump_context_footprint( void ) {
218 0 : ulong l = FD_LAYOUT_INIT;
219 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_txn_dump_ctx_t), sizeof(fd_txn_dump_ctx_t) );
220 0 : l = FD_LAYOUT_APPEND( l, fd_spad_align(), fd_spad_footprint( FD_TXN_DUMP_CTX_SPAD_MEM_MAX ) );
221 0 : l = FD_LAYOUT_FINI( l, fd_spad_align() );
222 0 : return l;
223 0 : }
224 :
225 : static inline void *
226 0 : fd_txn_dump_context_new( void * mem ) {
227 0 : FD_SCRATCH_ALLOC_INIT( l, mem );
228 0 : fd_txn_dump_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txn_dump_ctx_t), sizeof(fd_txn_dump_ctx_t) );
229 0 : fd_spad_t * spad = FD_SCRATCH_ALLOC_APPEND( l, fd_spad_align(), fd_spad_footprint( FD_TXN_DUMP_CTX_SPAD_MEM_MAX ) );
230 :
231 0 : ctx->spad = fd_spad_new( spad, FD_TXN_DUMP_CTX_SPAD_MEM_MAX );
232 0 : fd_memset( &ctx->fixture, 0, sizeof(ctx->fixture) );
233 0 : return ctx;
234 0 : }
235 :
236 : static inline fd_txn_dump_ctx_t *
237 0 : fd_txn_dump_context_join( void * mem ) {
238 0 : if( FD_UNLIKELY( !mem ) ) {
239 0 : FD_LOG_ERR(( "NULL mem" ));
240 0 : return NULL;
241 0 : }
242 :
243 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_txn_dump_context_align() ) ) ) {
244 0 : FD_LOG_ERR(( "misaligned mem" ));
245 0 : return NULL;
246 0 : }
247 :
248 0 : fd_txn_dump_ctx_t * ctx = (fd_txn_dump_ctx_t *)mem;
249 0 : ctx->spad = fd_spad_join( ctx->spad );
250 0 : return ctx;
251 0 : }
252 :
253 : static inline void *
254 0 : fd_txn_dump_context_delete( void * mem ) {
255 0 : if( FD_UNLIKELY( !mem ) ) {
256 0 : FD_LOG_ERR(( "NULL mem" ));
257 0 : return NULL;
258 0 : }
259 0 : return mem;
260 0 : }
261 :
262 : static inline void *
263 0 : fd_txn_dump_context_leave( fd_txn_dump_ctx_t * ctx ) {
264 0 : if( FD_UNLIKELY( !ctx ) ) {
265 0 : FD_LOG_ERR(( "NULL ctx" ));
266 0 : return NULL;
267 0 : }
268 0 : return (void *)ctx;
269 0 : }
270 :
271 : static inline void
272 0 : fd_txn_dump_context_reset( fd_txn_dump_ctx_t * ctx ) {
273 0 : fd_memset( &ctx->fixture, 0, sizeof(ctx->fixture) );
274 0 : fd_spad_reset( ctx->spad );
275 0 : }
276 :
277 : /****** Actual dumping functions ******/
278 :
279 : void
280 : fd_dump_instr_to_protobuf( fd_runtime_t * runtime,
281 : fd_bank_t * bank,
282 : fd_txn_in_t const * txn_in,
283 : fd_txn_out_t * txn_out,
284 : fd_instr_info_t * instr,
285 : ushort instruction_idx );
286 :
287 : void
288 : fd_dump_txn_to_protobuf( fd_runtime_t * runtime,
289 : fd_bank_t * bank,
290 : fd_txn_in_t const * txn_in,
291 : fd_txn_out_t * txn_out );
292 :
293 : /* Builds a TxnResult protobuf message from the transaction execution
294 : results into a caller-provided buffer. The result struct and all
295 : sub-allocations (account data, return data) are bump-allocated
296 : within [out_buf, out_buf+out_bufsz). Returns the number of bytes
297 : consumed from out_buf. *txn_result_out is set to point to the
298 : result struct within out_buf.
299 :
300 : Shared between the transaction dumper and the fuzz harness. */
301 : ulong
302 : create_txn_result_protobuf_from_txn( fd_exec_test_txn_result_t ** txn_result_out,
303 : void * out_buf,
304 : ulong out_bufsz,
305 : fd_txn_in_t const * txn_in,
306 : fd_txn_out_t * txn_out,
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 : int exec_res );
338 :
339 : void
340 : fd_dump_txn_fixture_to_file( fd_txn_dump_ctx_t * txn_dump_ctx,
341 : fd_dump_proto_ctx_t const * dump_proto_ctx,
342 : fd_txn_in_t const * txn_in );
343 :
344 : /* Block dumping is a little bit different than the other harnesses due
345 : to the architecture of our system. Unlike the other dumping
346 : functions, blocks are dumped in two separate stages - transaction
347 : execution and block finalization. Transactions are streamed into
348 : the exec tile as they come in from the dispatcher, so we maintain a
349 : running list of transaction descriptors to dump within the dumping
350 : context (using fd_dump_block_to_protobuf_collect_tx). When the block
351 : is finalized, we take the accumulated transaction descriptors and
352 : convert them into Protobuf messages using
353 : fd_dump_block_to_protobuf, along with other fields in the slot /
354 : epoch context and any stake, vote, and transaction accounts.
355 :
356 : How it works in the replay tile:
357 :
358 : ...boot up backtest...
359 : unprivledged_init() {
360 : fd_block_dump_context_new()
361 : }
362 :
363 : ...start executing transactions...
364 :
365 : while( txns_to_execute ) {
366 : fd_dump_block_to_protobuf_collect_tx()
367 : }
368 :
369 : ...finalize the block...
370 : fd_dump_block_to_protobuf()
371 : fd_block_dump_context_reset() */
372 : void
373 : fd_dump_block_to_protobuf_collect_tx( fd_block_dump_ctx_t * dump_block_ctx,
374 : fd_txn_p_t const * txn );
375 :
376 : void
377 : fd_dump_block_to_protobuf( fd_block_dump_ctx_t * dump_block_ctx,
378 : fd_banks_t * banks,
379 : fd_bank_t * bank,
380 : fd_accdb_t * accdb,
381 : fd_dump_proto_ctx_t const * dump_proto_ctx,
382 : fd_runtime_stack_t * runtime_stack );
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 */
|