Skip to content

Commit

Permalink
feat(eqvoc): equivocation tile
Browse files Browse the repository at this point in the history
Introduce a tile and logic (fd_eqvoc) to detect equivocating shreds.

There are 2 cases to consider:
1. Equivocation for the same FEC set
2. Equivocation for overlapping FEC sets

The first case is trivially checked by making sure the signatures of
all shreds in the same FEC set are the same.

The second case is trickier. Two (or more) FEC sets might overlap in
their shred indices. Because there are two separate FEC sets, we expect
the signatures to be different. However, this still implies
equivocation, because in order for FEC sets to overlap the same shred
idx occurs twice. See fd_eqvoc.h for details.

The tile is currently mostly a stub to setup the links and topo. The
full impl will come with subsequent PRs.
  • Loading branch information
lidatong committed Oct 16, 2024
1 parent cd11f18 commit b5939a0
Show file tree
Hide file tree
Showing 21 changed files with 755 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ deps-bundle.tar.zst
# TVU
testnet.toml
mainnet.toml
localnet.toml
sim.toml
backtest.toml
testnet.sh
mainnet.sh
localnet.sh
sim.sh
backtest.sh
2 changes: 1 addition & 1 deletion contrib/test/setup_fd_cluster_stakes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ solana -u $RPC_URL --keypair fd-identity-keypair-2.json create-stake-account fd-
solana -u $RPC_URL --keypair fd-identity-keypair-2.json delegate-stake fd-stake-keypair-2.json fd-vote-keypair-2.json

solana -u $RPC_URL --keypair fd-identity-keypair-2.json vote-account fd-vote-keypair-2.json
solana -u $RPC_URL --keypair fd-identity-keypair-2.json stake-account fd-stake-keypair-2.json
solana -u $RPC_URL --keypair fd-identity-keypair-2.json stake-account fd-stake-keypair-2.json
1 change: 1 addition & 0 deletions contrib/test/test_firedancer_leader.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ name = \"fd1\"
[log]
path = \"fddev.log\"
level_stderr = \"INFO\"
level_logfile = \"NOTICE\"
level_flush = \"ERR\"
[consensus]
vote = true
Expand Down
2 changes: 2 additions & 0 deletions src/app/fdctl/Local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ $(call add-objs,run/tiles/fd_replay,fd_fdctl)
$(call add-objs,run/tiles/fd_replay_thread,fd_fdctl)
$(call add-objs,run/tiles/fd_poh_int,fd_fdctl)
$(call add-objs,run/tiles/fd_sender,fd_fdctl)
$(call add-objs,run/tiles/fd_eqvoc,fd_fdctl)
endif

# fdctl topologies
Expand Down Expand Up @@ -96,6 +97,7 @@ $(OBJDIR)/obj/app/fdctl/run/tiles/fd_gossip.o: src/app/fdctl/run/tiles/generated
$(OBJDIR)/obj/app/fdctl/run/tiles/fd_store_int.o: src/app/fdctl/run/tiles/generated/store_int_seccomp.h
$(OBJDIR)/obj/app/fdctl/run/tiles/fd_replay.o: src/app/fdctl/run/tiles/generated/replay_seccomp.h
$(OBJDIR)/obj/app/fdctl/run/tiles/fd_sender.o: src/app/fdctl/run/tiles/generated/sender_seccomp.h
$(OBJDIR)/obj/app/fdctl/run/tiles/fd_eqvoc.o: src/app/fdctl/run/tiles/generated/eqvoc_seccomp.h
endif

check-agave-hash:
Expand Down
18 changes: 18 additions & 0 deletions src/app/fdctl/run/tiles/eqvoc.seccomppolicy
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# logfile_fd: It can be disabled by configuration, but typically tiles
# will open a log file on boot and write all messages there.
unsigned int logfile_fd

# logging: all log messages are written to a file and/or pipe
#
# 'WARNING' and above are written to the STDERR pipe, while all messages
# are always written to the log file.
#
# arg 0 is the file descriptor to write to. The boot process ensures
# that descriptor 2 is always STDERR.
write: (or (eq (arg 0) 2)
(eq (arg 0) logfile_fd))

# logging: 'WARNING' and above fsync the logfile to disk immediately
#
# arg 0 is the file descriptor to fsync.
fsync: (eq (arg 0) logfile_fd)
263 changes: 263 additions & 0 deletions src/app/fdctl/run/tiles/fd_eqvoc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
#define _GNU_SOURCE

#include "../../../../disco/tiles.h"

#include "../../../../choreo/fd_choreo.h"
#include "../../../../flamenco/fd_flamenco.h"
#include "../../../../flamenco/leaders/fd_leaders.h"
#include "../../../../flamenco/repair/fd_repair.h"
#include "../../../../flamenco/runtime/fd_blockstore.h"
#include "../../../../util/fd_util.h"
#include "generated/eqvoc_seccomp.h"

