Skip to content

Commit daab8f5

Browse files
committed
gui: leader schedule + peer vote info
1 parent e74f7ce commit daab8f5

File tree

11 files changed

+575
-137
lines changed

11 files changed

+575
-137
lines changed

book/api/websocket.md

Lines changed: 36 additions & 34 deletions
Large diffs are not rendered by default.

src/app/firedancer/topology.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ fd_topo_initialize( config_t * config ) {
258258

259259
fd_topob_wksp( topo, "shred_out" );
260260
fd_topob_wksp( topo, "replay_stake" );
261+
fd_topob_wksp( topo, "replay_votes" );
261262
fd_topob_wksp( topo, "replay_exec" );
262263
fd_topob_wksp( topo, "replay_out" );
263264
fd_topob_wksp( topo, "tower_out" );
@@ -345,6 +346,9 @@ fd_topo_initialize( config_t * config ) {
345346
/**/ fd_topob_link( topo, "dedup_resolv", "dedup_resolv", 65536UL, FD_TPU_PARSED_MTU, 1UL );
346347
FOR(resolv_tile_cnt) fd_topob_link( topo, "resolv_pack", "resolv_pack", 65536UL, FD_TPU_RESOLVED_MTU, 1UL );
347348
/**/ fd_topob_link( topo, "replay_stake", "replay_stake", 128UL, FD_STAKE_OUT_MTU, 1UL ); /* TODO: This should be 2 but requires fixing STEM_BURST */
349+
if( FD_LIKELY( config->tiles.gui.enabled ) ) { /* the gui, which is optional, is the only consumer of replay_votes */
350+
fd_topob_link( topo, "replay_votes", "replay_votes", 128UL, FD_RUNTIME_MAX_VOTE_ACCOUNTS*sizeof(fd_replay_vote_t), 1UL );
351+
}
348352
/**/ fd_topob_link( topo, "replay_out", "replay_out", 8192UL, sizeof(fd_replay_message_t), 1UL );
349353
/**/ fd_topob_link( topo, "pack_poh", "pack_poh", 128UL, sizeof(fd_done_packing_t), 1UL );
350354
/* pack_bank is shared across all banks, so if one bank stalls due to complex transactions, the buffer neeeds to be large so that
@@ -502,6 +506,9 @@ fd_topo_initialize( config_t * config ) {
502506
/**/ fd_topob_tile_in ( topo, "replay", 0UL, "metric_in", "genesi_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
503507
/**/ fd_topob_tile_out( topo, "replay", 0UL, "replay_out", 0UL );
504508
/**/ fd_topob_tile_out( topo, "replay", 0UL, "replay_stake", 0UL );
509+
if( FD_LIKELY( config->tiles.gui.enabled ) ) { /* the gui, which is optional, is the only consumer of replay_votes */
510+
fd_topob_tile_out( topo, "replay", 0UL, "replay_votes", 0UL );
511+
}
505512
/**/ fd_topob_tile_out( topo, "replay", 0UL, "executed_txn", 0UL );
506513
/**/ fd_topob_tile_out( topo, "replay", 0UL, "replay_exec", 0UL );
507514
/**/ fd_topob_tile_in ( topo, "replay", 0UL, "metric_in", "tower_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
@@ -654,6 +661,8 @@ fd_topo_initialize( config_t * config ) {
654661
/**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "gossip_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
655662
/**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "tower_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
656663
/**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "replay_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
664+
/**/ fd_topob_tile_in ( topo, "gui", 0UL, "metric_in", "replay_stake", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
665+
/**/ fd_topob_tile_in ( topo, "gui", 0UL, "metric_in", "replay_votes", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
657666

658667
if( FD_LIKELY( snapshots_enabled ) ) {
659668
fd_topob_tile_in ( topo, "gui", 0UL, "metric_in", "snaprd_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );

src/disco/gui/fd_gui.c

Lines changed: 104 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,20 @@ fd_gui_ws_open( fd_gui_t * gui,
229229
FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
230230
}
231231

232+
/* todo .. temporary workaround to skip the blur until frontend boot
233+
screen lands */
234+
if( FD_UNLIKELY( gui->summary.is_full_client ) ) {
235+
ulong real_mls = fd_ulong_if( gui->summary.catch_up_repair_sz>0UL, gui->summary.catch_up_repair[ 0 ], 0UL );
236+
uchar prev_phase = gui->summary.startup_progress.phase;
237+
ulong prev_mls = gui->summary.startup_progress.startup_ledger_max_slot;
238+
gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_RUNNING;
239+
gui->summary.startup_progress.startup_ledger_max_slot = real_mls;
240+
fd_gui_printf_startup_progress( gui );
241+
FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
242+
gui->summary.startup_progress.phase = prev_phase;
243+
gui->summary.startup_progress.startup_ledger_max_slot = prev_mls;
244+
}
245+
232246
if( FD_LIKELY( gui->block_engine.has_block_engine ) ) {
233247
fd_gui_printf_block_engine( gui );
234248
FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
@@ -1353,55 +1367,48 @@ fd_gui_clear_slot( fd_gui_t * gui,
13531367
return slot;
13541368
}
13551369

1356-
static void
1357-
fd_gui_handle_leader_schedule( fd_gui_t * gui,
1358-
ulong const * msg,
1359-
long now ) {
1360-
ulong epoch = msg[ 0 ];
1361-
ulong staked_cnt = msg[ 1 ];
1362-
ulong start_slot = msg[ 2 ];
1363-
ulong slot_cnt = msg[ 3 ];
1364-
ulong excluded_stake = msg[ 4 ];
1365-
ulong vote_keyed_lsched = msg[ 5 ];
1366-
1367-
FD_TEST( staked_cnt<=MAX_STAKED_LEADERS );
1368-
FD_TEST( slot_cnt<=MAX_SLOTS_PER_EPOCH );
1369-
1370-
ulong idx = epoch % 2UL;
1370+
void
1371+
fd_gui_handle_leader_schedule( fd_gui_t * gui,
1372+
fd_stake_weight_msg_t const * leader_schedule,
1373+
long now ) {
1374+
FD_TEST( leader_schedule->staked_cnt<=MAX_STAKED_LEADERS );
1375+
FD_TEST( leader_schedule->slot_cnt<=MAX_SLOTS_PER_EPOCH );
1376+
1377+
ulong idx = leader_schedule->epoch % 2UL;
13711378
gui->epoch.has_epoch[ idx ] = 1;
13721379

1373-
gui->epoch.epochs[ idx ].epoch = epoch;
1374-
gui->epoch.epochs[ idx ].start_slot = start_slot;
1375-
gui->epoch.epochs[ idx ].end_slot = start_slot + slot_cnt - 1; // end_slot is inclusive.
1376-
gui->epoch.epochs[ idx ].excluded_stake = excluded_stake;
1380+
gui->epoch.epochs[ idx ].epoch = leader_schedule->epoch;
1381+
gui->epoch.epochs[ idx ].start_slot = leader_schedule->start_slot;
1382+
gui->epoch.epochs[ idx ].end_slot = leader_schedule->start_slot + leader_schedule->slot_cnt - 1; // end_slot is inclusive.
1383+
gui->epoch.epochs[ idx ].excluded_stake = leader_schedule->excluded_stake;
13771384
gui->epoch.epochs[ idx ].my_total_slots = 0UL;
13781385
gui->epoch.epochs[ idx ].my_skipped_slots = 0UL;
13791386

13801387
memset( gui->epoch.epochs[ idx ].rankings, (int)(UINT_MAX), sizeof(gui->epoch.epochs[ idx ].rankings) );
13811388
memset( gui->epoch.epochs[ idx ].my_rankings, (int)(UINT_MAX), sizeof(gui->epoch.epochs[ idx ].my_rankings) );
13821389

1383-
gui->epoch.epochs[ idx ].rankings_slot = start_slot;
1390+
gui->epoch.epochs[ idx ].rankings_slot = leader_schedule->start_slot;
13841391

1385-
fd_vote_stake_weight_t const * stake_weights = fd_type_pun_const( msg+6UL );
1386-
memcpy( gui->epoch.epochs[ idx ].stakes, stake_weights, staked_cnt*sizeof(fd_vote_stake_weight_t) );
1392+
fd_vote_stake_weight_t const * stake_weights = leader_schedule->weights;
1393+
fd_memcpy( gui->epoch.epochs[ idx ].stakes, stake_weights, leader_schedule->staked_cnt*sizeof(fd_vote_stake_weight_t) );
13871394

13881395
fd_epoch_leaders_delete( fd_epoch_leaders_leave( gui->epoch.epochs[ idx ].lsched ) );
13891396
gui->epoch.epochs[idx].lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( gui->epoch.epochs[ idx ]._lsched,
1390-
epoch,
1397+
leader_schedule->epoch,
13911398
gui->epoch.epochs[ idx ].start_slot,
1392-
slot_cnt,
1393-
staked_cnt,
1399+
leader_schedule->slot_cnt,
1400+
leader_schedule->staked_cnt,
13941401
gui->epoch.epochs[ idx ].stakes,
1395-
excluded_stake,
1396-
vote_keyed_lsched ) );
1402+
leader_schedule->excluded_stake,
1403+
leader_schedule->vote_keyed_lsched ) );
13971404

1398-
if( FD_UNLIKELY( start_slot==0UL ) ) {
1405+
if( FD_UNLIKELY( leader_schedule->start_slot==0UL ) ) {
13991406
gui->epoch.epochs[ 0 ].start_time = now;
14001407
} else {
14011408
gui->epoch.epochs[ idx ].start_time = LONG_MAX;
14021409

1403-
for( ulong i=0UL; i<fd_ulong_min( start_slot-1UL, FD_GUI_SLOTS_CNT ); i++ ) {
1404-
fd_gui_slot_t const * slot = fd_gui_get_slot_const( gui, start_slot-i );
1410+
for( ulong i=0UL; i<fd_ulong_min( leader_schedule->start_slot-1UL, FD_GUI_SLOTS_CNT ); i++ ) {
1411+
fd_gui_slot_t const * slot = fd_gui_get_slot_const( gui, leader_schedule->start_slot-i );
14051412
if( FD_UNLIKELY( !slot ) ) break;
14061413
else if( FD_UNLIKELY( slot->skipped ) ) continue;
14071414

@@ -1829,8 +1836,7 @@ fd_gui_handle_rooted_slot_legacy( fd_gui_t * gui,
18291836
if( FD_UNLIKELY( !slot ) ) break;
18301837

18311838
if( FD_UNLIKELY( slot->slot!=parent_slot ) ) {
1832-
FD_LOG_ERR(( "_slot %lu i %lu we expect parent_slot %lu got slot->slot %lu", _slot, i, parent_slot, slot->slot ));
1833-
}
1839+
FD_LOG_ERR(( "_slot %lu i %lu we expect parent_slot %lu got slot->slot %lu", _slot, i, parent_slot, slot->slot )); }
18341840
if( FD_UNLIKELY( slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break;
18351841

18361842
slot->level = FD_GUI_SLOT_LEVEL_ROOTED;
@@ -2072,6 +2078,11 @@ fd_gui_handle_reset_slot( fd_gui_t * gui, ulong reset_slot, long now ) {
20722078
ulong prev_slot_completed = gui->summary.slot_completed;
20732079
gui->summary.slot_completed = reset_slot;
20742080

2081+
if( FD_LIKELY( fd_gui_get_slot( gui, gui->summary.slot_completed ) ) ) {
2082+
fd_gui_printf_slot( gui, gui->summary.slot_completed );
2083+
fd_http_server_ws_broadcast( gui->http );
2084+
}
2085+
20752086
fd_gui_printf_completed_slot( gui );
20762087
fd_http_server_ws_broadcast( gui->http );
20772088

@@ -2183,7 +2194,6 @@ fd_gui_handle_reset_slot( fd_gui_t * gui, ulong reset_slot, long now ) {
21832194

21842195
static void
21852196
fd_gui_handle_rooted_slot( fd_gui_t * gui, ulong root_slot ) {
2186-
// ulong unstaged_cnt = 0UL;
21872197
for( ulong i=0UL; i<fd_ulong_min( root_slot, FD_GUI_SLOTS_CNT ); i++ ) {
21882198
ulong parent_slot = root_slot - i;
21892199

@@ -2195,49 +2205,72 @@ fd_gui_handle_rooted_slot( fd_gui_t * gui, ulong root_slot ) {
21952205
}
21962206
if( FD_UNLIKELY( slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break;
21972207

2198-
/* TODO: commented out due to being too slow */
2199-
// /* archive root shred events */
2200-
// slot->shreds.start_offset = gui->shreds.history_tail;
2201-
// for( ulong i=gui->shreds.staged_head; i<gui->shreds.staged_tail; i++ ) {
2202-
// if( FD_UNLIKELY( gui->shreds.staged[ i ].slot==slot->slot ) ) {
2203-
// /* move event to history */
2204-
// gui->shreds.history[ gui->shreds.history_tail ].timestamp = gui->shreds.staged[ i ].timestamp;
2205-
// gui->shreds.history[ gui->shreds.history_tail ].shred_idx = gui->shreds.staged[ i ].shred_idx;
2206-
// gui->shreds.history[ gui->shreds.history_tail ].event = gui->shreds.staged[ i ].event;
2207-
// gui->shreds.history_tail++;
2208-
2209-
// gui->shreds.staged[ i ].slot = ULONG_MAX;
2210-
// unstaged_cnt++;
2211-
// }
2212-
2213-
// /* evict older slots staged also */
2214-
// if( FD_UNLIKELY( gui->shreds.staged[ i ].slot<slot->slot ) ) {
2215-
// gui->shreds.staged[ i ].slot = ULONG_MAX;
2216-
// unstaged_cnt++;
2217-
// }
2218-
// }
2219-
// slot->shreds.end_offset = gui->shreds.history_tail;
2220-
2221-
// /* change notarization levels and rebroadcast */
2222-
// slot->level = FD_GUI_SLOT_LEVEL_ROOTED;
2223-
// fd_gui_printf_slot( gui, parent_slot );
2224-
// fd_http_server_ws_broadcast( gui->http );
2208+
/* change notarization levels and rebroadcast */
2209+
slot->level = FD_GUI_SLOT_LEVEL_ROOTED;
2210+
fd_gui_printf_slot( gui, parent_slot );
2211+
fd_http_server_ws_broadcast( gui->http );
2212+
}
2213+
2214+
/* archive root shred events. We want to avoid n^2 iteration here
2215+
since it can significantly slow things down. Instead, we copy
2216+
over all rooted shreds to a scratch space, stable sort by slot,
2217+
copy the sorted arrays to the shred history. */
2218+
ulong evicted_cnt = 0UL; /* the total number evicted, including ignored */
2219+
ulong archive_cnt = 0UL; /* the total number evicted, NOT including ignored */
2220+
for( ulong i=gui->shreds.staged_head; i<gui->shreds.staged_tail; i++ ) {
2221+
/* ignore new shred events that came in after their slot was rooted */
2222+
if( FD_UNLIKELY( gui->shreds.history_slot!=ULONG_MAX && gui->shreds.staged[ i ].slot<=gui->shreds.history_slot ) ) {
2223+
gui->shreds.staged[ i ].slot = ULONG_MAX;
2224+
evicted_cnt++;
2225+
}
2226+
2227+
if( FD_UNLIKELY( gui->shreds.staged[ i ].slot<=root_slot ) ) {
2228+
/* move to scratch */
2229+
fd_memcpy( gui->shreds._staged_scratch, &gui->shreds.staged[ i ], sizeof(fd_gui_slot_staged_shred_event_t) );
2230+
archive_cnt++;
2231+
2232+
/* evict from staged */
2233+
gui->shreds.staged[ i ].slot = ULONG_MAX;
2234+
evicted_cnt++;
2235+
}
22252236
}
22262237

22272238
/* The entries from the staging area are evicted by setting their
22282239
slot field to ULONG MAX, then sorting the staging area.
22292240
22302241
IMPORTANT: this sort needs to be stable since we always keep
22312242
valid un-broadcast events at the end of the ring buffer */
2232-
// if( FD_LIKELY( unstaged_cnt ) ) {
2233-
// fd_gui_slot_staged_shred_event_sort_insert( &gui->shreds.staged[ gui->shreds.staged_head ], gui->shreds.staged_tail-gui->shreds.staged_head );
2234-
// gui->shreds.staged_head += unstaged_cnt;
2235-
// }
2236-
2237-
// /* In the rare case that we are archiving any shred events that have
2238-
// not yet been broadcast, we'll increment
2239-
// gui->shreds.staged_next_broadcast to keep it in bounds. */
2240-
// gui->shreds.staged_next_broadcast = fd_ulong_max( gui->shreds.staged_head, gui->shreds.staged_next_broadcast );
2243+
if( FD_LIKELY( evicted_cnt ) ) {
2244+
fd_gui_slot_staged_shred_event_evict_sort_stable( &gui->shreds.staged[ gui->shreds.staged_head ], gui->shreds.staged_tail-gui->shreds.staged_head, gui->shreds._staged_scratch2 );
2245+
gui->shreds.staged_head += evicted_cnt;
2246+
2247+
/* In the rare case that we are archiving any shred events that have
2248+
not yet been broadcast, we'll increment
2249+
gui->shreds.staged_next_broadcast to keep it in bounds. */
2250+
gui->shreds.staged_next_broadcast = fd_ulong_max( gui->shreds.staged_head, gui->shreds.staged_next_broadcast );
2251+
2252+
/* sort scratch by slot increasing */
2253+
fd_gui_slot_staged_shred_event_slot_sort_stable( gui->shreds._staged_scratch, archive_cnt, gui->shreds._staged_scratch2 );
2254+
2255+
/* copy shred events to archive */
2256+
for( ulong i=0UL; i<archive_cnt; i++ ) {
2257+
if( FD_UNLIKELY( gui->shreds._staged_scratch[ i ].slot!=gui->shreds.history_slot ) ) {
2258+
fd_gui_slot_t * prev_slot = fd_gui_get_slot( gui, gui->shreds.history_slot );
2259+
if( FD_LIKELY( prev_slot ) ) prev_slot->shreds.end_offset = gui->shreds.history_tail;
2260+
2261+
gui->shreds.history_slot = gui->shreds._staged_scratch[ i ].slot;
2262+
2263+
fd_gui_slot_t * next_slot = fd_gui_get_slot( gui, gui->shreds.history_slot );
2264+
if( FD_LIKELY( next_slot ) ) next_slot->shreds.start_offset = gui->shreds.history_tail;
2265+
}
2266+
2267+
gui->shreds.history[ gui->shreds.history_tail ].timestamp = gui->shreds._staged_scratch[ i ].timestamp;
2268+
gui->shreds.history[ gui->shreds.history_tail ].shred_idx = gui->shreds._staged_scratch[ i ].shred_idx;
2269+
gui->shreds.history[ gui->shreds.history_tail ].event = gui->shreds._staged_scratch[ i ].event;
2270+
2271+
gui->shreds.history_tail++;
2272+
}
2273+
}
22412274

22422275
gui->summary.slot_rooted = root_slot;
22432276
fd_gui_printf_root_slot( gui );
@@ -2365,7 +2398,8 @@ fd_gui_plugin_message( fd_gui_t * gui,
23652398
break;
23662399
}
23672400
case FD_PLUGIN_MSG_LEADER_SCHEDULE: {
2368-
fd_gui_handle_leader_schedule( gui, (ulong const *)msg, now );
2401+
FD_STATIC_ASSERT( sizeof(fd_stake_weight_msg_t)==6*sizeof(ulong), "sanity check" );
2402+
fd_gui_handle_leader_schedule( gui, (fd_stake_weight_msg_t *)msg, now );
23692403
break;
23702404
}
23712405
case FD_PLUGIN_MSG_SLOT_START: {

src/disco/gui/fd_gui.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,11 +348,16 @@ struct fd_gui_slot_staged_shred_event {
348348

349349
typedef struct fd_gui_slot_staged_shred_event fd_gui_slot_staged_shred_event_t;
350350

351-
#define SORT_NAME fd_gui_slot_staged_shred_event_sort
351+
#define SORT_NAME fd_gui_slot_staged_shred_event_evict_sort
352352
#define SORT_KEY_T fd_gui_slot_staged_shred_event_t
353353
#define SORT_BEFORE(a,b) (__extension__({ (void)(b); (a).slot==ULONG_MAX; }))
354354
#include "../../util/tmpl/fd_sort.c"
355355

356+
#define SORT_NAME fd_gui_slot_staged_shred_event_slot_sort
357+
#define SORT_KEY_T fd_gui_slot_staged_shred_event_t
358+
#define SORT_BEFORE(a,b) ((a).slot<(b).slot)
359+
#include "../../util/tmpl/fd_sort.c"
360+
356361
struct __attribute__((packed)) fd_gui_slot_history_shred_event {
357362
long timestamp;
358363
ushort shred_idx;
@@ -735,6 +740,10 @@ struct fd_gui {
735740
ulong history_slot; /* the largest slot store in history */
736741
ulong history_tail; /* history_tail % FD_GUI_SHREDS_STAGING_SZ is the last valid event in history +1 */
737742
fd_gui_slot_history_shred_event_t history[ FD_GUI_SHREDS_HISTORY_SZ ];
743+
744+
/* scratch space for inplace stable sorts */
745+
fd_gui_slot_staged_shred_event_t _staged_scratch [ FD_GUI_SHREDS_STAGING_SZ ];
746+
fd_gui_slot_staged_shred_event_t _staged_scratch2[ FD_GUI_SHREDS_STAGING_SZ ];
738747
} shreds; /* full client */
739748
};
740749

@@ -840,6 +849,11 @@ void
840849
fd_gui_handle_snapshot_update( fd_gui_t * gui,
841850
fd_snaprd_update_t const * msg );
842851

852+
void
853+
fd_gui_handle_leader_schedule( fd_gui_t * gui,
854+
fd_stake_weight_msg_t const * leader_schedule,
855+
long now );
856+
843857
void
844858
fd_gui_handle_tower_update( fd_gui_t * gui,
845859
fd_tower_slot_done_t const * msg,

0 commit comments

Comments
 (0)