From b5939a069d3aa80e4e734051e4529bbf75cfa26c Mon Sep 17 00:00:00 2001 From: Charles Li Date: Thu, 10 Oct 2024 14:44:27 +0000 Subject: [PATCH] feat(eqvoc): equivocation tile 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. --- .gitignore | 2 + contrib/test/setup_fd_cluster_stakes.sh | 2 +- contrib/test/test_firedancer_leader.sh | 1 + src/app/fdctl/Local.mk | 2 + src/app/fdctl/run/tiles/eqvoc.seccomppolicy | 18 ++ src/app/fdctl/run/tiles/fd_eqvoc.c | 263 ++++++++++++++++++ src/app/fdctl/run/tiles/fd_gossip.c | 22 ++ .../fdctl/run/tiles/generated/eqvoc_seccomp.h | 62 +++++ src/app/fdctl/run/topos/fd_firedancer.c | 10 +- src/app/fddev/main1.c | 2 + src/choreo/eqvoc/Local.mk | 5 + src/choreo/eqvoc/fd_eqvoc.c | 160 +++++++++++ src/choreo/eqvoc/fd_eqvoc.h | 148 ++++++++++ src/choreo/eqvoc/test_eqvoc.c | 46 +++ src/choreo/fd_choreo.h | 1 + src/disco/shred/fd_fec_resolver.h | 4 +- src/disco/store/fd_pending_slots.c | 6 +- src/disco/topo/fd_topo.h | 4 + src/disco/topo/fd_topob.c | 1 + src/flamenco/runtime/fd_bank_hash_cmp.c | 2 +- src/flamenco/runtime/fd_blockstore.h | 4 +- 21 files changed, 755 insertions(+), 10 deletions(-) create mode 100644 src/app/fdctl/run/tiles/eqvoc.seccomppolicy create mode 100644 src/app/fdctl/run/tiles/fd_eqvoc.c create mode 100644 src/app/fdctl/run/tiles/generated/eqvoc_seccomp.h create mode 100644 src/choreo/eqvoc/Local.mk create mode 100644 src/choreo/eqvoc/fd_eqvoc.c create mode 100644 src/choreo/eqvoc/fd_eqvoc.h create mode 100644 src/choreo/eqvoc/test_eqvoc.c diff --git a/.gitignore b/.gitignore index 13c66cbd37..3b7aa62afa 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/contrib/test/setup_fd_cluster_stakes.sh b/contrib/test/setup_fd_cluster_stakes.sh index 974484915b..6d1515c04a 100755 --- a/contrib/test/setup_fd_cluster_stakes.sh +++ b/contrib/test/setup_fd_cluster_stakes.sh @@ -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 \ No newline at end of file +solana -u $RPC_URL --keypair fd-identity-keypair-2.json stake-account fd-stake-keypair-2.json diff --git a/contrib/test/test_firedancer_leader.sh b/contrib/test/test_firedancer_leader.sh index 5129862870..020fb2a71a 100755 --- a/contrib/test/test_firedancer_leader.sh +++ b/contrib/test/test_firedancer_leader.sh @@ -64,6 +64,7 @@ name = \"fd1\" [log] path = \"fddev.log\" level_stderr = \"INFO\" + level_logfile = \"NOTICE\" level_flush = \"ERR\" [consensus] vote = true diff --git a/src/app/fdctl/Local.mk b/src/app/fdctl/Local.mk index 14cd964511..b303e9c8eb 100644 --- a/src/app/fdctl/Local.mk +++ b/src/app/fdctl/Local.mk @@ -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 @@ -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: diff --git a/src/app/fdctl/run/tiles/eqvoc.seccomppolicy b/src/app/fdctl/run/tiles/eqvoc.seccomppolicy new file mode 100644 index 0000000000..efb7dec4f4 --- /dev/null +++ b/src/app/fdctl/run/tiles/eqvoc.seccomppolicy @@ -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) diff --git a/src/app/fdctl/run/tiles/fd_eqvoc.c b/src/app/fdctl/run/tiles/fd_eqvoc.c new file mode 100644 index 0000000000..8519f2f894 --- /dev/null +++ b/src/app/fdctl/run/tiles/fd_eqvoc.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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, +}; diff --git a/src/app/fdctl/run/tiles/fd_gossip.c b/src/app/fdctl/run/tiles/fd_gossip.c index 34d758c551..7c0d4d105c 100644 --- a/src/app/fdctl/run/tiles/fd_gossip.c +++ b/src/app/fdctl/run/tiles/fd_gossip.c @@ -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) @@ -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; @@ -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 ); } } diff --git a/src/app/fdctl/run/tiles/generated/eqvoc_seccomp.h b/src/app/fdctl/run/tiles/generated/eqvoc_seccomp.h new file mode 100644 index 0000000000..bf629a6a62 --- /dev/null +++ b/src/app/fdctl/run/tiles/generated/eqvoc_seccomp.h @@ -0,0 +1,62 @@ +/* THIS FILE WAS GENERATED BY generate_filters.py. DO NOT EDIT BY HAND! */ +#ifndef HEADER_fd_src_app_fdctl_run_tiles_generated_eqvoc_seccomp_h +#define HEADER_fd_src_app_fdctl_run_tiles_generated_eqvoc_seccomp_h + +#include "../../../../../../src/util/fd_util_base.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__i386__) +# define ARCH_NR AUDIT_ARCH_I386 +#elif defined(__x86_64__) +# define ARCH_NR AUDIT_ARCH_X86_64 +#elif defined(__aarch64__) +# define ARCH_NR AUDIT_ARCH_AARCH64 +#else +# error "Target architecture is unsupported by seccomp." +#endif +static const unsigned int sock_filter_policy_eqvoc_instr_cnt = 14; + +static void populate_sock_filter_policy_eqvoc( ulong out_cnt, struct sock_filter * out, unsigned int logfile_fd) { + FD_TEST( out_cnt >= 14 ); + struct sock_filter filter[14] = { + /* Check: Jump to RET_KILL_PROCESS if the script's arch != the runtime arch */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, ( offsetof( struct seccomp_data, arch ) ) ), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ARCH_NR, 0, /* RET_KILL_PROCESS */ 10 ), + /* loading syscall number in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, ( offsetof( struct seccomp_data, nr ) ) ), + /* allow write based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_write, /* check_write */ 2, 0 ), + /* allow fsync based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 5, 0 ), + /* none of the syscalls matched */ + { BPF_JMP | BPF_JA, 0, 0, /* RET_KILL_PROCESS */ 6 }, +// check_write: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 2, /* RET_ALLOW */ 5, /* lbl_1 */ 0 ), +// lbl_1: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, logfile_fd, /* RET_ALLOW */ 3, /* RET_KILL_PROCESS */ 2 ), +// check_fsync: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, logfile_fd, /* RET_ALLOW */ 1, /* RET_KILL_PROCESS */ 0 ), +// RET_KILL_PROCESS: + /* KILL_PROCESS is placed before ALLOW since it's the fallthrough case. */ + BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), +// RET_ALLOW: + /* ALLOW has to be reached by jumping */ + BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), + }; + fd_memcpy( out, filter, sizeof( filter ) ); +} + +#endif diff --git a/src/app/fdctl/run/topos/fd_firedancer.c b/src/app/fdctl/run/topos/fd_firedancer.c index f21a23224a..5479db0aee 100644 --- a/src/app/fdctl/run/topos/fd_firedancer.c +++ b/src/app/fdctl/run/topos/fd_firedancer.c @@ -157,7 +157,8 @@ fd_topo_initialize( config_t * config ) { fd_topob_wksp( topo, "funk" ); fd_topob_wksp( topo, "pohi" ); fd_topob_wksp( topo, "voter" ); - fd_topob_wksp( topo, "poh_slot" ); + fd_topob_wksp( topo, "poh_slot" ); + fd_topob_wksp( topo, "eqvoc" ); #define FOR(cnt) for( ulong i=0UL; itile_cnt ], 0, "repair_store", 0UL ); /**/ fd_topob_tile( topo, "sender", "voter", "metric_in", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, NULL, 0UL ); /**/ fd_topob_tile( topo, "bhole", "bhole", "metric_in", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, NULL, 0UL ); + /**/ fd_topob_tile( topo, "eqvoc", "eqvoc", "metric_in", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, NULL, 0UL ); /**/ fd_topob_tile( topo, "replay", "replay", "metric_in", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, "stake_out", 0UL ); /* These thread tiles must be defined immediately after the replay tile. We subtract one because the replay tile acts as a thread in the tpool as well. */ @@ -342,6 +344,7 @@ fd_topo_initialize( config_t * config ) { FOR(verify_tile_cnt) for( ulong j=0UL; jsender.src_mac_addr, config->tiles.net.mac_addr, 6UL ); strncpy( tile->sender.identity_key_path, config->consensus.identity_path, sizeof(tile->sender.identity_key_path) ); + } else if( FD_UNLIKELY( !strcmp( tile->name, "eqvoc" ) ) ) { + strncpy( tile->eqvoc.identity_key_path, config->consensus.identity_path, sizeof(tile->eqvoc.identity_key_path) ); } else { FD_LOG_ERR(( "unknown tile name %lu `%s`", i, tile->name )); } diff --git a/src/app/fddev/main1.c b/src/app/fddev/main1.c index 508b1c089d..dd0d074447 100644 --- a/src/app/fddev/main1.c +++ b/src/app/fddev/main1.c @@ -58,6 +58,7 @@ extern fd_topo_run_tile_t fd_tile_replay; extern fd_topo_run_tile_t fd_tile_replay_thread; extern fd_topo_run_tile_t fd_tile_poh_int; extern fd_topo_run_tile_t fd_tile_sender; +extern fd_topo_run_tile_t fd_tile_eqvoc; #endif fd_topo_run_tile_t * TILES[] = { @@ -86,6 +87,7 @@ fd_topo_run_tile_t * TILES[] = { &fd_tile_replay_thread, &fd_tile_poh_int, &fd_tile_sender, + &fd_tile_eqvoc, #endif NULL, }; diff --git a/src/choreo/eqvoc/Local.mk b/src/choreo/eqvoc/Local.mk new file mode 100644 index 0000000000..fb34cb9eb0 --- /dev/null +++ b/src/choreo/eqvoc/Local.mk @@ -0,0 +1,5 @@ +ifdef FD_HAS_INT128 +$(call add-hdrs,fd_eqvoc.h) +$(call add-objs,fd_eqvoc,fd_choreo) +$(call make-unit-test,test_eqvoc,test_eqvoc,fd_choreo fd_flamenco fd_ballet fd_util) +endif diff --git a/src/choreo/eqvoc/fd_eqvoc.c b/src/choreo/eqvoc/fd_eqvoc.c new file mode 100644 index 0000000000..155d850f54 --- /dev/null +++ b/src/choreo/eqvoc/fd_eqvoc.c @@ -0,0 +1,160 @@ +#include "fd_eqvoc.h" + +void * +fd_eqvoc_new( void * shmem, ulong key_max, ulong seed ) { + + if( FD_UNLIKELY( !shmem ) ) { + FD_LOG_WARNING( ( "NULL mem" ) ); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_eqvoc_align() ) ) ) { + FD_LOG_WARNING( ( "misaligned mem" ) ); + return NULL; + } + + ulong footprint = fd_eqvoc_footprint( key_max ); + + fd_memset( shmem, 0, footprint ); + ulong laddr = (ulong)shmem; + fd_eqvoc_t * eqvoc = (void *)laddr; + laddr += sizeof( fd_eqvoc_t ); + + laddr = fd_ulong_align_up( laddr, fd_eqvoc_pool_align() ); + eqvoc->pool = fd_eqvoc_pool_new( (void *)laddr, key_max ); + laddr += fd_eqvoc_pool_footprint( key_max ); + + laddr = fd_ulong_align_up( laddr, fd_eqvoc_map_align() ); + eqvoc->map = fd_eqvoc_map_new( (void *)laddr, key_max, seed ); + laddr += fd_eqvoc_map_footprint( key_max ); + + laddr = fd_ulong_align_up( laddr, fd_eqvoc_align() ); + FD_TEST( laddr == (ulong)shmem + footprint ); + + return shmem; +} + +fd_eqvoc_t * +fd_eqvoc_join( void * sheqvoc ) { + + if( FD_UNLIKELY( !sheqvoc ) ) { + FD_LOG_WARNING( ( "NULL eqvoc" ) ); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)sheqvoc, fd_eqvoc_align() ) ) ) { + FD_LOG_WARNING( ( "misaligned eqvoc" ) ); + return NULL; + } + + ulong laddr = (ulong)sheqvoc; /* offset from a memory region */ + fd_eqvoc_t * eqvoc = (void *)sheqvoc; + laddr += sizeof( fd_eqvoc_t ); + + laddr = fd_ulong_align_up( laddr, fd_eqvoc_pool_align() ); + eqvoc->pool = fd_eqvoc_pool_join( (void *)laddr ); + ulong key_max = fd_eqvoc_pool_max( eqvoc->pool ); + laddr += fd_eqvoc_pool_footprint( key_max ); + + laddr = fd_ulong_align_up( laddr, fd_eqvoc_map_align() ); + eqvoc->map = fd_eqvoc_map_join( (void *)laddr ); + laddr += fd_eqvoc_map_footprint( key_max ); + + laddr = fd_ulong_align_up( laddr, fd_eqvoc_align() ); + FD_TEST( laddr == (ulong)sheqvoc + fd_eqvoc_footprint( key_max ) ); + + return eqvoc; +} + +void * +fd_eqvoc_leave( fd_eqvoc_t const * eqvoc ) { + + if( FD_UNLIKELY( !eqvoc ) ) { + FD_LOG_WARNING( ( "NULL eqvoc" ) ); + return NULL; + } + + return (void *)eqvoc; +} + +void * +fd_eqvoc_delete( void * eqvoc ) { + + if( FD_UNLIKELY( !eqvoc ) ) { + FD_LOG_WARNING( ( "NULL eqvoc" ) ); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)eqvoc, fd_eqvoc_align() ) ) ) { + FD_LOG_WARNING( ( "misaligned eqvoc" ) ); + return NULL; + } + + return eqvoc; +} + +void +fd_eqvoc_insert( fd_eqvoc_t * eqvoc, fd_shred_t const * shred ) { + fd_eqvoc_entry_t * entry = fd_eqvoc_pool_ele_acquire( eqvoc->pool ); + entry->key.slot = shred->slot; + entry->key.fec_set_idx = shred->fec_set_idx; + memcpy( entry->sig, shred->signature, FD_ED25519_SIG_SZ ); + + if( FD_LIKELY( fd_shred_is_code( fd_shred_type( shred->variant ) ) ) ) { + + /* optimize for coding shreds (code_cnt >= data_cnt) */ + + entry->code_cnt = shred->code.code_cnt; + entry->data_cnt = shred->code.data_cnt; + } + + fd_eqvoc_map_ele_insert( eqvoc->map, entry, eqvoc->pool ); +} + +int +fd_eqvoc_test( fd_eqvoc_t const * eqvoc, fd_shred_t const * shred ) { + fd_eqvoc_key_t key = { shred->slot, shred->fec_set_idx }; + + /* If we've already received a shred for this FEC set, check this new + shred's signature matches. */ + + fd_eqvoc_entry_t const * entry = fd_eqvoc_map_ele_query_const( eqvoc->map, + &key, + NULL, + eqvoc->pool ); + + /* If we've already seen a shred in this FEC set, make sure the + signature matches. */ + + if( FD_UNLIKELY( entry && 0 != memcmp( entry->sig, shred->signature, FD_ED25519_SIG_SZ ) ) ) { + return 0; + } + + /* Look backward FEC_MAX idxs for overlap. */ + + for( uint i = 1; shred->fec_set_idx >= i && i < FD_EQVOC_FEC_MAX; i++ ) { + fd_eqvoc_key_t key = { shred->slot, shred->fec_set_idx - i }; + fd_eqvoc_entry_t const * entry = fd_eqvoc_map_ele_query_const( eqvoc->map, + &key, + NULL, + eqvoc->pool ); + + if( FD_UNLIKELY( entry && entry->data_cnt > 0 && + entry->key.fec_set_idx + entry->data_cnt > shred->fec_set_idx ) ) { + return 0; /* Equivocation detected */ + } + } + + /* Look forward data_cnt idxs for overlap. */ + + for( uint i = 1; entry && i < entry->data_cnt; i++ ) { + fd_eqvoc_key_t key = { shred->slot, shred->fec_set_idx + i }; + fd_eqvoc_entry_t const * entry = fd_eqvoc_map_ele_query_const( eqvoc->map, + &key, + NULL, + eqvoc->pool ); + if( FD_UNLIKELY( entry ) ) return 0; /* Equivocation detected */ + } + + return 1; +} diff --git a/src/choreo/eqvoc/fd_eqvoc.h b/src/choreo/eqvoc/fd_eqvoc.h new file mode 100644 index 0000000000..ef8c6e0ac9 --- /dev/null +++ b/src/choreo/eqvoc/fd_eqvoc.h @@ -0,0 +1,148 @@ +#ifndef HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h +#define HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h + +#include "../../ballet/shred/fd_shred.h" +#include "../fd_choreo_base.h" + +/* FD_EQVOC_USE_HANDHOLDING: Define this to non-zero at compile time + to turn on additional runtime checks and logging. */ + +#ifndef FD_EQVOC_USE_HANDHOLDING +#define FD_EQVOC_USE_HANDHOLDING 1 +#endif + +#define FD_EQVOC_MAX ( 1UL << 10 ) +#define FD_EQVOC_FEC_MAX ( 67UL ) + +struct fd_eqvoc_key { + ulong slot; + uint fec_set_idx; +}; +typedef struct fd_eqvoc_key fd_eqvoc_key_t; + +/* clang-format off */ +static const fd_eqvoc_key_t fd_eqvoc_key_null = { 0 }; +#define FD_EQVOC_KEY_NULL fd_eqvoc_key_null +#define FD_EQVOC_KEY_INVAL(key) (!((key).slot) & !((key).fec_set_idx)) +#define FD_EQVOC_KEY_EQ(k0,k1) (!(((k0).slot) ^ ((k1).slot))) & !(((k0).fec_set_idx) ^ (((k1).fec_set_idx))) +#define FD_EQVOC_KEY_HASH(key) ((uint)(((key).slot)<<15UL) | (((key).fec_set_idx))) +/* clang-format on */ + +struct fd_eqvoc_entry { + fd_eqvoc_key_t key; + ulong next; + fd_ed25519_sig_t sig; + ulong code_cnt; + ulong data_cnt; +}; +typedef struct fd_eqvoc_entry fd_eqvoc_entry_t; + +#define POOL_NAME fd_eqvoc_pool +#define POOL_T fd_eqvoc_entry_t +#include "../../util/tmpl/fd_pool.c" + +/* clang-format off */ +#define MAP_NAME fd_eqvoc_map +#define MAP_ELE_T fd_eqvoc_entry_t +#define MAP_KEY_T fd_eqvoc_key_t +#define MAP_KEY_EQ(k0,k1) (FD_EQVOC_KEY_EQ(*k0,*k1)) +#define MAP_KEY_HASH(key,seed) (FD_EQVOC_KEY_HASH(*key)^seed) +#include "../../util/tmpl/fd_map_chain.c" + +struct fd_eqvoc { + fd_eqvoc_map_t * map; + fd_eqvoc_entry_t * pool; +}; +typedef struct fd_eqvoc fd_eqvoc_t; + +/* fd_eqvoc_{align,footprint} return the required alignment and + footprint of a memory region suitable for use as eqvoc with up to + node_max nodes and vote_max votes. */ + +FD_FN_CONST static inline ulong +fd_eqvoc_align( void ) { + return alignof(fd_eqvoc_t); +} + +FD_FN_CONST static inline ulong +fd_eqvoc_footprint( ulong key_max ) { + return FD_LAYOUT_FINI( + FD_LAYOUT_APPEND( + FD_LAYOUT_APPEND( + FD_LAYOUT_APPEND( + FD_LAYOUT_INIT, + alignof( fd_eqvoc_t ), sizeof( fd_eqvoc_t ) ), + fd_eqvoc_pool_align(), fd_eqvoc_pool_footprint( key_max ) ), + fd_eqvoc_map_align(), fd_eqvoc_map_footprint( FD_EQVOC_MAX ) ), + fd_eqvoc_align() ); +} +/* clang-format on */ + +/* fd_eqvoc_new formats an unused memory region for use as a eqvoc. + mem is a non-NULL pointer to this region in the local address space + with the required footprint and alignment. */ + +void * +fd_eqvoc_new( void * shmem, ulong key_max, ulong seed ); + +/* fd_eqvoc_join joins the caller to the eqvoc. eqvoc points to the + first byte of the memory region backing the eqvoc in the caller's + address space. + + Returns a pointer in the local address space to eqvoc on success. */ + +fd_eqvoc_t * +fd_eqvoc_join( void * sheqvoc ); + +/* fd_eqvoc_leave leaves a current local join. Returns a pointer to the + underlying shared memory region on success and NULL on failure (logs + details). Reasons for failure include eqvoc is NULL. */ + +void * +fd_eqvoc_leave( fd_eqvoc_t const * eqvoc ); + +/* fd_eqvoc_delete unformats a memory region used as a eqvoc. + Assumes only the nobody is joined to the region. Returns a + pointer to the underlying shared memory region or NULL if used + obviously in error (e.g. eqvoc is obviously not a eqvoc ... logs + details). The ownership of the memory region is transferred to the + caller. */ + +void * +fd_eqvoc_delete( void * sheqvoc ); + +/* fd_eqvoc_insert inserts shred's signature into eqvoc keyed by (slot, + fec_set_idx). Every FEC set must have the same signature for every + shred in the set, so a different signature would indicate + equivocation. */ + +void +fd_eqvoc_insert( fd_eqvoc_t * eqvoc, fd_shred_t const * shred ); + +/* fd_eqvoc_test tests for equivocation given a new shred. Equivocation + is when there are two or more shreds for the same (slot, idx) pair. + + A FEC set overlaps with another one if they both contain a data shred + at idx. For example, say we have a FEC set containing data shred the + idxs in the interval [13, 15] and another set containing idxs [15, + 20]. The first FEC set has fec_set_idx 13 and data_cnt 3. The + second FEC set has fec_set_idx 15 and data_cnt + 6. The overlapping data shred idx is 15. + + We can detect this arithmetically by adding the data_cnt to the + fec_set_idx that starts earlier. If the result is greater than + fec_set_idx that starts later, we know at least one data shred idx + must overlap. In this example, 13 + 3 > 15, which indicates + equivocation. + + We can check for this overlap both backwards and forwards. We know + the max number of data shred idxs in a valid FEC set is 67. So we + need to look back at most 67 FEC set idxs to find the previous FEC + set. Similarly, we look forward at most data_cnt idxs to find the + next FEC set. + */ + +int +fd_eqvoc_test( fd_eqvoc_t const * eqvoc, fd_shred_t const * shred ); + +#endif /* HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h */ diff --git a/src/choreo/eqvoc/test_eqvoc.c b/src/choreo/eqvoc/test_eqvoc.c new file mode 100644 index 0000000000..11960793ec --- /dev/null +++ b/src/choreo/eqvoc/test_eqvoc.c @@ -0,0 +1,46 @@ +#include "fd_eqvoc.h" +#include + +int +main( int argc, char ** argv ) { + fd_boot( &argc, &argv ); + + ulong page_cnt = 1; + char * _page_sz = "gigantic"; + ulong numa_idx = fd_shmem_numa_idx( 0 ); + FD_LOG_NOTICE( ( "Creating workspace (--page-cnt %lu, --page-sz %s, --numa-idx %lu)", + page_cnt, + _page_sz, + numa_idx ) ); + fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), + page_cnt, + fd_shmem_cpu_idx( numa_idx ), + "wksp", + 0UL ); + FD_TEST( wksp ); + + ulong key_max = 1 << 10UL; + void * mem = fd_wksp_alloc_laddr( wksp, fd_eqvoc_align(), fd_eqvoc_footprint( key_max ), 1UL ); + FD_TEST( mem ); + fd_eqvoc_t * eqvoc = fd_eqvoc_join( fd_eqvoc_new( mem, key_max, 0UL ) ); + + /* Insert 13-15, 15-20 */ + + fd_shred_t shred13 = { .variant = 0x60, + .slot = 42, + .fec_set_idx = 13, + .signature = { 13 }, + .code = { .data_cnt = 3 } }; + fd_eqvoc_insert( eqvoc, &shred13 ); + fd_shred_t shred15 = { .variant = 96, + .slot = 42, + .fec_set_idx = 15, + .signature = { 15 }, + .code = { .data_cnt = 6 } }; + FD_TEST( !fd_eqvoc_test( eqvoc, &shred15 ) ); + + fd_wksp_free_laddr( mem ); + + fd_halt(); + return 0; +} diff --git a/src/choreo/fd_choreo.h b/src/choreo/fd_choreo.h index 513f05a519..571321062b 100644 --- a/src/choreo/fd_choreo.h +++ b/src/choreo/fd_choreo.h @@ -2,6 +2,7 @@ #define HEADER_fd_src_choreo_fd_choreo_h #include "fd_choreo_base.h" +#include "eqvoc/fd_eqvoc.h" #include "forks/fd_forks.h" #include "ghost/fd_ghost.h" #include "tower/fd_tower.h" diff --git a/src/disco/shred/fd_fec_resolver.h b/src/disco/shred/fd_fec_resolver.h index 9b4d9a246b..54258dcb09 100644 --- a/src/disco/shred/fd_fec_resolver.h +++ b/src/disco/shred/fd_fec_resolver.h @@ -7,8 +7,8 @@ tile, which is why it's in disco/shred. The primary complication in the interface comes from lifetimes. - Buffers returned by the networking layer are typically ephemeral, we - need to hold onto the data until we've finished the FEC set, so we + Buffers returned by the networking layer are typically ephemeral and + we need to hold onto the data until we've finished the FEC set, so we need at least one memcpy. Once we complete the FEC set, the result needs to go out on an mcache/dcache pair and on the network, which also have different lifetime requirements. The FEC resolver goes out diff --git a/src/disco/store/fd_pending_slots.c b/src/disco/store/fd_pending_slots.c index 690f1f4529..958218103b 100644 --- a/src/disco/store/fd_pending_slots.c +++ b/src/disco/store/fd_pending_slots.c @@ -163,7 +163,7 @@ fd_pending_slots_add( fd_pending_slots_t * pending_slots, pending_slots->start = slot; pending_slots->end = slot+1U; pending[slot & FD_PENDING_MASK] = when; - FD_LOG_WARNING(("PENDING QUEUE: EMPTY, START SLOT: %lu, END SLOT: %lu", pending_slots->start, pending_slots->end)); + FD_LOG_DEBUG(("PENDING QUEUE: EMPTY, START SLOT: %lu, END SLOT: %lu", pending_slots->start, pending_slots->end)); } else if ( slot < pending_slots->start ) { /* Grow down */ @@ -175,7 +175,7 @@ fd_pending_slots_add( fd_pending_slots_t * pending_slots, pending[i & FD_PENDING_MASK] = 0; } pending_slots->start = slot; - FD_LOG_WARNING(("PENDING QUEUE: GROW DOWN, START SLOT: %lu", pending_slots->start)); + FD_LOG_DEBUG(("PENDING QUEUE: GROW DOWN, START SLOT: %lu", pending_slots->start)); } else if ( slot >= pending_slots->end ) { /* Grow up */ @@ -187,7 +187,7 @@ fd_pending_slots_add( fd_pending_slots_t * pending_slots, pending[i & FD_PENDING_MASK] = 0; } pending_slots->end = slot+1U; - FD_LOG_WARNING(("PENDING QUEUE: GROW UP, END SLOT: %lu", pending_slots->end)); + FD_LOG_DEBUG(("PENDING QUEUE: GROW UP, END SLOT: %lu", pending_slots->end)); } else { /* Update in place */ diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index 8d4f089708..895d8cf8a8 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -307,6 +307,10 @@ typedef struct { uchar src_mac_addr[ 6 ]; char identity_key_path[ PATH_MAX ]; } sender; + + struct { + char identity_key_path[ PATH_MAX ]; + } eqvoc; }; } fd_topo_tile_t; diff --git a/src/disco/topo/fd_topob.c b/src/disco/topo/fd_topob.c index 3547e51935..9207922079 100644 --- a/src/disco/topo/fd_topob.c +++ b/src/disco/topo/fd_topob.c @@ -431,6 +431,7 @@ fd_topob_auto_layout( fd_topo_t * topo ) { "replay", /* FIREDANCER only */ "thread", /* FIREDANCER only */ "sender", /* FIREDANCER only */ + "eqvoc", /* FIREDANCER only */ #endif }; diff --git a/src/flamenco/runtime/fd_bank_hash_cmp.c b/src/flamenco/runtime/fd_bank_hash_cmp.c index 04aa83c4be..1594dee921 100644 --- a/src/flamenco/runtime/fd_bank_hash_cmp.c +++ b/src/flamenco/runtime/fd_bank_hash_cmp.c @@ -210,7 +210,7 @@ fd_bank_hash_cmp_check( fd_bank_hash_cmp_t * bank_hash_cmp, ulong slot ) { cmp->stakes[i] )); } } - return -1; + return 1; } else { FD_LOG_NOTICE(( "\n\n[Bank Hash Comparison]\n" "slot: %lu\n" diff --git a/src/flamenco/runtime/fd_blockstore.h b/src/flamenco/runtime/fd_blockstore.h index 6072d14e67..0950d68d52 100644 --- a/src/flamenco/runtime/fd_blockstore.h +++ b/src/flamenco/runtime/fd_blockstore.h @@ -107,8 +107,8 @@ typedef struct fd_buf_shred fd_buf_shred_t; #define MAP_NAME fd_buf_shred_map #define MAP_ELE_T fd_buf_shred_t #define MAP_KEY_T fd_shred_key_t -#define MAP_KEY_EQ(k0,k1) FD_SHRED_KEY_EQ(*k0,*k1) -#define MAP_KEY_HASH(key,seed) (FD_SHRED_KEY_HASH(*key) ^ seed) +#define MAP_KEY_EQ(k0,k1) (FD_SHRED_KEY_EQ(*k0,*k1)) +#define MAP_KEY_HASH(key,seed) (FD_SHRED_KEY_HASH(*key)^seed) #include "../../util/tmpl/fd_map_chain.c" /* clang-format on */