#include <arpa/inet.h>
#include <linux/unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/random.h>
#include <sys/socket.h>
#include <unistd.h>

#include "../../../../disco/fd_disco.h"
#include "../../../../disco/keyguard/fd_keyguard.h"
#include "../../../../disco/keyguard/fd_keyload.h"
#include "../../../../disco/shred/fd_stake_ci.h"
#include "../../../../disco/store/fd_store.h"
#include "../../../../disco/topo/fd_pod_format.h"
#include "../../../../flamenco/leaders/fd_leaders.h"
#include "../../../../flamenco/runtime/fd_runtime.h"
#include "../../../../util/net/fd_eth.h"
#include "../../../../util/net/fd_ip4.h"
#include "../../../../util/net/fd_udp.h"

#include "../../../../util/net/fd_net_headers.h"

#define SCRATCH_MAX ( 4UL /*KiB*/ << 10 )
#define SCRATCH_DEPTH ( 4UL ) /* 4 scratch frames */

struct fd_eqvoc_tile_ctx {
fd_pubkey_t identity_key[1];

fd_stake_ci_t * stake_ci;
fd_shred_dest_weighted_t * new_dest_ptr;
ulong new_dest_cnt;

ulong contact_in_idx;
fd_wksp_t * contact_in_mem;
ulong contact_in_chunk0;
ulong contact_in_wmark;

fd_shred_t shred;

ulong shred_net_in_idx;
fd_wksp_t * shred_net_in_mem;
ulong shred_net_in_chunk0;
ulong shred_net_in_wmark;
};
typedef struct fd_eqvoc_tile_ctx fd_eqvoc_tile_ctx_t;

FD_FN_CONST static inline ulong
scratch_align( void ) {
return 128UL;
}

FD_FN_PURE static inline ulong
loose_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
return 0UL;
}

FD_FN_PURE static inline ulong
scratch_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
/* clang-format off */
ulong l = FD_LAYOUT_INIT;
l = FD_LAYOUT_APPEND( l, alignof(fd_eqvoc_tile_ctx_t), sizeof(fd_eqvoc_tile_ctx_t) );
l = FD_LAYOUT_APPEND( l, fd_stake_ci_align(), fd_stake_ci_footprint() );
// l = FD_LAYOUT_APPEND( l, fd_eqvoc_align(), fd_eqvoc_footprint( FD_EQVOC_MAX ) );
return FD_LAYOUT_FINI( l, scratch_align() );
/* clang-format on */
}

FD_FN_CONST static inline void *
mux_ctx( void * scratch ) {
return (void *)fd_ulong_align_up( (ulong)scratch, alignof( fd_eqvoc_tile_ctx_t ) );
}

static inline void
handle_new_cluster_contact_info( fd_eqvoc_tile_ctx_t * ctx, uchar const * buf, ulong buf_sz ) {
ulong const * header = (ulong const *)fd_type_pun_const( buf );

ulong dest_cnt = buf_sz;

if( dest_cnt >= MAX_SHRED_DESTS )
FD_LOG_ERR( ( "Cluster nodes had %lu destinations, which was more than the max of %lu",
dest_cnt,
MAX_SHRED_DESTS ) );

fd_shred_dest_wire_t const * in_dests = fd_type_pun_const( header );
fd_shred_dest_weighted_t * dests = fd_stake_ci_dest_add_init( ctx->stake_ci );

ctx->new_dest_ptr = dests;
ctx->new_dest_cnt = dest_cnt;

for( ulong i = 0UL; i < dest_cnt; i++ ) {
memcpy( dests[i].pubkey.uc, in_dests[i].pubkey, 32UL );
dests[i].ip4 = in_dests[i].ip4_addr;
dests[i].port = in_dests[i].udp_port;
}
}

static inline void
finalize_new_cluster_contact_info( fd_eqvoc_tile_ctx_t * ctx ) {
fd_stake_ci_dest_add_fini( ctx->stake_ci, ctx->new_dest_cnt );
}

