Skip to content

Commit

Permalink
gui: add support for dynamic cluster discovery.
Browse files Browse the repository at this point in the history
  • Loading branch information
jherrera-jump authored and ptaffet-jump committed Jan 30, 2025
1 parent e61f9a3 commit 0252cc3
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 60 deletions.
2 changes: 1 addition & 1 deletion agave
15 changes: 10 additions & 5 deletions book/api/websocket.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,20 @@ application level ping/pong and not a WebSocket control frame.
The current version of the running validator.

#### `summary.cluster`
| frequency | type | example |
|-----------|----------|----------------|
| *Once* | `string` | `"mainnet-beta"` |
| frequency | type | example |
|-----------------|----------|----------------|
| *Once* + *Live* | `string` | `"mainnet-beta"` |

One of `mainnet-beta`, `devnet`, `testnet`, `pythtest`, `pythnet`,
`development`, or `unknown`. Indicates the cluster that the validator is
likely to be running on. The cluster is guessed by looking at the
genesis hash of the chain and entrypoints that the validator connects
to.
genesis hash of the chain and comparing it to known cluster genesis
hashes. The cluster cannot change once the validator is running, but
because it may not be known when the validator first starts, you
might get two cluster messages. One `unknown` immediately when the
validator is booted, and then an a message with `mainnet` (or other
known cluster) when the validator learns its cluster from a downloaded
snapshot.

#### `summary.identity_key`
| frequency | type | example |
Expand Down
56 changes: 6 additions & 50 deletions src/app/fdctl/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "../../ballet/toml/fd_toml.h"
#include "../../disco/topo/fd_pod_format.h"
#include "../../flamenco/genesis/fd_genesis_cluster.h"
#include "../../flamenco/runtime/fd_blockstore.h"
#include "../../flamenco/runtime/fd_txncache.h"
#include "../../funk/fd_funk.h"
Expand Down Expand Up @@ -348,38 +349,6 @@ validate_ports( config_t * result ) {
result->dynamic_port_range ));
}

/* These CLUSTER_* values must be ordered from least important to most
important network. Eg, it's important that if a config has the
MAINNET_BETA genesis hash, but has a bunch of entrypoints that we
recognize as TESTNET, we classify it as MAINNET_BETA so we can be
maximally restrictive. This is done by a high-to-low comparison. */
#define FD_CONFIG_CLUSTER_UNKNOWN (0UL)
#define FD_CONFIG_CLUSTER_PYTHTEST (1UL)
#define FD_CONFIG_CLUSTER_TESTNET (2UL)
#define FD_CONFIG_CLUSTER_DEVNET (3UL)
#define FD_CONFIG_CLUSTER_PYTHNET (4UL)
#define FD_CONFIG_CLUSTER_MAINNET_BETA (5UL)

