Line data Source code
1 : /* The backtest command spawns a smaller topology for replaying shreds from
2 : rocksdb (or other sources TBD) and reproduce the behavior of replay tile.
3 :
4 : The smaller topology is:
5 : repair_repla replay_exec exec_writer
6 : backtest-------------->replay------------->exec------------->writer
7 : ^ |^ | | ^
8 : |____________________|| | |___________________________________|
9 : replay_notif | |
10 : | |------------------------------>no consumer
11 : no producer------------- stake_out, send_out, poh_out
12 : store_replay,
13 : pack_replay,
14 : batch_replay
15 :
16 : */
17 :
18 : #include "../../firedancer/topology.h"
19 : #include "../../shared/commands/run/run.h" /* initialize_workspaces */
20 : #include "../../shared/fd_config.h" /* config_t */
21 : #include "../../../disco/tiles.h"
22 : #include "../../../disco/topo/fd_cpu_topo.h" /* fd_topo_cpus */
23 : #include "../../../disco/topo/fd_topob.h"
24 : #include "../../../util/pod/fd_pod_format.h"
25 : #include "../../../discof/replay/fd_replay_notif.h"
26 : #include "../../../flamenco/runtime/fd_runtime.h"
27 : #include "../../../flamenco/runtime/fd_txncache.h"
28 :
29 : #include <unistd.h> /* pause */
30 : extern fd_topo_obj_callbacks_t * CALLBACKS[];
31 : fd_topo_run_tile_t fdctl_tile_run( fd_topo_tile_t const * tile );
32 :
33 : static void
34 0 : backtest_topo( config_t * config ) {
35 0 : ulong exec_tile_cnt = config->firedancer.layout.exec_tile_count;
36 0 : ulong writer_tile_cnt = config->firedancer.layout.writer_tile_count;
37 :
38 0 : fd_topo_t * topo = { fd_topob_new( &config->topo, config->name ) };
39 0 : topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size );
40 0 : topo->gigantic_page_threshold = config->hugetlbfs.gigantic_page_threshold_mib << 20;
41 :
42 0 : ulong cpu_idx = 0;
43 :
44 : /**********************************************************************/
45 : /* Add the metric tile to topo */
46 : /**********************************************************************/
47 0 : fd_topob_wksp( topo, "metric" );
48 0 : fd_topob_wksp( topo, "metric_in" );
49 0 : fd_topob_tile( topo, "metric", "metric", "metric_in", cpu_idx++, 0, 0 );
50 :
51 : /**********************************************************************/
52 : /* Add the backtest tile to topo */
53 : /**********************************************************************/
54 0 : fd_topob_wksp( topo, "back" );
55 0 : fd_topo_tile_t * backtest_tile = fd_topob_tile( topo, "back", "back", "metric_in", cpu_idx++, 0, 0 );
56 0 : FD_LOG_NOTICE(( "Found rocksdb path from config: %s", backtest_tile->archiver.archiver_path ));
57 :
58 : /**********************************************************************/
59 : /* Add the replay tile to topo */
60 : /**********************************************************************/
61 0 : fd_topob_wksp( topo, "replay" );
62 0 : fd_topo_tile_t * replay_tile = fd_topob_tile( topo, "replay", "replay", "metric_in", cpu_idx++, 0, 0 );
63 :
64 : /* specified by [tiles.replay] */
65 :
66 0 : fd_topob_wksp( topo, "funk" );
67 0 : fd_topo_obj_t * funk_obj = setup_topo_funk( topo, "funk",
68 0 : config->firedancer.funk.max_account_records,
69 0 : config->firedancer.funk.max_database_transactions,
70 0 : config->firedancer.funk.heap_size_gib );
71 :
72 0 : fd_topob_tile_uses( topo, replay_tile, funk_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
73 :
74 : /**********************************************************************/
75 : /* Add the executor tiles to topo */
76 : /**********************************************************************/
77 0 : fd_topob_wksp( topo, "exec" );
78 0 : #define FOR(cnt) for( ulong i=0UL; i<cnt; i++ )
79 0 : FOR(exec_tile_cnt) fd_topob_tile( topo, "exec", "exec", "metric_in", cpu_idx++, 0, 0 );
80 :
81 : /**********************************************************************/
82 : /* Add the writer tiles to topo */
83 : /**********************************************************************/
84 0 : fd_topob_wksp( topo, "writer" );
85 0 : FOR(writer_tile_cnt) fd_topob_tile( topo, "writer", "writer", "metric_in", cpu_idx++, 0, 0 );
86 :
87 : /**********************************************************************/
88 : /* Setup backtest->replay links in topo */
89 : /**********************************************************************/
90 :
91 : /* The repair tile is replaced by the backtest tile for the repair to
92 : replay link. The frag interface is a "slice", ie. entry batch,
93 : which is provided by the backtest tile, which reads in the entry
94 : batches from the CLI-specified source (eg. RocksDB). */
95 :
96 0 : fd_topob_wksp( topo, "repair_repla" );
97 0 : fd_topob_link( topo, "repair_repla", "repair_repla", 65536UL, sizeof(ulong), 1UL );
98 0 : fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "repair_repla", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
99 0 : fd_topob_tile_out( topo, "back", 0UL, "repair_repla", 0UL );
100 :
101 : /**********************************************************************/
102 : /* Setup pack/batch->replay links in topo w/o a producer */
103 : /**********************************************************************/
104 0 : fd_topob_wksp( topo, "pack_replay" );
105 0 : fd_topob_wksp( topo, "batch_replay" );
106 0 : fd_topob_link( topo, "pack_replay", "pack_replay", 65536UL, USHORT_MAX, 1UL );
107 0 : fd_topob_link( topo, "batch_replay", "batch_replay", 128UL, 32UL, 1UL );
108 0 : fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "pack_replay", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
109 0 : fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "batch_replay", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
110 0 : topo->links[ replay_tile->in_link_id[ fd_topo_find_tile_in_link( topo, replay_tile, "pack_replay", 0 ) ] ].permit_no_producers = 1;
111 0 : topo->links[ replay_tile->in_link_id[ fd_topo_find_tile_in_link( topo, replay_tile, "batch_replay", 0 ) ] ].permit_no_producers = 1;
112 :
113 : /**********************************************************************/
114 : /* More backtest->replay links in topo */
115 : /**********************************************************************/
116 :
117 : /* The tower tile is replaced by the backtest tile for the tower to
118 : replay link. The backtest tile simply sends monotonically
119 : increasing rooted slot numbers to the replay tile, once after each
120 : "replayed a full slot" notification received from the replay tile.
121 : This allows the replay tile to advance its watermark, and publish
122 : various data structures. This is an oversimplified barebones mock
123 : of the tower tile. */
124 0 : fd_topob_wksp( topo, "tower_replay" );
125 0 : fd_topob_link( topo, "tower_replay", "tower_replay", 128UL, 0UL, 1UL );
126 0 : fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "tower_replay", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
127 0 : fd_topob_tile_out( topo, "back", 0UL, "tower_replay", 0UL );
128 :
129 : /**********************************************************************/
130 : /* Setup replay->stake/send/poh links in topo w/o consumers */
131 : /**********************************************************************/
132 0 : fd_topob_wksp( topo, "stake_out" );
133 0 : fd_topob_wksp( topo, "replay_poh" );
134 :
135 0 : fd_topob_link( topo, "stake_out", "stake_out", 128UL, 40UL + 40200UL * 40UL, 1UL );
136 0 : ulong bank_tile_cnt = config->layout.bank_tile_count;
137 0 : FOR(bank_tile_cnt) fd_topob_link( topo, "replay_poh", "replay_poh", 128UL, (4096UL*sizeof(fd_txn_p_t))+sizeof(fd_microblock_trailer_t), 1UL );
138 :
139 0 : fd_topob_tile_out( topo, "replay", 0UL, "stake_out", 0UL );
140 0 : FOR(bank_tile_cnt) fd_topob_tile_out( topo, "replay", 0UL, "replay_poh", i );
141 :
142 0 : topo->links[ replay_tile->out_link_id[ fd_topo_find_tile_out_link( topo, replay_tile, "stake_out", 0 ) ] ].permit_no_consumers = 1;
143 0 : FOR(bank_tile_cnt) topo->links[ replay_tile->out_link_id[ fd_topo_find_tile_out_link( topo, replay_tile, "replay_poh", i ) ] ].permit_no_consumers = 1;
144 :
145 : /**********************************************************************/
146 : /* Setup replay->backtest link (replay_notif) in topo */
147 : /**********************************************************************/
148 0 : fd_topob_wksp( topo, "replay_notif" );
149 0 : fd_topob_link( topo, "replay_notif", "replay_notif", FD_REPLAY_NOTIF_DEPTH, FD_REPLAY_NOTIF_MTU, 1UL );
150 0 : fd_topob_tile_in( topo, "back", 0UL, "metric_in", "replay_notif", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
151 0 : fd_topob_tile_out( topo, "replay", 0UL, "replay_notif", 0UL );
152 :
153 : /**********************************************************************/
154 : /* Setup replay->exec links in topo */
155 : /**********************************************************************/
156 0 : fd_topob_wksp( topo, "replay_exec" );
157 0 : for( ulong i=0; i<exec_tile_cnt; i++ ) {
158 0 : fd_topob_link( topo, "replay_exec", "replay_exec", 128UL, 10240UL, exec_tile_cnt );
159 0 : fd_topob_tile_out( topo, "replay", 0UL, "replay_exec", i );
160 0 : fd_topob_tile_in( topo, "exec", i, "metric_in", "replay_exec", i, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
161 0 : }
162 :
163 : /**********************************************************************/
164 : /* Setup exec->writer links in topo */
165 : /**********************************************************************/
166 0 : fd_topob_wksp( topo, "exec_writer" );
167 0 : FOR(exec_tile_cnt) fd_topob_link( topo, "exec_writer", "exec_writer", 128UL, FD_EXEC_WRITER_MTU, 1UL );
168 0 : FOR(exec_tile_cnt) fd_topob_tile_out( topo, "exec", i, "exec_writer", i );
169 0 : FOR(writer_tile_cnt) for( ulong j=0UL; j<exec_tile_cnt; j++ )
170 0 : fd_topob_tile_in( topo, "writer", i, "metric_in", "exec_writer", j, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
171 :
172 : /**********************************************************************/
173 : /* Setup the shared objs used by replay and exec tiles */
174 : /**********************************************************************/
175 :
176 0 : fd_topob_wksp( topo, "slot_fseqs" ); /* fseqs for marked slots eg. turbine slot */
177 :
178 : /* blockstore_obj shared by replay and backtest tiles */
179 0 : fd_topob_wksp( topo, "blockstore" );
180 0 : fd_topo_obj_t * blockstore_obj = setup_topo_blockstore( topo, "blockstore", config->firedancer.blockstore.shred_max, config->firedancer.blockstore.block_max, config->firedancer.blockstore.idx_max, config->firedancer.blockstore.txn_max, config->firedancer.blockstore.alloc_max );
181 0 : fd_topob_tile_uses( topo, replay_tile, blockstore_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
182 0 : fd_topob_tile_uses( topo, backtest_tile, blockstore_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
183 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, blockstore_obj->id, "blockstore" ) );
184 :
185 0 : fd_topo_obj_t * root_slot_obj = fd_topob_obj( topo, "fseq", "slot_fseqs" );
186 0 : fd_topob_tile_uses( topo, backtest_tile, root_slot_obj, FD_SHMEM_JOIN_MODE_READ_ONLY );
187 0 : fd_topob_tile_uses( topo, replay_tile, root_slot_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
188 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, root_slot_obj->id, "root_slot" ) );
189 :
190 0 : fd_topo_obj_t * turbine_slot0_obj = fd_topob_obj( topo, "fseq", "slot_fseqs" );
191 0 : fd_topob_tile_uses( topo, backtest_tile, turbine_slot0_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
192 0 : fd_topob_tile_uses( topo, replay_tile, turbine_slot0_obj, FD_SHMEM_JOIN_MODE_READ_ONLY );
193 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, turbine_slot0_obj->id, "turbine_slot0" ) );
194 :
195 0 : fd_topo_obj_t * turbine_slot_obj = fd_topob_obj( topo, "fseq", "slot_fseqs" );
196 0 : fd_topob_tile_uses( topo, backtest_tile, turbine_slot_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
197 0 : fd_topob_tile_uses( topo, replay_tile, turbine_slot_obj, FD_SHMEM_JOIN_MODE_READ_ONLY );
198 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, turbine_slot_obj->id, "turbine_slot" ) );
199 :
200 : /* runtime_pub_obj shared by replay, exec and writer tiles */
201 0 : fd_topob_wksp( topo, "runtime_pub" );
202 0 : fd_topo_obj_t * runtime_pub_obj = setup_topo_runtime_pub( topo, "runtime_pub", config->firedancer.runtime.heap_size_gib<<30 );
203 0 : fd_topob_tile_uses( topo, replay_tile, runtime_pub_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
204 0 : FOR(exec_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "exec", i ) ], runtime_pub_obj, FD_SHMEM_JOIN_MODE_READ_ONLY );
205 0 : FOR(writer_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "writer", i ) ], runtime_pub_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
206 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, runtime_pub_obj->id, "runtime_pub" ) );
207 :
208 : /* banks_obj shared by replay, exec and writer tiles */
209 0 : fd_topob_wksp( topo, "banks" );
210 0 : FD_LOG_WARNING(("max_banks: %lu", config->firedancer.runtime.limits.max_banks));
211 0 : fd_topo_obj_t * banks_obj = setup_topo_banks( topo, "banks", config->firedancer.runtime.limits.max_banks );
212 0 : fd_topob_tile_uses( topo, replay_tile, banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
213 0 : FOR(exec_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "exec", i ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
214 0 : FOR(writer_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "writer", i ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
215 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, banks_obj->id, "banks" ) );
216 :
217 : /* bank_hash_cmp_obj shared by replay, exec and writer tiles */
218 0 : fd_topob_wksp( topo, "bh_cmp" );
219 0 : fd_topo_obj_t * bank_hash_cmp_obj = setup_topo_bank_hash_cmp( topo, "bh_cmp" );
220 0 : fd_topob_tile_uses( topo, replay_tile, bank_hash_cmp_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
221 0 : FOR(exec_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "exec", i ) ], bank_hash_cmp_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
222 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, bank_hash_cmp_obj->id, "bh_cmp" ) );
223 :
224 : /* exec_spad_obj shared by replay, exec and writer tiles */
225 0 : fd_topob_wksp( topo, "exec_spad" );
226 0 : for( ulong i=0UL; i<exec_tile_cnt; i++ ) {
227 0 : fd_topo_obj_t * exec_spad_obj = fd_topob_obj( topo, "exec_spad", "exec_spad" );
228 0 : fd_topob_tile_uses( topo, replay_tile, exec_spad_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
229 0 : fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "exec", i ) ], exec_spad_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
230 0 : for( ulong j=0UL; j<writer_tile_cnt; j++ ) {
231 : /* For txn_ctx. */
232 0 : fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "writer", j ) ], exec_spad_obj, FD_SHMEM_JOIN_MODE_READ_ONLY );
233 0 : }
234 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, exec_spad_obj->id, "exec_spad.%lu", i ) );
235 0 : }
236 :
237 : /* exec_fseq_obj shared by replay and exec tiles */
238 0 : fd_topob_wksp( topo, "exec_fseq" );
239 0 : for( ulong i=0UL; i<exec_tile_cnt; i++ ) {
240 0 : fd_topo_obj_t * exec_fseq_obj = fd_topob_obj( topo, "fseq", "exec_fseq" );
241 0 : fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "exec", i ) ], exec_fseq_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
242 0 : fd_topob_tile_uses( topo, replay_tile, exec_fseq_obj, FD_SHMEM_JOIN_MODE_READ_ONLY );
243 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, exec_fseq_obj->id, "exec_fseq.%lu", i ) );
244 0 : }
245 :
246 : /* writer_fseq_obj shared by replay and writer tiles */
247 0 : fd_topob_wksp( topo, "writer_fseq" );
248 0 : for( ulong i=0UL; i<writer_tile_cnt; i++ ) {
249 0 : fd_topo_obj_t * writer_fseq_obj = fd_topob_obj( topo, "fseq", "writer_fseq" );
250 0 : fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "writer", i ) ], writer_fseq_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
251 0 : fd_topob_tile_uses( topo, replay_tile, writer_fseq_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
252 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, writer_fseq_obj->id, "writer_fseq.%lu", i ) );
253 0 : }
254 :
255 : /* txncache_obj, busy_obj, poh_slot_obj and constipated_obj only by replay tile */
256 0 : fd_topob_wksp( topo, "tcache" );
257 0 : fd_topob_wksp( topo, "bank_busy" );
258 0 : fd_topob_wksp( topo, "constipate" );
259 0 : fd_topo_obj_t * txncache_obj = setup_topo_txncache( topo, "tcache",
260 0 : config->firedancer.runtime.limits.max_rooted_slots,
261 0 : config->firedancer.runtime.limits.max_live_slots,
262 0 : config->firedancer.runtime.limits.max_transactions_per_slot,
263 0 : fd_txncache_max_constipated_slots_est( config->firedancer.runtime.limits.snapshot_grace_period_seconds ) );
264 0 : fd_topob_tile_uses( topo, replay_tile, txncache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
265 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, txncache_obj->id, "txncache" ) );
266 0 : for( ulong i=0UL; i<bank_tile_cnt; i++ ) {
267 0 : fd_topo_obj_t * busy_obj = fd_topob_obj( topo, "fseq", "bank_busy" );
268 0 : fd_topob_tile_uses( topo, replay_tile, busy_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
269 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, busy_obj->id, "bank_busy.%lu", i ) );
270 0 : }
271 0 : fd_topo_obj_t * constipated_obj = fd_topob_obj( topo, "fseq", "constipate" );
272 0 : fd_topob_tile_uses( topo, replay_tile, constipated_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
273 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, constipated_obj->id, "constipate" ) );
274 :
275 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
276 0 : fd_topo_tile_t * tile = &topo->tiles[ i ];
277 0 : if( !strcmp( tile->name, "rocksdb" ) ) {
278 0 : tile->archiver.end_slot = config->tiles.archiver.end_slot;
279 0 : strncpy( tile->archiver.archiver_path, config->tiles.archiver.archiver_path, PATH_MAX );
280 0 : if( FD_UNLIKELY( 0==strlen( tile->archiver.archiver_path ) ) ) {
281 0 : FD_LOG_ERR(( "Rocksdb not found, check `archiver.archiver_path` in toml" ));
282 0 : } else {
283 0 : FD_LOG_NOTICE(( "Found rocksdb path from config: %s", tile->archiver.archiver_path ));
284 0 : }
285 0 : } else if( !fd_topo_configure_tile( tile, config ) ) {
286 0 : FD_LOG_ERR(( "unknown tile name %lu `%s`", i, tile->name ));
287 0 : }
288 :
289 : /* Override */
290 0 : if( !strcmp( tile->name, "replay" ) ) {
291 0 : tile->replay.enable_bank_hash_cmp = 0;
292 0 : tile->replay.enable_features_cnt = config->tiles.replay.enable_features_cnt;
293 0 : for( ulong i = 0; i < tile->replay.enable_features_cnt; i++ ) {
294 0 : strncpy( tile->replay.enable_features[i], config->tiles.replay.enable_features[i], sizeof(tile->replay.enable_features[i]) );
295 0 : }
296 0 : }
297 0 : }
298 :
299 : /**********************************************************************/
300 : /* Finish and print out the topo information */
301 : /**********************************************************************/
302 0 : fd_topob_finish( topo, CALLBACKS );
303 0 : fd_topo_print_log( /* stdout */ 1, topo );
304 0 : }
305 :
306 : static void
307 : backtest_cmd_fn( args_t * args FD_PARAM_UNUSED,
308 0 : config_t * config ) {
309 0 : backtest_topo( config );
310 :
311 0 : initialize_workspaces( config );
312 0 : initialize_stacks( config );
313 0 : fd_topo_t * topo = &config->topo;
314 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_WRITE );
315 :
316 0 : fd_topo_run_single_process( topo, 2, config->uid, config->gid, fdctl_tile_run, NULL );
317 0 : for(;;) pause();
318 0 : }
319 :
320 : static void
321 : backtest_cmd_perm( args_t * args FD_PARAM_UNUSED,
322 : fd_cap_chk_t * chk FD_PARAM_UNUSED,
323 0 : config_t const * config FD_PARAM_UNUSED ) {}
324 :
325 : static void
326 : backtest_cmd_args( int * pargc FD_PARAM_UNUSED,
327 : char *** pargv FD_PARAM_UNUSED,
328 0 : args_t * args FD_PARAM_UNUSED ) {}
329 :
330 : action_t fd_action_backtest = {
331 : .name = "backtest",
332 : .args = backtest_cmd_args,
333 : .fn = backtest_cmd_fn,
334 : .perm = backtest_cmd_perm,
335 : };
|