static void
during_frag( void * _ctx,
ulong in_idx,
ulong seq FD_PARAM_UNUSED,
ulong sig FD_PARAM_UNUSED,
ulong chunk,
ulong sz,
int * opt_filter FD_PARAM_UNUSED ) {
fd_eqvoc_tile_ctx_t * ctx = (fd_eqvoc_tile_ctx_t *)_ctx;

if( FD_UNLIKELY( in_idx == ctx->contact_in_idx ) ) {
if( FD_UNLIKELY( chunk < ctx->contact_in_chunk0 || chunk > ctx->contact_in_wmark ) ) {
FD_LOG_ERR( ( "chunk %lu %lu corrupt, not in range [%lu,%lu]",
chunk,
sz,
ctx->contact_in_chunk0,
ctx->contact_in_wmark ) );
}

uchar const * dcache_entry = fd_chunk_to_laddr_const( ctx->contact_in_mem, chunk );
handle_new_cluster_contact_info( ctx, dcache_entry, sz );
} else if ( FD_UNLIKELY( in_idx == ctx->shred_net_in_idx ) ) {
if( FD_UNLIKELY( chunk < ctx->shred_net_in_chunk0 || chunk > ctx->shred_net_in_wmark ) ) {
FD_LOG_ERR( ( "chunk %lu %lu corrupt, not in range [%lu,%lu]",
chunk,
sz,
ctx->shred_net_in_chunk0,
ctx->shred_net_in_wmark ) );
}

uchar * packet = fd_chunk_to_laddr( ctx->shred_net_in_mem, chunk );
// memcpy( packet + sizeof(fd_net_hdrs_t), packet, sizeof(fd_shred_t) );
fd_shred_t * shred = (fd_shred_t *)(packet + sizeof(fd_net_hdrs_t));
memcpy( &ctx->shred, shred, sizeof(fd_shred_t) );
}
}

static void
after_frag( void * _ctx,
ulong in_idx,
ulong seq FD_PARAM_UNUSED,
ulong * opt_sig FD_PARAM_UNUSED,
ulong * opt_chunk FD_PARAM_UNUSED,
ulong * opt_sz FD_PARAM_UNUSED,
ulong * opt_tsorig FD_PARAM_UNUSED,
int * opt_filter FD_PARAM_UNUSED,
fd_mux_context_t * mux FD_PARAM_UNUSED ) {
fd_eqvoc_tile_ctx_t * ctx = (fd_eqvoc_tile_ctx_t *)_ctx;

if( FD_UNLIKELY( in_idx == ctx->contact_in_idx ) ) {
finalize_new_cluster_contact_info( ctx );
return;
}

FD_LOG_DEBUG(( "got shred %lu %u", ctx->shred.slot, ctx->shred.idx ));
}

static void
privileged_init( fd_topo_t * topo FD_PARAM_UNUSED, fd_topo_tile_t * tile, void * scratch ) {

FD_SCRATCH_ALLOC_INIT( l, scratch );
fd_eqvoc_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l,
alignof( fd_eqvoc_tile_ctx_t ),
sizeof( fd_eqvoc_tile_ctx_t ) );

if( FD_UNLIKELY( !strcmp( tile->eqvoc.identity_key_path, "" ) ) )
FD_LOG_ERR( ( "identity_key_path not set" ) );

ctx->identity_key[0] = *(fd_pubkey_t const *)
fd_type_pun_const( fd_keyload_load( tile->eqvoc.identity_key_path,
/* pubkey only: */ 1 ) );
}

static void
unprivileged_init( fd_topo_t * topo, fd_topo_tile_t * tile, void * scratch ) {
fd_flamenco_boot( NULL, NULL );

if( FD_UNLIKELY( tile->out_link_id_primary != ULONG_MAX ) )
FD_LOG_ERR( ( "eqvoc has a primary output link" ) );

FD_SCRATCH_ALLOC_INIT( l, scratch );
fd_eqvoc_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l,
alignof( fd_eqvoc_tile_ctx_t ),
sizeof( fd_eqvoc_tile_ctx_t ) );

ctx->stake_ci = fd_stake_ci_join(
fd_stake_ci_new( FD_SCRATCH_ALLOC_APPEND( l, fd_stake_ci_align(), fd_stake_ci_footprint() ),
ctx->identity_key ) );

ctx->contact_in_idx = fd_topo_find_tile_in_link( topo, tile, "gossip_voter", 0 );
FD_TEST( ctx->contact_in_idx != ULONG_MAX );
fd_topo_link_t * contact_in_link = &topo->links[tile->in_link_id[ctx->contact_in_idx]];
ctx->contact_in_mem = topo->workspaces[topo->objs[contact_in_link->dcache_obj_id].wksp_id].wksp;
ctx->contact_in_chunk0 = fd_dcache_compact_chunk0( ctx->contact_in_mem, contact_in_link->dcache );
ctx->contact_in_wmark = fd_dcache_compact_wmark( ctx->contact_in_mem,
contact_in_link->dcache,
contact_in_link->mtu );