FD_FN_PURE static ulong
determine_cluster( char * expected_genesis_hash ) {
char const * DEVNET_GENESIS_HASH = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
char const * TESTNET_GENESIS_HASH = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY";
char const * MAINNET_BETA_GENESIS_HASH = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
char const * PYTHTEST_GENESIS_HASH = "EkCkB7RWVrgkcpariRpd3pjf7GwiCMZaMHKUpB5Na1Ve";
char const * PYTHNET_GENESIS_HASH = "GLKkBUr6r72nBtGrtBPJLRqtsh8wXZanX4xfnqKnWwKq";

ulong cluster = FD_CONFIG_CLUSTER_UNKNOWN;
if( FD_LIKELY( expected_genesis_hash ) ) {
if( FD_UNLIKELY( !strcmp( expected_genesis_hash, DEVNET_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_DEVNET;
else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, TESTNET_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_TESTNET;
else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, MAINNET_BETA_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_MAINNET_BETA;
else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, PYTHTEST_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_PYTHTEST;
else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, PYTHNET_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_PYTHNET;
}

return cluster;
}

FD_FN_CONST static int
parse_log_level( char const * level ) {
if( FD_UNLIKELY( !strcmp( level, "DEBUG" ) ) ) return 0;
Expand All @@ -393,19 +362,6 @@ parse_log_level( char const * level ) {
return -1;
}

FD_FN_CONST static char *
cluster_to_cstr( ulong cluster ) {
switch( cluster ) {
case FD_CONFIG_CLUSTER_UNKNOWN: return "unknown";
case FD_CONFIG_CLUSTER_PYTHTEST: return "pythtest";
case FD_CONFIG_CLUSTER_TESTNET: return "testnet";
case FD_CONFIG_CLUSTER_DEVNET: return "devnet";
case FD_CONFIG_CLUSTER_PYTHNET: return "pythnet";
case FD_CONFIG_CLUSTER_MAINNET_BETA: return "mainnet-beta";
default: return "unknown";
}
}

static char *
default_user( void ) {
char * name = getenv( "SUDO_USER" );
Expand Down Expand Up @@ -503,8 +459,8 @@ fdctl_cfg_from_env( int * pargc,
FD_LOG_ERR(( "could not get name of interface with index %d", ifindex ));
}

ulong cluster = determine_cluster( config->consensus.expected_genesis_hash );
config->is_live_cluster = cluster != FD_CONFIG_CLUSTER_UNKNOWN;
ulong cluster = fd_genesis_cluster_identify( config->consensus.expected_genesis_hash );
config->is_live_cluster = cluster != FD_CLUSTER_UNKNOWN;

if( FD_UNLIKELY( config->development.netns.enabled ) ) {
/* not currently supporting multihoming on netns */
Expand Down Expand Up @@ -669,17 +625,17 @@ fdctl_cfg_from_env( int * pargc,
replace( config->consensus.authorized_voter_paths[ i ], "{name}", config->name );
}

strcpy( config->cluster, cluster_to_cstr( cluster ) );
strcpy( config->cluster, fd_genesis_cluster_name( cluster ) );

#ifdef FD_HAS_NO_AGAVE
if( FD_UNLIKELY( config->is_live_cluster && cluster!=FD_CONFIG_CLUSTER_TESTNET ) )
if( FD_UNLIKELY( config->is_live_cluster && cluster!=FD_CLUSTER_TESTNET ) )
FD_LOG_ERR(( "Attempted to start against live cluster `%s`. Firedancer is not "
"ready for production deployment, has not been tested, and is "
"missing consensus critical functionality. Joining a live Solana "
"cluster may destabilize the network. Please do not attempt. You "
"can start against the testnet cluster by specifying the testnet "
"entrypoints from https://docs.solana.com/clusters under "
"[gossip.entrypoints] in your configuration file.", cluster_to_cstr( cluster ) ));
"[gossip.entrypoints] in your configuration file.", fd_genesis_cluster_name( cluster ) ));
#endif

if( FD_LIKELY( config->is_live_cluster) ) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/fdctl/run/tiles/fd_plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ after_frag( fd_plugin_ctx_t * ctx,
switch( in_idx ) {
/* replay_plugin */
case 0UL: {
FD_TEST( sig==FD_PLUGIN_MSG_SLOT_ROOTED || sig==FD_PLUGIN_MSG_SLOT_OPTIMISTICALLY_CONFIRMED || sig==FD_PLUGIN_MSG_SLOT_COMPLETED || sig==FD_PLUGIN_MSG_SLOT_RESET || sig==FD_PLUGIN_MSG_START_PROGRESS );
FD_TEST( sig==FD_PLUGIN_MSG_SLOT_ROOTED || sig==FD_PLUGIN_MSG_SLOT_OPTIMISTICALLY_CONFIRMED || sig==FD_PLUGIN_MSG_SLOT_COMPLETED || sig==FD_PLUGIN_MSG_SLOT_RESET || sig==FD_PLUGIN_MSG_START_PROGRESS || sig==FD_PLUGIN_MSG_GENESIS_HASH_KNOWN );
break;
}
/* gossip_plugin */
Expand Down
9 changes: 8 additions & 1 deletion src/app/fdctl/run/tiles/fd_poh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1880,9 +1880,16 @@ fd_ext_plugin_publish_replay_stage( ulong sig,
}

void
fd_ext_plugin_publish_start_progress( ulong sig,
fd_ext_plugin_publish_genesis_hash( ulong sig,
uchar * data,
ulong data_len ) {
poh_link_publish( &replay_plugin, sig, data, data_len );
}

void
fd_ext_plugin_publish_start_progress( ulong sig,
uchar * data,
ulong data_len ) {
poh_link_publish( &start_progress_plugin, sig, data, data_len );
}

Expand Down
19 changes: 19 additions & 0 deletions src/disco/gui/fd_gui.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "../../ballet/base58/fd_base58.h"
#include "../../ballet/json/cJSON.h"
#include "../../flamenco/genesis/fd_genesis_cluster.h"

FD_FN_CONST ulong
fd_gui_align( void ) {
Expand Down Expand Up @@ -1437,6 +1438,20 @@ fd_gui_handle_start_progress( fd_gui_t * gui,
fd_http_server_ws_broadcast( gui->http );
}

static void
fd_gui_handle_genesis_hash( fd_gui_t * gui,
uchar const * msg ) {
FD_BASE58_ENCODE_32_BYTES(msg, hash_cstr);
ulong cluster = fd_genesis_cluster_identify(hash_cstr);
char const * cluster_name = fd_genesis_cluster_name(cluster);

if( FD_LIKELY( strcmp( gui->summary.cluster, cluster_name ) ) ) {
gui->summary.cluster = fd_genesis_cluster_name(cluster);
fd_gui_printf_cluster( gui );
fd_http_server_ws_broadcast( gui->http );
}
}

void
fd_gui_plugin_message( fd_gui_t * gui,
ulong plugin_msg,
Expand Down Expand Up @@ -1493,6 +1508,10 @@ fd_gui_plugin_message( fd_gui_t * gui,
fd_gui_handle_start_progress( gui, msg );
break;
}
case FD_PLUGIN_MSG_GENESIS_HASH_KNOWN: {
fd_gui_handle_genesis_hash( gui, msg );
break;
}
default:
FD_LOG_ERR(( "Unhandled plugin msg: 0x%lx", plugin_msg ));
break;
Expand Down
1 change: 1 addition & 0 deletions src/disco/plugin/fd_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ typedef struct {
#define FD_PLUGIN_MSG_SLOT_RESET (10UL)
#define FD_PLUGIN_MSG_BALANCE (11UL)
#define FD_PLUGIN_MSG_START_PROGRESS (12UL)
#define FD_PLUGIN_MSG_GENESIS_HASH_KNOWN (13UL)

struct __attribute__((packed, aligned(8))) fd_replay_complete_msg {
ulong slot;
Expand Down
4 changes: 2 additions & 2 deletions src/flamenco/genesis/Local.mk
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ifdef FD_HAS_INT128
$(call add-hdrs,fd_genesis_create.h)
$(call add-objs,fd_genesis_create,fd_flamenco)
$(call add-hdrs,fd_genesis_create.h fd_genesis_cluster.h)
$(call add-objs,fd_genesis_create fd_genesis_cluster,fd_flamenco)
ifdef FD_HAS_HOSTED
$(call make-unit-test,test_genesis_create,test_genesis_create,fd_flamenco fd_funk fd_ballet fd_util)
$(call run-unit-test,test_genesis_create)
Expand Down
34 changes: 34 additions & 0 deletions src/flamenco/genesis/fd_genesis_cluster.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "fd_genesis_cluster.h"

FD_FN_PURE ulong
fd_genesis_cluster_identify( char const * expected_genesis_hash ) {
char const * DEVNET_GENESIS_HASH = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
char const * TESTNET_GENESIS_HASH = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY";
char const * MAINNET_BETA_GENESIS_HASH = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
char const * PYTHTEST_GENESIS_HASH = "EkCkB7RWVrgkcpariRpd3pjf7GwiCMZaMHKUpB5Na1Ve";
char const * PYTHNET_GENESIS_HASH = "GLKkBUr6r72nBtGrtBPJLRqtsh8wXZanX4xfnqKnWwKq";

ulong cluster = FD_CLUSTER_UNKNOWN;
if( FD_LIKELY( expected_genesis_hash ) ) {
if( FD_UNLIKELY( !strcmp( expected_genesis_hash, DEVNET_GENESIS_HASH ) ) ) cluster = FD_CLUSTER_DEVNET;
else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, TESTNET_GENESIS_HASH ) ) ) cluster = FD_CLUSTER_TESTNET;
else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, MAINNET_BETA_GENESIS_HASH ) ) ) cluster = FD_CLUSTER_MAINNET_BETA;
else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, PYTHTEST_GENESIS_HASH ) ) ) cluster = FD_CLUSTER_PYTHTEST;
else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, PYTHNET_GENESIS_HASH ) ) ) cluster = FD_CLUSTER_PYTHNET;
}

