diff --git a/book/api/websocket.md b/book/api/websocket.md index 9b7c8344fb4..9cdbf9c5039 100644 --- a/book/api/websocket.md +++ b/book/api/websocket.md @@ -324,9 +324,11 @@ Frankendancer client will always publish `null` for this message | *Once* | `CatchUpHistory` | see below | This validator records a history of all slots that were received from -turbine as well as slots for which a repair request was made while it is +turbine or repair responses, as well as shred events that occurred while catching up. After catching up, slots are no longer recorded in this -history. +history. For repair and turbine slots, the history is available for the +lifetime of the validator. Shred events are only available if the +validator is in the catching up phase. ::: details Example @@ -336,13 +338,29 @@ history. "key": "catch_up_history", "value": { "repair": [11, 12, 13, ...], - "turbine": [21, 22, 23, ...] + "turbine": [21, 22, 23, ...], + "shreds": { + "reference_slot": 289245044, + "reference_ts": "1739657041588242791", + "slot_delta": [0, 0], + "shred_idx": [1234, null], + "event": [0, 1], + "event_ts_delta": ["1000000", "2000000"] + } } } ``` ::: +**`CatchUpHistory`** +| Field | Type | Description | +|------------|---------------|-------------| +| repair | `number[]` | A list of all slots for which a repair shred was received that are older than `summary.caught_up_slot` | +| turbine | `number[]` | A list of all slots for which a turbine shred was received that are older than `summary.caught_up_slot` | +| shreds | `SlotShreds` | A list of shred events which have occurred for this validator in the past 15 seconds. If the validator has already caught up, or has not yet started catching up, then `null` | + + #### `summary.startup_time_nanos` | frequency | type | example | |-----------|----------|---------------------| @@ -1594,7 +1612,7 @@ rooted. #### `slot.live_shreds` | frequency | type | example | |-------------|---------------|---------| -| *10ms* | `SlotShred[]` | below | +| *10ms* | `SlotShreds` | below | The validator sends a continous stream of update messages with detailed information about the time and duration of different shred state @@ -1620,7 +1638,7 @@ and is broadcast to all WebSocket clients. ::: -**`SlotShred`** +**`SlotShreds`** | Field | Type | Description | |-----------------|--------------------|-------------| | reference_slot | `number`   | The smallest slot number across all the shreds in a given message | @@ -1633,7 +1651,7 @@ and is broadcast to all WebSocket clients. #### `slot.query_shreds` | frequency | type | example | |-------------|---------------|---------| -| *Request* | `SlotShred[]\null` | below | +| *Request* | `SlotShreds\|null` | below | | param | type | description | |-------|----------|-------------| diff --git a/src/disco/gui/fd_gui.c b/src/disco/gui/fd_gui.c index 0c39d405b6c..f662b291254 100644 --- a/src/disco/gui/fd_gui.c +++ b/src/disco/gui/fd_gui.c @@ -1284,7 +1284,7 @@ fd_gui_request_slot_shreds( fd_gui_t * gui, fd_gui_slot_t const * slot = fd_gui_get_slot( gui, _slot ); if( FD_UNLIKELY( !slot || gui->shreds.history_tail > slot->shreds.end_offset + FD_GUI_SHREDS_HISTORY_SZ ) ) { - fd_gui_printf_null_query_response( gui->http, "slot", "query_rankings", request_id ); + fd_gui_printf_null_query_response( gui->http, "slot", "query_shreds", request_id ); FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); return 0; } diff --git a/src/disco/gui/fd_gui_printf.c b/src/disco/gui/fd_gui_printf.c index 2c209d4f88c..c689bad86ee 100644 --- a/src/disco/gui/fd_gui_printf.c +++ b/src/disco/gui/fd_gui_printf.c @@ -275,6 +275,112 @@ fd_gui_printf_catch_up_history( fd_gui_t * gui ) { } } jsonp_close_array( gui->http ); + + if( FD_LIKELY( gui->summary.boot_progress.phase==FD_GUI_BOOT_PROGRESS_TYPE_CATCHING_UP ) ) { + ulong min_slot = ULONG_MAX; + long min_ts = LONG_MAX; + + for( ulong i=gui->shreds.staged_tail; i>0UL && i>gui->shreds.staged_head; i-- ) { + long ts = gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].timestamp; + if( FD_UNLIKELY( ts < gui->summary.boot_progress.catching_up_time_nanos - 15000000000 ) ) break; + ulong slot = gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].slot; + min_slot = fd_ulong_min( min_slot, slot ); + min_ts = fd_long_min( min_ts, ts ); + } + + fd_gui_slot_t * s = fd_gui_get_slot( gui, gui->shreds.history_slot ); + while( s + && s->shreds.start_offset!=ULONG_MAX + && s->shreds.end_offset!=ULONG_MAX + && gui->shreds.history[ s->shreds.start_offset % FD_GUI_SHREDS_HISTORY_SZ ].timestamp < (gui->summary.boot_progress.catching_up_time_nanos - 15000000000) ) { + min_slot = fd_ulong_min( min_slot, s->slot ); + min_ts = fd_long_min( min_ts, gui->shreds.history[ s->shreds.start_offset % FD_GUI_SHREDS_HISTORY_SZ ].timestamp ); + s = fd_gui_get_slot( gui, s->parent_slot ); + } + + jsonp_open_object( gui->http, "shreds" ); + jsonp_ulong ( gui->http, "reference_slot", min_slot ); + jsonp_long_as_str( gui->http, "reference_ts", min_ts ); + + jsonp_open_array( gui->http, "slot_delta" ); + for( ulong i=gui->shreds.staged_tail; i>0UL && i>gui->shreds.staged_head; i-- ) { + long ts = gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].timestamp; + if( FD_UNLIKELY( ts < gui->summary.boot_progress.catching_up_time_nanos - 15000000000 ) ) break; + jsonp_ulong( gui->http, NULL, gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].slot-min_slot ); + } + + s = fd_gui_get_slot( gui, gui->shreds.history_slot ); + while( s + && s->shreds.start_offset!=ULONG_MAX + && s->shreds.end_offset!=ULONG_MAX + && gui->shreds.history[ s->shreds.start_offset % FD_GUI_SHREDS_HISTORY_SZ ].timestamp < (gui->summary.boot_progress.catching_up_time_nanos - 15000000000) ) { + for( ulong i=s->shreds.start_offset; ishreds.end_offset; i++ ) { + jsonp_ulong( gui->http, NULL, s->slot-min_slot ); + } + s = fd_gui_get_slot( gui, s->parent_slot ); + } + jsonp_close_array( gui->http ); + jsonp_open_array( gui->http, "shred_idx" ); + for( ulong i=gui->shreds.staged_tail; i>0UL && i>gui->shreds.staged_head; i-- ) { + long ts = gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].timestamp; + if( FD_UNLIKELY( ts < gui->summary.boot_progress.catching_up_time_nanos - 15000000000 ) ) break; + if( FD_LIKELY( gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].shred_idx!=USHORT_MAX ) ) jsonp_ulong( gui->http, NULL, gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].shred_idx ); + else jsonp_null ( gui->http, NULL ); + } + + s = fd_gui_get_slot( gui, gui->shreds.history_slot ); + while( s + && s->shreds.start_offset!=ULONG_MAX + && s->shreds.end_offset!=ULONG_MAX + && gui->shreds.history[ s->shreds.start_offset % FD_GUI_SHREDS_HISTORY_SZ ].timestamp < (gui->summary.boot_progress.catching_up_time_nanos - 15000000000) ) { + for( ulong i=s->shreds.start_offset; ishreds.end_offset; i++ ) { + if( FD_LIKELY( gui->shreds.history[ i % FD_GUI_SHREDS_HISTORY_SZ ].shred_idx!=USHORT_MAX ) ) jsonp_ulong( gui->http, NULL, gui->shreds.history[ i % FD_GUI_SHREDS_HISTORY_SZ ].shred_idx ); + else jsonp_null ( gui->http, NULL ); + } + s = fd_gui_get_slot( gui, s->parent_slot ); + } + jsonp_close_array( gui->http ); + jsonp_open_array( gui->http, "event" ); + for( ulong i=gui->shreds.staged_tail; i>0UL && i>gui->shreds.staged_head; i-- ) { + long ts = gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].timestamp; + if( FD_UNLIKELY( ts < gui->summary.boot_progress.catching_up_time_nanos - 15000000000 ) ) break; + jsonp_ulong( gui->http, NULL, gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].event ); + } + + s = fd_gui_get_slot( gui, gui->shreds.history_slot ); + while( s + && s->shreds.start_offset!=ULONG_MAX + && s->shreds.end_offset!=ULONG_MAX + && gui->shreds.history[ s->shreds.start_offset % FD_GUI_SHREDS_HISTORY_SZ ].timestamp < (gui->summary.boot_progress.catching_up_time_nanos - 15000000000) ) { + for( ulong i=s->shreds.start_offset; ishreds.end_offset; i++ ) { + jsonp_ulong( gui->http, NULL, gui->shreds.history[ i % FD_GUI_SHREDS_HISTORY_SZ ].event ); + } + s = fd_gui_get_slot( gui, s->parent_slot ); + } + jsonp_close_array( gui->http ); + jsonp_open_array( gui->http, "event_ts_delta" ); + for( ulong i=gui->shreds.staged_tail; i>0UL && i>gui->shreds.staged_head; i-- ) { + long ts = gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].timestamp; + if( FD_UNLIKELY( ts < gui->summary.boot_progress.catching_up_time_nanos - 15000000000 ) ) break; + jsonp_long_as_str( gui->http, NULL, gui->shreds.staged[ (i-1UL) % FD_GUI_SHREDS_STAGING_SZ ].timestamp-min_ts ); + } + + s = fd_gui_get_slot( gui, gui->shreds.history_slot ); + while( s + && s->shreds.start_offset!=ULONG_MAX + && s->shreds.end_offset!=ULONG_MAX + && gui->shreds.history[ s->shreds.start_offset % FD_GUI_SHREDS_HISTORY_SZ ].timestamp < (gui->summary.boot_progress.catching_up_time_nanos - 15000000000) ) { + for( ulong i=s->shreds.start_offset; ishreds.end_offset; i++ ) { + jsonp_long_as_str( gui->http, NULL, gui->shreds.history[ i % FD_GUI_SHREDS_HISTORY_SZ ].timestamp-min_ts ); + } + s = fd_gui_get_slot( gui, s->parent_slot ); + } + jsonp_close_array( gui->http ); + jsonp_close_object( gui->http ); + } else { + jsonp_null( gui->http, "shreds" ); + } + jsonp_close_object( gui->http ); jsonp_close_envelope( gui->http ); } @@ -2120,37 +2226,45 @@ void fd_gui_printf_slot_shred_updates( fd_gui_t * gui, ulong _slot, ulong id ) { - ulong _start_offset = gui->slots[ _slot % FD_GUI_SLOTS_CNT ]->shreds.start_offset; - ulong _end_offset = gui->slots[ _slot % FD_GUI_SLOTS_CNT ]->shreds.end_offset; + fd_gui_slot_t * slot = fd_gui_get_slot( gui, _slot ); + + if( FD_UNLIKELY( !slot || slot->shreds.end_offset <= gui->shreds.history_tail-FD_GUI_SHREDS_HISTORY_SZ ) ) { + jsonp_open_envelope( gui->http, "slot", "query_shreds" ); + jsonp_ulong( gui->http, "id", id ); + jsonp_null( gui->http, "value" ); + jsonp_close_envelope( gui->http ); + return; + } + + ulong _start_offset = slot->shreds.start_offset; + ulong _end_offset = slot->shreds.end_offset; - ulong min_slot = ULONG_MAX; long min_ts = LONG_MAX; for( ulong i=_start_offset; i<_end_offset; i++ ) { - min_slot = fd_ulong_min( min_slot, gui->shreds.staged[ i % FD_GUI_SHREDS_STAGING_SZ ].slot ); - min_ts = fd_long_min ( min_ts, gui->shreds.staged[ i % FD_GUI_SHREDS_STAGING_SZ ].timestamp ); + min_ts = fd_long_min ( min_ts, gui->shreds.history[ i % FD_GUI_SHREDS_HISTORY_SZ ].timestamp ); } jsonp_open_envelope( gui->http, "slot", "query_shreds" ); jsonp_ulong( gui->http, "id", id ); jsonp_open_object( gui->http, "value" ); - jsonp_ulong ( gui->http, "reference_slot", min_slot ); + jsonp_ulong ( gui->http, "reference_slot", _slot ); jsonp_long_as_str( gui->http, "reference_ts", min_ts ); jsonp_open_array( gui->http, "slot_delta" ); - for( ulong i=_start_offset; i<_end_offset; i++ ) jsonp_ulong( gui->http, NULL, gui->shreds.staged[ i % FD_GUI_SHREDS_STAGING_SZ ].slot-min_slot ); + for( ulong i=_start_offset; i<_end_offset; i++ ) jsonp_ulong( gui->http, NULL, 0UL ); jsonp_close_array( gui->http ); jsonp_open_array( gui->http, "shred_idx" ); for( ulong i=_start_offset; i<_end_offset; i++ ) { - if( FD_LIKELY( gui->shreds.staged[ i % FD_GUI_SHREDS_STAGING_SZ ].shred_idx!=USHORT_MAX ) ) jsonp_ulong( gui->http, NULL, gui->shreds.staged[ i % FD_GUI_SHREDS_STAGING_SZ ].shred_idx ); + if( FD_LIKELY( gui->shreds.staged[ i % FD_GUI_SHREDS_STAGING_SZ ].shred_idx!=USHORT_MAX ) ) jsonp_ulong( gui->http, NULL, gui->shreds.history[ i % FD_GUI_SHREDS_HISTORY_SZ ].shred_idx ); else jsonp_null ( gui->http, NULL ); } jsonp_close_array( gui->http ); jsonp_open_array( gui->http, "event" ); - for( ulong i=_start_offset; i<_end_offset; i++ ) jsonp_ulong( gui->http, NULL, gui->shreds.staged[ i % FD_GUI_SHREDS_STAGING_SZ ].event ); + for( ulong i=_start_offset; i<_end_offset; i++ ) jsonp_ulong( gui->http, NULL, gui->shreds.history[ i % FD_GUI_SHREDS_HISTORY_SZ ].event ); jsonp_close_array( gui->http ); jsonp_open_array( gui->http, "event_ts_delta" ); - for( ulong i=_start_offset; i<_end_offset; i++ ) jsonp_long_as_str( gui->http, NULL, gui->shreds.staged[ i % FD_GUI_SHREDS_STAGING_SZ ].timestamp-min_ts ); + for( ulong i=_start_offset; i<_end_offset; i++ ) jsonp_long_as_str( gui->http, NULL, gui->shreds.history[ i % FD_GUI_SHREDS_HISTORY_SZ ].timestamp-min_ts ); jsonp_close_array( gui->http ); jsonp_close_object( gui->http ); jsonp_close_envelope( gui->http );