ctx->shred_net_in_idx = fd_topo_find_tile_in_link( topo, tile, "shred_net", 0 );
FD_TEST( ctx->shred_net_in_idx != ULONG_MAX );
fd_topo_link_t * shred_net_in_link = &topo->links[tile->in_link_id[ctx->shred_net_in_idx]];
ctx->shred_net_in_mem = topo->workspaces[topo->objs[shred_net_in_link->dcache_obj_id].wksp_id].wksp;
ctx->shred_net_in_chunk0 = fd_dcache_compact_chunk0( ctx->shred_net_in_mem, shred_net_in_link->dcache );
ctx->shred_net_in_wmark = fd_dcache_compact_wmark( ctx->shred_net_in_mem,
shred_net_in_link->dcache,
shred_net_in_link->mtu );

ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
if( FD_UNLIKELY( scratch_top != (ulong)scratch + scratch_footprint( tile ) ) ) {
FD_LOG_ERR( ( "scratch overflow %lu %lu %lu",
scratch_top - (ulong)scratch - scratch_footprint( tile ),
scratch_top,
(ulong)scratch + scratch_footprint( tile ) ) );
}
}

static ulong
populate_allowed_seccomp( void * scratch FD_PARAM_UNUSED,
ulong out_cnt,
struct sock_filter * out ) {
populate_sock_filter_policy_eqvoc( out_cnt, out, (uint)fd_log_private_logfile_fd() );
return sock_filter_policy_eqvoc_instr_cnt;
}

static ulong
populate_allowed_fds( void * scratch FD_PARAM_UNUSED, ulong out_fds_cnt, int * out_fds ) {
if( FD_UNLIKELY( out_fds_cnt < 2 ) ) FD_LOG_ERR( ( "out_fds_cnt %lu", out_fds_cnt ) );

ulong out_cnt = 0;
out_fds[out_cnt++] = 2; /* stderr */
if( FD_LIKELY( -1 != fd_log_private_logfile_fd() ) )
out_fds[out_cnt++] = fd_log_private_logfile_fd(); /* logfile */
return out_cnt;
}

fd_topo_run_tile_t fd_tile_eqvoc = {
.name = "eqvoc",
.mux_flags = FD_MUX_FLAG_MANUAL_PUBLISH | FD_MUX_FLAG_COPY,
.burst = 1UL,
.loose_footprint = loose_footprint,
.mux_ctx = mux_ctx,
.mux_during_frag = during_frag,
.mux_after_frag = after_frag,
.populate_allowed_seccomp = populate_allowed_seccomp,
.populate_allowed_fds = populate_allowed_fds,
.scratch_align = scratch_align,
.scratch_footprint = scratch_footprint,
.privileged_init = privileged_init,
.unprivileged_init = unprivileged_init,
};
22 changes: 22 additions & 0 deletions src/app/fdctl/run/tiles/fd_gossip.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#define DEDUP_OUT_IDX 2
#define SIGN_OUT_IDX 3
#define VOTER_OUT_IDX 4
#define EQVOC_OUT_IDX 5

#define CONTACT_INFO_PUBLISH_TIME_NS ((long)5e9)

Expand Down Expand Up @@ -120,6 +121,16 @@ struct fd_gossip_tile_ctx {
ulong dedup_out_wmark;
ulong dedup_out_chunk;

fd_frag_meta_t * duplicate_shred_out_mcache;
ulong * duplicate_shred_out_sync;
ulong duplicate_shred_out_depth;
ulong duplicate_shred_out_seq;

fd_wksp_t * duplicate_shred_out_mem;
ulong duplicate_shred_out_chunk0;
ulong duplicate_shred_out_wmark;
ulong duplicate_shred_out_chunk;

fd_wksp_t * replay_in_mem;
ulong replay_in_chunk0;
ulong replay_in_wmark;
Expand Down Expand Up @@ -325,6 +336,17 @@ gossip_deliver_fun( fd_crds_data_t * data, void * arg ) {
if (ele) {
ele->contact_info = contact_info;
}
} else if( fd_crds_data_is_duplicate_shred( data ) ) {
fd_gossip_duplicate_shred_t const * duplicate_shred = &data->inner.duplicate_shred;

uchar * duplicate_shred_msg = fd_chunk_to_laddr( ctx->duplicate_shred_out_mem, ctx->duplicate_shred_out_chunk );
memcpy( duplicate_shred_msg, duplicate_shred->chunk, duplicate_shred->chunk_len );

ulong sig = 1UL;
fd_mcache_publish( ctx->duplicate_shred_out_mcache, ctx->duplicate_shred_out_depth, ctx->duplicate_shred_out_seq, sig, ctx->duplicate_shred_out_chunk,
duplicate_shred->chunk_len, 0UL, 0, 0 );
ctx->duplicate_shred_out_seq = fd_seq_inc( ctx->duplicate_shred_out_seq, 1UL );
ctx->duplicate_shred_out_chunk = fd_dcache_compact_next( ctx->duplicate_shred_out_chunk, duplicate_shred->chunk_len, ctx->duplicate_shred_out_chunk0, ctx->duplicate_shred_out_wmark );
}
}

Expand Down
Loading

0 comments on commit b5939a0

Please sign in to comment.