return cluster;
}

FD_FN_CONST char const *
fd_genesis_cluster_name( ulong cluster ) {
switch( cluster ) {
case FD_CLUSTER_UNKNOWN: return "unknown";
case FD_CLUSTER_PYTHTEST: return "pythtest";
case FD_CLUSTER_TESTNET: return "testnet";
case FD_CLUSTER_DEVNET: return "devnet";
case FD_CLUSTER_PYTHNET: return "pythnet";
case FD_CLUSTER_MAINNET_BETA: return "mainnet-beta";
default: return "unknown";
}
}
33 changes: 33 additions & 0 deletions src/flamenco/genesis/fd_genesis_cluster.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef HEADER_fd_src_flamenco_genesis_fd_genesis_cluster_h
#define HEADER_fd_src_flamenco_genesis_fd_genesis_cluster_h

#include "../types/fd_types.h"

#define FD_CLUSTER_UNKNOWN (0UL)
#define FD_CLUSTER_PYTHTEST (1UL)
#define FD_CLUSTER_TESTNET (2UL)
#define FD_CLUSTER_DEVNET (3UL)
#define FD_CLUSTER_PYTHNET (4UL)
#define FD_CLUSTER_MAINNET_BETA (5UL)

/* Convert a base58 encoded hash to a FD_CLUSTER_* macro.
genesis_hash should point to a non-NULL cstr. It expects a
base58 encoded hash, which will be compared against known hash
values for public clusters. If a match isn't found, this function
returns FD_CLUSTER_UNKNOWN */

FD_FN_PURE ulong
fd_genesis_cluster_identify( char const * genesis_hash );

/* Convert a FD_CLUSTER_* macro to its corresponding cstr.
This function returns the human-readable name associated with cluster
as a cstr with a static lifetime. For example, FD_CLUSTER_TESTNET
resolves to "testnet". If cluster is not a FD_CLUSTER_* macro,
this function returns "unknown" */

FD_FN_CONST char const *
fd_genesis_cluster_name( ulong cluster );

#endif /* HEADER_fd_src_flamenco_genesis_fd_genesis_cluster_h */

0 comments on commit 0252cc3

Please sign in to comment.