diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md
index a97e4348054..f8f0f226ca9 100644
--- a/book/api/metrics-generated.md
+++ b/book/api/metrics-generated.md
@@ -1104,3 +1104,20 @@
| snapwr_vinyl_bytes_written | gauge | Number of bytes written so far to the vinyl snapshot file. Might decrease if snapshot creation is aborted and restarted |
+
+## Tower Tile
+
+
+
+| Metric | Type | Description |
+|--------|------|-------------|
+| tower_ancestor_rollback | counter | Rollback to an ancestor of our prev vote (can't vote) |
+| tower_sibling_confirmed | counter | Duplicate sibling got confirmed (can't vote) |
+| tower_same_fork | counter | Same fork as prev vote (can vote) |
+| tower_switch_pass | counter | Prev vote was on a different fork, but we are allowed to switch (can vote) |
+| tower_switch_fail | counter | Prev vote was on a different fork, and we are not allowed to switch (can't vote) |
+| tower_lockout_fail | counter | Locked out (can't vote) |
+| tower_threshold_fail | counter | Did not pass threshold check (can't vote) |
+| tower_propagated_fail | counter | Prev leader block did not propagate (can't vote) |
+
+
diff --git a/src/app/firedancer-dev/commands/send_test/send_test_helpers.c b/src/app/firedancer-dev/commands/send_test/send_test_helpers.c
index cdfff7ef516..0bc70ac736c 100644
--- a/src/app/firedancer-dev/commands/send_test/send_test_helpers.c
+++ b/src/app/firedancer-dev/commands/send_test/send_test_helpers.c
@@ -215,7 +215,7 @@ encode_vote( send_test_ctx_t * ctx, fd_tower_slot_done_t * slot_done ) {
/* Create minimal mock tower with one vote */
uchar tower_mem[ FD_TOWER_FOOTPRINT ] __attribute__((aligned(FD_TOWER_ALIGN)));
fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem ) );
- fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = vote_slot, .conf = 1 } );
+ fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = vote_slot, .conf = 1 } );
/* Mock values */
fd_lockout_offset_t lockouts_scratch[1];
diff --git a/src/app/firedancer/config/default.toml b/src/app/firedancer/config/default.toml
index cc4412c4c71..3cb071f573d 100644
--- a/src/app/firedancer/config/default.toml
+++ b/src/app/firedancer/config/default.toml
@@ -467,6 +467,15 @@ user = ""
# abort with an error.
expected_genesis_hash = ""
+ # Firedancer can process at most this many slots without rooting in
+ # the consensus rules before it must begin evicting.
+ #
+ # This is an estimate and should be set as generously as possible to
+ # allow for brief anomalies such as the validator disconnecting from
+ # part of the cluster due to data center issues. Roughly, at 400 ms
+ # per slot, the default allows for 30 minutes without rooting.
+ max_unrooted_slots = 4096
+
# This section configures the "funk" account database. Currently, funk
# stores all Solana accounts. In future versions of Firedancer, most
# accounts will be offloaded to the "groove" database.
diff --git a/src/app/firedancer/topology.c b/src/app/firedancer/topology.c
index 07343b5498d..e351b0e9625 100644
--- a/src/app/firedancer/topology.c
+++ b/src/app/firedancer/topology.c
@@ -1,6 +1,7 @@
#include "topology.h"
#include "../../ballet/lthash/fd_lthash.h"
+#include "../../choreo/fd_choreo_base.h"
#include "../../discof/reasm/fd_reasm.h"
#include "../../discof/poh/fd_poh.h"
#include "../../discof/replay/fd_exec.h"
@@ -376,6 +377,7 @@ fd_topo_initialize( config_t * config ) {
/* TODO: Explain this .... USHORT_MAX is not dcache max */
ulong pending_fec_shreds_depth = fd_ulong_min( fd_ulong_pow2_up( config->tiles.shred.max_pending_shred_sets * FD_REEDSOL_DATA_SHREDS_MAX ), USHORT_MAX + 1 /* dcache max */ );
+ ulong max_unrooted_slots = config->firedancer.consensus.max_unrooted_slots;
/* topo, link_name, wksp_name, depth, mtu, burst */
/**/ fd_topob_link( topo, "gossip_net", "net_gossip", config->net.ingress_buffer_size, FD_NET_MTU, 1UL );
@@ -438,7 +440,7 @@ fd_topo_initialize( config_t * config ) {
FOR(shred_tile_cnt) fd_topob_link( topo, "shred_out", "shred_out", pending_fec_shreds_depth, FD_SHRED_OUT_MTU, 3UL ); /* TODO: Pretty sure burst of 3 is incorrect here */
FOR(shred_tile_cnt) fd_topob_link( topo, "repair_shred", "shred_out", pending_fec_shreds_depth, sizeof(fd_ed25519_sig_t), 1UL ); /* TODO: Also pending_fec_shreds_depth? Seems wrong */
- /**/ fd_topob_link( topo, "tower_out", "tower_out", 1024UL, sizeof(fd_tower_slot_done_t), 1UL );
+ /**/ fd_topob_link( topo, "tower_out", "tower_out", max_unrooted_slots, sizeof(fd_tower_msg_t), 1UL );
/**/ fd_topob_link( topo, "send_out", "send_out", 128UL, FD_TPU_RAW_MTU, 1UL );
fd_topob_link( topo, "replay_exec", "replay_exec", 16384UL, sizeof(fd_exec_task_msg_t), 1UL );
@@ -593,14 +595,18 @@ fd_topo_initialize( config_t * config ) {
fd_topob_tile_in ( topo, "replay", 0UL, "metric_in", "snapin_manif", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
}
- /**/ fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "poh_replay", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
- FOR(exec_tile_cnt) fd_topob_tile_in( topo, "exec", i, "metric_in", "replay_exec", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
+ /**/ fd_topob_tile_in ( topo, "replay", 0UL, "metric_in", "poh_replay", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
+ FOR(exec_tile_cnt) fd_topob_tile_in ( topo, "exec", i, "metric_in", "replay_exec", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
+
+ /**/ fd_topob_tile_in ( topo, "tower", 0UL, "metric_in", "dedup_resolv", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
/**/ fd_topob_tile_in ( topo, "tower", 0UL, "metric_in", "genesi_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
+ /**/ fd_topob_tile_in ( topo, "tower", 0UL, "metric_in", "gossip_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
/**/ fd_topob_tile_in ( topo, "tower", 0UL, "metric_in", "replay_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
if( snapshots_enabled ) {
fd_topob_tile_in ( topo, "tower", 0UL, "metric_in", "snapin_manif", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
}
/**/ fd_topob_tile_out( topo, "tower", 0UL, "tower_out", 0UL );
+
/**/ fd_topob_tile_in ( topo, "send", 0UL, "metric_in", "replay_stake", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
/**/ fd_topob_tile_in ( topo, "send", 0UL, "metric_in", "gossip_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
/**/ fd_topob_tile_in ( topo, "send", 0UL, "metric_in", "tower_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
@@ -1071,9 +1077,10 @@ fd_topo_configure_tile( fd_topo_tile_t * tile,
} else if( FD_UNLIKELY( !strcmp( tile->name, "tower" ) ) ) {
- strncpy( tile->tower.identity_key_path, config->paths.identity_key, sizeof(tile->tower.identity_key_path) );
- strncpy( tile->tower.vote_acc_path, config->paths.vote_account, sizeof(tile->tower.vote_acc_path) );
- strncpy( tile->tower.ledger_path, config->paths.ledger, sizeof(tile->tower.ledger_path) );
+ tile->tower.slot_max = config->firedancer.consensus.max_unrooted_slots;
+ strncpy( tile->tower.identity_key, config->paths.identity_key, sizeof(tile->tower.identity_key) );
+ strncpy( tile->tower.vote_account, config->paths.vote_account, sizeof(tile->tower.vote_account) );
+ strncpy( tile->tower.base_path, config->paths.base, sizeof(tile->tower.base_path) );
} else if( FD_UNLIKELY( !strcmp( tile->name, "send" ) ) ) {
diff --git a/src/app/shared/fd_config.h b/src/app/shared/fd_config.h
index fafed1dea8e..c2d7c287500 100644
--- a/src/app/shared/fd_config.h
+++ b/src/app/shared/fd_config.h
@@ -129,6 +129,10 @@ struct fd_configf {
char host[ 256 ];
} gossip;
+ struct {
+ ulong max_unrooted_slots;
+ } consensus;
+
struct {
struct {
uint max_local_full_effective_age;
diff --git a/src/app/shared/fd_config_parse.c b/src/app/shared/fd_config_parse.c
index 662aea9924e..32541131c50 100644
--- a/src/app/shared/fd_config_parse.c
+++ b/src/app/shared/fd_config_parse.c
@@ -80,6 +80,8 @@ fd_config_extract_podf( uchar * pod,
fd_configf_t * config ) {
CFG_POP ( cstr, gossip.host );
+ CFG_POP ( ulong, consensus.max_unrooted_slots );
+
CFG_POP ( uint, layout.exec_tile_count );
CFG_POP ( uint, layout.sign_tile_count );
CFG_POP ( uint, layout.gossvf_tile_count );
diff --git a/src/app/shared_dev/commands/dev.c b/src/app/shared_dev/commands/dev.c
index 2f2d649694a..8cc64375352 100644
--- a/src/app/shared_dev/commands/dev.c
+++ b/src/app/shared_dev/commands/dev.c
@@ -108,13 +108,6 @@ update_config_for_dev( fd_config_t * config ) {
shred->shred.expected_shred_version = shred_version;
}
}
- ulong store_id = fd_topo_find_tile( &config->topo, "storei", 0 );
- if( FD_UNLIKELY( store_id!=ULONG_MAX ) ) {
- fd_topo_tile_t * storei = &config->topo.tiles[ store_id ];
- if( FD_LIKELY( storei->store_int.expected_shred_version==(ushort)0 ) ) {
- storei->store_int.expected_shred_version = shred_version;
- }
- }
}
/* Run Firedancer entirely in a single process for development and
diff --git a/src/choreo/epoch/Local.mk b/src/choreo/epoch/Local.mk
deleted file mode 100644
index 23df1919199..00000000000
--- a/src/choreo/epoch/Local.mk
+++ /dev/null
@@ -1,4 +0,0 @@
-ifdef FD_HAS_INT128
-$(call add-hdrs,fd_epoch.h)
-$(call add-objs,fd_epoch,fd_choreo)
-endif
diff --git a/src/choreo/epoch/fd_epoch.c b/src/choreo/epoch/fd_epoch.c
deleted file mode 100644
index f9ab732c479..00000000000
--- a/src/choreo/epoch/fd_epoch.c
+++ /dev/null
@@ -1,146 +0,0 @@
-#include "fd_epoch.h"
-
-void *
-fd_epoch_new( void * shmem, ulong voter_max ) {
- if( FD_UNLIKELY( !shmem ) ) {
- FD_LOG_WARNING(( "NULL mem" ));
- return NULL;
- }
-
- if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_epoch_align() ) ) ) {
- FD_LOG_WARNING(( "misaligned mem" ));
- return NULL;
- }
-
- ulong footprint = fd_epoch_footprint( voter_max );
- if( FD_UNLIKELY( !footprint ) ) {
- FD_LOG_WARNING(( "bad mem" ));
- return NULL;
- }
-
- fd_wksp_t * wksp = fd_wksp_containing( shmem );
- if( FD_UNLIKELY( !wksp ) ) {
- FD_LOG_WARNING(( "shmem must be part of a workspace" ));
- return NULL;
- }
-
- fd_memset( shmem, 0, footprint );
- int lg_slot_cnt = fd_ulong_find_msb( fd_ulong_pow2_up( voter_max ) ) + 2; /* fill ratio <= 0.25 */
-
- FD_SCRATCH_ALLOC_INIT( l, shmem );
- fd_epoch_t * epoch = FD_SCRATCH_ALLOC_APPEND( l, fd_epoch_align(), sizeof(fd_epoch_t) );
- void * epoch_voters = FD_SCRATCH_ALLOC_APPEND( l, fd_epoch_voters_align(), fd_epoch_voters_footprint( lg_slot_cnt ) );
- FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_epoch_align() ) == (ulong)shmem + footprint );
-
- epoch->voters_gaddr = fd_wksp_gaddr_fast( wksp, fd_epoch_voters_join( fd_epoch_voters_new( epoch_voters, lg_slot_cnt ) ) );
-
- epoch->epoch_gaddr = fd_wksp_gaddr_fast( wksp, epoch );
- epoch->total_stake = 0UL;
- epoch->first_slot = FD_SLOT_NULL;
- epoch->last_slot = FD_SLOT_NULL;
-
- FD_COMPILER_MFENCE();
- FD_VOLATILE( epoch->magic ) = FD_EPOCH_MAGIC;
- FD_COMPILER_MFENCE();
-
- return shmem;
-}
-
-fd_epoch_t *
-fd_epoch_join( void * shepoch ) {
- fd_epoch_t * epoch = (fd_epoch_t *)shepoch;
-
- if( FD_UNLIKELY( !epoch ) ) {
- FD_LOG_WARNING(( "NULL epoch" ));
- return NULL;
- }
-
- if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)epoch, fd_epoch_align() ) ) ) {
- FD_LOG_WARNING(( "misaligned epoch" ));
- return NULL;
- }
-
- fd_wksp_t * wksp = fd_wksp_containing( epoch );
- if( FD_UNLIKELY( !wksp ) ) {
- FD_LOG_WARNING(( "epoch must be part of a workspace" ));
- return NULL;
- }
-
- if( FD_UNLIKELY( epoch->magic!=FD_EPOCH_MAGIC ) ) {
- FD_LOG_WARNING(( "bad magic" ));
- return NULL;
- }
-
- return epoch;
-}
-
-void *
-fd_epoch_leave( fd_epoch_t const * epoch ) {
-
- if( FD_UNLIKELY( !epoch ) ) {
- FD_LOG_WARNING(( "NULL epoch" ));
- return NULL;
- }
-
- return (void *)epoch;
-}
-
-void *
-fd_epoch_delete( void * epoch ) {
-
- if( FD_UNLIKELY( !epoch ) ) {
- FD_LOG_WARNING(( "NULL epoch" ));
- return NULL;
- }
-
- if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)epoch, fd_epoch_align() ) ) ) {
- FD_LOG_WARNING(( "misaligned epoch" ));
- return NULL;
- }
-
- return epoch;
-}
-
-void
-fd_epoch_init( fd_epoch_t * epoch,
- ulong eah_start_slot,
- ulong eah_stop_slot,
- fd_vote_states_t const * vote_accounts ) {
-
- epoch->first_slot = eah_start_slot;
- epoch->last_slot = eah_stop_slot;
-
- fd_voter_t * epoch_voters = fd_epoch_voters( epoch );
- fd_vote_states_iter_t iter_[1];
- for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( iter_, vote_accounts ); !fd_vote_states_iter_done( iter ); fd_vote_states_iter_next( iter ) ) {
- fd_vote_state_ele_t const * vote_state = fd_vote_states_iter_ele( iter );
-
- if( FD_UNLIKELY( vote_state->stake>0UL ) ) {
-
- #if FD_EPOCH_USE_HANDHOLDING
- FD_TEST( !fd_epoch_voters_query( epoch_voters, vote_state->vote_account, NULL ) );
- FD_TEST( fd_epoch_voters_key_cnt( epoch_voters ) < fd_epoch_voters_key_max( epoch_voters ) );
- #endif
-
- fd_voter_t * voter = fd_epoch_voters_insert( epoch_voters, vote_state->vote_account );
-
- #if FD_EPOCH_USE_HANDHOLDING
- FD_TEST( 0 == memcmp( &voter->key, &vote_state->vote_account, sizeof(fd_pubkey_t) ) );
- FD_TEST( fd_epoch_voters_query( epoch_voters, voter->key, NULL ) );
- #endif
-
- voter->stake = vote_state->stake;
-
- voter->replay_vote.slot = FD_SLOT_NULL;
- voter->gossip_vote.slot = FD_SLOT_NULL;
- voter->rooted_vote.slot = FD_SLOT_NULL;
- }
- epoch->total_stake += vote_state->stake;
- }
-}
-
-void
-fd_epoch_fini( fd_epoch_t * epoch ) {
- fd_epoch_voters_clear( fd_epoch_voters( epoch ) );
- epoch->total_stake = 0UL;
-}
diff --git a/src/choreo/epoch/fd_epoch.h b/src/choreo/epoch/fd_epoch.h
deleted file mode 100644
index b71d5c2ef92..00000000000
--- a/src/choreo/epoch/fd_epoch.h
+++ /dev/null
@@ -1,133 +0,0 @@
-#ifndef HEADER_fd_src_choreo_epoch_fd_epoch_h
-#define HEADER_fd_src_choreo_epoch_fd_epoch_h
-
-#include "../fd_choreo_base.h"
-#include "../voter/fd_voter.h"
-#include "../../flamenco/stakes/fd_vote_states.h"
-
-/* FD_EPOCH_USE_HANDHOLDING: Define this to non-zero at compile time
- to turn on additional runtime checks and logging. */
-
-#ifndef FD_EPOCH_USE_HANDHOLDING
-#define FD_EPOCH_USE_HANDHOLDING 1
-#endif
-
-#define FD_EPOCH_MAGIC (0xf17eda2ce7e90c40UL) /* firedancer epoch version 0 */
-
-struct __attribute__((aligned(128UL))) fd_epoch {
- ulong magic; /* ==FD_EPOCH_MAGIC */
- ulong epoch_gaddr; /* wksp gaddr of this in the backing wksp, non-zero gaddr */
- ulong total_stake; /* total amount of stake in the epoch. */
- ulong first_slot; /* first slot in the epoch */
- ulong last_slot; /* last slot in the epoch */
-
- /* voters_gaddr is the global address of a fd_map_dynamic that
- contains all voters in the current epoch keyed by pubkey (vote
- account address). */
-
- ulong voters_gaddr;
-};
-typedef struct fd_epoch fd_epoch_t;
-
-#define MAP_NAME fd_epoch_voters
-#define MAP_T fd_voter_t
-#define MAP_KEY_T fd_pubkey_t
-#define MAP_KEY_NULL (fd_pubkey_t){0}
-#define MAP_KEY_EQUAL(k0,k1) (!(memcmp((k0).key,(k1).key,sizeof(fd_pubkey_t))))
-#define MAP_KEY_INVAL(k) (MAP_KEY_EQUAL((k),MAP_KEY_NULL))
-#define MAP_KEY_EQUAL_IS_SLOW 1
-#define MAP_KEY_HASH(key) ((key).ui[3])
-#include "../../util/tmpl/fd_map_dynamic.c"
-
-/* fd_epoch_{align,footprint} return the required alignment and
- footprint of a memory region suitable for use as epoch. align is
- double cache line to mitigate false sharing. */
-
-FD_FN_CONST static inline ulong
-fd_epoch_align( void ) {
- return alignof(fd_epoch_t);
-}
-
-FD_FN_CONST static inline ulong
-fd_epoch_footprint( ulong voter_max ) {
- int lg_slot_cnt = fd_ulong_find_msb( fd_ulong_pow2_up( voter_max ) ) + 2; /* fill ratio <= 0.25 */
- return FD_LAYOUT_FINI(
- FD_LAYOUT_APPEND(
- FD_LAYOUT_APPEND(
- FD_LAYOUT_INIT,
- alignof(fd_epoch_t), sizeof(fd_epoch_t) ),
- fd_epoch_voters_align(), fd_epoch_voters_footprint( lg_slot_cnt ) ),
- fd_epoch_align() );
-}
-
-/* fd_epoch_new formats an unused memory region for use as an epoch. mem
- is a non-NULL pointer to this region in the local address space with
- the required footprint and alignment. */
-
-void *
-fd_epoch_new( void * mem, ulong voter_max );
-
-/* fd_epoch_join joins the caller to the epoch. epoch points to the
- first byte of the memory region backing the epoch in the caller's
- address space.
-
- Returns a pointer in the local address space to epoch on success. */
-
-fd_epoch_t *
-fd_epoch_join( void * epoch );
-
-/* fd_epoch_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 epoch is NULL. */
-
-void *
-fd_epoch_leave( fd_epoch_t const * epoch );
-
-/* fd_epoch_delete unformats a memory region used as an epoch. Assumes
- only the local process is joined to the region. Returns a pointer to
- the underlying shared memory region or NULL if used obviously in
- error (e.g. epoch is obviously not an epoch ... logs details). The
- ownership of the memory region is transferred to the caller. */
-
-void *
-fd_epoch_delete( void * epoch );
-
-/* fd_epoch_init initializes a fd_choreo epoch using vote states from
- the runtime bank. Assumes epoch is a valid local join and epoch has
- not already been initialized. This should only be called once
- at the beginning of an epoch. */
-
-void
-fd_epoch_init( fd_epoch_t * epoch,
- ulong eah_start_slot,
- ulong eah_stop_slot,
- fd_vote_states_t const * vote_accounts );
-
-/* fd_epoch_fini finishes an epoch. Assumes epoch is a valid local join
- and epoch has already been initialized. This should only be called
- once at the end of an epoch. */
-
-void
-fd_epoch_fini( fd_epoch_t * epoch );
-
-/* fd_epoch_wksp returns the local join to the wksp backing the epoch.
- The lifetime of the returned pointer is at least as long as the
- lifetime of the local join. Assumes epoch is a current local
- join. */
-
-FD_FN_PURE static inline fd_wksp_t *
-fd_epoch_wksp( fd_epoch_t const * epoch ) {
- return (fd_wksp_t *)( ( (ulong)epoch ) - epoch->epoch_gaddr );
-}
-
-FD_FN_PURE static inline fd_voter_t *
-fd_epoch_voters( fd_epoch_t * epoch ) {
- return fd_wksp_laddr_fast( fd_epoch_wksp( epoch ), epoch->voters_gaddr );
-}
-
-FD_FN_PURE static inline fd_voter_t const *
-fd_epoch_voters_const( fd_epoch_t const * epoch ) {
- return fd_wksp_laddr_fast( fd_epoch_wksp( epoch ), epoch->voters_gaddr );
-}
-
-#endif /* HEADER_fd_src_choreo_epoch_fd_epoch_h */
diff --git a/src/choreo/fd_choreo.h b/src/choreo/fd_choreo.h
index 60151d0526a..dd516fbb289 100644
--- a/src/choreo/fd_choreo.h
+++ b/src/choreo/fd_choreo.h
@@ -2,7 +2,6 @@
#define HEADER_fd_src_choreo_fd_choreo_h
#include "fd_choreo_base.h"
-#include "epoch/fd_epoch.h"
#include "eqvoc/fd_eqvoc.h"
#include "ghost/fd_ghost.h"
#include "notar/fd_notar.h"
diff --git a/src/choreo/fd_choreo_base.h b/src/choreo/fd_choreo_base.h
index 3f7b498aa11..667bee7c691 100644
--- a/src/choreo/fd_choreo_base.h
+++ b/src/choreo/fd_choreo_base.h
@@ -19,7 +19,6 @@
#include "../flamenco/types/fd_types.h"
/* clang-format off */
-#define FD_BLOCK_MAX (4096) /* the maximum # of blocks we support holding at once. must be >=512. */
#define FD_VOTER_MAX (4096) /* the maximum # of unique voters ie. node pubkeys. */
#define FD_EQVOCSAFE_PCT (0.52)
#define FD_CONFIRMED_PCT (2.0 / 3.0)
@@ -32,17 +31,10 @@
#define FD_SLOT_PUBKEY_HASH(key,seed) FD_SLOT_HASH_HASH(key,seed)
/* clang-format on */
-/* The block_id is the merkle root of the last FEC set for a slot. This
- is guaranteed to be unique (practically speaking, the probability of
- collision before sun burns out is negligibly miniscule).
-
- This is used as the identifier for a block (hence "block_id") because
- unlike the slot number, if a leader equivocates (ie. produces
- multiple blocks for the same slot), the block_id will remain unique
- unlike the slot. */
-
typedef uchar fd_block_id_t[ 32UL ];
typedef fd_slot_hash_t fd_slot_pubkey_t;
+static const fd_pubkey_t pubkey_null = {{ 0 }};
+
#endif /* HEADER_fd_src_choreo_fd_choreo_base_h */
diff --git a/src/choreo/ghost/fd_ghost.c b/src/choreo/ghost/fd_ghost.c
index 993a7f8089e..77444a84cd1 100644
--- a/src/choreo/ghost/fd_ghost.c
+++ b/src/choreo/ghost/fd_ghost.c
@@ -1,9 +1,35 @@
#include "fd_ghost.h"
+#include "fd_ghost_private.h"
#define LOGGING 0
+ulong
+fd_ghost_align( void ) {
+ return alignof(fd_ghost_t);
+}
+
+ulong
+fd_ghost_footprint( ulong blk_max,
+ ulong vtr_max ) {
+ int lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( vtr_max ) ) + 1;
+ return FD_LAYOUT_FINI(
+ FD_LAYOUT_APPEND(
+ FD_LAYOUT_APPEND(
+ FD_LAYOUT_APPEND(
+ FD_LAYOUT_APPEND(
+ FD_LAYOUT_INIT,
+ alignof(fd_ghost_t), sizeof(fd_ghost_t) ),
+ pool_align(), pool_footprint( blk_max ) ),
+ blk_map_align(), blk_map_footprint( blk_max ) ),
+ vtr_map_align(), vtr_map_footprint( lg_vtr_max ) ),
+ fd_ghost_align() );
+}
+
void *
-fd_ghost_new( void * shmem, ulong ele_max, ulong seed ) {
+fd_ghost_new( void * shmem,
+ ulong blk_max,
+ ulong vtr_max,
+ ulong seed ) {
if( FD_UNLIKELY( !shmem ) ) {
FD_LOG_WARNING(( "NULL mem" ));
@@ -15,9 +41,9 @@ fd_ghost_new( void * shmem, ulong ele_max, ulong seed ) {
return NULL;
}
- ulong footprint = fd_ghost_footprint( ele_max );
+ ulong footprint = fd_ghost_footprint( blk_max, vtr_max );
if( FD_UNLIKELY( !footprint ) ) {
- FD_LOG_WARNING(( "bad ele_max (%lu)", ele_max ));
+ FD_LOG_WARNING(( "bad blk_max (%lu)", blk_max ));
return NULL;
}
@@ -29,28 +55,19 @@ fd_ghost_new( void * shmem, ulong ele_max, ulong seed ) {
fd_memset( shmem, 0, footprint );
- int elg_max = fd_ulong_find_msb( fd_ulong_pow2_up( ele_max ) );
+ int lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( FD_VOTER_MAX ) ) + 1;
FD_SCRATCH_ALLOC_INIT( l, shmem );
- fd_ghost_t * ghost = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(), sizeof( fd_ghost_t ) );
- void * pool = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_pool_align(), fd_ghost_pool_footprint ( ele_max ) );
- void * hash = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_hash_map_align(), fd_ghost_hash_map_footprint( ele_max ) );
- void * slot = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_slot_map_align(), fd_ghost_slot_map_footprint( ele_max ) );
- void * dup = FD_SCRATCH_ALLOC_APPEND( l, fd_dup_seen_map_align(), fd_dup_seen_map_footprint ( elg_max ) );
+ fd_ghost_t * ghost = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ghost_t), sizeof(fd_ghost_t) );
+ void * pool = FD_SCRATCH_ALLOC_APPEND( l, pool_align(), pool_footprint( blk_max ) );
+ void * map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint( blk_max ) );
+ void * vtr = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(), vtr_map_footprint( lg_vtr_max ) );
FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint );
- ghost->pool_gaddr = fd_wksp_gaddr_fast( wksp, fd_ghost_pool_join ( fd_ghost_pool_new ( pool, ele_max ) ) );
- ghost->hash_map_gaddr = fd_wksp_gaddr_fast( wksp, fd_ghost_hash_map_join( fd_ghost_hash_map_new( hash, ele_max, seed ) ) );
- ghost->slot_map_gaddr = fd_wksp_gaddr_fast( wksp, fd_ghost_slot_map_join( fd_ghost_slot_map_new( slot, ele_max, seed ) ) );
- ghost->dup_map_gaddr = fd_wksp_gaddr_fast( wksp, fd_dup_seen_map_join ( fd_dup_seen_map_new ( dup, elg_max ) ) );
-
- ghost->ghost_gaddr = fd_wksp_gaddr_fast( wksp, ghost );
- ghost->seed = seed;
- ghost->root = fd_ghost_pool_idx_null( fd_ghost_pool( ghost ) );
-
- FD_COMPILER_MFENCE();
- FD_VOLATILE( ghost->magic ) = FD_GHOST_MAGIC;
- FD_COMPILER_MFENCE();
+ ghost->pool = pool_new( pool, blk_max );
+ ghost->blk_map = blk_map_new( map, blk_max, seed );
+ ghost->vtr_map = vtr_map_new( vtr, lg_vtr_max );
+ ghost->root = pool_idx_null( ghost->pool );
return shmem;
}
@@ -69,16 +86,9 @@ fd_ghost_join( void * shghost ) {
return NULL;
}
- fd_wksp_t * wksp = fd_wksp_containing( ghost );
- if( FD_UNLIKELY( !wksp ) ) {
- FD_LOG_WARNING(( "ghost must be part of a workspace" ));
- return NULL;
- }
-
- if( FD_UNLIKELY( ghost->magic!=FD_GHOST_MAGIC ) ) {
- FD_LOG_WARNING(( "bad magic" ));
- return NULL;
- }
+ ghost->pool = pool_join( ghost->pool );
+ ghost->blk_map = blk_map_join( ghost->blk_map );
+ ghost->vtr_map = vtr_map_join( ghost->vtr_map );
return ghost;
}
@@ -110,546 +120,335 @@ fd_ghost_delete( void * ghost ) {
return ghost;
}
-/* Inserts element into the hash-keyed map. If there isn't already a
- block executed for the same slot, insert into the slot-keyed map. */
-static void
-maps_insert( fd_ghost_t * ghost, fd_ghost_ele_t * ele ) {
- fd_ghost_hash_map_t * maph = fd_ghost_hash_map( ghost );
- fd_ghost_slot_map_t * maps = fd_ghost_slot_map( ghost );
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
- ulong null = fd_ghost_pool_idx_null( pool );
-
- fd_ghost_hash_map_ele_insert( maph, ele, pool );
- fd_ghost_ele_t * ele_slot = fd_ghost_slot_map_ele_query( maps, &ele->slot, NULL, pool );
- if( FD_LIKELY( !ele_slot ) ) {
- fd_ghost_slot_map_ele_insert( maps, ele, pool ); /* cannot fail */
- } else {
- /* If the slot is already in the map, then we have a duplicate */
- while( FD_UNLIKELY( ele_slot->eqvoc != null ) ) {
- ele_slot = fd_ghost_pool_ele( pool, ele_slot->eqvoc );
- }
- ele_slot->eqvoc = fd_ghost_pool_idx( pool, ele );
- }
+fd_ghost_blk_t *
+fd_ghost_root( fd_ghost_t * ghost ) {
+ return pool_ele( ghost->pool, ghost->root );
}
-/* Removes all occurrences of `hash` from the maps. */
-static fd_ghost_ele_t *
-maps_remove( fd_ghost_t * ghost, fd_hash_t * hash ) {
- fd_ghost_hash_map_t * maph = fd_ghost_hash_map( ghost );
- fd_ghost_slot_map_t * maps = fd_ghost_slot_map( ghost );
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
-
- fd_ghost_ele_t * ele = fd_ghost_hash_map_ele_remove( maph, hash, NULL, pool );
- if( FD_LIKELY( ele ) ) {
- fd_ghost_ele_t * eles = fd_ghost_slot_map_ele_query( maps, &ele->slot, NULL, pool );
- if( FD_LIKELY( eles && memcmp( &ele->key, hash, sizeof(fd_hash_t) ) == 0 ) ) {
- fd_ghost_slot_map_ele_remove( maps, &ele->slot, NULL, pool );
- }
- }
- return ele;
+fd_ghost_blk_t *
+fd_ghost_query( fd_ghost_t * ghost,
+ fd_hash_t const * block_id ) {
+ return blk_map_ele_query( ghost->blk_map, block_id, NULL, ghost->pool );
}
-void
-fd_ghost_init( fd_ghost_t * ghost, ulong root_slot, fd_hash_t * hash ) {
-
- if( FD_UNLIKELY( !ghost ) ) {
- FD_LOG_WARNING(( "NULL ghost" ));
- return;
- }
-
- if( FD_UNLIKELY( root_slot == FD_SLOT_NULL ) ) {
- FD_LOG_WARNING(( "NULL root" ));
- return;
- }
-
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
- ulong null = fd_ghost_pool_idx_null( pool );
-
- if( FD_UNLIKELY( ghost->root != null ) ) {
- FD_LOG_WARNING(( "ghost already initialized" ));
- return;
+fd_ghost_blk_t *
+fd_ghost_best( fd_ghost_t * ghost,
+ fd_ghost_blk_t * root ) {
+ fd_ghost_blk_t * pool = ghost->pool;
+ ulong null = pool_idx_null( pool );
+ fd_ghost_blk_t * best = root;
+ while( FD_LIKELY( best->child != null ) ) {
+ int valid = 0; /* at least one child is valid */
+ fd_ghost_blk_t * child = pool_ele( ghost->pool, best->child );
+ while( FD_LIKELY( child ) ) { /* greedily pick the heaviest valid child */
+ if( FD_LIKELY( child->valid ) ) {
+ if( FD_LIKELY( !valid ) ) { /* this is the first valid child, so progress the head */
+ best = child;
+ valid = 1;
+ }
+ best = fd_ptr_if(
+ fd_int_if(
+ child->stake == best->stake, /* if the weights are equal */
+ child->slot < best->slot, /* then tie-break by lower slot number */
+ child->stake > best->stake ), /* else return heavier */
+ child, best );
+ }
+ child = pool_ele( ghost->pool, child->sibling );
+ }
+ if( FD_UNLIKELY( !valid ) ) break; /* no children are valid, so short-circuit traversal */
}
-
- /* Initialize the root ele from a pool element. */
-
- fd_ghost_ele_t * root = fd_ghost_pool_ele_acquire( pool );
- root->key = *hash;
- root->slot = root_slot;
- root->next = null;
- root->nexts = null;
- root->eqvoc = null;
- root->parent = null;
- root->child = null;
- root->sibling = null;
- root->weight = 0;
- root->replay_stake = 0;
- root->gossip_stake = 0;
- root->rooted_stake = 0;
- root->valid = 1;
-
- /* Insert the root and record the root ele's pool idx. */
-
- maps_insert( ghost, root ); /* cannot fail */
- ghost->root = fd_ghost_pool_idx( pool, root );
-
- /* Sanity checks. */
-
- FD_TEST( fd_ghost_root( ghost ) );
- FD_TEST( fd_ghost_root( ghost ) == fd_ghost_query( ghost, hash ) );
- FD_TEST( fd_ghost_root( ghost )->slot == root_slot );
-
- return;
+ return best;
}
-int
-fd_ghost_verify( fd_ghost_t const * ghost ) {
- if( FD_UNLIKELY( !ghost ) ) {
- FD_LOG_WARNING(( "NULL ghost" ));
- return -1;
- }
-
- if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ghost, fd_ghost_align() ) ) ) {
- FD_LOG_WARNING(( "misaligned ghost" ));
- return -1;
- }
-
- fd_wksp_t * wksp = fd_wksp_containing( ghost );
- if( FD_UNLIKELY( !wksp ) ) {
- FD_LOG_WARNING(( "ghost must be part of a workspace" ));
- return -1;
- }
+fd_ghost_blk_t *
+fd_ghost_deepest( fd_ghost_t * ghost,
+ fd_ghost_blk_t * root ) {
+ fd_ghost_blk_t * pool = ghost->pool;
+ ulong null = pool_idx_null( pool );
+ fd_ghost_blk_t * head = blk_map_ele_remove( ghost->blk_map, &root->id, NULL, pool ); /* remove ele from map to reuse `.next` */
+ fd_ghost_blk_t * tail = head;
- if( FD_UNLIKELY( ghost->magic!=FD_GHOST_MAGIC ) ) {
- FD_LOG_WARNING(( "bad magic" ));
- return -1;
- }
+ /* Below is a level-order traversal (BFS), returning the last leaf
+ which is guaranteed to return an element of the max depth.
- fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
- ulong null = fd_ghost_pool_idx_null( pool );
- fd_ghost_hash_map_t const * maph = fd_ghost_hash_map_const( ghost );
+ It temporarily removes elements of the map when pushing onto the
+ BFS queue to reuse the .next pointer and then inserts back into
+ the map on queue pop. */
- /* Check every ele that exists in pool exists in map. */
-
- if( fd_ghost_hash_map_verify( maph, fd_ghost_pool_max( pool ), pool ) ) return -1;
-
- /* Check every ele's weight is >= sum of children's weights. */
-
- fd_ghost_ele_t const * parent = fd_ghost_root_const( ghost );
- while( FD_LIKELY( parent ) ) {
- ulong weight = 0;
- fd_ghost_ele_t const * child = fd_ghost_child_const( ghost, parent );
- while( FD_LIKELY( child && child->sibling != null ) ) {
- weight += child->weight;
- child = fd_ghost_sibling_const( ghost, child );
+ head->next = null;
+ while( FD_LIKELY( head ) ) {
+ fd_ghost_blk_t const * child = pool_ele( ghost->pool, head->child );
+ while( FD_LIKELY( child ) ) {
+ tail->next = pool_idx( pool, blk_map_ele_remove( ghost->blk_map, &child->id, NULL, pool ) );
+ tail = pool_ele( pool, tail->next );
+ tail->next = pool_idx_null( pool );
+ child = pool_ele( pool, child->sibling ); /* next sibling */
}
- # if FD_GHOST_USE_HANDHOLDING
- FD_TEST( parent->weight >= weight );
- # endif
- parent = fd_ghost_pool_ele_const( pool, parent->next );
+ fd_ghost_blk_t * next = pool_ele( pool, head->next ); /* pop prune queue head */
+ blk_map_ele_insert( ghost->blk_map, head, pool ); /* re-insert head into map */
+ head = next;
}
-
- return 0;
+ return head;
}
-static void
-fd_ghost_mark_valid( fd_ghost_t * ghost, fd_hash_t const * bid ) {
- fd_ghost_ele_t * ele = fd_ghost_query( ghost, bid );
- if( FD_LIKELY( ele ) ) ele->valid = 1;
+#define PREDICATE_ANCESTOR( predicate ) do { \
+ fd_ghost_blk_t * ancestor = descendant; \
+ while( FD_LIKELY( ancestor ) ) { \
+ if( FD_LIKELY( predicate ) ) return ancestor; \
+ ancestor = pool_ele( ghost->pool, ancestor->parent ); \
+ } \
+ return NULL; \
+ } while(0)
+
+fd_ghost_blk_t *
+fd_ghost_ancestor( fd_ghost_t * ghost,
+ fd_ghost_blk_t * descendant,
+ fd_hash_t const * ancestor_id ) {
+ PREDICATE_ANCESTOR( 0==memcmp( &ancestor->id, ancestor_id, sizeof(fd_hash_t) ) );
}
-static void
-fd_ghost_mark_invalid( fd_ghost_t * ghost, ulong slot, ulong total_stake ) {
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
- ulong null = fd_ghost_pool_idx_null( pool );
- fd_hash_t const * hash = fd_ghost_hash( ghost, slot );
- FD_TEST( hash ); /* mark_invalid should never get called on a non-existing slot */
-
- fd_ghost_ele_t * ele = fd_ghost_query( ghost, hash );
- if( FD_LIKELY( ele && !is_duplicate_confirmed( ghost, &ele->key, total_stake ) ) ) ele->valid = 0;
- while( FD_UNLIKELY( ele->eqvoc != null ) ) {
- fd_ghost_ele_t * eqvoc = fd_ghost_pool_ele( pool, ele->eqvoc );
- if( FD_LIKELY( !is_duplicate_confirmed( ghost, &eqvoc->key, total_stake ) ) ) eqvoc->valid = 0;
- ele = eqvoc;
- }
+fd_ghost_blk_t *
+fd_ghost_slot_ancestor( fd_ghost_t * ghost,
+ fd_ghost_blk_t * descendant,
+ ulong slot ) {
+ PREDICATE_ANCESTOR( ancestor->slot == slot );
}
-fd_ghost_ele_t *
-fd_ghost_insert( fd_ghost_t * ghost, fd_hash_t const * parent_hash, ulong slot, fd_hash_t const * hash, ulong total_stake ) {
-# if LOGGING
- FD_LOG_NOTICE(( "[%s] slot: %lu, %s. parent: %s.", __func__, slot, FD_BASE58_ENC_32_ALLOCA(hash), FD_BASE58_ENC_32_ALLOCA(parent_hash) ));
-# endif
+fd_ghost_blk_t *
+fd_ghost_invalid_ancestor( fd_ghost_t * ghost,
+ fd_ghost_blk_t * descendant ) {
+ PREDICATE_ANCESTOR( !ancestor->valid );
+}
-# if FD_GHOST_USE_HANDHOLDING
- FD_TEST( ghost->magic == FD_GHOST_MAGIC );
-# endif
+fd_ghost_blk_t *
+fd_ghost_insert( fd_ghost_t * ghost,
+ fd_hash_t const * block_id,
+ fd_hash_t const * parent_block_id,
+ ulong slot ) {
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
- ulong null = fd_ghost_pool_idx_null( pool );
- fd_ghost_ele_t * parent = fd_ghost_query( ghost, parent_hash );
- fd_ghost_ele_t const * root = fd_ghost_root( ghost );
+ fd_ghost_blk_t * pool = ghost->pool;
+ ulong null = pool_idx_null( pool );
+ fd_ghost_blk_t * blk = blk_map_ele_query( ghost->blk_map, block_id, NULL, pool );
# if FD_GHOST_USE_HANDHOLDING
- if( FD_UNLIKELY( fd_ghost_query( ghost, hash ) ) ) { FD_LOG_WARNING(( "[%s] hash %s already in ghost.", __func__, FD_BASE58_ENC_32_ALLOCA(hash) )); return NULL; }
- if( FD_UNLIKELY( !parent ) ) { FD_LOG_WARNING(( "[%s] missing `parent_id` %s for (%s, %lu)", __func__, FD_BASE58_ENC_32_ALLOCA(parent_hash), FD_BASE58_ENC_32_ALLOCA(hash), slot )); return NULL; }
- if( FD_UNLIKELY( !fd_ghost_pool_free( pool ) ) ) { FD_LOG_WARNING(( "[%s] ghost full.", __func__ )); return NULL; }
- if( FD_UNLIKELY( slot <= root->slot ) ) { FD_LOG_WARNING(( "[%s] slot %lu <= root %lu", __func__, slot, root->slot )); return NULL; }
+ if( FD_UNLIKELY( blk ) ) { FD_LOG_WARNING(( "[%s] hash %s already in ghost", __func__, FD_BASE58_ENC_32_ALLOCA( block_id ) )); return NULL; }
+ if( FD_UNLIKELY( !pool_free( pool ) ) ) { FD_LOG_WARNING(( "[%s] ghost full", __func__ )); return NULL; }
# endif
- fd_ghost_ele_t * ele = fd_ghost_pool_ele_acquire( pool );
- ele->key = *hash;
- ele->slot = slot;
- ele->eqvoc = null;
- ele->next = null;
- ele->nexts = null;
- ele->parent = null;
- ele->child = null;
- ele->sibling = null;
- ele->weight = 0;
- ele->replay_stake = 0;
- ele->gossip_stake = 0;
- ele->rooted_stake = 0;
- ele->valid = 1;
- ele->parent = fd_ghost_pool_idx( pool, parent );
+ blk = pool_ele_acquire( pool );
+ blk->id = *block_id;
+ blk->slot = slot;
+ blk->next = null;
+ blk->parent = null;
+ blk->child = null;
+ blk->sibling = null;
+ blk->stake = 0;
+ blk->total_stake = 0;
+ blk->eqvoc = 0;
+ blk->conf = 0;
+ blk->valid = 1;
+ blk_map_ele_insert( ghost->blk_map, blk, pool );
+
+ if( FD_UNLIKELY( !parent_block_id ) ) {
+ ghost->root = pool_idx( pool, blk );
+ return blk;
+ }
+
+ fd_ghost_blk_t * parent = blk_map_ele_query( ghost->blk_map, parent_block_id, NULL, pool );
+ FD_TEST( parent ); /* parent must exist if this is not the first insertion */
+ blk->parent = pool_idx( pool, parent );
if( FD_LIKELY( parent->child == null ) ) {
- parent->child = fd_ghost_pool_idx( pool, ele ); /* left-child */
+ parent->child = pool_idx( pool, blk ); /* left-child */
} else {
- fd_ghost_ele_t * curr = fd_ghost_pool_ele( pool, parent->child );
- while( curr->sibling != null ) curr = fd_ghost_pool_ele( pool, curr->sibling );
- curr->sibling = fd_ghost_pool_idx( pool, ele ); /* right-sibling */
- }
- maps_insert( ghost, ele );
-
- /* Checks if block has a duplicate message, but the message arrived
- before the block was added to ghost. */
-
- if( FD_UNLIKELY( fd_dup_seen_map_query( fd_ghost_dup_map( ghost ), slot, NULL ) ) ) {
- fd_ghost_mark_invalid( ghost, slot, total_stake );
+ fd_ghost_blk_t * sibling = pool_ele( pool, parent->child );
+ while( sibling->sibling != null ) sibling = pool_ele( pool, sibling->sibling );
+ sibling->sibling = pool_idx( pool, blk ); /* right-sibling */
}
- return ele;
-}
-
-fd_ghost_ele_t const *
-fd_ghost_head( fd_ghost_t const * ghost, fd_ghost_ele_t const * root ) {
-
-# if FD_GHOST_USE_HANDHOLDING
- FD_TEST( ghost->magic == FD_GHOST_MAGIC );
- FD_TEST( root );
-# endif
- if( FD_UNLIKELY( !root->valid ) ) return NULL; /* no valid ghost heads */
-
- fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
- fd_ghost_ele_t const * head = root;
- ulong null = fd_ghost_pool_idx_null( pool );
-
- while( FD_LIKELY( head->child != null ) ) {
- int valid_child = 0; /* at least one child is valid */
- fd_ghost_ele_t const * child = fd_ghost_child_const( ghost, head );
- while( FD_LIKELY( child ) ) { /* greedily pick the heaviest valid child */
- if( FD_LIKELY( child->valid ) ) {
- if( FD_LIKELY( !valid_child ) ) { /* this is the first valid child, so progress the head */
- head = child;
- valid_child = 1;
- }
- head = fd_ptr_if(
- fd_int_if(
- child->weight == head->weight, /* if the weights are equal */
- child->slot < head->slot, /* then tie-break by lower slot number */
- child->weight > head->weight ), /* else return heavier */
- child, head );
- }
- child = fd_ghost_sibling_const( ghost, child );
- }
- if( FD_UNLIKELY( !valid_child ) ) break; /* no children are valid, so short-circuit traversal */
- }
- return head;
+ return blk;
}
void
-fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, fd_hash_t const * hash ) {
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
- fd_vote_record_t vote = voter->replay_vote;
- fd_ghost_ele_t const * root = fd_ghost_root( ghost );
- fd_ghost_ele_t const * vote_ele = fd_ghost_query_const( ghost, hash );
- ulong slot = vote_ele->slot;
-
-# if LOGGING
- FD_LOG_INFO(( "[%s] voter: %s slot_hash: (%s, %lu) last: %lu", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key), FD_BASE58_ENC_32_ALLOCA(hash), slot, vote.slot ));
-# endif
-
- /* Short-circuit if the vote slot is older than the root. */
+fd_ghost_count_vote( fd_ghost_t * ghost,
+ fd_ghost_blk_t * blk,
+ fd_pubkey_t const * vtr_addr,
+ ulong stake,
+ ulong slot ) {
- if( FD_UNLIKELY( slot < root->slot ) ) return;
+ fd_ghost_blk_t const * root = fd_ghost_root( ghost );
+ fd_ghost_blk_t * pool = ghost->pool;
+ fd_ghost_vtr_t * vtr = vtr_map_query( ghost->vtr_map, *vtr_addr, NULL );
- /* Short-circuit if the vote is unchanged. It's possible that voter is
- switching from A to A', which should be a slashable offense. */
+ if( FD_UNLIKELY( slot == ULONG_MAX ) ) return; /* hasn't voted */
+ if( FD_UNLIKELY( slot < root->slot ) ) return; /* vote older than root */
- if( FD_UNLIKELY( memcmp( &vote.hash, hash, sizeof(fd_hash_t) ) == 0 ) ) return;
+ if( FD_UNLIKELY( !vtr ) ) vtr = vtr_map_insert( ghost->vtr_map, *vtr_addr );
+ else {
- /* TODO add logic that only the least bank hash is kept if the same
- voter votes for the same slot multiple times. */
+ /* Only process the vote if it is not the same as the previous vote
+ and also that the vote slot is most recent. It's possible for
+ ghost to process votes out of order because votes happen in
+ replay order which is concurrent across different forks.
- /* Short-circuit if this vote slot < the last vote slot we processed
- for this voter. The order we replay forks is non-deterministic due
- to network propagation variance, so it is possible we are see an
- older vote after a newer vote (relative to the slot in which the
- vote actually landed).
+ For example, if a voter votes for 3 then switches to 5, we might
+ observe the vote for 5 before the vote for 3. */
- For example, 3-4 and 5-6 fork from 2, we might see the vote for 5
- in block 6 then the vote for 3 in block 4. We ignore the vote for 3
- in block 4 if we already processed the vote for 5 in block 6. */
+ if( FD_UNLIKELY( !( slot > vtr->prev_slot ) ) ) return;
- if( FD_UNLIKELY( vote.slot != FD_SLOT_NULL && slot < vote.slot ) ) return;
+ /* LMD-rule: subtract the voter's stake from the entire fork they
+ previously voted for. */
- /* LMD-rule: subtract the voter's stake from the ghost ele
- corresponding to their previous vote slot. If the voter's previous
- vote slot is not in ghost than we have either not processed
- this voter previously or their previous vote slot was already
- pruned (because we published a new root). */
+ /* TODO can optimize this if they're voting for the same fork */
- fd_ghost_ele_t * prev = fd_ghost_query( ghost, &vote.hash );
- if( FD_LIKELY( prev && vote.slot != FD_SLOT_NULL ) ) { /* no previous vote or pruned */
-# if LOGGING
- FD_LOG_INFO(( "[%s] subtracting (%s, %lu, %lu, %s)", __func__, FD_BASE58_ENC_32_ALLOCA( &voter->key ), voter->stake, vote.slot, FD_BASE58_ENC_32_ALLOCA( &vote.hash ) ));
-# endif
- int cf = __builtin_usubl_overflow( prev->replay_stake, voter->stake, &prev->replay_stake );
- if( FD_UNLIKELY( cf ) ) FD_LOG_CRIT(( "[%s] sub overflow. prev->replay_stake %lu voter->stake %lu", __func__, prev->replay_stake, voter->stake ));
- fd_ghost_ele_t * ancestor = prev;
+ fd_ghost_blk_t * ancestor = blk_map_ele_query( ghost->blk_map, &vtr->prev_block_id, NULL, pool );
while( FD_LIKELY( ancestor ) ) {
- cf = __builtin_usubl_overflow( ancestor->weight, voter->stake, &ancestor->weight );
- if( FD_UNLIKELY( cf ) ) FD_LOG_CRIT(( "[%s] sub overflow. ancestor->weight %lu latest_vote->stake %lu", __func__, ancestor->weight, voter->stake ));
- ancestor = fd_ghost_pool_ele( pool, ancestor->parent );
+ int cf = __builtin_usubl_overflow( ancestor->stake, vtr->prev_stake, &ancestor->stake );
+ if( FD_UNLIKELY( cf ) ) FD_LOG_CRIT(( "[%s] overflow: %lu - %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, vtr->prev_stake, ancestor->slot, FD_BASE58_ENC_32_ALLOCA( &ancestor->id ) ));
+ ancestor = pool_ele( pool, ancestor->parent );
}
}
- /* Add voter's stake to the ghost ele keyed by `slot`. Propagate the
- vote stake up the ancestry. We do this for all cases we exited
+ /* Add voter's stake to the entire fork they are voting for. Propagate
+ the vote stake up the ancestry. We do this for all cases we exited
above: this vote is the first vote we've seen from a pubkey, this
vote is switched from a previous vote that was on a missing ele
- (pruned), or the regular case */
-
- fd_ghost_ele_t * curr = fd_ghost_query( ghost, hash );
- if( FD_UNLIKELY( !curr ) ) FD_LOG_CRIT(( "corrupt ghost" ));
+ (pruned), or the regular case. */
-# if LOGGING
- FD_LOG_INFO(( "[%s] adding (%s, %lu, %lu)", __func__, FD_BASE58_ENC_32_ALLOCA( &voter->key ), voter->stake, slot ));
-# endif
- int cf = __builtin_uaddl_overflow( curr->replay_stake, voter->stake, &curr->replay_stake );
- if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] add overflow. ele->stake %lu latest_vote->stake %lu", __func__, curr->replay_stake, voter->stake ));
- fd_ghost_ele_t * ancestor = curr;
+ fd_ghost_blk_t * ancestor = blk;
while( FD_LIKELY( ancestor ) ) {
- int cf = __builtin_uaddl_overflow( ancestor->weight, voter->stake, &ancestor->weight );
- if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] add overflow. ancestor->weight %lu latest_vote->stake %lu", __func__, ancestor->weight, voter->stake ));
- ancestor = fd_ghost_parent( ghost, ancestor );
+ int cf = __builtin_uaddl_overflow( ancestor->stake, stake, &ancestor->stake );
+ if( FD_UNLIKELY( cf ) ) FD_LOG_CRIT(( "[%s] overflow: %lu + %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, stake, ancestor->slot, FD_BASE58_ENC_32_ALLOCA( &ancestor->id ) ));
+ ancestor = pool_ele( ghost->pool, ancestor->parent );
}
- voter->replay_vote.slot = slot; /* update the cached replay vote slot on voter */
- voter->replay_vote.hash = *hash; /* update the cached replay vote hash on voter */
-}
-
-void
-fd_ghost_gossip_vote( FD_PARAM_UNUSED fd_ghost_t * ghost,
- FD_PARAM_UNUSED fd_voter_t * voter,
- FD_PARAM_UNUSED ulong slot ) {
- FD_LOG_ERR(( "unimplemented" ));
+ vtr->prev_block_id = blk->id;
+ vtr->prev_stake = stake;
}
void
-fd_ghost_rooted_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong root ) {
-# if LOGGING
- FD_LOG_INFO(( "[%s] root %lu, pubkey %s, stake %lu", __func__, root, FD_BASE58_ENC_32_ALLOCA(&voter->key), voter->stake ));
-# endif
-
- /* It is invariant that the voter's root is found in ghost (as long as
- voter's root >= our root ). This is because voter's root is sourced
- from their vote state, so it must be on the fork we're replaying
- and we must have already inserted their root slot into ghost. */
-
- fd_ghost_ele_t * ele = fd_ghost_query( ghost, fd_ghost_hash( ghost, root ) );
-# if FD_GHOST_USE_HANDHOLDING
- if( FD_UNLIKELY( !ele ) ) FD_LOG_CRIT(( "[%s] missing voter %s's root %lu.", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key), root ));
-# endif
-
- /* Add to the rooted stake. */
-
- ele->rooted_stake += voter->stake;
-}
+fd_ghost_publish( fd_ghost_t * ghost,
+ fd_ghost_blk_t * newr ) {
-fd_ghost_ele_t const *
-fd_ghost_publish( fd_ghost_t * ghost, fd_hash_t const * hash ) {
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
- ulong null = fd_ghost_pool_idx_null( pool );
- fd_ghost_ele_t * oldr = fd_ghost_root( ghost );
- fd_ghost_ele_t * newr = fd_ghost_query( ghost, hash );
+ fd_ghost_blk_t * pool = ghost->pool;
+ ulong null = pool_idx_null( pool );
+ fd_ghost_blk_t * oldr = fd_ghost_root( ghost );
-# if FD_GHOST_USE_HANDHOLDING
- if( FD_UNLIKELY( !newr ) ) { FD_LOG_WARNING(( "[%s] publish hash %s not found", __func__, FD_BASE58_ENC_32_ALLOCA(hash) )); return NULL; }
- if( FD_UNLIKELY( newr->slot <= oldr->slot ) ) { FD_LOG_WARNING(( "[%s] publish slot %lu <= root %lu.", __func__, newr->slot, oldr->slot )); return NULL; }
- if( FD_UNLIKELY( !fd_ghost_is_ancestor( ghost, &oldr->key, &newr->key ) ) ) { FD_LOG_WARNING(( "[%s] publish slot %lu not ancestor %lu.", __func__, newr->slot, oldr->slot )); return NULL; }
-# endif
+ if( FD_UNLIKELY( oldr==newr ) ) return;
/* First, remove the previous root, and add it to the prune list. In
this context, head is the list head (not to be confused with the
ghost head.) */
- fd_ghost_ele_t * head = maps_remove( ghost, &oldr->key );
- fd_ghost_ele_t * tail = head;
+ fd_ghost_blk_t * head = blk_map_ele_remove( ghost->blk_map, &oldr->id, NULL, pool ); /* remove ele from map to reuse `.next` */
+ fd_ghost_blk_t * tail = head;
/* Second, BFS down the tree, pruning all of root's ancestors and also
any descendants of those ancestors. */
head->next = null;
while( FD_LIKELY( head ) ) {
- fd_ghost_ele_t * child = fd_ghost_pool_ele( pool, head->child );
+ fd_ghost_blk_t * child = pool_ele( pool, head->child );
while( FD_LIKELY( child ) ) { /* iterate over children */
if( FD_LIKELY( child != newr ) ) { /* stop at new root */
- tail->next = fd_ghost_pool_idx( pool, maps_remove( ghost, &child->key ) ); /* remove ele from map to reuse `.next` */
- tail = fd_ghost_pool_ele( pool, tail->next ); /* push onto prune queue (so descendants can be pruned) */
- tail->next = fd_ghost_pool_idx_null( pool );
+ tail->next = blk_map_idx_remove( ghost->blk_map, &child->id, null, pool ); /* remove ele from map to reuse `.next` */
+ tail = pool_ele( pool, tail->next ); /* push onto prune queue (so descendants can be pruned) */
+ tail->next = pool_idx_null( pool );
}
- child = fd_ghost_pool_ele( pool, child->sibling ); /* next sibling */
+ child = pool_ele( pool, child->sibling ); /* next sibling */
}
- fd_ghost_ele_t * next = fd_ghost_pool_ele( pool, head->next ); /* pop prune queue head */
- fd_ghost_pool_ele_release( pool, head ); /* free prune queue head */
- head = next; /* move prune queue head forward */
+ fd_ghost_blk_t * next = pool_ele( pool, head->next ); /* pop prune queue head */
+ pool_ele_release( pool, head ); /* free prune queue head */
+ head = next; /* move prune queue head forward */
}
- newr->parent = null; /* unlink old root*/
- ghost->root = fd_ghost_pool_idx( pool, newr ); /* replace with new root */
- return newr;
-}
-
-fd_ghost_ele_t const *
-fd_ghost_gca( fd_ghost_t const * ghost, fd_hash_t const * hash1, fd_hash_t const * hash2 ) {
- fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
- fd_ghost_ele_t const * ele1 = fd_ghost_query_const( ghost, hash1 );
- fd_ghost_ele_t const * ele2 = fd_ghost_query_const( ghost, hash2 );
-
-# if FD_GHOST_USE_HANDHOLDING
- if( FD_UNLIKELY( !ele1 ) ) { FD_LOG_WARNING(( "hash1 %s missing", FD_BASE58_ENC_32_ALLOCA(hash1) )); return NULL; }
- if( FD_UNLIKELY( !ele2 ) ) { FD_LOG_WARNING(( "hash2 %s missing", FD_BASE58_ENC_32_ALLOCA(hash2) )); return NULL; }
-# endif
-
- /* Find the greatest common ancestor. */
-
- while( FD_LIKELY( ele1 && ele2 ) ) {
- if( FD_UNLIKELY( memcmp( &ele1->key, &ele2->key, sizeof(fd_hash_t) ) == 0 ) ) return ele1;
- if( ele1->slot > ele2->slot ) ele1 = fd_ghost_pool_ele_const( pool, ele1->parent );
- else ele2 = fd_ghost_pool_ele_const( pool, ele2->parent );
- }
- FD_LOG_CRIT(( "invariant violation" )); /* unreachable */
+ newr->parent = null; /* unlink old root */
+ ghost->root = pool_idx( pool, newr ); /* replace with new root */
}
int
-fd_ghost_is_ancestor( fd_ghost_t const * ghost, fd_hash_t const * ancestor, fd_hash_t const * hash ) {
- fd_ghost_ele_t const * root = fd_ghost_root_const ( ghost );
- fd_ghost_ele_t const * curr = fd_ghost_query_const( ghost, hash );
- fd_ghost_ele_t const * anc = fd_ghost_query_const( ghost, ancestor );
-
- if( FD_UNLIKELY( !anc ) ) {
-# if LOGGING
- FD_LOG_NOTICE(( "[%s] ancestor %s missing", __func__, FD_BASE58_ENC_32_ALLOCA(ancestor) ));
-# endif
- return 0;
+fd_ghost_verify( fd_ghost_t * ghost ) {
+ if( FD_UNLIKELY( !ghost ) ) {
+ FD_LOG_WARNING(( "NULL ghost" ));
+ return -1;
}
-# if FD_GHOST_USE_HANDHOLDING
- if( FD_UNLIKELY( anc->slot < root->slot ) ) { FD_LOG_WARNING(( "[%s] ancestor %lu too old. root %lu.", __func__, anc->slot, root->slot )); return 0; }
- if( FD_UNLIKELY( !curr ) ) { FD_LOG_WARNING(( "[%s] hash %s not in ghost.", __func__, FD_BASE58_ENC_32_ALLOCA(hash) )); return 0; }
-# endif
-
- /* Look for `ancestor` in the fork ancestry.
-
- Stop looking when there is either no ancestry remaining or there is
- no reason to look further because we've searched past the
- `ancestor`. */
-
- while( FD_LIKELY( curr && curr->slot >= anc->slot ) ) {
- if( FD_UNLIKELY( memcmp( &curr->key, &anc->key, sizeof(fd_hash_t) ) == 0 ) ) return 1; /* optimize for depth > 1 */
- curr = fd_ghost_pool_ele_const( fd_ghost_pool_const( ghost ), curr->parent );
+ if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ghost, fd_ghost_align() ) ) ) {
+ FD_LOG_WARNING(( "misaligned ghost" ));
+ return -1;
}
- return 0; /* not found */
-}
-int
-fd_ghost_invalid( fd_ghost_t const * ghost, fd_ghost_ele_t const * ele ) {
- fd_ghost_ele_t const * anc = ele;
- while( FD_LIKELY( anc ) ) {
- if( FD_UNLIKELY( ( !anc->valid ) ) ) return 1;
- anc = fd_ghost_parent_const( ghost, anc );
+ fd_wksp_t * wksp = fd_wksp_containing( ghost );
+ if( FD_UNLIKELY( !wksp ) ) {
+ FD_LOG_WARNING(( "ghost must be part of a workspace" ));
+ return -1;
}
- return 0;
-}
+ fd_ghost_blk_t const * pool = ghost->pool;
+ ulong null = pool_idx_null( pool );
-void
-process_duplicate_confirmed( fd_ghost_t * ghost, fd_hash_t const * hash, ulong slot ) {
- fd_ghost_ele_t const * confirmed = fd_ghost_query( ghost, hash );
- fd_ghost_ele_t const * current = fd_ghost_query( ghost, fd_ghost_hash( ghost, slot ) );
- if( FD_UNLIKELY( !confirmed ) ) {
- FD_LOG_WARNING(( "[%s] duplicate confirmed slot %lu, %s not in ghost. Need to repair & replay. ", __func__, slot, FD_BASE58_ENC_32_ALLOCA(hash) ) );
- return;
- }
- if( FD_UNLIKELY( !current ) ) FD_LOG_ERR(( "[%s] slot %lu doesn't exist in ghost, but we're processing a duplicate confirmed signal for it.", __func__, slot ));
+ /* Check every ele that exists in pool exists in map. */
- while( current != NULL ) {
- fd_ghost_mark_valid( ghost, ¤t->key );
- current = fd_ghost_parent_const( ghost, current );
- }
-}
+ if( blk_map_verify( ghost->blk_map, pool_max( pool ), pool ) ) return -1;
-void
-process_duplicate( fd_ghost_t * ghost, ulong slot, ulong total_stake ) {
- fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost );
- fd_dup_seen_map_insert( dup_map, slot );
-
- if( fd_ghost_hash( ghost, slot ) ) {
- /* slot is already replayed, so we can immediately mark invalid */
- FD_LOG_WARNING(( "[%s] duplicate message for slot %lu, marking invalid", __func__, slot ));
- fd_ghost_mark_invalid( ghost, slot, total_stake );
- return;
+ /* Check every ele's stake is >= sum of children's stakes. */
+
+ fd_ghost_blk_t const * parent = fd_ghost_root( ghost );
+ while( FD_LIKELY( parent ) ) {
+ ulong weight = 0;
+ fd_ghost_blk_t const * child = pool_ele( ghost->pool, parent->child );
+ while( FD_LIKELY( child && child->sibling != null ) ) {
+ weight += child->stake;
+ child = pool_ele( ghost->pool, child->sibling );
+ }
+ # if FD_GHOST_USE_HANDHOLDING
+ FD_TEST( parent->stake >= weight );
+ # endif
+ parent = pool_ele_const( pool, parent->next );
}
+
+ return 0;
}
#include
static void
-print( fd_ghost_t const * ghost, fd_ghost_ele_t const * ele, int space, const char * prefix, ulong total ) {
- fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
+print( fd_ghost_t const * ghost, fd_ghost_blk_t const * ele, ulong total_stake, int space, const char * prefix ) {
+ fd_ghost_blk_t const * pool = ghost->pool;
- if( ele == NULL ) return;
+ if( FD_UNLIKELY( ele == NULL ) ) return;
- if( space > 0 ) printf( "\n" );
+ if( FD_LIKELY( space > 0 ) ) printf( "\n" );
for( int i = 0; i < space; i++ )
printf( " " );
- if( FD_UNLIKELY( ele->weight > 100 ) ) {
+ if( FD_UNLIKELY( ele->stake > 100 ) ) {
}
- if( FD_UNLIKELY( total == 0 ) ) {
- printf( "%s%lu (%lu)", prefix, ele->slot, ele->weight );
+ if( FD_UNLIKELY( total_stake == 0 ) ) {
+ printf( "%s%lu (%lu)", prefix, ele->slot, ele->stake );
} else {
- double pct = ( (double)ele->weight / (double)total ) * 100;
+ double pct = ( (double)ele->stake / (double)total_stake ) * 100;
if( FD_UNLIKELY( pct < 0.99 )) {
- printf( "%s%lu (%.0lf%%, %lu)", prefix, ele->slot, pct, ele->weight );
+ printf( "%s%lu (%.0lf%%, %lu)", prefix, ele->slot, pct, ele->stake );
} else {
printf( "%s%lu (%.0lf%%)", prefix, ele->slot, pct );
}
}
- fd_ghost_ele_t const * curr = fd_ghost_pool_ele_const( pool, ele->child );
- char new_prefix[1024]; /* FIXME size this correctly */
+ fd_ghost_blk_t const * curr = pool_ele_const( pool, ele->child );
+ char new_prefix[1024]; /* FIXME size this correctly */
while( curr ) {
- if( fd_ghost_pool_ele_const( pool, curr->sibling ) ) {
+ if( FD_UNLIKELY( pool_ele_const( pool, curr->sibling ) ) ) {
sprintf( new_prefix, "├── " ); /* branch indicating more siblings follow */
- print( ghost, curr, space + 4, new_prefix, total );
+ print( ghost, curr, total_stake, space + 4, new_prefix );
} else {
sprintf( new_prefix, "└── " ); /* end branch */
- print( ghost, curr, space + 4, new_prefix, total );
+ print( ghost, curr, total_stake, space + 4, new_prefix );
}
- curr = fd_ghost_pool_ele_const( pool, curr->sibling );
+ curr = pool_ele_const( pool, curr->sibling );
}
}
void
-fd_ghost_print( fd_ghost_t const * ghost, ulong total_stake, fd_ghost_ele_t const * ele ) {
+fd_ghost_print( fd_ghost_t const * ghost,
+ fd_ghost_blk_t const * root ) {
FD_LOG_NOTICE( ( "\n\n[Ghost]" ) );
- print( ghost, ele, 0, "", total_stake );
+ print( ghost, root, root->total_stake, 0, "" );
printf( "\n\n" );
}
diff --git a/src/choreo/ghost/fd_ghost.h b/src/choreo/ghost/fd_ghost.h
index daefbe5ecaf..9a81a001a33 100644
--- a/src/choreo/ghost/fd_ghost.h
+++ b/src/choreo/ghost/fd_ghost.h
@@ -4,117 +4,87 @@
/* fd_ghost implements Solana's LMD-GHOST ("latest message-driven greedy
heaviest-observed subtree") fork choice rule.
- Protocol details:
-
- - LMD is an acronym for "latest message-driven". It describes how
- votes are counted when picking the best fork. In this scheme, only
- a validator's latest vote counts. So if a validator votes for slot
- 3 and then slot 5, the vote for 5 overwrites the vote for 3.
-
- - GHOST is an acronym for "greedy heaviest-observed subtree":
-
- greedy: for each depth of the tree, pick the locally optimal
- child / subtree / fork. This will result in the global
- optimal choice.
-
- heaviest: pick based on the highest stake weight.
-
- observed: this is the validator's local view, and other validators
- may have differing views because they've observed
- different votes.
-
- subtree: pick based on the weight of an entire subtree, not just
- an individual ele. For example, if slot 3 has 10 stake
- and slot 5 has 5 stake, but slot 5 has two children 6
- and 7 that each have 3 stake, our weights are
-
- slot 3 subtree [3] = 10
- slot 5 subtree [5, 6, 7] = 11 (5+3+3)
-
- Therefore slot 5 would be the heaviest.
-
- In-memory representation:
-
- There are two maps, both indexing the same pool of tree elements.
-
- - One map is keyed by slot number, and one map is keyed by hash_id
- (currently the block id, which is the merkle root of the last FEC
- set in the slot).
-
- - The elements in the slot map are a subset of the elements in the
- hash_id map.
-
- - Each tree ele tracks the amount of stake (`stake`) that has voted
- for its slot, as well as the recursive sum of stake for the subtree
- rooted at that ele (`weight`).
-
- The map keyed by slot is the "happy tree." i.e. the first version of
- a block we see and replay is going to be the version visible in the
- slot map. This is also the version of the slot that our tower is
- referring to. The map keyed by hash_id maintains every block that
- we've seen evidence of, including the equivocating blocks.
-
- both equivocating blocks seen only one block seen
-
- 0 0
- / \ /
- 1 1' (both invalid) 1' (valid)
- / \ /
- 2 3 3
-
- The first version of block 1 we see / replay is going to be the
- version visible in the slot map. Block 1' will be available in the
- hash-keyed tree, but not in the slot map. Block 3 will also be
- available in the slot-keyed tree, despite being a descendant of
- something not existing in the slot map.
-
- Thus in ghost,
-
- both equivocating blocks seen only one block seen
- slot_map: [0, 1, 2, 3] slot_map: [0, 1', 3]
- hash_map: [1'] hash_map: []
-
- Whatever is in the slot map is the slot referenced to by tower. Tower
- is *pure* and has no notion of duplicitness. So tower just needs to
- worry about querying ghost_slot_map for the proper hash_id.
-
- Slot 4 arrives, chained off of 1. In the right case where we didn't
- see block 1, block 4 now provides evidence for 1. We mark 1' as
- invalid for fork choice, and we repair the parent of 4 (getting 1').
- Then we replay down 1' and then replay down 4. Now the maps in this
- case look like:
-
- both equivocating blocks seen only one block seen
-
- 0 0
- / \ / \
- 1 1' (both invalid) 1' 1 (both invalid)
- / \ \ / \
- 2 4 3 3 4
-
- both equivocating blocks seen only one block seen
- slot_map: [0, 1, 2, 3, 4] slot_map: [0, 1', 3, 4]
- hash_map: [1'] hash_map: [1']
-
- Let's say 1' get's duplicate confirmed. Then the left case needs to
- switch forks in tower. Then ghost will need to itself swap the
- corresponding ghost hash_id in the slot_map to the hash_map and vice
- versa.
-
- both equivocating blocks seen only one block seen
- slot_map: [0, 1', 2, 3, 4] slot_map: [0, 1', 3, 4]
- hash_map: [1] hash_map: [1']
- (no change)
-
- 1' is marked as valid for fork choice. 1 remains invalid.
-
- Link to original GHOST paper: https://eprint.iacr.org/2013/881.pdf.
- This is simply a reference for those curious about the etymology, and
- not prerequisite reading for understanding this implementation. */
+ LMD ("latest message-driven") means only a validator's latest vote
+ counts. If a validator votes for one fork than subsequently votes
+ for a different fork, their vote only counts towards the latter fork
+ and not the former.
+
+ GHOST ("greedy heaviest-observed subtree") describes the fork choice
+ rule. Forks form a tree, where each node is a block. Here's an
+ example of a fork tree in which every block is labeled with its slot:
+
+ /-- 3
+ 1-- 2
+ \-- 4
+
+ In the above tree 3 and 4 are different forks. The responsibility of
+ fork choice is to decide whether the validator should vote for 3 or 4
+ which ultimately determines which fork the cluster converges on.
+
+ In Solana, votes are stake-weighted. Here is the same tree with
+ stakes associated with each block.
+
+ /-- 3 (30%)
+ 1 (80%) -- 2 (70%)
+ \-- 4 (38%)
+
+ 80% of stake voted for 1, 70% for 2, 30% for 3 and 38% for 4. How
+ does fork choice pick 3 or 4? It traverses down the tree, beginning
+ from the root (1), then picks (2), then picks (4) where it terminates
+ and returns 4 as the best leaf.
+
+ greedy: fork choice is a greedy algorithm. During traversal, on
+ each level of the tree it picks the locally optimal value.
+
+ heaviest: pick based on the heaviest (highest) stake.
+
+ observed: this is the validator's local view, and other validators
+ may have differing views because they've observed
+ different votes or different forks.
+
+ subtree: sum the vote stake of an entire subtree rooted at a given
+ block, not just votes for the individual block itself. In
+ the tree above, 1 and 2 both have strictly more stake than
+ 3 or 4. That is because the stake for 3 and 4 both rolled
+ up into 2, and the stake for 2 rolled up into 1. There
+ were also votes for 1 and 2 that weren't just roll-ups so
+ the total stake for a parent can exceed (>=) the sum of its
+ children.
+
+ The above diagrams used slot numbers for simplicity but ghost in fact
+ uses the `block_id`, a 32-byte hash that uniquely identifies a block,
+ to key the elements of the tree. The block_id ensures that when
+ there is equivocation (two or more blocks labeled with the same slot)
+ the blocks can be disambiguated.
+
+ Ghost handles equivocation by marking forks invalid for fork choice
+ if any block along that fork equivocates. For example, consider the
+ following tree:
+
+ /-- 4'(30%)
+ 1 (80%) -- 2 (70%)
+ \-- 4 (38%)
+
+ This is the same tree from earlier except 3 has been replaced with
+ 4'. There are two equivocating blocks for slot 4: how does ghost
+ handle this? Ghost marks both 4 and 4' as invalid for fork choice.
+ So in this example, fork choice will pick 2 as the best leaf.
+
+ Ghost can mark a fork valid again if it becomes "duplicate confirmed"
+ ie. it has received votes from >=52% of the cluster. Revisiting the
+ above tree, modified:
+
+ /-- 4'(30%)
+ 1 (80%) -- 2 (70%)
+ \-- 4 (52%) <- gossip duplicate confirmed (>=52%)
+
+ Now that 4 is "duplicate confirmed", ghost marks 4 as valid again.
+ Fork choice picks 4 as the best leaf. Note gossip duplicate
+ confirmation is separately tracked outside of fd_ghost. See the
+ fd_ghost API for how that works. */
#include "../fd_choreo_base.h"
-#include "../epoch/fd_epoch.h"
-#include "../../tango/fseq/fd_fseq.h"
+#include "../tower/fd_tower_accts.h"
/* FD_GHOST_USE_HANDHOLDING: Define this to non-zero at compile time
to turn on additional runtime checks. */
@@ -123,7 +93,9 @@
#define FD_GHOST_USE_HANDHOLDING 1
#endif
-/* fd_ghost_ele_t implements a left-child, right-sibling n-ary tree.
+typedef struct fd_ghost fd_ghost_t; /* forward decl*/
+
+/* fd_ghost_blk_t implements a left-child, right-sibling n-ary tree.
Each ele maintains the `pool` index of its left-most child
(`child_idx`), its immediate-right sibling (`sibling_idx`), and its
parent (`parent_idx`).
@@ -131,88 +103,20 @@
This tree structure is gaddr-safe and supports accesses and
operations from processes with separate local ghost joins. */
-struct __attribute__((aligned(128UL))) fd_ghost_ele {
- fd_hash_t key; /* hash_id (merkle root of the last FEC set in the slot) */
- ulong slot; /* slot this ele is tracking */
- ulong next; /* reserved for internal use by fd_pool, hash_map fd_map_chain and fd_ghost_publish */
- ulong nexts; /* reserved for internal use by slot_map fd_map_chain */
- ulong eqvoc; /* pool idx of a duplicate of this slot */
- ulong parent; /* pool idx of the parent */
- ulong child; /* pool idx of the left-child */
- ulong sibling; /* pool idx of the right-sibling */
- ulong weight; /* total stake from replay votes for this slot or any of its descendants */
- ulong replay_stake; /* total stake from replay votes for this slot */
- ulong gossip_stake; /* total stake from gossip votes for this slot */
- ulong rooted_stake; /* replay stake that has rooted this slot */
- int valid; /* whether this ele is valid for fork choice */
-};
-typedef struct fd_ghost_ele fd_ghost_ele_t;
-
-#define POOL_NAME fd_ghost_pool
-#define POOL_T fd_ghost_ele_t
-#include "../../util/tmpl/fd_pool.c"
-
-#define MAP_NAME fd_ghost_hash_map
-#define MAP_ELE_T fd_ghost_ele_t
-#define MAP_KEY_T fd_hash_t
-#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_hash_t)))
-#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
-#define MAP_NEXT next
-#include "../../util/tmpl/fd_map_chain.c"
-
-#define MAP_NAME fd_ghost_slot_map
-#define MAP_ELE_T fd_ghost_ele_t
-#define MAP_KEY slot
-#define MAP_NEXT nexts
-#include "../../util/tmpl/fd_map_chain.c"
-
-struct fd_dup_seen {
- ulong slot;
-};
-typedef struct fd_dup_seen fd_dup_seen_t;
-
-#define MAP_NAME fd_dup_seen_map
-#define MAP_T fd_dup_seen_t
-#define MAP_KEY slot
-#define MAP_MEMOIZE 0
-#include "../../util/tmpl/fd_map_dynamic.c"
-
-/* fd_ghost_t is the top-level structure that holds the root of the
- tree, as well as the memory pools and map structures for tracking
- ghost eles and votes.
-
- These structures are bump-allocated and laid out contiguously in
- memory from the fd_ghost_t * pointer which points to the beginning of
- the memory region.
-
- ---------------------- <- fd_ghost_t *
- | metadata |
- ----------------------
- | pool |
- ----------------------
- | map |
- ----------------------
-
- A valid, initialized ghost is always non-empty. After
- `fd_ghost_init` the ghost will always have a root ele unless
- modified improperly out of ghost's API. */
-
-#define FD_GHOST_MAGIC (0xf17eda2ce7940570UL) /* firedancer ghost version 0 */
-
-struct __attribute__((aligned(128UL))) fd_ghost {
-
- /* Metadata */
-
- ulong magic; /* ==FD_GHOST_MAGIC */
- ulong ghost_gaddr; /* wksp gaddr of this in the backing wksp, non-zero gaddr */
- ulong seed; /* seed for various hashing function used under the hood, arbitrary */
- ulong root; /* pool idx of the root */
- ulong pool_gaddr; /* wksp gaddr of the pool backing this ghost, non-zero gaddr */
- ulong hash_map_gaddr; /* wksp gaddr of the map (for fast O(1) querying by hash) backing this ghost, non-zero gaddr */
- ulong slot_map_gaddr; /* wksp gaddr of the map (for fast O(1) querying by slot) backing this ghost, non-zero gaddr */
- ulong dup_map_gaddr; /* wksp gaddr of the map (for fast O(1) querying, non-zero gaddr */
+struct __attribute__((aligned(128UL))) fd_ghost_blk {
+ fd_hash_t id; /* block_id (merkle root of the last FEC set in the slot) */
+ ulong slot; /* slot this ele is tracking */
+ ulong next; /* reserved for internal use by fd_pool, fd_map_chain and fd_ghost_publish */
+ ulong parent; /* pool idx of the parent */
+ ulong child; /* pool idx of the left-child */
+ ulong sibling; /* pool idx of the right-sibling */
+ ulong stake; /* sum of stake that has voted for this slot or any of its descendants */
+ ulong total_stake; /* total stake for this blk */
+ int eqvoc; /* whether this block is equivocating. if so, it is invalid for fork choice unless duplicate confirmed */
+ int conf; /* whether this block is "duplicate confirmed" via gossip votes (>= 52% of stake) */
+ int valid; /* whether this block is valid for fork choice. an equivocating block is valid iff duplicate confirmed */
};
-typedef struct fd_ghost fd_ghost_t;
+typedef struct fd_ghost_blk fd_ghost_blk_t;
FD_PROTOTYPES_BEGIN
@@ -220,37 +124,24 @@ FD_PROTOTYPES_BEGIN
/* fd_ghost_{align,footprint} return the required alignment and
footprint of a memory region suitable for use as ghost with up to
- ele_max eles and vote_max votes. */
-
-FD_FN_CONST static inline ulong
-fd_ghost_align( void ) {
- return alignof(fd_ghost_t);
-}
-
-FD_FN_CONST static inline ulong
-fd_ghost_footprint( ulong ele_max ) {
- int lg_ele_max = fd_ulong_find_msb( fd_ulong_pow2_up( ele_max ) );
- return FD_LAYOUT_FINI(
- FD_LAYOUT_APPEND(
- FD_LAYOUT_APPEND(
- FD_LAYOUT_APPEND(
- FD_LAYOUT_APPEND(
- FD_LAYOUT_APPEND(
- FD_LAYOUT_INIT,
- alignof(fd_ghost_t), sizeof(fd_ghost_t) ),
- fd_ghost_pool_align(), fd_ghost_pool_footprint ( ele_max ) ),
- fd_ghost_hash_map_align(), fd_ghost_hash_map_footprint( ele_max ) ),
- fd_ghost_slot_map_align(), fd_ghost_slot_map_footprint( ele_max ) ),
- fd_dup_seen_map_align(), fd_dup_seen_map_footprint ( lg_ele_max ) ),
- fd_ghost_align() );
-}
+ blk_max blocks and vtr_max voters. */
+
+FD_FN_CONST ulong
+fd_ghost_align( void );
+
+FD_FN_CONST ulong
+fd_ghost_footprint( ulong blk_max,
+ ulong vtr_max );
/* fd_ghost_new formats an unused memory region for use as a ghost.
mem is a non-NULL pointer to this region in the local address space
with the required footprint and alignment. */
void *
-fd_ghost_new( void * shmem, ulong ele_max, ulong seed );
+fd_ghost_new( void * shmem,
+ ulong blk_max,
+ ulong vtr_max,
+ ulong seed );
/* fd_ghost_join joins the caller to the ghost. ghost points to the
first byte of the memory region backing the ghost in the caller's
@@ -278,182 +169,111 @@ fd_ghost_leave( fd_ghost_t const * ghost );
void *
fd_ghost_delete( void * ghost );
-/* fd_ghost_init initializes a ghost. Assumes ghost is a valid local
- join and no one else is joined. root is the initial root ghost will
- use. This is the snapshot slot if booting from a snapshot, 0 if the
- genesis slot. hash is the hash_id of the initial root.
-
- In general, this should be called by the same process that formatted
- ghost's memory, ie. the caller of fd_ghost_new. */
-
-void
-fd_ghost_init( fd_ghost_t * ghost, ulong root, fd_hash_t * hash );
-
/* Accessors */
-/* fd_ghost_wksp returns the local join to the wksp backing the ghost.
- The lifetime of the returned pointer is at least as long as the
- lifetime of the local join. Assumes ghost is a current local
- join. */
-
-FD_FN_PURE static inline fd_wksp_t *
-fd_ghost_wksp( fd_ghost_t const * ghost ) {
- return (fd_wksp_t *)( ( (ulong)ghost ) - ghost->ghost_gaddr );
-}
-
-/* fd_ghost_{pool,map,root} returns a pointer in the caller's address
- space to the corresponding ghost field. const versions for each are
- also provided. */
-
-FD_FN_PURE static inline fd_ghost_ele_t * fd_ghost_pool ( fd_ghost_t * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->pool_gaddr ); }
-FD_FN_PURE static inline fd_ghost_ele_t const * fd_ghost_pool_const ( fd_ghost_t const * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->pool_gaddr ); }
-FD_FN_PURE static inline fd_ghost_hash_map_t * fd_ghost_hash_map ( fd_ghost_t * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->hash_map_gaddr ); }
-FD_FN_PURE static inline fd_ghost_hash_map_t const * fd_ghost_hash_map_const( fd_ghost_t const * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->hash_map_gaddr ); }
-FD_FN_PURE static inline fd_ghost_slot_map_t * fd_ghost_slot_map ( fd_ghost_t * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->slot_map_gaddr ); }
-FD_FN_PURE static inline fd_ghost_slot_map_t const * fd_ghost_slot_map_const( fd_ghost_t const * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->slot_map_gaddr ); }
-FD_FN_PURE static inline fd_dup_seen_t * fd_ghost_dup_map ( fd_ghost_t * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->dup_map_gaddr ); }
-FD_FN_PURE static inline fd_dup_seen_t const * fd_ghost_dup_map_const ( fd_ghost_t const * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->dup_map_gaddr ); }
-
-/* fd_ghost_{parent,child,sibling} returns a pointer in the caller's
- address space to the corresponding {parent,left-child,right-sibling}
- of fec. Assumes ghost is a current local join and fec is a valid
- pointer to a pool element inside ghost. const versions for each are
- also provided. */
-
-FD_FN_PURE static inline fd_ghost_ele_t * fd_ghost_root ( fd_ghost_t * ghost ) { return fd_ghost_pool_ele ( fd_ghost_pool ( ghost ), ghost->root ); }
-FD_FN_PURE static inline fd_ghost_ele_t const * fd_ghost_root_const ( fd_ghost_t const * ghost ) { return fd_ghost_pool_ele_const( fd_ghost_pool_const( ghost ), ghost->root ); }
-FD_FN_PURE static inline fd_ghost_ele_t * fd_ghost_parent ( fd_ghost_t * ghost, fd_ghost_ele_t * ele ) { return fd_ghost_pool_ele ( fd_ghost_pool ( ghost ), ele->parent ); }
-FD_FN_PURE static inline fd_ghost_ele_t const * fd_ghost_parent_const ( fd_ghost_t const * ghost, fd_ghost_ele_t const * ele ) { return fd_ghost_pool_ele_const( fd_ghost_pool_const( ghost ), ele->parent ); }
-FD_FN_PURE static inline fd_ghost_ele_t * fd_ghost_child ( fd_ghost_t * ghost, fd_ghost_ele_t * ele ) { return fd_ghost_pool_ele ( fd_ghost_pool ( ghost ), ele->child ); }
-FD_FN_PURE static inline fd_ghost_ele_t const * fd_ghost_child_const ( fd_ghost_t const * ghost, fd_ghost_ele_t const * ele ) { return fd_ghost_pool_ele_const( fd_ghost_pool_const( ghost ), ele->child ); }
-FD_FN_PURE static inline fd_ghost_ele_t * fd_ghost_sibling ( fd_ghost_t * ghost, fd_ghost_ele_t * ele ) { return fd_ghost_pool_ele ( fd_ghost_pool ( ghost ), ele->sibling ); }
-FD_FN_PURE static inline fd_ghost_ele_t const * fd_ghost_sibling_const( fd_ghost_t const * ghost, fd_ghost_ele_t const * ele ) { return fd_ghost_pool_ele_const( fd_ghost_pool_const( ghost ), ele->sibling ); }
-
-/* fd_ghost_{query,query_const} returns the ele keyed by `hash_id`,
- NULL if not found. */
-
-FD_FN_PURE static inline fd_ghost_ele_t *
-fd_ghost_query( fd_ghost_t * ghost, fd_hash_t const * hash ) {
- if( FD_UNLIKELY( !hash ) ) { return NULL; }
- fd_ghost_hash_map_t * map = fd_ghost_hash_map( ghost );
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
- return fd_ghost_hash_map_ele_query( map, hash, NULL, pool );
-}
-
-FD_FN_PURE static inline fd_ghost_ele_t const *
-fd_ghost_query_const( fd_ghost_t const * ghost, fd_hash_t const * hash ) {
- if( FD_UNLIKELY( !hash ) ) { return NULL; }
- fd_ghost_hash_map_t const * map = fd_ghost_hash_map_const ( ghost );
- fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
- return fd_ghost_hash_map_ele_query_const( map, hash, NULL, pool );
-}
-
-/* fd_ghost_hash returns the hash_id of the ele keyed by `slot`.
- NULL if the slot is not found. */
-
-FD_FN_PURE static inline fd_hash_t const *
-fd_ghost_hash( fd_ghost_t const * ghost, ulong slot ) {
- fd_ghost_slot_map_t const * maps = fd_ghost_slot_map_const( ghost );
- fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
- fd_ghost_ele_t const * ele = fd_ghost_slot_map_ele_query_const( maps, &slot, NULL, pool );
- return ele ? &ele->key : NULL;
-}
-
-/* fd_ghost_head greedily traverses down the ghost beginning from root,
- recursively picking the child with most `weight` on each level of the
- tree, terminating once it reaches a leaf (see top-level documentation
- for more traversal details). Assumes ghost is a current local join
- and has been initialized with fd_ghost_init and is therefore
- non-empty. */
-
-fd_ghost_ele_t const *
-fd_ghost_head( fd_ghost_t const * ghost, fd_ghost_ele_t const * root );
-
-/* fd_ghost_gca returns the greatest common ancestor of block1, block2
- in ghost. Assumes block1 or block2 are present in ghost (warns and
- returns NULL with handholding enabled). This is guaranteed to be
- non-NULL if block1 and block2 are both present. */
-
-fd_ghost_ele_t const *
-fd_ghost_gca( fd_ghost_t const * ghost, fd_hash_t const * bid1, fd_hash_t const * bid2 );
-
-/* fd_ghost_is_ancestor returns 1 if `ancestor` is `slot`'s ancestor, 0
- otherwise. Also returns 0 if either `ancestor` or `slot` are not in
- ghost. */
-
-int
-fd_ghost_is_ancestor( fd_ghost_t const * ghost, fd_hash_t const * ancestor, fd_hash_t const * slot );
-
-/* fd_ghost_anc_eqvoc. */
+/* fd_ghost_{root,parent,child,sibling} returns a pointer in the
+ caller's address space to the {root,parent,left-child,right-sibling}.
+ Assumes ghost is a current local join and blk is a valid pointer to a
+ pool element inside ghost. const versions for each are also
+ provided. */
-int
-fd_ghost_invalid( fd_ghost_t const * ghost, fd_ghost_ele_t const * ele );
+fd_ghost_blk_t *
+fd_ghost_root( fd_ghost_t * ghost );
-/* Operations */
+/* fd_ghost_query returns the block keyed by block_id. Returns NULL if
+ not found. */
-/* fd_ghost_insert inserts a new ele keyed by `hash_id`, for the slot
- `slot` into the ghost. Inserts an ele keyed by `slot` into the slot
- map if one doesn't already exist as well. Assumes slot >= ghost->smr,
- parent_hash_id is already in ghost, and the ele pool has a free
- element (if handholding is enabled, explicitly checks and errors).
- Returns the inserted ghost ele. */
+fd_ghost_blk_t *
+fd_ghost_query( fd_ghost_t * ghost,
+ fd_hash_t const * block_id );
-/* FIXME: total_stake as an arg is a little unwieldy. is there a better
- way to design this API? ghost->total_stake runs the risk of forgetting
- to update*/
+/* fd_ghost_best returns the best block (according to fork choice) in
+ the subtree beginning at root. This is the ideal block to vote on or
+ reset to, and feeds into downstream TowerBFT rules. This is usually
+ a leaf node in the tree but may not when blocks are marked invalid
+ due to unconfirmed duplicates. Assumes root is marked valid so this
+ will never return NULL. */
-fd_ghost_ele_t *
-fd_ghost_insert( fd_ghost_t * ghost, fd_hash_t const * parent_hash, ulong slot, fd_hash_t const * hash_id, ulong total_stake );
+fd_ghost_blk_t *
+fd_ghost_best( fd_ghost_t * ghost,
+ fd_ghost_blk_t * root );
-/* fd_ghost_replay_vote votes for hash_id, adding pubkey's stake to the
- `stake` field for slot and to the `weight` field for both slot and
- slot's ancestors. If pubkey has previously voted, pubkey's stake is
- also subtracted from `weight` for its previous vote slot and its
- ancestors.
+/* fd_ghost_deepest returns the deepest ghost block (highest tree depth)
+ in the subtree beginning at root. Unlike fd_ghost_best, deepest can
+ return a block marked invalid for fork choice. In case of ties, the
+ returned block will be the most recently inserted one. This will
+ never return NULL. */
- Assumes slot is present in ghost (if handholding is enabled,
- explicitly checks and errors).
+fd_ghost_blk_t *
+fd_ghost_deepest( fd_ghost_t * ghost,
+ fd_ghost_blk_t * root );
- TODO the implementation can be made more efficient by
- short-circuiting and doing fewer traversals. Currently this is
- bounded to O(h), where h is the height of ghost. */
-void
-fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, fd_hash_t const * hash_id );
+/* fd_ghost_ancestor returns the ancestor on the same fork as descendant
+ keyed by ancestor_id. Returns NULL if not found. */
-/* fd_ghost_gossip_vote adds stake amount to the gossip_stake field of
- slot.
+fd_ghost_blk_t *
+fd_ghost_ancestor( fd_ghost_t * ghost,
+ fd_ghost_blk_t * descendant,
+ fd_hash_t const * ancestor_id );
- Assumes slot is present in ghost (if handholding is enabled,
- explicitly checks and errors). Returns the ghost ele keyed by slot.
+/* fd_ghost_slot_ancestor returns the ancestor on the same fork as
+ descendant with ancestor_slot. Returns NULL if not found. */
- Unlike fd_ghost_replay_vote, this stake is not propagated to
- the weight field for slot and slot's ancestors. It is only counted
- towards slot itself, as gossip votes are only used for optimistic
- confirmation and not fork choice. */
+fd_ghost_blk_t *
+fd_ghost_slot_ancestor( fd_ghost_t * ghost,
+ fd_ghost_blk_t * descendant,
+ ulong ancestor_slot );
-void
-fd_ghost_gossip_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot );
+/* fd_ghost_invalid_ancestor returns the first ancestor on the same fork
+ as descendant that is marked invalid. Does not include descendant
+ itself. Returns NULL if there are no invalid ancestors. */
-/* fd_ghost_rooted_vote adds stake amount to the rooted_stake field of
- slot.
+fd_ghost_blk_t *
+fd_ghost_invalid_ancestor( fd_ghost_t * ghost,
+ fd_ghost_blk_t * descendant );
- Assumes slot is present in ghost (if handholding is enabled,
- explicitly checks and errors). Returns the ghost ele keyed by slot.
+/* Operations */
- Note rooting a slot implies rooting its ancestor, but ghost does not
- explicitly track this. */
+/* fd_ghost_insert inserts a new tree node representing a block keyed by
+ block_id (and slot). parent_block_id is used to link this new block
+ to its parent in the ghost tree. parent_block_id may only be NULL if
+ this is the very first ghost insert, in which case this new block
+ will be set to the ghost root. Returns the new block.*/
+
+fd_ghost_blk_t *
+fd_ghost_insert( fd_ghost_t * ghost,
+ fd_hash_t const * block_id,
+ fd_hash_t const * parent_block_id,
+ ulong slot );
+
+/* fd_ghost_count_vote updates ghost's stake weights with the latest
+ vote on the given voter's tower. This counts pubkey's stake towards
+ all ancestors on the same fork as block_id. If the voter has
+ previously voted, it subtracts the voter's previous stake from all
+ ancestors of vtr->prev_block_id. This ensures that only the most
+ recent vote from a voter is counted (see top-level documentation
+ about the LMD rule).
+
+ TODO the implementation can be made more efficient by incrementally
+ updating and short-circuiting ancestor traversals. Currently this is
+ bounded to O(h), where h is the height of ghost ie. O(block_max) in
+ the worst case. */
void
-fd_ghost_rooted_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong root );
+fd_ghost_count_vote( fd_ghost_t * ghost,
+ fd_ghost_blk_t * blk,
+ fd_pubkey_t const * vtr_addr,
+ ulong stake,
+ ulong slot );
-/* fd_ghost_publish publishes slot as the new ghost root, setting the
- subtree beginning from slot as the new ghost tree (ie. slot and all
- its descendants). Prunes all eles not in slot's ancestry. Assumes
- slot is present in ghost. Returns the new root. */
+/* fd_ghost_publish publishes newr as the new ghost root, pruning any
+ blocks not in the subtree beginning from the new root (ie. new root
+ and all its descendants). Returns the new root. */
-fd_ghost_ele_t const *
-fd_ghost_publish( fd_ghost_t * ghost, fd_hash_t const * hash_id );
+void
+fd_ghost_publish( fd_ghost_t * ghost,
+ fd_ghost_blk_t * newr );
/* Misc */
@@ -463,7 +283,7 @@ fd_ghost_publish( fd_ghost_t * ghost, fd_hash_t const * hash_id );
verify succeeds, -1 otherwise. */
int
-fd_ghost_verify( fd_ghost_t const * ghost );
+fd_ghost_verify( fd_ghost_t * ghost );
/* fd_ghost_print pretty-prints a formatted ghost tree. Printing begins
from `ele` (it will appear as the root in the print output).
@@ -484,27 +304,8 @@ fd_ghost_verify( fd_ghost_t const * ghost );
Callers should add null-checks as appropriate in actual usage. */
void
-fd_ghost_print( fd_ghost_t const * ghost, ulong total_stake, fd_ghost_ele_t const * ele );
-
-static int FD_FN_UNUSED
-is_duplicate_confirmed( fd_ghost_t * ghost, fd_hash_t const * hash, ulong total_stake ) {
- fd_ghost_ele_t const * ele = fd_ghost_query( ghost, hash );
- if( FD_UNLIKELY( !ele ) ) {
- FD_LOG_WARNING(( "[%s] slot %s was not in ghost", __func__, FD_BASE58_ENC_32_ALLOCA(hash) ));
- return 0;
- }
- double pct = (double)( ele->weight + ele->gossip_stake ) / (double)total_stake; /* TODO make gossip weight a field as well */
- return pct > FD_EQVOCSAFE_PCT;
-}
-
-/* Duplicate confirmed signal */
-
-void
-process_duplicate_confirmed( fd_ghost_t * ghost, fd_hash_t const * hash, ulong slot );
-
-void
-process_duplicate( fd_ghost_t * ghost, ulong slot, ulong total_stake );
-
+fd_ghost_print( fd_ghost_t const * ghost,
+ fd_ghost_blk_t const * root );
FD_PROTOTYPES_END
diff --git a/src/choreo/ghost/fd_ghost_private.h b/src/choreo/ghost/fd_ghost_private.h
new file mode 100644
index 00000000000..0dbc67cdad6
--- /dev/null
+++ b/src/choreo/ghost/fd_ghost_private.h
@@ -0,0 +1,64 @@
+#include "fd_ghost.h"
+
+/* fd_ghost_vtr_t keeps track of what a voter's previously voted for. */
+
+struct fd_ghost_vtr {
+ fd_pubkey_t addr; /* map key, vote account address */
+ uint hash; /* reserved for fd_map_dynamic */
+ ulong prev_stake; /* previous vote stake (vote can be from prior epoch) */
+ ulong prev_slot; /* previous vote slot */
+ fd_hash_t prev_block_id; /* previous vote block_id */
+};
+typedef struct fd_ghost_vtr fd_ghost_vtr_t;
+
+#define POOL_NAME pool
+#define POOL_T fd_ghost_blk_t
+#include "../../util/tmpl/fd_pool.c"
+
+#define MAP_NAME blk_map
+#define MAP_ELE_T fd_ghost_blk_t
+#define MAP_KEY id
+#define MAP_KEY_T fd_hash_t
+#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_hash_t)))
+#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
+#define MAP_NEXT next
+#include "../../util/tmpl/fd_map_chain.c"
+
+#define MAP_NAME vtr_map
+#define MAP_T fd_ghost_vtr_t
+#define MAP_KEY addr
+#define MAP_KEY_T fd_pubkey_t
+#define MAP_KEY_NULL pubkey_null
+#define MAP_KEY_EQUAL_IS_SLOW 1
+#define MAP_KEY_INVAL(k) MAP_KEY_EQUAL((k),MAP_KEY_NULL)
+#define MAP_KEY_EQUAL(k0,k1) (!memcmp( (k0).key, (k1).key, 32UL ))
+#define MAP_KEY_HASH(key) ((MAP_HASH_T)( (key).ul[1] ))
+#include "../../util/tmpl/fd_map_dynamic.c"
+
+/* fd_ghost_t is the top-level structure that holds the root of the
+ tree, as well as the memory pools and map structures for tracking
+ ghost eles and votes.
+
+ These structures are bump-allocated and laid out contiguously in
+ memory from the fd_ghost_t * pointer which points to the beginning of
+ the memory region.
+
+ ---------------------- <- fd_ghost_t *
+ | root |
+ ----------------------
+ | pool |
+ ----------------------
+ | map |
+ ----------------------
+ | bid |
+ ----------------------
+ | vtr |
+ ---------------------- */
+
+struct __attribute__((aligned(128UL))) fd_ghost {
+ ulong root; /* pool idx of the root tree element */
+ fd_ghost_blk_t * pool; /* pool of tree elements (blocks) */
+ blk_map_t * blk_map; /* map of block_id->ghost_blk for fast O(1) querying */
+ fd_ghost_vtr_t * vtr_map; /* map of pubkey->prior vote */
+};
+typedef struct fd_ghost fd_ghost_t;
diff --git a/src/choreo/ghost/test_ghost.c b/src/choreo/ghost/test_ghost.c
index 864635586b6..80dbc4c1f07 100644
--- a/src/choreo/ghost/test_ghost.c
+++ b/src/choreo/ghost/test_ghost.c
@@ -1,41 +1,11 @@
#include "fd_ghost.h"
-#include "../epoch/fd_epoch.h"
-
+#include "fd_ghost_private.h"
+#include "../voter/fd_voter.h"
#include
-#define PRINT 1
-
-#define INSERT( c, p ) \
- fd_ghost_insert( ghost, &hash_##p, c, &hash_##c, 20 );
-
-fd_ghost_ele_t *
-query_mut( fd_ghost_t * ghost, ulong slot ) {
- fd_wksp_t * wksp = fd_wksp_containing( ghost );
- fd_ghost_slot_map_t * map = fd_wksp_laddr_fast( wksp, ghost->slot_map_gaddr );
- fd_ghost_ele_t * pool = fd_wksp_laddr_fast( wksp, ghost->pool_gaddr );
- return fd_ghost_slot_map_ele_query( map, &slot, NULL, pool );
-}
-
-fd_epoch_t *
-mock_epoch( fd_wksp_t * wksp, ulong total_stake, ulong voter_cnt, ... ) {
- void * epoch_mem = fd_wksp_alloc_laddr( wksp, fd_epoch_align(), fd_epoch_footprint( voter_cnt ), 1UL );
- FD_TEST( epoch_mem );
- fd_epoch_t * epoch = fd_epoch_join( fd_epoch_new( epoch_mem, voter_cnt ) );
- FD_TEST( epoch );
-
- va_list ap;
- va_start( ap, voter_cnt );
- for( ulong i = 0; i < voter_cnt; i++ ) {
- fd_pubkey_t key = va_arg( ap, fd_pubkey_t );
- fd_voter_t * voter = fd_epoch_voters_insert( fd_epoch_voters( epoch ), key );
- voter->stake = va_arg( ap, ulong );
- voter->replay_vote.slot = FD_SLOT_NULL;
- }
- va_end( ap );
-
- epoch->total_stake = total_stake;
- return epoch;
-}
+// void setup_block_ids( fd_hash_t * block_ids, ulong cnt ) {
+// for( ulong i = 0; i < cnt; i++ ) block_ids[i] = (fd_hash_t){ .ul = { i } };
+// }
/*
slot 0
@@ -50,52 +20,72 @@ mock_epoch( fd_wksp_t * wksp, ulong total_stake, ulong voter_cnt, ... ) {
slot 6
*/
-void
-test_ghost_simple( fd_wksp_t * wksp ) {
- ulong node_max = 8;
-
- void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( node_max ), 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- //fd_ghost_ele_t * pool = fd_wksp_laddr_fast( wksp, ghost->pool_gaddr );
-
- // define hash_0, hash_1, hash_2, hash_3, hash_4, hash_5, hash_6
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_3 = { .key = { 3 } };
- fd_hash_t hash_4 = { .key = { 4 } };
- fd_hash_t hash_5 = { .key = { 5 } };
- fd_hash_t hash_6 = { .key = { 6 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
- INSERT( 1, 0 );
- INSERT( 2, 1 );
- INSERT( 3, 1 );
- INSERT( 4, 2 );
- INSERT( 5, 3 );
- INSERT( 6, 5 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_pubkey_t key = { .key = { 1 } };
- fd_epoch_t * epoch = mock_epoch( wksp, 10, 1, key, 1 );
- fd_voter_t * voter = fd_epoch_voters_query( fd_epoch_voters( epoch ), key, NULL );
-
-# if PRINT
- fd_ghost_print( ghost, 10, fd_ghost_root( ghost ) );
-# endif
- fd_ghost_replay_vote( ghost, voter, &hash_2 );
-# if PRINT
- fd_ghost_print( ghost, 10, fd_ghost_root( ghost ) );
-# endif
- fd_ghost_replay_vote( ghost, voter, &hash_3 );
-# if PRINT
- fd_ghost_print( ghost, 10, fd_ghost_root( ghost ) );
-# endif
-
- fd_wksp_free_laddr( fd_epoch_delete( fd_epoch_leave( epoch ) ) );
- fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) );
-}
+// fd_ghost_t *
+// setup_ghost( fd_wksp_t * wksp, ulong blk_max, ulong vtr_max ) {
+// void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, vtr_max ), 42UL );
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, vtr_max, 42UL ) );
+// FD_TEST( ghost );
+// fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 );
+// fd_ghost_insert( ghost, &block_ids[0], NULL , 0 );
+// fd_ghost_insert( ghost, &block_ids[1], &block_ids[0], 1 );
+// fd_ghost_insert( ghost, &block_ids[2], &block_ids[1], 2 );
+// fd_ghost_insert( ghost, &block_ids[3], &block_ids[1], 3 );
+// fd_ghost_insert( ghost, &block_ids[4], &block_ids[2], 4 );
+// fd_ghost_insert( ghost, &block_ids[5], &block_ids[3], 5 );
+// fd_ghost_insert( ghost, &block_ids[6], &block_ids[5], 6 );
+// return ghost;
+// }
+
+// void
+// teardown_ghost( fd_ghost_t * ghost ) {
+// fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) );
+// }
+
+// fd_tower_accts_t *
+// setup_tower_accts( fd_wksp_t * wksp, ulong max, ... ) {
+// void * mem = fd_wksp_alloc_laddr( wksp, fd_tower_accts_align(), fd_tower_accts_footprint( max ), 1UL );
+// fd_tower_accts_t * tower_accts = fd_tower_accts_join( fd_tower_accts_new( mem, max ) );
+// FD_TEST( tower_accts );
+
+// va_list ap;
+// va_start( ap, max );
+// for( ulong i = 0; i < max; i++ ) {
+// ulong addr = va_arg( ap, ulong );
+// ulong stake = va_arg( ap, ulong );
+// ulong vote = va_arg( ap, ulong );
+
+// uchar data[3762];
+// memset( data, 0, sizeof(data) );
+// fd_voter_state_t * state = (fd_voter_state_t *)fd_type_pun( data );
+// state->kind = FD_VOTER_STATE_CURRENT;
+// state->cnt = 1;
+// state->votes[0] = (fd_voter_vote_t){ .slot = vote };
+
+// fd_tower_accts_push_tail( tower_accts, (fd_tower_accts_t){ .addr = (fd_pubkey_t){ .ul = { addr } }, .stake = stake, .data = data } );
+// }
+// va_end( ap );
+// return tower_accts;
+// }
+
+// void
+// teardown_tower_accts( fd_tower_accts_t * accts ) {
+// fd_wksp_free_laddr( fd_tower_accts_delete( fd_tower_accts_leave( accts ) ) );
+// }
+
+// void
+// test_simple( fd_wksp_t * wksp ) {
+// fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 );
+// fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 );
+
+// fd_ghost_blk_t const * root = fd_ghost_root_const( ghost );
+// FD_TEST( root );
+// FD_TEST( 0==memcmp( &root->key, &block_ids[0], sizeof(fd_hash_t) ) );
+// FD_TEST( fd_ghost_best ( ghost, root )==fd_ghost_query( ghost, &block_ids[6] ) );
+// FD_TEST( fd_ghost_deepest( ghost, root )==fd_ghost_query( ghost, &block_ids[6] ) );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// teardown_ghost( ghost );
+// }
/*
slot 0
@@ -116,67 +106,33 @@ test_ghost_simple( fd_wksp_t * wksp ) {
slot 4
*/
-void
-test_ghost_publish_left( fd_wksp_t * wksp ) {
- ulong node_max = 8;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- fd_ghost_ele_t * pool = fd_wksp_laddr_fast( wksp, ghost->pool_gaddr );
-
-
- // define hash_0, hash_1, hash_2, hash_3, hash_4, hash_5, hash_6
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_3 = { .key = { 3 } };
- fd_hash_t hash_4 = { .key = { 4 } };
- fd_hash_t hash_5 = { .key = { 5 } };
- fd_hash_t hash_6 = { .key = { 6 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
- INSERT( 1, 0 );
- INSERT( 2, 1 );
- INSERT( 3, 1 );
- INSERT( 4, 2 );
- INSERT( 5, 3 );
- INSERT( 6, 5 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_pubkey_t pk1 = { { 1 } };
- fd_epoch_t * epoch = mock_epoch( wksp, 2, 1, pk1, 1 );
- fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL );
-
- fd_ghost_replay_vote( ghost, v1, &hash_2 );
-# if PRINT
- fd_ghost_print( ghost, 2, fd_ghost_root( ghost ) );
-# endif
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_replay_vote( ghost, v1, &hash_3 );
- fd_ghost_ele_t const * node2 = fd_ghost_query( ghost, &hash_2 );
- FD_TEST( node2 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
-# if PRINT
- fd_ghost_print( ghost, 2, fd_ghost_root( ghost ) );
-# endif
- fd_ghost_publish( ghost, &hash_2 );
- fd_ghost_ele_t const * root = fd_ghost_root( ghost );
- FD_TEST( root->slot == 2 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- FD_TEST( fd_ghost_pool_ele( pool, root->child )->slot == 4 );
- FD_TEST( fd_ghost_pool_free( pool ) == node_max - 2 );
-# if PRINT
- fd_ghost_print( ghost, 2, fd_ghost_root( ghost ) );
-# endif
-
- fd_wksp_free_laddr( mem );
-}
+// void
+// test_publish_left( fd_wksp_t * wksp ) {
+// fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 );
+// fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 );
+
+// fd_ghost_blk_t * blk2 = fd_ghost_query( ghost, &block_ids[2] );
+// FD_TEST( blk2 );
+// fd_ghost_publish( ghost, blk2 );
+
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[0] ) );
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[1] ) );
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[3] ) );
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[5] ) );
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[6] ) );
+// FD_TEST( fd_ghost_query( ghost, &block_ids[2] ) );
+// FD_TEST( fd_ghost_query( ghost, &block_ids[4] ) );
+
+// fd_ghost_blk_t * root = fd_ghost_root( ghost );
+// FD_TEST( root==fd_ghost_query( ghost, &block_ids[2] ) );
+// FD_TEST( fd_ghost_child( ghost, root )->slot == 4 );
+// FD_TEST( fd_ghost_best ( ghost, root )==fd_ghost_query( ghost, &block_ids[4] ) );
+// FD_TEST( fd_ghost_deepest( ghost, root )==fd_ghost_query( ghost, &block_ids[4] ) );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+// FD_TEST( pool_free( ghost->pool )==pool_max( ghost->pool ) - 2 );
+
+// teardown_ghost( ghost );
+// }
/*
slot 0
@@ -199,484 +155,268 @@ test_ghost_publish_left( fd_wksp_t * wksp ) {
slot 6
*/
-void
-test_ghost_publish_right( fd_wksp_t * wksp ) {
- ulong node_max = 8;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- fd_ghost_ele_t * pool = fd_wksp_laddr_fast( wksp, ghost->pool_gaddr );
-
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_3 = { .key = { 3 } };
- fd_hash_t hash_4 = { .key = { 4 } };
- fd_hash_t hash_5 = { .key = { 5 } };
- fd_hash_t hash_6 = { .key = { 6 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
- INSERT( 1, 0 );
- INSERT( 2, 1 );
- INSERT( 3, 1 );
- INSERT( 4, 2 );
- INSERT( 5, 3 );
- INSERT( 6, 5 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_pubkey_t pk1 = { { 1 } };
- ulong total = 2;
- fd_epoch_t * epoch = mock_epoch( wksp, total, 1, pk1, 1 );
- fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL );
-
- fd_ghost_replay_vote( ghost, v1, &hash_2 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_replay_vote( ghost, v1, &hash_3 );
- FD_TEST( !fd_ghost_verify( ghost ) );
- fd_ghost_ele_t const * node3 = fd_ghost_query( ghost, &hash_3 );
- FD_TEST( node3 );
-
-# if PRINT
- fd_ghost_print( ghost, total, fd_ghost_root( ghost ) );
-# endif
- fd_ghost_publish( ghost, &hash_3 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_ele_t * root = fd_ghost_pool_ele( pool, ghost->root );
- FD_TEST( root->slot == 3 );
- FD_TEST( fd_ghost_pool_ele( pool, root->child )->slot == 5 );
- FD_TEST( fd_ghost_child( ghost, fd_ghost_child( ghost, root ) )->slot == 6 );
- FD_TEST( fd_ghost_pool_free( pool ) == node_max - 3 );
-# if PRINT
- fd_ghost_print( ghost, total, fd_ghost_root( ghost ) );
-# endif
-
- fd_wksp_free_laddr( mem );
-}
-
-void
-test_ghost_gca( fd_wksp_t * wksp ) {
- ulong node_max = 8;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- //fd_ghost_ele_t * pool = fd_wksp_laddr_fast( wksp, ghost->pool_gaddr );
-
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_3 = { .key = { 3 } };
- fd_hash_t hash_4 = { .key = { 4 } };
- fd_hash_t hash_5 = { .key = { 5 } };
- fd_hash_t hash_6 = { .key = { 6 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
- INSERT( 1, 0 );
- INSERT( 2, 1 );
- INSERT( 3, 1 );
- INSERT( 4, 2 );
- INSERT( 5, 3 );
- INSERT( 6, 5 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
-# if PRINT
- fd_ghost_print( ghost, 0, fd_ghost_root( ghost ) );
-# endif
-
- FD_TEST( fd_ghost_gca( ghost, &hash_0, &hash_0 )->slot == 0 );
-
- FD_TEST( fd_ghost_gca( ghost, &hash_0, &hash_1 )->slot == 0 );
- FD_TEST( fd_ghost_gca( ghost, &hash_1, &hash_1 )->slot == 1 );
-
- FD_TEST( fd_ghost_gca( ghost, &hash_0, &hash_2 )->slot == 0 );
- FD_TEST( fd_ghost_gca( ghost, &hash_1, &hash_2 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_2, &hash_2 )->slot == 2 );
-
- FD_TEST( fd_ghost_gca( ghost, &hash_0, &hash_3 )->slot == 0 );
- FD_TEST( fd_ghost_gca( ghost, &hash_1, &hash_3 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_2, &hash_3 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_3, &hash_3 )->slot == 3 );
-
- FD_TEST( fd_ghost_gca( ghost, &hash_0, &hash_4 )->slot == 0 );
- FD_TEST( fd_ghost_gca( ghost, &hash_1, &hash_4 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_2, &hash_4 )->slot == 2 );
- FD_TEST( fd_ghost_gca( ghost, &hash_3, &hash_4 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_4, &hash_4 )->slot == 4 );
-
- FD_TEST( fd_ghost_gca( ghost, &hash_0, &hash_5 )->slot == 0 );
- FD_TEST( fd_ghost_gca( ghost, &hash_1, &hash_5 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_2, &hash_5 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_3, &hash_5 )->slot == 3 );
- FD_TEST( fd_ghost_gca( ghost, &hash_4, &hash_5 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_5, &hash_5 )->slot == 5 );
-
- FD_TEST( fd_ghost_gca( ghost, &hash_0, &hash_6 )->slot == 0 );
- FD_TEST( fd_ghost_gca( ghost, &hash_1, &hash_6 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_2, &hash_6 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_3, &hash_6 )->slot == 3 );
- FD_TEST( fd_ghost_gca( ghost, &hash_4, &hash_6 )->slot == 1 );
- FD_TEST( fd_ghost_gca( ghost, &hash_5, &hash_6 )->slot == 5 );
- FD_TEST( fd_ghost_gca( ghost, &hash_6, &hash_6 )->slot == 6 );
-}
-
-/*void
-test_ghost_print( fd_wksp_t * wksp ) {
- ulong node_max = 16;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- ulong slots[node_max];
- ulong parent_slots[node_max];
- ulong i = 0;
-
- ulong total = 300;
-
- fd_ghost_init( ghost, 268538758 );
- INSERT( 268538759, 268538758 );
- INSERT( 268538760, 268538759 );
- INSERT( 268538761, 268538758 );
-
- fd_ghost_ele_t * node;
- ulong query;
-
- query = 268538758;
- node = query_mut( ghost, query );
- node->weight = 32;
-
- query = 268538759;
- node = query_mut( ghost, query );
- node->weight = 8;
-
- query = 268538760;
- node = query_mut( ghost, query );
- node->weight = 9;
-
- query = 268538761;
- node = query_mut( ghost, query );
- node->weight = 10;
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_ele_t const * grandparent = fd_ghost_parent( ghost, fd_ghost_parent( ghost, fd_ghost_query( ghost, 268538760 ) ) );
-# if PRINT
- fd_ghost_print( ghost, total, grandparent );
-# else
- (void)grandparent;
- (void)total;
-# endif
-
- fd_wksp_free_laddr( mem );
-}*/
-
-
-/*
- slot 10
- / \
- slot 11 |
- | slot 12
- slot 13
-*/
-
-void
-test_ghost_head( fd_wksp_t * wksp ){
- ulong node_max = 16;
- void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( node_max ), 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- fd_pubkey_t pk1 = { { 1 } };
- fd_pubkey_t pk2 = { { 2 } };
- ulong total = 150;
- fd_epoch_t * epoch = mock_epoch( wksp, 150, 2, pk1, 50, pk2, 100 );
- fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL );
- fd_voter_t * v2 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk2, NULL );
-
- fd_hash_t hash_10 = { .key = { 10 } };
- fd_hash_t hash_11 = { .key = { 11 } };
- fd_hash_t hash_12 = { .key = { 12 } };
- fd_hash_t hash_13 = { .key = { 13 } };
-
- fd_ghost_init( ghost, 10, &hash_10 );
- INSERT( 11, 10 );
- INSERT( 12, 10 );
- INSERT( 13, 11 );
-
- fd_ghost_replay_vote( ghost, v1, &hash_11 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_replay_vote( ghost, v2, &hash_12 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_ele_t const * head = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
- FD_TEST( head->slot == 12 );
-
- fd_ghost_replay_vote( ghost, v1, &hash_13 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_ele_t const * head2 = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
- FD_TEST( head2->slot == 12 );
-
-# if PRINT
- fd_ghost_print( ghost, total, fd_ghost_root( ghost ) );
-# else
- (void)total;
-# endif
-
- fd_wksp_free_laddr( mem );
-}
-
-void
-test_ghost_vote_leaves( fd_wksp_t * wksp ) {
- ulong node_max = 8;
- ulong total_stake = 40;
- int d = 3;
-
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- fd_hash_t hash_arr[node_max];
- hash_arr[0] = (fd_hash_t) { .ul = { ULONG_MAX } };
- for( ulong i = 1; i < node_max; i++){
- hash_arr[i] = (fd_hash_t) { .key = { (uchar)i } };
- }
-
- fd_ghost_init( ghost, 0, &hash_arr[0] );
-
- /* make a full binary tree */
- for( ulong i = 1; i < node_max - 1; i++){
- FD_LOG_NOTICE(("inserting %lu with parent %lu", i, (i-1)/2));
- fd_ghost_insert( ghost, &hash_arr[(i-1)/2], i, &hash_arr[i], total_stake );
- FD_TEST( !fd_ghost_verify( ghost ) );
- }
- FD_TEST( !fd_ghost_verify( ghost ) );
-
-
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-
- /* one validator changes votes along leaves */
- ulong first_leaf = fd_ulong_pow2(d-1) - 1;
- fd_voter_t v = { .key = { { 0 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
- for( ulong i = first_leaf; i < node_max - 1; i++){
- fd_ghost_replay_vote( ghost, &v, &hash_arr[i] );
- }
- FD_TEST( !fd_ghost_verify( ghost ) );
-
-
-# if PRINT
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-# else
- (void)total_stake;
-# endif
-
- ulong path[d];
- ulong leaf = node_max - 2;
- for( int i = d - 1; i >= 0; i--){
- path[i] = leaf;
- leaf = (leaf - 1) / 2;
- }
-
- /* check weights and stakes */
- int j = 0;
- for( ulong i = 0; i < node_max - 1; i++){
- fd_ghost_ele_t const * node = fd_ghost_query( ghost, &hash_arr[i] );
- if ( i == node_max - 2) FD_TEST( node->replay_stake == 10 );
- else FD_TEST( node->replay_stake == 0 );
-
- if( i == path[j] ) { /* if on fork */
- FD_TEST( node->weight == 10 );
- j++;
- } else {
- FD_TEST( node->weight == 0 );
- }
- }
-
- /* have other validators vote for rest of leaves */
- for ( ulong i = first_leaf; i < node_max - 2; i++){
- fd_voter_t v = { .key = { .key = { (uchar)i } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
- fd_ghost_replay_vote( ghost, &v, &hash_arr[i] );
- FD_TEST( !fd_ghost_verify( ghost ) );
- }
-
- /* check weights and stakes */
- for( ulong i = 0; i < node_max - 1; i++){
- fd_ghost_ele_t const * node = fd_ghost_query( ghost, &hash_arr[i] );
- if ( i >= first_leaf){
- FD_TEST( node->replay_stake == 10 );
- FD_TEST( node->weight == 10 );
- } else {
- FD_TEST( node->replay_stake == 0 );
- FD_TEST( node->weight > 10);
- }
- }
-
- FD_TEST( !fd_ghost_verify( ghost ) );
-# if PRINT
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-# endif
-}
-
-void
-test_ghost_old_vote_pruned( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 50;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- fd_hash_t hash_arr[node_max];
- hash_arr[0] = (fd_hash_t) { .ul = { ULONG_MAX } };
- for( ulong i = 1; i < node_max; i++){
- hash_arr[i] = (fd_hash_t) { .key = { (uchar)i } };
- }
-
- fd_ghost_init( ghost, 0, &hash_arr[0] );
- for ( ulong i = 1; i < node_max - 1; i++ ) {
- fd_ghost_insert( ghost, &hash_arr[(i-1)/2], i, &hash_arr[i], total_stake );
- fd_voter_t v = { .key = { { (uchar)i } }, .stake = i, .replay_vote = { .slot = FD_SLOT_NULL } };
- fd_ghost_replay_vote( ghost, &v, &hash_arr[i] );
- }
-
- fd_ghost_publish( ghost, &hash_arr[1]);
-# if PRINT
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-# else
- (void)total_stake;
-# endif
-
- fd_voter_t switch_voter = { .key = { { 5 } }, .stake = 5, .replay_vote = { .slot = 5 } };
- fd_ghost_replay_vote( ghost, &switch_voter, &hash_arr[9] );
- /* switching to vote 9, from voting 5, that is > than the root */
-# if PRINT
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-# endif
-
- FD_TEST( fd_ghost_query( ghost, &hash_arr[9] )->weight == 14 );
- FD_TEST( fd_ghost_query( ghost, &hash_arr[3] )->weight == 18 );
- FD_TEST( fd_ghost_query( ghost, &hash_arr[4] )->weight == 28 );
- FD_TEST( fd_ghost_query( ghost, &hash_arr[1] )->weight == 47 ); /* full tree */
-
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_publish( ghost, &hash_arr[3] ); /* cut down to nodes 3,7,8 */
- /* now previously voted 2 ( < the root ) votes for 7 */
- fd_voter_t switch_voter2 = { .key = { { 2 } }, .stake = 2, .replay_vote = { .slot = 2 } };
- fd_ghost_replay_vote( ghost, &switch_voter2, &hash_arr[7] );
-
-# if PRINT
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-# endif
- FD_TEST( fd_ghost_query( ghost, &hash_arr[7] )->weight == 9 );
- FD_TEST( fd_ghost_query( ghost, &hash_arr[8] )->weight == 8 );
- FD_TEST( fd_ghost_query( ghost, &hash_arr[3] )->weight == 20 );
-
- FD_TEST( !fd_ghost_verify( ghost ) );
-}
-
-void
-test_ghost_head_full_tree( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 120;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- fd_hash_t hash_arr[node_max];
- hash_arr[0] = (fd_hash_t) { .ul = { ULONG_MAX } };
- for( ulong i = 1; i < node_max; i++){
- hash_arr[i] = (fd_hash_t) { .key = { (uchar)i } };
- }
-
- fd_ghost_init( ghost, 0, &hash_arr[0] );
-
- for ( ulong i = 1; i < node_max - 1; i++ ) {
- fd_ghost_insert( ghost, &hash_arr[(i-1)/2], i, &hash_arr[i], total_stake );
- fd_voter_t v = { .key = { { (uchar)i } }, .stake = i, .replay_vote = { .slot = FD_SLOT_NULL } };
- fd_ghost_replay_vote( ghost, &v, &hash_arr[i] );
- }
-
- for ( ulong i = 0; i < node_max - 1; i++ ) {
- fd_ghost_ele_t const * node = fd_ghost_query( ghost, &hash_arr[i] );
- FD_TEST( node->replay_stake == i );
- }
-
- FD_TEST( !fd_ghost_verify( ghost ) );
-
-# if PRINT
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-# endif
- fd_ghost_ele_t const * head = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
-
- /* head will always be rightmost node in this complete binary tree */
-
- FD_TEST( head->slot == 14 );
-
- /* add one more node */
-
- fd_ghost_insert( ghost, &hash_arr[(node_max-2)/2], node_max - 1, &hash_arr[node_max - 1], total_stake );
- fd_voter_t v = { .key = { { (uchar)( node_max - 1 ) } }, .stake = node_max - 1, .replay_vote = { .slot = FD_SLOT_NULL } };
- fd_ghost_replay_vote( ghost, &v, &hash_arr[node_max - 1]);
-
- FD_TEST( !fd_ghost_verify( ghost ) );
- head = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
- FD_TEST( head->slot == 14 );
-
- /* adding one more node would fail. */
-}
-
-void
-test_rooted_vote( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 50;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- fd_pubkey_t pk1 = { .key = { 1 } };
- fd_pubkey_t pk2 = { .key = { 2 } };
- fd_epoch_t * epoch = mock_epoch( wksp, 120, 2, pk1, 20, pk2, 10 );
- fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL );
- fd_voter_t * v2 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk2, NULL );
-
- fd_hash_t hash_0 = { .ul = { ULONG_MAX} };
- fd_hash_t hash_1 = { .key = { 1 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
-
- fd_ghost_insert( ghost, &hash_0, 1, &hash_1, total_stake );
- fd_ghost_replay_vote( ghost, v1, &hash_1 );
-
- fd_ghost_rooted_vote( ghost, v2, 1 );
-
- fd_ghost_ele_t const * node = fd_ghost_query( ghost, &hash_1 );
- FD_TEST( node->replay_stake == 20 );
- FD_TEST( node->weight == 20 );
- FD_TEST( node->rooted_stake == 10 );
-
- FD_TEST( !fd_ghost_verify( ghost ) );
-}
+// void
+// test_publish_right( fd_wksp_t * wksp ) {
+// fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 );
+// fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 );
+
+// fd_ghost_blk_t * blk3 = fd_ghost_query( ghost, &block_ids[3] );
+// FD_TEST( blk3 );
+// fd_ghost_publish( ghost, blk3 );
+
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[0] ) );
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[1] ) );
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[2] ) );
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[4] ) );
+// FD_TEST( !fd_ghost_query( ghost, &block_ids[3] ) );
+// FD_TEST( fd_ghost_query( ghost, &block_ids[5] ) );
+// FD_TEST( fd_ghost_query( ghost, &block_ids[6] ) );
+
+// fd_ghost_blk_t * root = fd_ghost_root( ghost );
+// FD_TEST( root==fd_ghost_query( ghost, &block_ids[3] ) );
+// FD_TEST( fd_ghost_child( ghost, root )->slot == 5 );
+// FD_TEST( fd_ghost_child( ghost, fd_ghost_child( ghost, root ) )->slot == 6 );
+// FD_TEST( fd_ghost_best ( ghost, root )==fd_ghost_query( ghost, &block_ids[4] ) );
+// FD_TEST( fd_ghost_deepest( ghost, root )==fd_ghost_query( ghost, &block_ids[4] ) );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+// FD_TEST( pool_free( ghost->pool )==pool_max( ghost->pool ) - 3 );
+
+// teardown_ghost( ghost );
+// }
+
+// void
+// test_best( fd_wksp_t * wksp ){
+// fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 );
+// fd_tower_accts_t * accts2 = setup_tower_accts( wksp, 'a', 50, 2 );
+// fd_tower_accts_t * accts3 = setup_tower_accts( wksp, 'b', 100, 3 );
+// fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 );
+
+// fd_ghost_count_votes( ghost, fd_ghost_query( ghost, &block_ids[4] ), accts2 );
+// fd_ghost_count_votes( ghost, fd_ghost_query( ghost, &block_ids[5] ), accts3 );
+
+// FD_TEST( fd_ghost_query( ghost, &block_ids[2] )->stake==50 );
+// FD_TEST( fd_ghost_query( ghost, &block_ids[3] )->stake==100 );
+
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot==6 );
+// FD_TEST( fd_ghost_deepest( ghost, fd_ghost_root( ghost ) )->slot==6 );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// teardown_tower_accts( accts2 );
+// teardown_tower_accts( accts3 );
+
+// accts2 = setup_tower_accts( wksp, 'a', 50, 3 ); /* both switched */
+// accts3 = setup_tower_accts( wksp, 'b', 100, 2 );
+
+// fd_ghost_count_votes( ghost, fd_ghost_query( ghost, &block_ids[4] ), accts2 );
+// fd_ghost_count_votes( ghost, fd_ghost_query( ghost, &block_ids[5] ), accts3 );
+
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot==4 );
+// FD_TEST( fd_ghost_deepest( ghost, fd_ghost_root( ghost ) )->slot==6 );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// teardown_ghost( ghost );
+// }
+
+// void
+// test_vote_leaves( fd_wksp_t * wksp ) {
+// ulong blk_max = 8;
+// ulong total_stake = 40;
+
+// void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( 8, 8 ), 42UL );
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, 8, 8, 42UL ) );
+// FD_TEST( ghost );
+
+// fd_hash_t block_ids[blk_max]; setup_block_ids( block_ids, blk_max );
+// block_ids[0] = (fd_hash_t) { .ul = { ULONG_MAX } };
+// for( ulong i = 1; i < blk_max; i++ ) {
+// block_ids[i] = (fd_hash_t) { .key = { (uchar)i } };
+// }
+
+// /* make a full binary tree */
+// fd_ghost_insert( ghost, &block_ids[0], NULL, 0 );
+// for( ulong i = 1; i < blk_max - 1; i++){
+// FD_LOG_NOTICE(( "inserting %lu with parent %lu", i, (i-1)/2 ));
+// fd_ghost_insert( ghost, &block_ids[i], &block_ids[(i-1)/2], i );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+// }
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// /* one validator changes votes along leaves */
+// int d = 3;
+// ulong first_leaf = fd_ulong_pow2(d-1) - 1;
+// fd_voter_t v = { .key = { { 0 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
+// for( ulong i = first_leaf; i < blk_max - 1; i++){
+// fd_ghost_count_vote( ghost, &v, &block_ids[i] );
+// }
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+
+// # if PRINT
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+// # else
+// (void)total_stake;
+// # endif
+
+// ulong path[d];
+// ulong leaf = blk_max - 2;
+// for( int i = d - 1; i >= 0; i--){
+// path[i] = leaf;
+// leaf = (leaf - 1) / 2;
+// }
+
+// /* check weights and stakes */
+// int j = 0;
+// for( ulong i = 0; i < blk_max - 1; i++){
+// fd_ghost_blk_t const * blk = fd_ghost_query( ghost, &hash_arr[i] );
+// if ( i == blk_max - 2) FD_TEST( blk->replay_stake == 10 );
+// else FD_TEST( blk->replay_stake == 0 );
+
+// if( i == path[j] ) { /* if on fork */
+// FD_TEST( blk->stake == 10 );
+// j++;
+// } else {
+// FD_TEST( blk->stake == 0 );
+// }
+// }
+
+// /* have other validators vote for rest of leaves */
+// for ( ulong i = first_leaf; i < blk_max - 2; i++){
+// fd_voter_t v = { .key = { .key = { (uchar)i } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
+// fd_ghost_count_vote( ghost, &v, &hash_arr[i] );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+// }
+
+// /* check weights and stakes */
+// for( ulong i = 0; i < blk_max - 1; i++){
+// fd_ghost_blk_t const * blk = fd_ghost_query( ghost, &hash_arr[i] );
+// if ( i >= first_leaf){
+// FD_TEST( blk->replay_stake == 10 );
+// FD_TEST( blk->stake == 10 );
+// } else {
+// FD_TEST( blk->replay_stake == 0 );
+// FD_TEST( blk->stake > 10);
+// }
+// }
+
+// FD_TEST( !fd_ghost_verify( ghost ) );
+// # if PRINT
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+// # endif
+// }
+
+// void
+// test_old_vote_pruned( fd_wksp_t * wksp ){
+// ulong blk_max = 16;
+// ulong total_stake = 50;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+
+// fd_hash_t hash_arr[blk_max];
+// hash_arr[0] = (fd_hash_t) { .ul = { ULONG_MAX } };
+// for( ulong i = 1; i < blk_max; i++){
+// hash_arr[i] = (fd_hash_t) { .key = { (uchar)i } };
+// }
+
+// fd_ghost_init( ghost, 0, &hash_arr[0] );
+// for ( ulong i = 1; i < blk_max - 1; i++ ) {
+// fd_ghost_update( ghost, &hash_arr[(i-1)/2], i, &hash_arr[i], total_stake );
+// fd_voter_t v = { .key = { { (uchar)i } }, .stake = i, .replay_vote = { .slot = FD_SLOT_NULL } };
+// fd_ghost_count_vote( ghost, &v, &hash_arr[i] );
+// }
+
+// fd_ghost_publish( ghost, &hash_arr[1]);
+// # if PRINT
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+// # else
+// (void)total_stake;
+// # endif
+
+// fd_voter_t switch_voter = { .key = { { 5 } }, .stake = 5, .replay_vote = { .slot = 5 } };
+// fd_ghost_count_vote( ghost, &switch_voter, &hash_arr[9] );
+// /* switching to vote 9, from voting 5, that is > than the root */
+// # if PRINT
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+// # endif
+
+// FD_TEST( fd_ghost_query( ghost, &hash_arr[9] )->stake == 14 );
+// FD_TEST( fd_ghost_query( ghost, &hash_arr[3] )->stake == 18 );
+// FD_TEST( fd_ghost_query( ghost, &hash_arr[4] )->stake == 28 );
+// FD_TEST( fd_ghost_query( ghost, &hash_arr[1] )->stake == 47 ); /* full tree */
+
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// fd_ghost_publish( ghost, &hash_arr[3] ); /* cut down to blks 3,7,8 */
+// /* now previously voted 2 ( < the root ) votes for 7 */
+// fd_voter_t switch_voter2 = { .key = { { 2 } }, .stake = 2, .replay_vote = { .slot = 2 } };
+// fd_ghost_count_vote( ghost, &switch_voter2, &hash_arr[7] );
+
+// # if PRINT
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+// # endif
+// FD_TEST( fd_ghost_query( ghost, &hash_arr[7] )->stake == 9 );
+// FD_TEST( fd_ghost_query( ghost, &hash_arr[8] )->stake == 8 );
+// FD_TEST( fd_ghost_query( ghost, &hash_arr[3] )->stake == 20 );
+
+// FD_TEST( !fd_ghost_verify( ghost ) );
+// }
+
+// void
+// test_best_full_tree( fd_wksp_t * wksp ){
+// ulong blk_max = 16;
+// ulong total_stake = 120;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+
+// fd_hash_t hash_arr[blk_max];
+// hash_arr[0] = (fd_hash_t) { .ul = { ULONG_MAX } };
+// for( ulong i = 1; i < blk_max; i++){
+// hash_arr[i] = (fd_hash_t) { .key = { (uchar)i } };
+// }
+
+// fd_ghost_init( ghost, 0, &hash_arr[0] );
+
+// for ( ulong i = 1; i < blk_max - 1; i++ ) {
+// fd_ghost_update( ghost, &hash_arr[(i-1)/2], i, &hash_arr[i], total_stake );
+// fd_voter_t v = { .key = { { (uchar)i } }, .stake = i, .replay_vote = { .slot = FD_SLOT_NULL } };
+// fd_ghost_count_vote( ghost, &v, &hash_arr[i] );
+// }
+
+// for ( ulong i = 0; i < blk_max - 1; i++ ) {
+// fd_ghost_blk_t const * blk = fd_ghost_query( ghost, &hash_arr[i] );
+// FD_TEST( blk->replay_stake == i );
+// }
+
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// # if PRINT
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+// # endif
+// fd_ghost_blk_t const * head = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
+
+// /* head will always be rightmost blk in this complete binary tree */
+
+// FD_TEST( head->slot == 14 );
+
+// /* add one more blk */
+
+// fd_ghost_update( ghost, &hash_arr[(blk_max-2)/2], blk_max - 1, &hash_arr[blk_max - 1], total_stake );
+// fd_voter_t v = { .key = { { (uchar)( blk_max - 1 ) } }, .stake = blk_max - 1, .replay_vote = { .slot = FD_SLOT_NULL } };
+// fd_ghost_count_vote( ghost, &v, &hash_arr[blk_max - 1]);
+
+// FD_TEST( !fd_ghost_verify( ghost ) );
+// head = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
+// FD_TEST( head->slot == 14 );
+
+// /* adding one more blk would fail. */
+// }
/*
slot 10
@@ -686,76 +426,73 @@ test_rooted_vote( fd_wksp_t * wksp ){
slot 13
*/
-void
-test_ghost_head_valid( fd_wksp_t * wksp ) {
- ulong node_max = 16;
- void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( node_max ), 1UL );
- FD_TEST( mem );
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- fd_pubkey_t pk1 = { { 1 } };
- fd_pubkey_t pk2 = { { 2 } };
- ulong total = 150;
- fd_epoch_t * epoch = mock_epoch( wksp, 150, 2, pk1, 50, pk2, 100 );
- fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL );
- fd_voter_t * v2 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk2, NULL );
-
- fd_hash_t hash_10 = { .key = { 10 } };
- fd_hash_t hash_11 = { .key = { 11 } };
- fd_hash_t hash_12 = { .key = { 12 } };
- fd_hash_t hash_13 = { .key = { 13 } };
-
- fd_ghost_init( ghost, 10, &hash_10 );
- INSERT( 11, 10 );
- INSERT( 12, 10 );
- INSERT( 13, 11 );
-
- fd_ghost_replay_vote( ghost, v1, &hash_11 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- fd_ghost_replay_vote( ghost, v2, &hash_12 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- // fd_ghost_node_t const * head = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
- // FD_TEST( head->slot == 12 );
-
- fd_ghost_replay_vote( ghost, v1, &hash_13 );
- FD_TEST( !fd_ghost_verify( ghost ) );
-
- // fd_ghost_node_t const * head2 = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
- // FD_TEST( head2->slot == 12 );
-
- query_mut( ghost, 12 )->valid = 0; // mark 12 as invalid
- // fd_ghost_node_t const * head3 = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
- // FD_TEST( head3->slot == 13 );
-
- fd_ghost_replay_vote( ghost, v2, &hash_13 );
- query_mut( ghost, 11 )->valid = 0; // mark 11 as invalid
- // fd_ghost_node_t const * head4 = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
- // FD_TEST( head4->slot == 10 );
-
- query_mut( ghost, 12 )->valid = 1; // mark 12 as valid
- fd_ghost_ele_t const * head5 = fd_ghost_head( ghost, fd_ghost_root( ghost ) );
- FD_TEST( head5->slot == 12 );
-
-# if PRINT
- fd_ghost_print( ghost, total, fd_ghost_root( ghost ) );
-# else
- (void)total;
-# endif
-
- fd_wksp_free_laddr( mem );
-}
-
-void
-test_duplicate_simple( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 10;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
+// void
+// test_best_valid( fd_wksp_t * wksp ) {
+// ulong blk_max = 16;
+// void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max ), 1UL );
+// FD_TEST( mem );
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+
+// fd_pubkey_t pk1 = { { 1 } };
+// fd_pubkey_t pk2 = { { 2 } };
+// ulong total = 150;
+// fd_epoch_t * epoch = mock_epoch( wksp, 150, 2, pk1, 50, pk2, 100 );
+// fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL );
+// fd_voter_t * v2 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk2, NULL );
+
+// fd_hash_t hash_10 = { .key = { 10 } };
+// fd_hash_t hash_11 = { .key = { 11 } };
+// fd_hash_t hash_12 = { .key = { 12 } };
+// fd_hash_t hash_13 = { .key = { 13 } };
+
+// fd_ghost_init( ghost, 10, &hash_10 );
+// INSERT( 11, 10 );
+// INSERT( 12, 10 );
+// INSERT( 13, 11 );
+
+// fd_ghost_count_vote( ghost, v1, &hash_11 );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// fd_ghost_count_vote( ghost, v2, &hash_12 );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// // fd_ghost_blk_t const * head = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
+// // FD_TEST( head->slot == 12 );
+
+// fd_ghost_count_vote( ghost, v1, &hash_13 );
+// FD_TEST( !fd_ghost_verify( ghost ) );
+
+// // fd_ghost_blk_t const * head2 = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
+// // FD_TEST( head2->slot == 12 );
+
+// query_mut( ghost, 12 )->valid = 0; // mark 12 as invalid
+// // fd_ghost_blk_t const * head3 = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
+// // FD_TEST( head3->slot == 13 );
+
+// fd_ghost_count_vote( ghost, v2, &hash_13 );
+// query_mut( ghost, 11 )->valid = 0; // mark 11 as invalid
+// // fd_ghost_blk_t const * head4 = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
+// // FD_TEST( head4->slot == 10 );
+
+// query_mut( ghost, 12 )->valid = 1; // mark 12 as valid
+// fd_ghost_blk_t const * head5 = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
+// FD_TEST( head5->slot == 12 );
+
+// # if PRINT
+// fd_ghost_print( ghost, total, fd_ghost_root( ghost ) );
+// # else
+// (void)total;
+// # endif
+
+// fd_wksp_free_laddr( mem );
+// }
+
+// void
+// test_duplicate_simple( fd_wksp_t * wksp ) {
+// ulong blk_max = 16;
+// ulong total_stake = 10;
+// void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max ), 1UL );
+// FD_TEST( mem );
/* 1
/ \
@@ -763,63 +500,63 @@ test_duplicate_simple( fd_wksp_t * wksp ){
| |
3 4 */
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
-
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_2_prime = { .key = { 2, 1 } };
- fd_hash_t hash_3 = { .key = { 3 } };
- fd_hash_t hash_4 = { .key = { 4 } };
-
- fd_ghost_init( ghost, 1, &hash_1 );
-
- /* We see 2 and 3 first, so we replay down the left branch first */
- fd_ghost_insert( ghost, &hash_1, 2, &hash_2, total_stake );
- fd_ghost_insert( ghost, &hash_2, 3, &hash_3, total_stake );
-
- /* We see evidence of 2' and 4. Add them to the tree */
- fd_ghost_insert( ghost, &hash_1, 2, &hash_2_prime, total_stake );
- fd_ghost_insert( ghost, &hash_2_prime, 4, &hash_4, total_stake );
-
- /* Only 1 - 2 - 3 should be visible in the slot map */
- FD_TEST( memcmp( fd_ghost_hash( ghost, 1 ), &hash_1, sizeof(fd_hash_t) ) == 0 );
- FD_TEST( memcmp( fd_ghost_hash( ghost, 2 ), &hash_2, sizeof(fd_hash_t) ) == 0 );
- FD_TEST( memcmp( fd_ghost_hash( ghost, 3 ), &hash_3, sizeof(fd_hash_t) ) == 0 );
- FD_TEST( memcmp( fd_ghost_hash( ghost, 4 ), &hash_4, sizeof(fd_hash_t) ) == 0 );
-
- fd_ghost_ele_t const * dup_child = fd_ghost_query( ghost, &hash_4 );
- fd_ghost_ele_t const * dup_parent = fd_ghost_pool_ele( pool, dup_child->parent );
- FD_TEST( dup_parent->slot == 2 );
- FD_TEST( memcmp( &dup_parent->key, &hash_2_prime, sizeof(fd_hash_t) ) == 0 );
-
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-
- FD_TEST( !fd_ghost_verify( ghost ) );
-}
-
-void
-test_many_duplicates( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 10;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
-
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- fd_ghost_slot_map_t * map_slot = fd_ghost_slot_map( ghost );
- fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
-
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_2_prime = { .key = { 2, 1 } };
- fd_hash_t hash_3 = { .key = { 3 } };
- fd_hash_t hash_3_prime = { .key = { 3, 1 } };
- fd_hash_t hash_4 = { .key = { 4 } };
- fd_hash_t hash_4_prime = { .key = { 4, 1 } };
- fd_hash_t hash_4_prime_prime = { .key = { 4, 1, 1 } };
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+// fd_ghost_blk_t * pool = pool( ghost );
+
+// fd_hash_t hash_1 = { .key = { 1 } };
+// fd_hash_t hash_2 = { .key = { 2 } };
+// fd_hash_t hash_2_prime = { .key = { 2, 1 } };
+// fd_hash_t hash_3 = { .key = { 3 } };
+// fd_hash_t hash_4 = { .key = { 4 } };
+
+// fd_ghost_init( ghost, 1, &hash_1 );
+
+// /* We see 2 and 3 first, so we replay down the left branch first */
+// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake );
+// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake );
+
+// /* We see evidence of 2' and 4. Add them to the tree */
+// fd_ghost_update( ghost, &hash_1, 2, &hash_2_prime, total_stake );
+// fd_ghost_update( ghost, &hash_2_prime, 4, &hash_4, total_stake );
+
+// /* Only 1 - 2 - 3 should be visible in the slot map */
+// FD_TEST( memcmp( fd_ghost_hash( ghost, 1 ), &hash_1, sizeof(fd_hash_t) ) == 0 );
+// FD_TEST( memcmp( fd_ghost_hash( ghost, 2 ), &hash_2, sizeof(fd_hash_t) ) == 0 );
+// FD_TEST( memcmp( fd_ghost_hash( ghost, 3 ), &hash_3, sizeof(fd_hash_t) ) == 0 );
+// FD_TEST( memcmp( fd_ghost_hash( ghost, 4 ), &hash_4, sizeof(fd_hash_t) ) == 0 );
+
+// fd_ghost_blk_t const * dup_child = fd_ghost_query( ghost, &hash_4 );
+// fd_ghost_blk_t const * dup_parent = pool_ele( pool, dup_child->parent );
+// FD_TEST( dup_parent->slot == 2 );
+// FD_TEST( memcmp( &dup_parent->key, &hash_2_prime, sizeof(fd_hash_t) ) == 0 );
+
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+
+// FD_TEST( !fd_ghost_verify( ghost ) );
+// }
+
+// void
+// test_many_duplicates( fd_wksp_t * wksp ){
+// ulong blk_max = 16;
+// ulong total_stake = 10;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+// fd_ghost_slot_map_t * map_slot = fd_ghost_slot_map( ghost );
+// fd_ghost_blk_t * pool = pool( ghost );
+
+// fd_hash_t hash_1 = { .key = { 1 } };
+// fd_hash_t hash_2 = { .key = { 2 } };
+// fd_hash_t hash_2_prime = { .key = { 2, 1 } };
+// fd_hash_t hash_3 = { .key = { 3 } };
+// fd_hash_t hash_3_prime = { .key = { 3, 1 } };
+// fd_hash_t hash_4 = { .key = { 4 } };
+// fd_hash_t hash_4_prime = { .key = { 4, 1 } };
+// fd_hash_t hash_4_prime_prime = { .key = { 4, 1, 1 } };
/* 1
/ \
@@ -829,277 +566,276 @@ test_many_duplicates( fd_wksp_t * wksp ){
/ \ |
4 4' 4'' */
- fd_ghost_init( ghost, 1, &hash_1 );
-
- /* Slots I see initially*/
-
- fd_ghost_insert( ghost, &hash_1, 2, &hash_2, total_stake );
- fd_ghost_insert( ghost, &hash_2, 3, &hash_3, total_stake );
- fd_ghost_insert( ghost, &hash_3, 4, &hash_4, total_stake );
-
- /* Evidence of duplicates */
-
- fd_ghost_insert( ghost, &hash_1, 2, &hash_2_prime, total_stake );
- fd_ghost_insert( ghost, &hash_2_prime, 3, &hash_3_prime, total_stake );
- fd_ghost_insert( ghost, &hash_3_prime, 4, &hash_4_prime_prime, total_stake );
- fd_ghost_insert( ghost, &hash_3, 4, &hash_4_prime, total_stake );
-
- ulong visible_slots[4] = { 3, 1, 2, 4 };
- fd_hash_t visible_hashes[4] = { hash_3, hash_1, hash_2, hash_4 };
- int cnt = 0;
- for( fd_ghost_slot_map_iter_t iter = fd_ghost_slot_map_iter_init( map_slot, pool );
- !fd_ghost_slot_map_iter_done( iter, map_slot, pool );
- iter = fd_ghost_slot_map_iter_next( iter, map_slot, pool ) ) {
- fd_ghost_ele_t const * ele = fd_ghost_slot_map_iter_ele( iter, map_slot, pool );
- FD_LOG_NOTICE(( "ele->slot: %lu, visible_slots[cnt]: %lu", ele->slot, visible_slots[cnt] ));
- FD_TEST( ele->slot == visible_slots[cnt] );
- FD_TEST( memcmp( &ele->key, &visible_hashes[cnt], sizeof(fd_hash_t) ) == 0 );
- cnt++;
- }
- FD_TEST( cnt == 4 );
-
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
-
- /* Vote down the left branch */
- fd_voter_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
- fd_ghost_replay_vote( ghost, &v1, &hash_4 );
-}
-
-/* Key differences between Agave and Firedancer: Agave inserts to their
- fork tracking structure before the bank is frozen. Thus they can
- build forks and mark things duplicate/duplicate confirmed, but it has
- no effect bevause the bank is not yet frozen. However, Firedancer
- only adds to ghost once the bank is frozen, and the slot has been
- fully replayed. */
-
-void
-run_test_state_duplicate_then_bank_frozen( fd_wksp_t * wksp ) {
- /* covers both mid-replay and haven't started replay test cases */
- ulong node_max = 16;
- ulong total_stake = 10;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
-
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
- fd_ghost_insert( ghost, &hash_0, 1, &hash_1, total_stake );
-
- /* Get a duplicate message shred/gossip/repair. Nothing happens because
- the slot 2 has not yet been replayed */
- process_duplicate( ghost, 2, total_stake );
- FD_TEST( fd_ghost_hash( ghost, 2 ) == NULL );
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 1 );
-
- /* Finish replaying slot 2, hash 2 */
- fd_ghost_insert( ghost, &hash_1, 2, &hash_2, total_stake );
- FD_TEST( !fd_ghost_query( ghost, &hash_2 )->valid );
- /* Parent of 2 is 1, so head should be 1 */
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 1 );
-}
-
-void
-test_state_ancestor_confirmed_descendant_duplicate( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 10;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
-
- /* 0 - 1 - 2 - 3 */
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost );
-
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_3 = { .key = { 3 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
- fd_ghost_insert( ghost, &hash_0, 1, &hash_1, total_stake );
- fd_ghost_insert( ghost, &hash_1, 2, &hash_2, total_stake );
- fd_ghost_insert( ghost, &hash_2, 3, &hash_3, total_stake );
-
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 3 );
- process_duplicate_confirmed( ghost, &hash_2, 2 );
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 3 );
-
- /* mark 3 as duplicate */
- process_duplicate( ghost, 3, total_stake );
- FD_TEST( fd_dup_seen_map_query( dup_map, 3, NULL ) );
-
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 2 );
-}
-
-void
-test_state_ancestor_duplicate_descendant_confirmed( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 18;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
-
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost );
-
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_3 = { .key = { 3 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
- fd_ghost_insert( ghost, &hash_0, 1, &hash_1, total_stake );
- fd_ghost_insert( ghost, &hash_1, 2, &hash_2, total_stake );
- fd_ghost_insert( ghost, &hash_2, 3, &hash_3, total_stake );
-
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 3 );
-
- process_duplicate( ghost, 2, total_stake );
- FD_TEST( fd_dup_seen_map_query( dup_map, 2, NULL ) );
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 1 );
-
- fd_voter_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
- fd_ghost_replay_vote( ghost, &v1, &hash_3 );
-
- FD_TEST( is_duplicate_confirmed( ghost, &hash_3, total_stake ) );
-
- /* 3 becomes duplicate confirmed */
- if( is_duplicate_confirmed( ghost, &hash_3, total_stake ) ) {
- process_duplicate_confirmed( ghost, &hash_3, 3 );
- }
- FD_TEST( fd_ghost_query( ghost, &hash_3 )->valid );
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 3 );
-}
-
-void
-test_state_descendant_confirmed_ancestor_duplicate( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 18;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
-
- /* 0 - 1 - 2 - 3 */
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost );
-
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_hash_t hash_2 = { .key = { 2 } };
- fd_hash_t hash_3 = { .key = { 3 } };
-
- fd_ghost_init( ghost, 0, &hash_0 );
- fd_ghost_insert( ghost, &hash_0, 1, &hash_1, total_stake );
- fd_ghost_insert( ghost, &hash_1, 2, &hash_2, total_stake );
- fd_ghost_insert( ghost, &hash_2, 3, &hash_3, total_stake );
-
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 3 );
- fd_voter_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
- fd_ghost_replay_vote( ghost, &v1, &hash_3 );
-
- FD_TEST( is_duplicate_confirmed( ghost, &hash_3, total_stake ) );
-
- /* 3 becomes duplicate confirmed */
- if( is_duplicate_confirmed( ghost, &hash_3, total_stake ) ) {
- process_duplicate_confirmed( ghost, &hash_3, 3 );
- }
- fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
- for( ulong slot = 0; slot < 4; slot++ ) {
- fd_ghost_ele_t const * ele = fd_ghost_query( ghost, fd_ghost_hash( ghost, slot ) );
- FD_TEST( ele->valid );
- FD_LOG_NOTICE(("slot %lu, ele->key: %s", slot, FD_BASE58_ENC_32_ALLOCA(&ele->key) ));
- FD_TEST( is_duplicate_confirmed( ghost, &ele->key, total_stake ) );
- }
-
- process_duplicate( ghost, 1, total_stake );
- FD_TEST( fd_dup_seen_map_query( dup_map, 1, NULL ) );
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 3 );
-
-}
-
-
-void
-test_duplicate_after_frozen( fd_wksp_t * wksp ){
- ulong node_max = 16;
- ulong total_stake = 10;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
-
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
-
- fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash_1 = { .key = { 1 } };
- fd_ghost_init( ghost, 0, &hash_0 );
- fd_ghost_insert( ghost, &hash_0, 1, &hash_1, total_stake );
- process_duplicate( ghost, 1, total_stake );
-
- FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot == 0 );
-}
-
-void
-test_duplicate_node_inserted( fd_wksp_t * wksp ) {
- ulong node_max = 16;
- ulong total_stake = 10;
- void * mem = fd_wksp_alloc_laddr( wksp,
- fd_ghost_align(),
- fd_ghost_footprint( node_max ),
- 1UL );
- FD_TEST( mem );
-
- fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, node_max, 0UL ) );
- fd_hash_t hash0 = { .ul = { ULONG_MAX } };
- fd_hash_t hash1 = { .key = { 1 } };
-
- fd_ghost_init( ghost, 0, &hash0 );
-
- ulong duplicate_slot = 1;
-
- FD_TEST( fd_ghost_hash( ghost, duplicate_slot ) == NULL );
-
- // Simulate finish replaying a bank by inserting the slot - equivalent to:
- fd_ghost_insert( ghost, &hash0, 1, &hash1, total_stake );
-
- // Test equivalent to: assert_eq!(blockstore.get_bank_hash(duplicate_slot).unwrap(), duplicate_slot_hash);
- fd_hash_t const * stored_hash = fd_ghost_hash( ghost, duplicate_slot );
- FD_TEST( stored_hash != NULL );
- FD_TEST( memcmp( stored_hash, &hash1, sizeof(fd_hash_t) ) == 0 );
-
- // Now test freezing another version of the same bank - this creates a duplicate scenario
- fd_hash_t new_bank_hash = { .key = { 2 } };
-
- // In Ghost, inserting the same slot with a different hash creates a duplicate
- fd_ghost_insert( ghost, &hash0, duplicate_slot, &new_bank_hash, total_stake );
-
- // The slot map should still point to the original version (the "happy tree")
- // but the new hash should be tracked in the hash map
- fd_hash_t const * slot_map_hash = fd_ghost_hash( ghost, duplicate_slot );
- FD_TEST( slot_map_hash != NULL );
- FD_TEST( memcmp( slot_map_hash, &hash1, sizeof(fd_hash_t) ) == 0 ); // Still original hash
-
- // The new hash should be queryable directly
- fd_ghost_ele_t const * new_hash_ele = fd_ghost_query( ghost, &new_bank_hash );
- FD_TEST( new_hash_ele != NULL );
- FD_TEST( new_hash_ele->slot == duplicate_slot );
-
- // Clean up
- fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) );
-}
+// fd_ghost_init( ghost, 1, &hash_1 );
+
+// /* Slots I see initially*/
+
+// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake );
+// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake );
+// fd_ghost_update( ghost, &hash_3, 4, &hash_4, total_stake );
+
+// /* Evidence of duplicates */
+
+// fd_ghost_update( ghost, &hash_1, 2, &hash_2_prime, total_stake );
+// fd_ghost_update( ghost, &hash_2_prime, 3, &hash_3_prime, total_stake );
+// fd_ghost_update( ghost, &hash_3_prime, 4, &hash_4_prime_prime, total_stake );
+// fd_ghost_update( ghost, &hash_3, 4, &hash_4_prime, total_stake );
+
+// ulong visible_slots[4] = { 3, 1, 2, 4 };
+// fd_hash_t visible_hashes[4] = { hash_3, hash_1, hash_2, hash_4 };
+// int cnt = 0;
+// for( fd_ghost_slot_map_iter_t iter = fd_ghost_slot_map_iter_init( map_slot, pool );
+// !fd_ghost_slot_map_iter_done( iter, map_slot, pool );
+// iter = fd_ghost_slot_map_iter_next( iter, map_slot, pool ) ) {
+// fd_ghost_blk_t const * ele = fd_ghost_slot_map_iter_ele( iter, map_slot, pool );
+// FD_LOG_NOTICE(( "ele->slot: %lu, visible_slots[cnt]: %lu", ele->slot, visible_slots[cnt] ));
+// FD_TEST( ele->slot == visible_slots[cnt] );
+// FD_TEST( memcmp( &ele->key, &visible_hashes[cnt], sizeof(fd_hash_t) ) == 0 );
+// cnt++;
+// }
+// FD_TEST( cnt == 4 );
+
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+
+// /* Vote down the left branch */
+// fd_voter_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
+// fd_ghost_count_vote( ghost, &v1, &hash_4 );
+// }
+
+// /* Key differences between Agave and Firedancer: Agave inserts to their
+// fork tracking structure before the bank is frozen. Thus they can
+// build forks and mark things duplicate/duplicate confirmed, but it has
+// no effect bevause the bank is not yet frozen. However, Firedancer
+// only adds to ghost once the bank is frozen, and the slot has been
+// fully replayed. */
+
+// void
+// run_test_state_duplicate_then_bank_frozen( fd_wksp_t * wksp ) {
+// /* covers both mid-replay and haven't started replay test cases */
+// ulong blk_max = 16;
+// ulong total_stake = 10;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+
+// fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
+// fd_hash_t hash_1 = { .key = { 1 } };
+// fd_hash_t hash_2 = { .key = { 2 } };
+
+// fd_ghost_init( ghost, 0, &hash_0 );
+// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake );
+
+// /* Get a duplicate message shred/gossip/repair. Nothing happens because
+// the slot 2 has not yet been replayed */
+// process_duplicate( ghost, 2, total_stake );
+// FD_TEST( fd_ghost_hash( ghost, 2 ) == NULL );
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 1 );
+
+// /* Finish replaying slot 2, hash 2 */
+// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake );
+// FD_TEST( !fd_ghost_query( ghost, &hash_2 )->valid );
+// /* Parent of 2 is 1, so head should be 1 */
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 1 );
+// }
+
+// void
+// test_state_ancestor_confirmed_descendant_duplicate( fd_wksp_t * wksp ){
+// ulong blk_max = 16;
+// ulong total_stake = 10;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+
+// /* 0 - 1 - 2 - 3 */
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+// fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost );
+
+// fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
+// fd_hash_t hash_1 = { .key = { 1 } };
+// fd_hash_t hash_2 = { .key = { 2 } };
+// fd_hash_t hash_3 = { .key = { 3 } };
+
+// fd_ghost_init( ghost, 0, &hash_0 );
+// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake );
+// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake );
+// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake );
+
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 );
+// process_duplicate_confirmed( ghost, &hash_2, 2 );
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 );
+
+// /* mark 3 as duplicate */
+// process_duplicate( ghost, 3, total_stake );
+// FD_TEST( fd_dup_seen_map_query( dup_map, 3, NULL ) );
+
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 2 );
+// }
+
+// void
+// test_state_ancestor_duplicate_descendant_confirmed( fd_wksp_t * wksp ){
+// ulong blk_max = 16;
+// ulong total_stake = 18;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+// fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost );
+
+// fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
+// fd_hash_t hash_1 = { .key = { 1 } };
+// fd_hash_t hash_2 = { .key = { 2 } };
+// fd_hash_t hash_3 = { .key = { 3 } };
+
+// fd_ghost_init( ghost, 0, &hash_0 );
+// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake );
+// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake );
+// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake );
+
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 );
+
+// process_duplicate( ghost, 2, total_stake );
+// FD_TEST( fd_dup_seen_map_query( dup_map, 2, NULL ) );
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 1 );
+
+// fd_voter_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
+// fd_ghost_count_vote( ghost, &v1, &hash_3 );
+
+// FD_TEST( is_duplicate_confirmed( ghost, &hash_3, total_stake ) );
+
+// /* 3 becomes duplicate confirmed */
+// if( is_duplicate_confirmed( ghost, &hash_3, total_stake ) ) {
+// process_duplicate_confirmed( ghost, &hash_3, 3 );
+// }
+// FD_TEST( fd_ghost_query( ghost, &hash_3 )->valid );
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 );
+// }
+
+// void
+// test_state_descendant_confirmed_ancestor_duplicate( fd_wksp_t * wksp ){
+// ulong blk_max = 16;
+// ulong total_stake = 18;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+
+// /* 0 - 1 - 2 - 3 */
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+// fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost );
+
+// fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
+// fd_hash_t hash_1 = { .key = { 1 } };
+// fd_hash_t hash_2 = { .key = { 2 } };
+// fd_hash_t hash_3 = { .key = { 3 } };
+
+// fd_ghost_init( ghost, 0, &hash_0 );
+// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake );
+// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake );
+// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake );
+
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 );
+// fd_voter_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } };
+// fd_ghost_count_vote( ghost, &v1, &hash_3 );
+
+// FD_TEST( is_duplicate_confirmed( ghost, &hash_3, total_stake ) );
+
+// /* 3 becomes duplicate confirmed */
+// if( is_duplicate_confirmed( ghost, &hash_3, total_stake ) ) {
+// process_duplicate_confirmed( ghost, &hash_3, 3 );
+// }
+// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) );
+// for( ulong slot = 0; slot < 4; slot++ ) {
+// fd_ghost_blk_t const * ele = fd_ghost_query( ghost, fd_ghost_hash( ghost, slot ) );
+// FD_TEST( ele->valid );
+// FD_LOG_NOTICE(("slot %lu, ele->key: %s", slot, FD_BASE58_ENC_32_ALLOCA(&ele->key) ));
+// FD_TEST( is_duplicate_confirmed( ghost, &ele->key, total_stake ) );
+// }
+
+// process_duplicate( ghost, 1, total_stake );
+// FD_TEST( fd_dup_seen_map_query( dup_map, 1, NULL ) );
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 );
+
+// }
+
+// void
+// test_duplicate_after_frozen( fd_wksp_t * wksp ){
+// ulong blk_max = 16;
+// ulong total_stake = 10;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+
+// fd_hash_t hash_0 = { .ul = { ULONG_MAX } };
+// fd_hash_t hash_1 = { .key = { 1 } };
+// fd_ghost_init( ghost, 0, &hash_0 );
+// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake );
+// process_duplicate( ghost, 1, total_stake );
+
+// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 0 );
+// }
+
+// void
+// test_duplicate_blk_inserted( fd_wksp_t * wksp ) {
+// ulong blk_max = 16;
+// ulong total_stake = 10;
+// void * mem = fd_wksp_alloc_laddr( wksp,
+// fd_ghost_align(),
+// fd_ghost_footprint( blk_max ),
+// 1UL );
+// FD_TEST( mem );
+
+// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) );
+// fd_hash_t hash0 = { .ul = { ULONG_MAX } };
+// fd_hash_t hash1 = { .key = { 1 } };
+
+// fd_ghost_init( ghost, 0, &hash0 );
+
+// ulong duplicate_slot = 1;
+
+// FD_TEST( fd_ghost_hash( ghost, duplicate_slot ) == NULL );
+
+// // Simulate finish replaying a bank by inserting the slot - equivalent to:
+// fd_ghost_update( ghost, &hash0, 1, &hash1, total_stake );
+
+// // Test equivalent to: assert_eq!(blockstore.get_bank_hash(duplicate_slot).unwrap(), duplicate_slot_hash);
+// fd_hash_t const * stored_hash = fd_ghost_hash( ghost, duplicate_slot );
+// FD_TEST( stored_hash != NULL );
+// FD_TEST( memcmp( stored_hash, &hash1, sizeof(fd_hash_t) ) == 0 );
+
+// // Now test freezing another version of the same bank - this creates a duplicate scenario
+// fd_hash_t new_bank_hash = { .key = { 2 } };
+
+// // In Ghost, inserting the same slot with a different hash creates a duplicate
+// fd_ghost_update( ghost, &hash0, duplicate_slot, &new_bank_hash, total_stake );
+
+// // The slot map should still point to the original version (the "happy tree")
+// // but the new hash should be tracked in the hash map
+// fd_hash_t const * slot_map_hash = fd_ghost_hash( ghost, duplicate_slot );
+// FD_TEST( slot_map_hash != NULL );
+// FD_TEST( memcmp( slot_map_hash, &hash1, sizeof(fd_hash_t) ) == 0 ); // Still original hash
+
+// // The new hash should be queryable directly
+// fd_ghost_blk_t const * new_hash_ele = fd_ghost_query( ghost, &new_bank_hash );
+// FD_TEST( new_hash_ele != NULL );
+// FD_TEST( new_hash_ele->slot == duplicate_slot );
+
+// // Clean up
+// fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) );
+// }
int
@@ -1112,29 +848,29 @@ main( int argc, char ** argv ) {
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 );
- test_duplicate_simple( wksp );
- test_many_duplicates( wksp );
- // test_ghost_print( wksp );
- test_ghost_simple( wksp );
- test_ghost_publish_left( wksp );
- test_ghost_publish_right( wksp );
- test_ghost_gca( wksp );
- test_ghost_vote_leaves( wksp );
- test_ghost_head_full_tree( wksp );
- test_ghost_head( wksp );
- test_rooted_vote( wksp );
- test_ghost_old_vote_pruned( wksp );
- test_ghost_head_valid( wksp );
-
- test_duplicate_after_frozen( wksp );
- test_duplicate_node_inserted( wksp );
-
- /* agave cluster_slot_state_verifier tests*/
- test_state_ancestor_confirmed_descendant_duplicate( wksp );
- run_test_state_duplicate_then_bank_frozen( wksp );
- test_state_ancestor_duplicate_descendant_confirmed( wksp );
- test_state_ancestor_duplicate_descendant_confirmed( wksp );
- test_state_descendant_confirmed_ancestor_duplicate( wksp );
+ // test_duplicate_simple( wksp );
+ // test_many_duplicates( wksp );
+ // // test_print( wksp );
+ // test_simple( wksp );
+ // test_publish_left( wksp );
+ // test_publish_right( wksp );
+ // test_gca( wksp );
+ // test_vote_leaves( wksp );
+ // test_best_full_tree( wksp );
+ // test_best( wksp );
+ // test_rooted_vote( wksp );
+ // test_old_vote_pruned( wksp );
+ // test_best_valid( wksp );
+
+ // test_duplicate_after_frozen( wksp );
+ // test_duplicate_blk_inserted( wksp );
+
+ // /* agave cluster_slot_state_verifier tests*/
+ // test_state_ancestor_confirmed_descendant_duplicate( wksp );
+ // run_test_state_duplicate_then_bank_frozen( wksp );
+ // test_state_ancestor_duplicate_descendant_confirmed( wksp );
+ // test_state_ancestor_duplicate_descendant_confirmed( wksp );
+ // test_state_descendant_confirmed_ancestor_duplicate( wksp );
fd_halt();
return 0;
diff --git a/src/choreo/notar/fd_notar.c b/src/choreo/notar/fd_notar.c
index efb0f7b5342..c5e7c5eb941 100644
--- a/src/choreo/notar/fd_notar.c
+++ b/src/choreo/notar/fd_notar.c
@@ -1,11 +1,9 @@
#include "fd_notar.h"
-
-#define PRO_CONF (1./3) /* propagation confirmed */
-#define DUP_CONF (0.52) /* duplicate confirmed */
-#define OPT_CONF (2./3) /* optimistically confirmed */
+#include "../../util/bits/fd_bits.h"
void *
-fd_notar_new( void * shmem, ulong blk_max ) {
+fd_notar_new( void * shmem,
+ ulong slot_max ) {
if( FD_UNLIKELY( !shmem ) ) {
FD_LOG_WARNING(( "NULL mem" ));
@@ -17,29 +15,33 @@ fd_notar_new( void * shmem, ulong blk_max ) {
return NULL;
}
- ulong footprint = fd_notar_footprint( blk_max );
+ ulong footprint = fd_notar_footprint( slot_max );
if( FD_UNLIKELY( !footprint ) ) {
- FD_LOG_WARNING(( "bad ele_max (%lu)", blk_max ));
- return NULL;
- }
-
- fd_wksp_t * wksp = fd_wksp_containing( shmem );
- if( FD_UNLIKELY( !wksp ) ) {
- FD_LOG_WARNING(( "shmem must be part of a workspace" ));
+ FD_LOG_WARNING(( "bad slot_max (%lu)", slot_max ));
return NULL;
}
fd_memset( shmem, 0, footprint );
- int lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( blk_max ) );
+ int lg_slot_max = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max ) ) + 1;
+ int lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max * FD_VOTER_MAX ) ) + 1;
+ int lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( FD_VOTER_MAX ) ) + 1;
+
FD_SCRATCH_ALLOC_INIT( l, shmem );
- fd_notar_t * notar = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_align(), sizeof(fd_notar_t) );
- void * blks = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_blk_align(), fd_notar_blk_footprint( lg_blk_max ) );
- void * vtrs = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_vtr_align(), fd_notar_vtr_footprint( lg_blk_max ) );
+ fd_notar_t * notar = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_align(), sizeof(fd_notar_t) );
+ void * slot_map = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_slot_align(), fd_notar_slot_footprint( lg_slot_max ) );
+ void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_blk_align(), fd_notar_blk_footprint( lg_blk_max ) );
+ void * vtr_map = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_vtr_align(), fd_notar_vtr_footprint( lg_vtr_max ) );
FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_notar_align() ) == (ulong)shmem + footprint );
- notar->blks = fd_notar_blk_new( blks, lg_blk_max );
- notar->vtrs = fd_notar_vtr_new( vtrs, lg_blk_max );
+ notar->slot_max = slot_max;
+ notar->slot_map = fd_notar_slot_new( slot_map, lg_slot_max );
+ notar->blk_map = fd_notar_blk_new( blk_map, lg_blk_max );
+ notar->vtr_map = fd_notar_vtr_new( vtr_map, lg_vtr_max );
+
+ notar->epoch = ULONG_MAX;
+ notar->lo_wmark = ULONG_MAX;
+ notar->hi_wmark = ULONG_MAX;
return shmem;
}
@@ -58,11 +60,9 @@ fd_notar_join( void * shnotar ) {
return NULL;
}
- fd_wksp_t * wksp = fd_wksp_containing( notar );
- if( FD_UNLIKELY( !wksp ) ) {
- FD_LOG_WARNING(( "notar must be part of a workspace" ));
- return NULL;
- }
+ notar->slot_map = fd_notar_slot_join( notar->slot_map );
+ notar->blk_map = fd_notar_blk_join( notar->blk_map );
+ notar->vtr_map = fd_notar_vtr_join( notar->vtr_map );
return notar;
}
@@ -94,106 +94,103 @@ fd_notar_delete( void * notar ) {
return notar;
}
-void
-fd_notar_vote( fd_notar_t * notar,
- fd_pubkey_t const * pubkey,
- ulong stake,
- fd_tower_t const * vote_tower,
- fd_hash_t const * vote_hash ) {
-
- /* Return early if the pubkey is not part of the set of voters we know
- from this epoch. Because these votes can come from gossip (and
- therefore have not been successfully validated and executed by the
- vote program), it's possible the vote itself is just invalid. */
-
- fd_notar_vtr_t const * vtr = fd_notar_vtr_query( notar->vtrs, *pubkey, NULL );
- if( FD_UNLIKELY( !vtr ) ) { FD_LOG_WARNING(( "unknown voter" )); return; };
-
- /* Return early if the tower is empty. As above, the vote could simply
- be invalid. */
-
- fd_tower_vote_t const * last_vote = fd_tower_votes_peek_tail_const( vote_tower );
- if( FD_UNLIKELY( !last_vote ) ) { FD_LOG_WARNING(( "empty tower" )); return; }
-
- /* It's possible we haven't yet replayed this slot being voted on.
- Even though votes can come from gossip (and therefore can be for
- slots ahead of what we've replayed), Agave verifies a vote's bank
- hash matches their own before counting it towards the confirmation
- thresholds.
-
- Therefore, only votes for slots we've already replayed (including
- gossip votes) can be counted.
-
- https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank_hash_cache.rs#L68-L71 */
-
- fd_notar_blk_t * last_blk = fd_notar_blk_query( notar->blks, last_vote->slot, NULL );
- if( FD_UNLIKELY( !last_blk ) ) { FD_LOG_WARNING(( "haven't replayed slot %lu", last_vote->slot )); return; };
-
- /* Agave does a weird thing where they always count the last vote slot
- in the tower towards confirmation, regardless of whether the bank
- hash matches. However, "intermediate slots" in the tower are not
- counted when the bank hash doesn't match.
-
- https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L476-L487 */
-
- if( FD_LIKELY( last_blk->vtrs[ vtr->bit ] ) ) return; /* already counted */
- fd_notar_blk_vtrs_insert( last_blk->vtrs, vtr->bit ); /* count the voter */
- last_blk->stake += stake;
-
- /* Don't count remaining votes in tower if bank hash doesn't match. */
-
- if( FD_UNLIKELY( memcmp( &last_blk->bank_hash, vote_hash, sizeof(fd_hash_t) ) ) ) return;
-
- /* The voter's bank hash matches our own so we expect that all the
- slots in their tower towards the propagation threshold for that slot. */
-
- int skip = 1;
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( vote_tower );
- !fd_tower_votes_iter_done_rev( vote_tower, iter );
- iter = fd_tower_votes_iter_prev ( vote_tower, iter ) ) {
-
- if( FD_UNLIKELY( skip ) ) { skip = 0; continue; } /* skip the last vote (iter rev), we've already counted it */
-
- fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( vote_tower, iter );
- if( FD_UNLIKELY( !vote ) ) continue;
- if( FD_UNLIKELY( vote->slot < notar->root ) ) continue;
-
- /* By definition every vote slot in the tower is an ancestor of the
- next vote slot. */
-
- fd_notar_blk_t * vote_blk = fd_notar_blk_query( notar->blks, vote->slot, NULL );
-
- /* Check this tower vote slot is in notar. If it's not, then the
- vote txn is invalid. We silently skip the vote the same way Agave
- does.
-
- https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L513-L518 */
-
- if( FD_UNLIKELY( !vote_blk ) ) continue;
-
- /* Check if we've already counted this voter's stake. */
-
- if( FD_LIKELY( vote_blk->vtrs[ vtr->bit ] ) ) continue;
-
- /* Count this voter's stake towards the confirmation thresholds. */
-
- fd_notar_blk_vtrs_insert( vote_blk->vtrs, vtr->bit );
- vote_blk->stake += stake;
+fd_notar_blk_t *
+fd_notar_count_vote( fd_notar_t * notar,
+ ulong total_stake,
+ fd_pubkey_t const * addr,
+ ulong vote_slot,
+ fd_hash_t const * vote_block_id ) {
+
+ if( FD_UNLIKELY( !notar ) ) { FD_LOG_WARNING(( "NULL notar" )); return NULL; }
+
+ /* Ignore if this vote slot isn't in range. */
+
+ if( FD_UNLIKELY( vote_slot < notar->lo_wmark || vote_slot > notar->hi_wmark ) ) return NULL;
+
+ /* Ignore if this vote account isn't in the voter set. */
+
+ fd_notar_vtr_t const * vtr = fd_notar_vtr_query( notar->vtr_map, *addr, NULL );
+ if( FD_UNLIKELY( !vtr ) ) return NULL;
+
+ /* Check we haven't already counted the voter's stake for this slot.
+ If a voter voted for multiple block ids for the same slot, we only
+ count their first one. Honest voters never vote more than once for
+ the same slot so the percentage of stake doing this should be small
+ per honest majority assumption. */
+
+ fd_notar_slot_t * notar_slot = fd_notar_slot_query( notar->slot_map, vote_slot, NULL );
+ if( FD_UNLIKELY( !notar_slot ) ) {
+ notar_slot = fd_notar_slot_insert( notar->slot_map, vote_slot );
+ notar_slot->parent_slot = ULONG_MAX;
+ notar_slot->prev_leader_slot = ULONG_MAX;
+ notar_slot->stake = 0;
+ notar_slot->is_leader = 0;
+ notar_slot->is_propagated = 0;
+ notar_slot->block_ids_cnt = 0;
+ fd_notar_slot_vtrs_null( notar_slot->prev_vtrs );
+ fd_notar_slot_vtrs_null( notar_slot->vtrs );
+ }
+ if( FD_LIKELY( fd_notar_slot_vtrs_test( notar_slot->vtrs, vtr->bit ) ) ) return NULL;
+ fd_notar_slot_vtrs_insert( notar_slot->vtrs, vtr->bit );
+ notar_slot->stake += vtr->stake;
+
+ /* Get the actual block with the block_id. */
+
+ fd_notar_blk_t * notar_blk = fd_notar_blk_query( notar->blk_map, *vote_block_id, NULL );
+ if( FD_UNLIKELY( !notar_blk ) ) {
+ notar_blk = fd_notar_blk_insert( notar->blk_map, *vote_block_id );
+ notar_blk->slot = vote_slot;
+ notar_blk->stake = 0;
+ FD_TEST( notar_slot->block_ids_cnt < FD_VOTER_MAX ); /* at most one unique block id per voter in a slot */
+ notar_slot->block_ids[notar_slot->block_ids_cnt++] = *vote_block_id;
+ }
+ notar_blk->stake += vtr->stake;
+ notar_blk->dup_conf = ((double)notar_blk->stake / (double)total_stake) > 0.52;
+ notar_blk->opt_conf = ((double)notar_blk->stake / (double)total_stake) > (2.0/3.0);
+ return notar_blk;
+}
- /* If a slot reaches propagation conf, then it's implied its
- ancestors are confirmed too because they're on the same fork. */
+void
+fd_notar_advance_epoch( fd_notar_t * notar,
+ fd_tower_accts_t * accts,
+ ulong epoch ) {
+ notar->epoch = epoch;
+ for( ulong i = 0; i < fd_notar_vtr_key_max( notar->vtr_map ); i++ ) {
+ fd_notar_vtr_t * vtr = ¬ar->vtr_map[i];
+ if( fd_notar_vtr_key_inval( vtr->addr ) ) continue;
+ vtr->prev_stake = vtr->stake;
+ vtr->stake = 0;
+ vtr->prev_bit = vtr->bit;
+ vtr->bit = ULONG_MAX;
+ }
- double r = (double)vote_blk->stake / (double)notar->stake;
- if( FD_UNLIKELY( !vote_blk->pro_conf && r >= PRO_CONF ) ) for( fd_notar_blk_t * anc_blk = vote_blk; FD_LIKELY( anc_blk ); anc_blk = fd_notar_blk_query( notar->blks, anc_blk->parent_slot, NULL ) ) anc_blk->pro_conf = 1;
+ ulong vtr_bit = 0;
+ for( fd_tower_accts_iter_t iter = fd_tower_accts_iter_init( accts );
+ !fd_tower_accts_iter_done( accts, iter );
+ iter = fd_tower_accts_iter_next( accts, iter ) ) {
+ fd_tower_accts_t const * acct = fd_tower_accts_iter_ele( accts, iter );
+ fd_notar_vtr_t * vtr = fd_notar_vtr_query( notar->vtr_map, acct->addr, NULL );
+ if( FD_UNLIKELY( !vtr ) ) vtr = fd_notar_vtr_insert( notar->vtr_map, acct->addr );
+ vtr->stake = acct->stake;
+ vtr->bit = vtr_bit++;
}
}
void
-fd_notar_publish( fd_notar_t * notar,
- ulong root ) {
- for( ulong slot = notar->root; slot < root; slot++ ) {
- fd_notar_blk_t * blk = fd_notar_blk_query( notar->blks, slot, NULL );
- if( FD_LIKELY( blk ) ) fd_notar_blk_remove( notar->blks, blk );
+fd_notar_advance_wmark( fd_notar_t * notar,
+ ulong wmark ) {
+ for(ulong slot = notar->lo_wmark; slot < wmark; slot++ ) {
+ fd_notar_slot_t * notar_slot = fd_notar_slot_query( notar->slot_map, slot, NULL );
+ if( FD_LIKELY( notar_slot ) ) {
+ for( ulong i=0; iblock_ids_cnt; i++ ) {
+ fd_hash_t const * block_id = ¬ar_slot->block_ids[i];
+ fd_notar_blk_t * notar_blk = fd_notar_blk_query( notar->blk_map, *block_id, NULL );
+ if( FD_UNLIKELY( !notar_blk ) ) FD_LOG_CRIT(( "missing %lu %s %lu %lu", slot, FD_BASE58_ENC_32_ALLOCA( block_id ), i, notar_slot->block_ids_cnt ));
+ fd_notar_blk_remove( notar->blk_map, notar_blk );
+ }
+ fd_notar_slot_remove( notar->slot_map, notar_slot );
+ }
}
- notar->root = root;
+ notar->lo_wmark = wmark;
+ notar->hi_wmark = wmark + notar->slot_max;
}
diff --git a/src/choreo/notar/fd_notar.h b/src/choreo/notar/fd_notar.h
index 61013b87d5b..191f84b0678 100644
--- a/src/choreo/notar/fd_notar.h
+++ b/src/choreo/notar/fd_notar.h
@@ -1,100 +1,181 @@
#ifndef HEADER_fd_src_choreo_notar_fd_notar_h
#define HEADER_fd_src_choreo_notar_fd_notar_h
-#include "../fd_choreo_base.h"
-#include "../tower/fd_tower.h"
-
-/* fd_notar ("notarization") is an API for tracking when blocks reach
- key stake thresholds from votes. Solana calls them "confirmation
- levels", and they are as follows:
+/* fd_notar ("notarized") processes vote transactions from both TPU and
+ gossip and tracks when blocks become confirmed. There are three key
+ confirmation thresholds in Solana:
- propagation confirmed: a block is propagated if it has received
votes from at least 1/3 of stake in the cluster. This threshold is
- important for the leader pipeline, which ensures a previous leader
- block has propagated before producing the next one. It is also
- used when voting, as we do not vote for forks in which our last
- leader block failed to propagate.
+ important in two contexts:
+
+ 1. When becoming leader, we need to check that our "previous"
+ leader block _as of_ the parent slot we're building on, has
+ propagated. If it's not propagated, we need to instead
+ retransmit our last block that failed to propagate. "Previous"
+ is quoted, because there is a grace period of one leader
+ rotation for leader blocks to propagate.
+
+ 2. When voting, we need to check our previous leader block _as of_
+ the slot we're voting for has propagated (unless we're voting
+ for one of our leader blocks). We cannot vote for slots in
+ which our last leader block failed to propagate.
- duplicate confirmed: a block is duplicate confirmed if it has
received votes from at least 52% of stake in the cluster. The
"duplicate" adjective is a bit of a misnomer, and a more accurate
technical term is equivocation: two (or more) different blocks for
the same slot. This threshold is important for consensus safety,
- because it ensures Solana eventually converges to a single block
- per slot.
+ because it ensures Solana eventually converges to the same block
+ per slot. Specifically fork choice allows choosing a fork if it is
+ duplicate confirmed, even if there is equivocation.
- - optimistically confirmed: a block is optimistically confirmed it
+ - optimistically confirmed: a block is optimistically confirmed if it
has received votes from at least 2/3 of stake in the cluster. This
threshold is important for end-users, who rely on the "confirmed"
commitment status of blocks (queryable via RPC) to determine that
- their transaction has landed on a block that will not rollback. */
-
-/* TODO duplicate confirmed / optimistc confirmed currently not
- implemented through this API */
+ their transaction has landed on a block that will not rollback.
+ This is unimplemented in Firedancer and only relevant for RPC.
+ (TODO verify this?)
+
+ Unlike duplicate and optimistic confirmation, propagation is at the
+ slot-level rather than block-level. So two votes for different block
+ ids would count towards the same slot. This mirrors Agave behavior.
+
+ On the similarities and differences between fd_ghost vs fd_notar:
+
+ The reason both fd_ghost and fd_notar exist even though they do
+ seemingly similar things (tracking vote stake on blocks) is because
+ Solana implements the rules quite differently.
+
+ In fd_ghost, we use the GHOST rule to recursively sum the stake of
+ the subtree (a slot and all its descendants). The LMD rule counts a
+ validator's stake to at most one fork. When the validator switches
+ forks, their stake is subtracted from the old fork and added to the
+ new fork. The tree is then traversed as part of fork choice to find
+ the best leaf ("head"). ghost bases fork choice purely on replay
+ votes, but marks forks valid or invalid with gossip votes.
+
+ In fd_notar, we count votes towards only the block itself, and not
+ its ancestors. Also a validator's stake can be counted towards
+ multiple forks at the same time if they vote on a fork then switch to
+ a different one, unlike ghost. notar uses both replay and gossip
+ votes when counting stake.
+
+ A note on slots and block ids: vote transactions only contain the
+ block_id of the last vote slot (and do not specify what block_ids
+ previous vote slots correspond to. Agave assumes if the hash of the
+ last vote slot matches, all the previous slots in the tower match as
+ well. Agave uses bank hashes instead of block_ids (the relevant code
+ predates block_ids) and maps slots to bank hashes during replay.
+
+ As a result, there can be multiple block ids for a given slot. notar
+ tracks the block_id for each slot using fd_tower_forks, and also
+ "duplicate confirmation". If notar observes a duplicate confirmation
+ for a different block_id than the one it has for a given slot, it
+ updates the block_id for that slot to the duplicate confirmed one. */
+
+/* FD_NOTAR_PARANOID: Define this to non-zero at compile time to turn
+ on additional runtime integrity checks. */
-/* FD_NOTAR_PARANOID: Define this to non-zero at compile time
- to turn on additional runtime integrity checks. */
+#include "../fd_choreo_base.h"
+#include "../tower/fd_tower_accts.h"
#ifndef FD_NOTAR_PARANOID
#define FD_NOTAR_PARANOID 1
#endif
-struct fd_notar_vtr {
- fd_pubkey_t pubkey; /* map key */
- uint memo; /* reserved for fd_map_dynamic */
- ulong bit; /* bit position in fd_notar_blk_vtrs (fd_set) */
- ulong vote; /* the most recent slot the validator voted for */
- fd_hash_t hash; /* the most recent hash the validator voted for */
+#define FD_NOTAR_FLAG_CONFIRMED_PROPAGATED (0)
+#define FD_NOTAR_FLAG_CONFIRMED_DUPLICATE (1)
+#define FD_NOTAR_FLAG_CONFIRMED_OPTIMISTIC (2)
+
+#define SET_NAME fd_notar_slot_vtrs
+#define SET_MAX FD_VOTER_MAX
+#include "../../util/tmpl/fd_set.c"
+
+struct fd_notar_slot {
+ ulong slot; /* map key, vote slot */
+ ulong parent_slot; /* parent slot */
+ ulong prev_leader_slot; /* previous slot in which we were leader */
+ ulong stake; /* amount of stake that has voted for this slot */
+ int is_leader; /* whether this slot was our own leader slot */
+ int is_propagated; /* whether this slot has reached 1/3 of stake */
+
+ fd_hash_t block_ids[FD_VOTER_MAX]; /* one block id per voter per slot */
+ ulong block_ids_cnt; /* count of block ids */
+
+ fd_notar_slot_vtrs_t prev_vtrs[fd_notar_slot_vtrs_word_cnt]; /* who has voted for this slot, prev epoch */
+ fd_notar_slot_vtrs_t vtrs [fd_notar_slot_vtrs_word_cnt]; /* who has voted for this slot, curr epoch */
};
-typedef struct fd_notar_vtr fd_notar_vtr_t;
+typedef struct fd_notar_slot fd_notar_slot_t;
-static const fd_pubkey_t pubkey_null = {{ 0 }};
+struct fd_notar_blk {
+ fd_hash_t block_id; /* map key */
+ uint hash; /* reserved for fd_map_dynamic */
+ ulong slot; /* slot associated with this block */
+ ulong stake; /* sum of stake that has voted for this block_id */
+ int dup_conf; /* whether this block has reached 52% of stake */
+ int opt_conf; /* whether this block has reached 2/3 of stake */
+};
+typedef struct fd_notar_blk fd_notar_blk_t;
-#define MAP_NAME fd_notar_vtr
-#define MAP_T fd_notar_vtr_t
-#define MAP_HASH memo
-#define MAP_KEY pubkey
-#define MAP_KEY_T fd_pubkey_t
-#define MAP_KEY_NULL pubkey_null
+static const fd_hash_t hash_null = {{ 0 }};
+
+#define MAP_NAME fd_notar_blk
+#define MAP_T fd_notar_blk_t
+#define MAP_KEY block_id
+#define MAP_KEY_T fd_hash_t
+#define MAP_KEY_NULL hash_null
#define MAP_KEY_EQUAL_IS_SLOW 1
#define MAP_KEY_INVAL(k) MAP_KEY_EQUAL((k),MAP_KEY_NULL)
#define MAP_KEY_EQUAL(k0,k1) (!memcmp( (k0).key, (k1).key, 32UL ))
#define MAP_KEY_HASH(key) ((MAP_HASH_T)( (key).ul[1] ))
#include "../../util/tmpl/fd_map_dynamic.c"
-#define SET_NAME fd_notar_blk_vtrs
-#define SET_MAX FD_VOTER_MAX
-#include "../../util/tmpl/fd_set.c"
-
-struct fd_notar_blk {
- ulong slot;
- ulong parent_slot;
- fd_hash_t bank_hash;
- ulong stake;
- int pro_conf;
- int dup_conf; /* TODO unimplemented */
- int opt_conf; /* TODO unimplemented */
- fd_notar_blk_vtrs_t vtrs[fd_notar_blk_vtrs_word_cnt]; /* pubkeys (validator identity) that have voted for this block */
-};
-typedef struct fd_notar_blk fd_notar_blk_t;
+/* TODO map key DOS */
-#define MAP_NAME fd_notar_blk
-#define MAP_T fd_notar_blk_t
+#define MAP_NAME fd_notar_slot
+#define MAP_T fd_notar_slot_t
#define MAP_KEY slot
#define MAP_MEMOIZE 0
#include "../../util/tmpl/fd_map_dynamic.c"
+struct fd_notar_vtr {
+ fd_pubkey_t addr; /* map key, vote account address */
+ uint hash; /* reserved for fd_map_dynamic */
+ ulong prev_stake; /* amount of stake this voter has in epoch - 1 */
+ ulong stake; /* amount of stake this voter has in epoch */
+ ulong prev_bit; /* bit position in fd_notar_slot_vtrs in epoch - 1 (ULONG_MAX if not set) */
+ ulong bit; /* bit position in fd_notar_slot_vtrs in epoch (ULONG_MAX if not set) */
+};
+typedef struct fd_notar_vtr fd_notar_vtr_t;
+
+#define MAP_NAME fd_notar_vtr
+#define MAP_T fd_notar_vtr_t
+#define MAP_KEY addr
+#define MAP_KEY_T fd_pubkey_t
+#define MAP_KEY_NULL pubkey_null
+#define MAP_KEY_EQUAL_IS_SLOW 1
+#define MAP_KEY_INVAL(k) MAP_KEY_EQUAL((k),MAP_KEY_NULL)
+#define MAP_KEY_EQUAL(k0,k1) (!memcmp( (k0).key, (k1).key, 32UL ))
+#define MAP_KEY_HASH(key) ((MAP_HASH_T)( (key).ul[1] ))
+#include "../../util/tmpl/fd_map_dynamic.c"
+
struct __attribute__((aligned(128UL))) fd_notar {
- fd_notar_blk_t * blks;
- fd_notar_vtr_t * vtrs;
- ulong root; /* current root slot */
- ulong stake; /* total stake in the current epoch */
+ ulong epoch; /* highest replayed epoch */
+ ulong lo_wmark; /* notar ignores votes < lo_wmark */
+ ulong hi_wmark; /* notar ignores votes > hi_wmark */
+ ulong slot_max; /* maximum number of slots notar can track */
+
+ fd_notar_slot_t * slot_map; /* tracks who has voted for a given slot */
+ fd_notar_blk_t * blk_map; /* tracks amount of stake for a given block (keyed by block id) */
+ fd_notar_vtr_t * vtr_map; /* tracks each voter's stake and prev vote */
};
typedef struct fd_notar fd_notar_t;
/* fd_notar_{align,footprint} return the required alignment and
footprint of a memory region suitable for use as a notar. align
- returns FD_NOTAR_ALIGN. footprint returns FD_NOTAR_FOOTPRINT. */
+ returns fd_notar_ALIGN. footprint returns fd_notar_FOOTPRINT. */
FD_FN_CONST static inline ulong
fd_notar_align( void ) {
@@ -102,17 +183,20 @@ fd_notar_align( void ) {
}
FD_FN_CONST static inline ulong
-fd_notar_footprint( ulong blk_max ) {
- int lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( blk_max ) );
- int lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( FD_VOTER_MAX ) );
+fd_notar_footprint( ulong slot_max ) {
+ int lg_slot_max = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max ) ) + 1;
+ int lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max * FD_VOTER_MAX ) ) + 1;
+ int lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( FD_VOTER_MAX ) ) + 1;
return FD_LAYOUT_FINI(
FD_LAYOUT_APPEND(
FD_LAYOUT_APPEND(
FD_LAYOUT_APPEND(
+ FD_LAYOUT_APPEND(
FD_LAYOUT_INIT,
- alignof(fd_notar_t), sizeof(fd_notar_t) ),
- fd_notar_blk_align(), fd_notar_blk_footprint( lg_blk_max ) ),
- fd_notar_vtr_align(), fd_notar_vtr_footprint( lg_vtr_max ) ),
+ alignof(fd_notar_t), sizeof(fd_notar_t) ),
+ fd_notar_slot_align(), fd_notar_slot_footprint( lg_slot_max ) ),
+ fd_notar_blk_align(), fd_notar_blk_footprint( lg_blk_max ) ),
+ fd_notar_vtr_align(), fd_notar_vtr_footprint( lg_vtr_max ) ),
fd_notar_align() );
}
@@ -121,7 +205,8 @@ fd_notar_footprint( ulong blk_max ) {
the required footprint and alignment. */
void *
-fd_notar_new( void * mem, ulong blk_max );
+fd_notar_new( void * mem,
+ ulong slot_max );
/* fd_notar_join joins the caller to the notar. notar points to the
first byte of the memory region backing the notar in the caller's
@@ -148,19 +233,25 @@ fd_notar_leave( fd_notar_t const * notar );
void *
fd_notar_delete( void * notar );
-/* fd_notar_vote updates notar with a "vote" which is a 4-tuple of
- (pubkey, stake, tower, hash). pubkey is the voter's validator
- identity, stake is the voter's stake in the current epoch, vote_tower
- is the parsed tower from either the gossip vote transaction or replay
- vote state, and vote_hash is the voter's bank hash for the last slot
- in the tower. */
+/* fd_notar_count_vote counts addr's stake towards the voted slots in
+ their tower. Returns 1 if block_id is duplicate confirmed by this
+ vote, otherwise 0 (useful for the downstream tower tile to implement
+ duplicate confirmation notifications). addr is the vote account
+ address, stake is the amount of stake associated with the vote
+ account in the current epoch, slot is slot being voted for, block_id
+ is the voter's proposed block id for this vote slot. */
+
+fd_notar_blk_t *
+fd_notar_count_vote( fd_notar_t * notar,
+ ulong total_stake,
+ fd_pubkey_t const * addr,
+ ulong slot,
+ fd_hash_t const * block_id );
void
-fd_notar_vote( fd_notar_t * notar,
- fd_pubkey_t const * pubkey,
- ulong stake,
- fd_tower_t const * vote_tower,
- fd_hash_t const * vote_hash );
+fd_notar_advance_epoch( fd_notar_t * notar,
+ fd_tower_accts_t * accts,
+ ulong epoch );
/* fd_notar_publish publishes root as the new notar root slot, removing
all blocks with slot numbers < the old notar root slot. Some slots
@@ -168,7 +259,7 @@ fd_notar_vote( fd_notar_t * notar,
but they will eventually be pruned as well as the root advances. */
void
-fd_notar_publish( fd_notar_t * notar,
- ulong root );
+fd_notar_advance_wmark( fd_notar_t * notar,
+ ulong root );
#endif /* HEADER_fd_src_choreo_notar_fd_notar_h */
diff --git a/src/choreo/notar/test_notar.c b/src/choreo/notar/test_notar.c
index b696117193f..0a631ff6191 100644
--- a/src/choreo/notar/test_notar.c
+++ b/src/choreo/notar/test_notar.c
@@ -2,38 +2,41 @@
void
test_notar_simple( fd_wksp_t * wksp ) {
- void * mem;
- ulong blk_max = 8;
-
- mem = fd_wksp_alloc_laddr( wksp, fd_notar_align(), fd_notar_footprint( blk_max ), 1UL );
- FD_TEST( mem );
- fd_notar_t * notar = fd_notar_join( fd_notar_new( mem, blk_max ) );
-
- ulong slot = 368778153;
-
- fd_notar_blk_t * blk = fd_notar_blk_insert( notar->blks, slot );
- blk->parent_slot = slot - 1;
- memset( &blk->bank_hash, 0, sizeof(fd_hash_t) );
- blk->bank_hash.ul[0] = slot;
- blk->stake = 0;
-
- fd_pubkey_t pubkeys[4] = { { .key = { 1 } },
- { .key = { 2 } },
- { .key = { 3 } },
- { .key = { 4 } } };
- for( ulong i = 0; i < sizeof(pubkeys) / sizeof(fd_pubkey_t); i++ ) {
- fd_notar_vtr_t * vtr = fd_notar_vtr_insert( notar->vtrs, pubkeys[i] );
- vtr->bit = i;
- }
-
- ulong stakes[4] = { 1, 2, 3, 4 };
-
- mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 1UL );
- FD_TEST( mem );
- fd_tower_t * tower = fd_tower_join( fd_tower_new( mem ) );
- fd_tower_vote( tower, 368778153 );
-
- fd_notar_vote( notar, &pubkeys[3], stakes[3], tower, NULL ); /* first valid vote */
+ ulong slot_max = 8;
+ void * mem = fd_wksp_alloc_laddr( wksp, fd_notar_align(), fd_notar_footprint( slot_max ), 1UL );
+ fd_notar_t * notar = fd_notar_join( fd_notar_new( mem, slot_max ) );
+ FD_TEST( notar );
+
+ // fd_hash_t block_id = { .ul = { slot } };
+ // ulong slot = 368778153;
+ // fd_hash_t bank_hash = { .ul = { slot } };
+
+ // fd_notar_blk_t * blk = fd_notar_blk_insert( notar->blk, block_id );
+ // blk->parent_slot = slot - 1;
+ // blk->bank_hash = bank_hash;
+ // blk->block_id = block_id;
+ // blk->stake = 0;
+ // blk->pro_conf = 0;
+ // blk->dup_conf = 0;
+ // blk->opt_conf = 0;
+
+ // fd_pubkey_t pubkeys[4] = { { .key = { 1 } },
+ // { .key = { 2 } },
+ // { .key = { 3 } },
+ // { .key = { 4 } } };
+ // for( ulong i = 0; i < sizeof(pubkeys) / sizeof(fd_pubkey_t); i++ ) {
+ // fd_notar_vtr_t * vtr = fd_notar_vtr_insert( notar->vtr, pubkeys[i] );
+ // vtr->bit = i;
+ // }
+
+ // ulong stakes[4] = { 1, 2, 3, 4 };
+
+ // mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 1UL );
+ // FD_TEST( mem );
+ // fd_tower_t * tower = fd_tower_join( fd_tower_new( mem ) );
+ // fd_tower_vote( tower, 368778153 );
+
+ // fd_notar_vote( notar, &pubkeys[3], tower, ); /* first valid vote */
fd_wksp_free_laddr( fd_notar_delete( fd_notar_leave( notar ) ) );
}
diff --git a/src/choreo/tower/Local.mk b/src/choreo/tower/Local.mk
index 17f2ebbaa1a..64fedbb361c 100644
--- a/src/choreo/tower/Local.mk
+++ b/src/choreo/tower/Local.mk
@@ -1,10 +1,14 @@
ifdef FD_HAS_INT128
ifdef FD_HAS_SECP256K1
-$(call add-hdrs,fd_tower.h)
+$(call add-hdrs,fd_tower.h fd_tower_accts.h fd_tower_forks.h fd_tower_serde.h)
$(call add-objs,fd_tower,fd_choreo)
+$(call add-objs,fd_tower_forks,fd_choreo)
+$(call add-objs,fd_tower_serde,fd_choreo)
ifdef FD_HAS_HOSTED
$(call make-unit-test,test_tower,test_tower,fd_choreo fd_flamenco fd_tango fd_ballet fd_util,$(SECP256K1_LIBS))
+$(call make-unit-test,test_tower_serde,test_tower_serde,fd_choreo fd_flamenco fd_tango fd_ballet fd_util,$(SECP256K1_LIBS))
$(call run-unit-test,test_tower)
+$(call run-unit-test,test_tower_serde)
endif
endif
endif
diff --git a/src/choreo/tower/fd_tower.c b/src/choreo/tower/fd_tower.c
index f796764ff17..936bf030098 100644
--- a/src/choreo/tower/fd_tower.c
+++ b/src/choreo/tower/fd_tower.c
@@ -1,887 +1,621 @@
+#include
#include
#include
#include
#include "fd_tower.h"
-#include "../../ballet/ed25519/fd_ed25519.h"
+#include "../voter/fd_voter.h"
#include "../../flamenco/txn/fd_txn_generate.h"
#include "../../flamenco/runtime/fd_system_ids.h"
-#define THRESHOLD_DEPTH (8)
-#define THRESHOLD_RATIO (2.0 / 3.0)
-#define SHALLOW_THRESHOLD_DEPTH (4)
-#define SHALLOW_THRESHOLD_RATIO (0.38)
-#define SWITCH_PCT (0.38)
-#define SERDE_KIND (1)
-#define SERDE_LAST_VOTE_KIND (3)
-
-void *
-fd_tower_new( void * shmem ) {
- if( FD_UNLIKELY( !shmem ) ) {
- FD_LOG_WARNING(( "NULL mem" ));
- return NULL;
- }
-
- if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) {
- FD_LOG_WARNING(( "misaligned mem" ));
- return NULL;
- }
+#define LOGGING 0
- return fd_tower_votes_new( shmem );
-}
-
-fd_tower_t *
-fd_tower_join( void * shtower ) {
-
- if( FD_UNLIKELY( !shtower ) ) {
- FD_LOG_WARNING(( "NULL tower" ));
- return NULL;
- }
+#define THRESHOLD_DEPTH (8)
+#define THRESHOLD_RATIO (2.0 / 3.0)
+#define SWITCH_RATIO (0.38)
- if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) {
- FD_LOG_WARNING(( "misaligned tower" ));
- return NULL;
- }
-
- return fd_tower_votes_join( shtower );
-}
-
-void *
-fd_tower_leave( fd_tower_t * tower ) {
-
- if( FD_UNLIKELY( !tower ) ) {
- FD_LOG_WARNING(( "NULL tower" ));
- return NULL;
- }
-
- return fd_tower_votes_leave( tower );
-}
-
-void *
-fd_tower_delete( void * tower ) {
-
- if( FD_UNLIKELY( !tower ) ) {
- FD_LOG_WARNING(( "NULL tower" ));
- return NULL;
- }
-
- if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) {
- FD_LOG_WARNING(( "misaligned tower" ));
- return NULL;
- }
-
- return fd_tower_votes_delete( tower );
-}
+/* expiration calculates the expiration slot of vote given a slot and
+ confirmation count. */
static inline ulong
-expiration( fd_tower_vote_t const * vote ) {
+expiration_slot( fd_tower_t const * vote ) {
ulong lockout = 1UL << vote->conf;
return vote->slot + lockout;
}
-static inline ulong
-simulate_vote( fd_tower_t const * tower, ulong slot ) {
- ulong cnt = fd_tower_votes_cnt( tower );
- while( cnt ) {
-
- /* Return early if we can't pop the top tower vote, even if votes
- below it are expired. */
+/* simulate_vote simulates voting for slot, popping all votes from the
+ top that would be consecutively expired by voting for slot. */
- if( FD_LIKELY( expiration( fd_tower_votes_peek_index_const( tower, cnt - 1 ) ) >= slot ) ) {
- break;
- }
+ulong
+simulate_vote( fd_tower_t const * tower,
+ ulong slot ) {
+ ulong cnt = fd_tower_cnt( tower );
+ while( cnt ) {
+ fd_tower_t const * top_vote = fd_tower_peek_index_const( tower, cnt - 1 );
+ if( FD_LIKELY( expiration_slot( top_vote ) >= slot ) ) break; /* expire only if consecutive */
cnt--;
}
return cnt;
}
-int
-fd_tower_lockout_check( fd_tower_t const * tower,
- fd_ghost_t const * ghost,
- ulong slot,
- fd_hash_t const * block_id ) {
-# if FD_TOWER_PARANOID
- FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
-# endif
-
- /* Simulate a vote to pop off all the votes that have been expired at
- the top of the tower. */
-
- ulong cnt = simulate_vote( tower, slot );
-
- /* By definition, all votes in the tower must be for the same fork, so
- check if the previous vote (ie. the last vote in the tower) is on
- the same fork as the fork we want to vote for. We do this using
- ghost by checking if the previous vote slot is an ancestor of the
- `slot`. If the previous vote slot is too old (ie. older than
- ghost->root), then we don't have ancestry information anymore and
- we just assume it is on the same fork.
+/* push_vote pushes a new vote for slot onto the tower. Pops and
+ returns the new root (bottom of the tower) if it reaches max lockout
+ as a result of the new vote. Otherwise, returns ULONG_MAX.
- FIXME discuss if it is safe to assume that? */
+ Max lockout is equivalent to 1 << FD_TOWER_VOTE_MAX + 1 (which
+ implies confirmation count is FD_TOWER_VOTE_MAX + 1). As a result,
+ fd_tower_vote also maintains the invariant that the tower contains at
+ most FD_TOWER_VOTE_MAX votes, because (in addition to vote expiry)
+ there will always be a pop before reaching FD_TOWER_VOTE_MAX + 1. */
- fd_tower_vote_t const * vote = fd_tower_votes_peek_index_const( tower, cnt - 1 );
- fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
+ulong
+push_vote( fd_tower_t * tower,
+ ulong slot ) {
- int lockout_check = (slot > vote->slot) && (vote->slot < root->slot || fd_ghost_is_ancestor( ghost, fd_ghost_hash( ghost, vote->slot ), block_id ));
-# if LOGGING
- FD_LOG_NOTICE(( "[%s] %d. top: (slot: %lu, conf: %lu). switch: %lu.", __func__, lockout_check, vote->slot, vote->conf, slot ));
+# if FD_TOWER_PARANOID
+ fd_tower_t const * vote = fd_tower_peek_tail_const( tower );
+ if( FD_UNLIKELY( vote && slot <= vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu <= vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
# endif
- return lockout_check;
-}
-
-int
-fd_tower_switch_check( fd_tower_t const * tower,
- fd_epoch_t const * epoch,
- fd_ghost_t const * ghost,
- ulong slot,
- fd_hash_t const * block_id ) {
- #if FD_TOWER_PARANOID
- FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
- #endif
-
- fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
- fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
- if( FD_UNLIKELY( vote->slot < root->slot ) ) {
+ /* Use simulate_vote to determine how many expired votes to pop. */
- /* It is possible our last vote slot precedes our ghost root. This
- can happen, for example, when we restart from a snapshot and set
- the ghost root to the snapshot slot (we won't have an ancestry
- before the snapshot slot.)
+ ulong cnt = simulate_vote( tower, slot );
- If this is the case, we assume it's ok to switch. */
+ /* Pop everything that got expired. */
- return 1;
+ while( FD_LIKELY( fd_tower_cnt( tower ) > cnt ) ) {
+ fd_tower_pop_tail( tower );
}
- /* fd_tower_switch_check is only called if latest_vote->slot and
- fork->slot are on different forks (determined by is_descendant), so
- they must not fall on the same ancestry path back to the gca.
-
- INVALID:
-
- 0
- \
- 1 <- a
- \
- 2 <- b
-
- VALID:
-
- 0
- / \
- 1 2
- ^ ^
- a b
-
- */
+ /* If the tower is still full after expiring, then pop and return the
+ bottom vote slot as the new root because this vote has incremented
+ it to max lockout. Otherwise this is a no-op and there is no new
+ root (FD_SLOT_NULL). */
-# if FD_TOWER_PARANOID
- FD_TEST( !fd_ghost_is_ancestor( ghost, fd_ghost_hash( ghost, vote->slot ), block_id ) );
-# endif
- fd_hash_t const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
- fd_ghost_hash_map_t const * maph = fd_ghost_hash_map_const( ghost );
- fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
- fd_ghost_ele_t const * gca = fd_ghost_gca( ghost, vote_block_id, block_id );
- ulong gca_idx = fd_ghost_hash_map_idx_query_const( maph, &gca->key, ULONG_MAX, pool );
-
- /* gca_child is our latest_vote slot's ancestor that is also a direct
- child of GCA. So we do not count it towards the stake of the
- different forks. */
-
- fd_ghost_ele_t const * gca_child = fd_ghost_query_const( ghost, vote_block_id );
- while( FD_LIKELY( gca_child->parent != gca_idx ) ) {
- gca_child = fd_ghost_pool_ele_const( pool, gca_child->parent );
+ ulong root = FD_SLOT_NULL;
+ if( FD_LIKELY( fd_tower_full( tower ) ) ) { /* optimize for full tower */
+ root = fd_tower_pop_head( tower ).slot;
}
- ulong switch_stake = 0;
- fd_ghost_ele_t const * child = fd_ghost_child_const( ghost, gca );
- while( FD_LIKELY( child ) ) {
- if( FD_LIKELY( child != gca_child ) ) {
- switch_stake += child->weight;
- }
- child = fd_ghost_pool_ele_const( pool, child->sibling );
+ /* Increment confirmations (double lockouts) for consecutive
+ confirmations in prior votes. */
+
+ ulong prev_conf = 0;
+ for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower );
+ !fd_tower_iter_done_rev( tower, iter );
+ iter = fd_tower_iter_prev ( tower, iter ) ) {
+ fd_tower_t * vote = fd_tower_iter_ele( tower, iter );
+ if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break;
+ vote->conf++;
}
- double switch_pct = (double)switch_stake / (double)epoch->total_stake;
- FD_LOG_DEBUG(( "[%s] ok? %d. top: %lu. switch: %lu. switch stake: %.0lf%%.", __func__, switch_pct > SWITCH_PCT, fd_tower_votes_peek_tail_const( tower )->slot, slot, switch_pct * 100.0 ));
- return switch_pct > SWITCH_PCT;
-}
+ /* Add the new vote to the tower. */
-int
-fd_tower_threshold_check( fd_tower_t const * tower,
- fd_epoch_t * epoch,
- fd_pubkey_t * vote_keys,
- fd_tower_t * const * vote_towers,
- ulong vote_cnt,
- ulong slot ) {
+ fd_tower_push_tail( tower, (fd_tower_t){ .slot = slot, .conf = 1 } );
- /* First, simulate a vote, popping off everything that would be
- expired by voting for the current slot. */
+ /* Return the new root (FD_SLOT_NULL if there is none). */
- ulong cnt = simulate_vote( tower, slot );
+ return root;
+}
- /* Return early if our tower is not at least THRESHOLD_DEPTH deep
- after simulating. */
+/* lockout_check checks if we are locked out from voting for slot.
+ Returns 1 if we can vote for slot without violating lockout, 0
+ otherwise.
- if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
+ After voting for a slot n, we are locked out for 2^k slots, where k
+ is the confirmation count of that vote. Once locked out, we cannot
+ vote for a different fork until that previously-voted fork expires at
+ slot n+2^k. This implies the earliest slot in which we can switch
+ from the previously-voted fork is (n+2^k)+1. We use `ghost` to
+ determine whether `slot` is on the same or different fork as previous
+ vote slots.
- /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
- is the 8th index back _including_ the simulated vote at index 0,
- which is not accounted for by `cnt`, so subtracting THRESHOLD_DEPTH
- will conveniently index the threshold vote. */
+ In the case of the tower, every vote has its own expiration slot
+ depending on confirmations. The confirmation count is the max number
+ of consecutive votes that have been pushed on top of the vote, and
+ not necessarily its current height in the tower.
- ulong threshold_slot = fd_tower_votes_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot;
+ For example, the following is a diagram of a tower pushing and
+ popping with each vote:
- /* Track the amount of stake that has vote slot >= threshold_slot. */
- ulong threshold_stake = 0;
+ slot | confirmation count
+ -----|-------------------
+ 4 | 1 <- vote
+ 3 | 2
+ 2 | 3
+ 1 | 4
- /* Iterate all the vote accounts. */
- for (ulong i = 0; i < vote_cnt; i++ ) {
- fd_tower_t const * vote_tower = vote_towers[i];
+ slot | confirmation count
+ -----|-------------------
+ 9 | 1 <- vote
+ 2 | 3
+ 1 | 4
- /* If this voter has not voted, continue. */
- if( FD_UNLIKELY( fd_tower_votes_empty( vote_tower ) ) ) continue;
+ slot | confirmation count
+ -----|-------------------
+ 10 | 1 <- vote
+ 9 | 2
+ 2 | 3
+ 1 | 4
- ulong cnt = simulate_vote( vote_tower, slot );
- /* Continue if their tower is empty after simulating. */
+ slot | confirmation count
+ -----|-------------------
+ 11 | 1 <- vote
+ 10 | 2
+ 9 | 3
+ 2 | 4
+ 1 | 5
- if( FD_UNLIKELY( !cnt ) ) continue;
- /* Get their latest vote. */
+ slot | confirmation count
+ -----|-------------------
+ 18 | 1 <- vote
+ 2 | 4
+ 1 | 5
- fd_tower_vote_t const * vote = fd_tower_votes_peek_index_const( vote_tower, cnt - 1 );
- /* Count their stake towards the threshold check if their latest
- vote slot >= our threshold slot.
+ In the final tower, note the gap in confirmation counts between slot
+ 18 and slot 2, even though slot 18 is directly above slot 2. */
- Because we are iterating vote accounts on the same fork that we
- we want to vote for, we know these slots must all occur along
- the same fork ancestry.
+int
+lockout_check( fd_tower_t const * tower,
+ fd_tower_forks_t * forks,
+ ulong slot ) {
- Therefore, if their latest vote slot >= our threshold slot, we
- know that vote must be for the threshold slot itself or one of
- threshold slot's descendants. */
+ if( FD_UNLIKELY( fd_tower_empty( tower ) ) ) return 1; /* always not locked out if we haven't voted. */
+ if( FD_UNLIKELY( slot <= fd_tower_peek_tail_const( tower )->slot ) ) return 0; /* always locked out from voting for slot <= last vote slot */
- if( FD_LIKELY( vote->slot >= threshold_slot ) ) {
- fd_voter_t * epoch_voters = fd_epoch_voters( epoch );
- fd_voter_t * voter = fd_epoch_voters_query( epoch_voters, vote_keys[i], NULL );
- if( FD_UNLIKELY( !voter ) ) {
- /* This means that the cached list of epoch voters is not in sync with the list passed
- through from replay. This likely means that we have crossed an epoch boundary and the
- epoch_voter list has not been updated.
+ /* Simulate a vote to pop off all the votes that would be expired by
+ voting for slot. Then check if the newly top-of-tower vote is on
+ the same fork as slot (if so this implies we can vote for it). */
- TODO: update the set of account in epoch_voter's to match the list received from replay,
- so that epoch_voters is correct across epoch boundaries. */
- FD_LOG_CRIT(( "[%s] voter %s was not in epoch voters", __func__,
- FD_BASE58_ENC_32_ALLOCA(&vote_keys[i]) ));
- continue;
- }
- threshold_stake += voter->stake;
- }
- }
+ ulong cnt = simulate_vote( tower, slot ); /* pop off votes that would be expired */
+ if( FD_UNLIKELY( !cnt ) ) return 1; /* tower is empty after popping expired votes */
- double threshold_pct = (double)threshold_stake / (double)epoch->total_stake;
+ fd_tower_t const * vote = fd_tower_peek_index_const( tower, cnt - 1 ); /* newly top-of-tower */
# if LOGGING
- FD_LOG_NOTICE(( "[%s] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.", __func__, threshold_pct > THRESHOLD_RATIO, fd_tower_votes_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 ));
+ FD_LOG_NOTICE(( "[%s] lockout_check for slot %lu against vote slot %lu", __func__, slot, vote->slot ));
# endif
- return threshold_pct > THRESHOLD_RATIO;
+ return fd_tower_forks_is_slot_descendant( forks, vote->slot, slot ); /* check if on same fork */
}
-ulong
-fd_tower_reset_slot( fd_tower_t const * tower,
- fd_epoch_t const * epoch,
- fd_ghost_t const * ghost ) {
-
- fd_tower_vote_t const * last = fd_tower_votes_peek_tail_const( tower );
- fd_ghost_ele_t const * vote = last ? fd_ghost_query_const( ghost, fd_ghost_hash( ghost, last->slot ) ) : NULL;
- fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
- fd_ghost_ele_t const * head = fd_ghost_head( ghost, root );
-
-# if FD_TOWER_PARANOID
- if( FD_UNLIKELY( !vote ) ) FD_LOG_CRIT(( "[%s] missing vote %lu", __func__, last->slot ));
- if( FD_UNLIKELY( !root ) ) FD_LOG_CRIT(( "[%s] missing root", __func__ ));
- if( FD_UNLIKELY( !head ) ) FD_LOG_CRIT(( "[%s] missing head", __func__ ));
-# endif
-
- /* Case 0: reset to the ghost head (ie. heaviest leaf slot of any
- fork) if any of the following is true:
-
- a. haven't voted
- b. last vote slot < ghost root slot
- c. ghost root is not an ancestor of last vote
+/* switch_check checks if we can switch to the fork of `slot`. Returns
+ 1 if we can switch, 0 otherwise. Assumes tower is non-empty.
- TODO can c. happen in non-exceptional conditions? error out? */
+ There are two forks of interest: our last vote fork ("vote fork") and
+ the fork we want to switch to ("switch fork"). The switch fork is on
+ the fork of `slot`.
- if( FD_UNLIKELY( !vote || vote->slot < root->slot || !fd_ghost_is_ancestor( ghost, &root->key, &vote->key ) ) )
- return head->slot;
+ In order to switch, FD_TOWER_SWITCH_PCT of stake must have voted for
+ a different descendant of the GCA of vote_fork and switch_fork, and
+ also must be locked out from our last vote slot.
- /* Case 1: last vote on same fork as heaviest leaf (ie. last vote slot
- is an ancestor of heaviest leaf ). This is the common case. */
+ Recall from the lockout check a validator is locked out from voting
+ for our last vote slot when their last vote slot is on a different
+ fork, and that vote's expiration slot > our last vote slot.
- else if( FD_LIKELY( fd_ghost_is_ancestor( ghost, &vote->key, &head->key ) ) )
- return head->slot;
+ The following pseudocode describes the algorithm:
- /* Case 2: last vote is on different fork from heaviest leaf (ie. last
- vote slot is _not_ an ancestor of heaviest leaf), but we have a
- valid switch proof for the heaviest leaf. */
+ ```
+ find the greatest common ancestor (gca) of vote_fork and switch_fork
+ for all validators v
+ if v's locked out[1] from voting for our latest vote slot
+ add v's stake to switch stake
+ return switch stake >= FD_TOWER_SWITCH_PCT
+ ```
- else if( FD_LIKELY( fd_tower_switch_check( tower, epoch, ghost, head->slot, &head->key ) ) )
- return head->slot;
+ The switch check is used to safeguard optimistic confirmation.
+ Specifically: FD_TOWER_OPT_CONF_PCT + FD_TOWER_SWITCH_PCT >= 1. */
- /* Case 3: same as case 2 except we don't have a valid switch proof,
- but we detect last vote is now on an "invalid" fork (ie. any
- ancestor of our last vote slot equivocates AND has not reached 52%
- of stake). If we do find such an ancestor, we reset to the heaviest
- leaf anyways, despite it being on a different fork and not having a
- valid switch proof. */
+int
+switch_check( fd_tower_t const * tower,
+ fd_tower_accts_t const * accts,
+ ulong total_stake,
+ ulong slot ) {
+
+ /* TODO requires banks / funk integration to query vote accounts */
+
+ (void)tower;
+ (void)accts;
+ (void)total_stake;
+ (void)slot;
+ return 1;
+}
- else if( FD_LIKELY( fd_ghost_invalid( ghost, vote ) ) )
- return head->slot;
+/* threshold_check checks if we pass the threshold required to vote for
+ `slot`. Returns 1 if we pass the threshold check, 0 otherwise.
- /* Case 4: same as case 3 except last vote's fork is not invalid. In
- this case we reset to the heaviest leaf starting from the subtree
- rooted at our last vote slot, instead of the overall heaviest leaf.
- This is done to ensure votes propagate (see top-level documentation
- in fd_tower.h for details) */
+ The following psuedocode describes the algorithm:
- else
- return fd_ghost_head( ghost, vote )->slot;
-}
+ ```
+ simulate that we have voted for `slot`
-ulong
-fd_tower_vote_slot( fd_tower_t const * tower,
- fd_epoch_t * epoch,
- fd_pubkey_t * vote_keys,
- fd_tower_t * const * vote_towers,
- ulong vote_cnt,
- fd_ghost_t const * ghost ) {
+ for all vote accounts in the current epoch
- fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
- fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
- fd_ghost_ele_t const * head = fd_ghost_head( ghost, root );
+ simulate that the vote account has voted for `slot`
- /* Vote for the ghost head if any of the following is true:
+ pop all votes expired by that simulated vote
- 1. haven't voted
- 2. last vote < ghost root
- 3. ghost root is not an ancestory of last vote
+ if the validator's latest tower vote after expiry >= our threshold
+ slot ie. our vote from THRESHOLD_DEPTH back also after simulating,
+ then add validator's stake to threshold_stake.
- FIXME need to ensure lockout safety for case 2 and 3 */
+ return threshold_stake >= FD_TOWER_THRESHOLD_RATIO
+ ```
- if( FD_UNLIKELY( !vote || vote->slot < root->slot ) ) {
- return head->slot;
- }
- fd_hash_t const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
- if( FD_UNLIKELY( !fd_ghost_is_ancestor( ghost, &root->key, vote_block_id ) ) ) {
- return head->slot;
- }
+ The threshold check simulates voting for the current slot to expire
+ stale votes. This is to prevent validators that haven't voted in a
+ long time from counting towards the threshold stake. */
- /* Optimize for when there is just one fork or that we already
- previously voted for the best fork. */
+int
+threshold_check( fd_tower_t const * tower,
+ fd_tower_accts_t const * accts,
+ ulong total_stake,
+ ulong slot ) {
- if( FD_LIKELY( fd_ghost_is_ancestor( ghost, vote_block_id, &head->key ) ) ) {
+ uchar __attribute__((aligned(FD_TOWER_ALIGN))) scratch[ FD_TOWER_FOOTPRINT ];
+ fd_tower_t * scratch_tower = fd_tower_join( fd_tower_new( scratch ) );
- /* The ghost head is on the same fork as our last vote slot, so we
- can vote fork it as long as we pass the threshold check. */
+ /* First, simulate a vote on our tower, popping off everything that
+ would be expired by voting for slot. */
- if( FD_LIKELY( head->slot > vote->slot && fd_tower_threshold_check( tower, epoch, vote_keys, vote_towers, vote_cnt, head->slot ) ) ) {
- FD_LOG_DEBUG(( "[%s] success (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
- return head->slot;
- }
- FD_LOG_DEBUG(( "[%s] failure (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
- return FD_SLOT_NULL; /* can't vote. need to wait for threshold check. */
- }
+ ulong cnt = simulate_vote( tower, slot );
- /* The ghost head is on a different fork from our last vote slot, so
- try to switch if we pass lockout and switch threshold. */
+ /* We can always vote if our tower is not at least THRESHOLD_DEPTH
+ deep after simulating. */
- if( FD_UNLIKELY( fd_tower_lockout_check( tower, ghost, head->slot, &head->key ) &&
- fd_tower_switch_check( tower, epoch, ghost, head->slot, &head->key ) ) ) {
- FD_LOG_DEBUG(( "[%s] success (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
- return head->slot;
- }
- FD_LOG_DEBUG(( "[%s] failure (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
- return FD_SLOT_NULL;
-}
+ if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
-ulong
-fd_tower_vote( fd_tower_t * tower, ulong slot ) {
- FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot ));
+ /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
+ is the 8th index back _including_ the simulated vote at index 0. */
- #if FD_TOWER_PARANOID
- fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
- if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
- #endif
+ ulong threshold_slot = fd_tower_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot;
+ ulong threshold_stake = 0;
+ for( fd_tower_accts_iter_t iter = fd_tower_accts_iter_init( accts );
+ !fd_tower_accts_iter_done( accts, iter );
+ iter = fd_tower_accts_iter_next( accts, iter ) ) {
+ fd_tower_accts_t const * acct = fd_tower_accts_iter_ele_const( accts, iter );
+ fd_tower_from_vote_acc( scratch_tower, acct->data );
- /* Use simulate_vote to determine how many expired votes to pop. */
+ ulong cnt = simulate_vote( scratch_tower, slot ); /* expire votes */
+ if( FD_UNLIKELY( !cnt ) ) continue; /* no votes left after expiry */
- ulong cnt = simulate_vote( tower, slot );
+ /* Count their stake towards the threshold check if their last vote
+ slot >= our threshold slot.
- /* Pop everything that got expired. */
+ We know these votes are for our own fork because towers are
+ sourced from vote _accounts_, not vote _transactions_.
- while( FD_LIKELY( fd_tower_votes_cnt( tower ) > cnt ) ) {
- fd_tower_votes_pop_tail( tower );
- }
+ Because we are iterating vote accounts on the same fork that we
+ we want to vote for, we know these slots must all occur along the
+ same fork ancestry.
- /* If the tower is still full after expiring, then pop and return the
- bottom vote slot as the new root because this vote has incremented
- it to max lockout. Otherwise this is a no-op and there is no new
- root (FD_SLOT_NULL). */
+ Therefore, if their latest vote slot >= our threshold slot, we
+ know that vote must be for the threshold slot itself or one of
+ threshold slot's descendants. */
- ulong root = FD_SLOT_NULL;
- if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */
- root = fd_tower_votes_pop_head( tower ).slot;
+ ulong last_vote = fd_tower_peek_index_const( scratch_tower, cnt - 1 )->slot;
+ if( FD_LIKELY( last_vote >= threshold_slot ) ) threshold_stake += acct->stake;
}
- /* Increment confirmations (double lockouts) for consecutive
- confirmations in prior votes. */
+ double threshold_pct = (double)threshold_stake / (double)total_stake;
+# if LOGGING
+ FD_LOG_NOTICE(( "[%s] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.", __func__, threshold_pct > THRESHOLD_RATIO, fd_tower_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 ));
+# endif
+ return threshold_pct > THRESHOLD_RATIO;
+}
- ulong prev_conf = 0;
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
- !fd_tower_votes_iter_done_rev( tower, iter );
- iter = fd_tower_votes_iter_prev ( tower, iter ) ) {
- fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter );
- if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break;
- vote->conf++;
- }
+int
+propagated_check( fd_notar_t * notar,
+ ulong slot ) {
- /* Add the new vote to the tower. */
+ fd_notar_slot_t * notar_slot = fd_notar_slot_query( notar->slot_map, slot, NULL );
+ if( FD_UNLIKELY( !notar_slot ) ) return 1;
- fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
+ if( FD_LIKELY( notar_slot->is_leader ) ) return 1; /* can always vote for slot in which we're leader */
+ if( FD_LIKELY( notar_slot->prev_leader_slot==ULONG_MAX ) ) return 1; /* haven't been leader yet */
- /* Return the new root (FD_SLOT_NULL if there is none). */
+ fd_notar_slot_t * prev_leader_notar_slot = fd_notar_slot_query( notar->slot_map, notar_slot->prev_leader_slot, NULL );
+ if( FD_LIKELY( !prev_leader_notar_slot ) ) return 1; /* already pruned rooted */
- return root;
+ return prev_leader_notar_slot->is_propagated;
}
-ulong
-fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
-# if FD_TOWER_PARANOID
- FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
-# endif
+fd_tower_out_t
+fd_tower_vote_and_reset( fd_tower_t * tower,
+ fd_tower_accts_t * accts,
+ fd_tower_forks_t * forks,
+ fd_ghost_t * ghost,
+ fd_notar_t * notar ) {
+
+ uchar flags = 0;
+ fd_ghost_blk_t const * best_blk = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
+ fd_ghost_blk_t const * reset_blk = NULL;
+ fd_ghost_blk_t const * vote_blk = NULL;
+
+ /* Case 0: if we haven't voted yet then we can always vote and reset
+ to ghost_best.
+
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L933-L935 */
+
+ if( FD_UNLIKELY( fd_tower_empty( tower ) ) ) {
+ fd_tower_forks_t * fork = fd_tower_forks_query( forks, best_blk->slot, NULL );
+ fork->voted = 1;
+ fork->voted_block_id = best_blk->id;
+ return (fd_tower_out_t){
+ .flags = flags,
+ .reset_slot = best_blk->slot,
+ .reset_block_id = best_blk->id,
+ .vote_slot = best_blk->slot,
+ .vote_block_id = best_blk->id,
+ .root_slot = push_vote( tower, best_blk->slot )
+ };
+ }
+
+ ulong prev_vote_slot = fd_tower_peek_tail_const( tower )->slot;
+ fd_tower_forks_t * prev_vote_fork = fd_tower_forks_query( forks, prev_vote_slot, NULL );
+ fd_hash_t * prev_vote_block_id = &prev_vote_fork->voted_block_id;
+ fd_ghost_blk_t * prev_vote_blk = fd_ghost_query( ghost, prev_vote_block_id );
+
+ /* Case 1: if an ancestor of our prev vote (including prev vote
+ itself) is an unconfirmed duplicate, then our prev vote was on a
+ duplicate fork.
+
+ There are two subcases to check. */
+
+ int invalid_ancestor = !!fd_ghost_invalid_ancestor( ghost, prev_vote_blk );
+ if( FD_UNLIKELY( invalid_ancestor ) ) { /* do we have an invalid ancestor? */
+
+ /* Case 1a: ghost_best is an ancestor of prev vote. This means
+ ghost_best is rolling back to an ancestor that precedes the
+ duplicate ancestor on the same fork as our prev vote. In this
+ case, we can't vote on our ancestor, but we do reset to that
+ ancestor.
+
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1016-L1019 */
+
+ int ancestor_rollback = prev_vote_blk != best_blk && !!fd_ghost_ancestor( ghost, prev_vote_blk, &best_blk->id );
+ if( FD_LIKELY( ancestor_rollback ) ) {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK );
+ reset_blk = best_blk;
+ }
- return simulate_vote( tower, slot );
-}
+ /* Case 1b: ghost_best is not an ancestor, but prev_vote is a
+ duplicate and we've confirmed its duplicate sibling. In this
+ case, we allow switching to ghost_best without a switch proof.
-static const uchar option_some = 1; /* this is a hack to lift the lifetime of a uchar outside fd_tower_sync_serde */
-
-fd_tower_sync_serde_t *
-fd_tower_to_tower_sync( fd_tower_t const * tower, ulong root, fd_hash_t * bank_hash, fd_hash_t * block_id, long ts, fd_tower_sync_serde_t * ser ) {
- ser->root = &root;
- ser->lockouts_cnt = (ushort)fd_tower_votes_cnt( tower );
- ushort i = 0;
- ulong prev = root;
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
- !fd_tower_votes_iter_done( tower, iter );
- iter = fd_tower_votes_iter_next( tower, iter ) ) {
- fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
- ser->lockouts[i].offset = vote->slot - prev;
- ser->lockouts[i].confirmation_count = (uchar const *)fd_type_pun_const( &vote->conf );
- i++;
- }
- ser->hash = bank_hash;
- ser->timestamp_option = &option_some;
- ser->timestamp = &ts;
- ser->block_id = block_id;
- return ser;
-}
+ Example: slot 5 is a duplicate. We first receive, replay and
+ vote for block 5, so that is our prev vote. We later receive
+ block 5' and observe that it is duplicate confirmed. ghost_best
+ now returns block 5' and we both vote and reset to block 5'
+ regardless of the switch check.
-int
-fd_tower_checkpt( fd_tower_t const * tower,
- ulong root,
- fd_tower_sync_serde_t * last_vote,
- uchar const pubkey[static 32],
- fd_tower_sign_fn * sign_fn,
- int fd,
- uchar * buf,
- ulong buf_max ) {
-
- /* TODO check no invalid ptrs */
-
- fd_tower_file_serde_t ser = { 0 };
-
- uint kind = SERDE_KIND;
- ulong threshold_depth = THRESHOLD_DEPTH;
- double threshold_size = THRESHOLD_RATIO;
-
- ser.kind = &kind;
- ser.threshold_depth = &threshold_depth;
- ser.threshold_size = &threshold_size;
-
- fd_voter_v2_serde_t * voter_v2_ser = &ser.vote_state;
-
- /* Agave defaults all fields except the actual tower votes and root
- https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/tower_vote_state.rs#L118-L128 */
-
- fd_pubkey_t pubkey_null = { 0 };
- voter_v2_ser->node_pubkey = &pubkey_null;
- voter_v2_ser->authorized_withdrawer = &pubkey_null;
- uchar commission = 0;
- voter_v2_ser->commission = &commission;
-
- ulong votes_cnt = fd_tower_votes_cnt( tower );
- voter_v2_ser->votes_cnt = &votes_cnt;
-
- ulong i = 0;
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
- !fd_tower_votes_iter_done( tower, iter );
- iter = fd_tower_votes_iter_next( tower, iter ) ) {
- fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
- voter_v2_ser->votes[i].slot = &vote->slot;
- voter_v2_ser->votes[i].confirmation_count = (uint const *)fd_type_pun_const( &vote->conf );
- i++;
- }
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1021-L1024 */
- uchar root_slot_option = root == ULONG_MAX;
- voter_v2_ser->root_slot_option = &root_slot_option;
- voter_v2_ser->root_slot = root_slot_option ? NULL : &root;
+ int sibling_confirmed = 0!=memcmp( &prev_vote_fork->voted_block_id, &prev_vote_fork->confirmed_block_id, sizeof(fd_hash_t) );
+ if( FD_LIKELY( sibling_confirmed ) ) {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SIBLING_CONFIRMED );
+ reset_blk = best_blk;
+ vote_blk = best_blk;
+ }
- ulong authorized_voters_cnt = 0;
- voter_v2_ser->authorized_voters_cnt = &authorized_voters_cnt;
+ /* At this point our prev vote was on a duplicate fork but didn't
+ match either of the above subcases.
- ulong start_epoch = 0;
- ulong end_epoch = 0;
- for( ulong i = 0; i < 32; i++ ) {
- voter_v2_ser->prior_voters.buf[i].pubkey = &pubkey_null;
- voter_v2_ser->prior_voters.buf[i].start_epoch = &start_epoch;
- voter_v2_ser->prior_voters.buf[i].end_epoch = &end_epoch;
+ In this case, we have to pass the switch check to reset to a
+ different fork from prev vote (same as non-duplicate case). */
}
- ulong idx = 31;
- voter_v2_ser->prior_voters.idx = &idx;
- uchar is_empty = 0;
- voter_v2_ser->prior_voters.is_empty = &is_empty;
- ulong epoch_credits_cnt = 0;
- voter_v2_ser->epoch_credits_cnt = &epoch_credits_cnt;
+ /* Case 3: if our prev vote slot is an ancestor of the best slot, then
+ they are on the same fork and we can both reset to it. We can also
+ vote for it if we pass the can_vote checks.
- ulong slot = 0;
- long timestamp = 0;
- voter_v2_ser->last_timestamp.slot = &slot;
- voter_v2_ser->last_timestamp.timestamp = ×tamp;
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1057 */
- /* Copy the last vote (reused from the actual ) into the Tower */
+ else if( FD_LIKELY( fd_tower_forks_is_slot_ancestor( forks, best_blk->slot, prev_vote_slot ) ) ) {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SAME_FORK );
+ reset_blk = best_blk;
+ vote_blk = best_blk;
+ }
- uint last_vote_kind = SERDE_LAST_VOTE_KIND;
- ser.last_vote_kind = &last_vote_kind;
- ser.last_vote = *last_vote;
+ /* Case 4: if our prev vote is not an ancestor of the best block, then
+ it is on a different fork. If we pass the switch check, we can
+ reset to it. If we additionally pass the lockout check, we can
+ also vote for it.
- ulong last_timestamp_slot = fd_tower_votes_peek_tail_const( tower )->slot;
- long last_timestamp_timestamp = fd_log_wallclock() / (long)1e9;
- ser.last_timestamp.slot = &last_timestamp_slot;
- ser.last_timestamp.timestamp = &last_timestamp_timestamp;
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1208-L1215
- int err;
+ Note also Agave uses the best blk's total stake for checking the
+ threshold.
- ulong buf_sz; err = fd_tower_serialize( &ser, buf, buf_max, &buf_sz );
- if( FD_UNLIKELY( err ) ) { FD_LOG_WARNING(( "fd_tower_serialize failed" )); return -1; }
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L443-L445 */
- ulong off = sizeof(uint) /* kind */ + FD_ED25519_SIG_SZ /* signature */ + sizeof(ulong) /* data_sz */;
- uchar * sig = buf + sizeof(uint);
- uchar * msg = buf + off;
- ulong msg_sz = buf_sz - off;
+ else if( FD_LIKELY( switch_check( tower, accts, best_blk->total_stake, best_blk->slot ) ) ) {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_PASS );
+ reset_blk = best_blk;
+ vote_blk = best_blk;
+ }
- sign_fn( pubkey, sig, msg, msg_sz );
+ /* Case 5: same as case 4 but we didn't pass the switch check. In
+ this case we reset to either ghost_best or ghost_deepest beginning
+ from our prev vote blk.
- ser.signature = (fd_ed25519_sig_t const *)fd_type_pun_const( &buf );
- ser.data_sz = &msg_sz;
+ We must reset to a block beginning from our prev vote fork to
+ ensure votes get a chance to propagate. Because in order for votes
+ to land, someone needs to build a block on that fork.
- ulong wsz; err = fd_io_write( fd, buf, buf_sz, buf_sz, &wsz );
- if( FD_UNLIKELY( err ) ) { FD_LOG_WARNING(( "fd_io_write failed: %s", strerror( err ) )); return -1; }
+ We reset to ghost_best or ghost_deepest depending on whether our
+ prev vote is valid. When it's invalid we use ghost_deepest instead
+ of ghost_best, because ghost_best won't be able to return a valid
+ block beginning from our prev_vote because by definition the entire
+ subtree will be invalid.
- fsync( fd );
+ When our prev vote fork is not a duplicate, we want to propagate
+ votes that might allow others to switch to our fork. In addition,
+ if our prev vote fork is a duplicate, we want to propagate votes
+ that might "duplicate confirm" that block (reach 52% of stake).
- return 0;
-}
+ See top-level documentation in fd_tower.h for more details on vote
+ propagation. */
-int
-fd_tower_restore( fd_tower_t * tower,
- ulong * root,
- long * ts,
- uchar const pubkey[static 32],
- int fd,
- uchar * buf,
- ulong buf_max,
- ulong * buf_sz ) {
- int err = fd_io_sz( fd, buf_sz );
- if( FD_UNLIKELY( err ) ) { FD_LOG_WARNING(( "%s: %s", __func__, fd_io_strerror( err ) )); return -1; }
- if( FD_UNLIKELY( buf_max<*buf_sz ) ) { FD_LOG_WARNING(( "%s: buf_max %lu < buf_sz %lu", __func__, buf_max, *buf_sz )); return -1; }
-
- ulong rsz; err = fd_io_read( fd, buf, *buf_sz, *buf_sz, &rsz );
- if( FD_UNLIKELY( err<0 ) ) { FD_LOG_WARNING(( "%s: unexpected EOF", __func__ )); return -1; }
- if( FD_UNLIKELY( *buf_sz!=rsz ) ) { FD_LOG_WARNING(( "%s: read %lu bytes, expected %lu", __func__, rsz, *buf_sz )); return -1; }
- if( FD_UNLIKELY( err>0 ) ) { FD_LOG_WARNING(( "%s: %s", __func__, fd_io_strerror( err ) )); return -1; }
-
- fd_tower_file_serde_t de = { 0 };
- fd_tower_deserialize( buf, *buf_sz, &de );
-
- uchar * msg = (uchar *)de.node_pubkey; /* signed data region begins at this field */
- ulong msg_sz = *de.data_sz;
- uchar const * sig = *de.signature;
- fd_sha512_t sha[1];
- err = fd_ed25519_verify( msg, msg_sz, sig, pubkey, sha );
- if( FD_UNLIKELY( err!=FD_ED25519_SUCCESS ) ) { FD_LOG_WARNING(( "serialized tower failed sigverify: %s", fd_ed25519_strerror( err ) )); return -1; }
- if( FD_UNLIKELY( 0!=memcmp( de.node_pubkey->uc, pubkey, 32 ) ) ) { FD_LOG_WARNING(( "node_pubkey does not match pubkey" )); return -1; }
- if( FD_UNLIKELY( *de.kind!=SERDE_KIND ) ) { FD_LOG_WARNING(( "serialized tower generated by too old agave version (required >= 2.3.7)" )); return -1; }
- if( FD_UNLIKELY( *de.threshold_depth!=THRESHOLD_DEPTH ) ) { FD_LOG_WARNING(( "threshold_depth does not match THRESHOLD_DEPTH" )); return -1; }
- if( FD_UNLIKELY( *de.threshold_size !=THRESHOLD_RATIO ) ) { FD_LOG_WARNING(( "threshold_size does not match THRESHOLD_RATIO" )); return -1; }
- if( FD_UNLIKELY( *de.vote_state.votes_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid votes_cnt %lu > 31", *de.vote_state.votes_cnt )); return -1; }
- if( FD_UNLIKELY( *de.vote_state.authorized_voters_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid authorized_voters_cnt %lu > 31", *de.vote_state.authorized_voters_cnt )); return -1; }
- if( FD_UNLIKELY( de.last_vote.lockouts_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid lockouts_cnt %u > 31", de.last_vote.lockouts_cnt )); return -1; }
-
- for( ulong i = 0; i < *de.vote_state.votes_cnt; i++ ) {
- fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = *de.vote_state.votes[i].slot, .conf = *de.vote_state.votes[i].confirmation_count } );
- }
- *root = *de.vote_state.root_slot_option ? *de.vote_state.root_slot : ULONG_MAX;
- *ts = *de.last_timestamp.timestamp;
+ else {
- return 0;
-}
+ /* Case 5a: failed switch check, duplicate.
-static ulong
-ser_short_vec_cnt( uchar * dst, ushort src ) {
- if ( FD_LIKELY( src < 0x80UL ) ) { *dst = (uchar) src; return 1; }
- else if( FD_LIKELY( src < 0x4000UL ) ) { *dst++ = (uchar)((src&0x7FUL)|0x80UL); *dst++ = (uchar)( src>>7); return 2; }
- else { *dst++ = (uchar)((src&0x7FUL)|0x80UL); *dst++ = (uchar)(((src>>7)&0x7FUL)|0x80UL); *dst++ = (uchar)(src>>14UL); return 3; }
-}
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/heaviest_subtree_fork_choice.rs#L1187 */
-static ulong
-ser_varint( uchar * dst, ulong src ) {
- ulong off = 0;
- while( FD_LIKELY( 1 ) ) {
- if( FD_LIKELY( src < 0x80UL ) ) {
- *(dst) = (uchar)src;
- off += 1;
- return off;
+ if( FD_UNLIKELY( invalid_ancestor ) ) {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
+ reset_blk = fd_ghost_deepest( ghost, prev_vote_blk );
}
- *(dst+off) = (uchar)((src&0x7FUL)|0x80UL);
- off += 1;
- src >>= 7;
- }
-}
-int
-fd_tower_serialize( fd_tower_file_serde_t * ser,
- uchar * buf,
- ulong buf_max,
- ulong * buf_sz ) {
-
- if( FD_UNLIKELY( *ser->threshold_depth!=THRESHOLD_DEPTH ) ) { FD_LOG_WARNING(( "threshold_depth does not match THRESHOLD_DEPTH" )); return -1; }
- if( FD_UNLIKELY( *ser->threshold_size !=THRESHOLD_RATIO ) ) { FD_LOG_WARNING(( "threshold_size does not match THRESHOLD_RATIO" )); return -1; }
- if( FD_UNLIKELY( *ser->vote_state.votes_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid votes_cnt %lu > 31", *ser->vote_state.votes_cnt )); return -1; }
- if( FD_UNLIKELY( *ser->vote_state.authorized_voters_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid authorized_voters_cnt %lu > 31", *ser->vote_state.authorized_voters_cnt )); return -1; }
- if( FD_UNLIKELY( ser->last_vote.lockouts_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid lockouts_cnt %u > 31", ser->last_vote.lockouts_cnt )); return -1; }
-
- #define SER( T, name ) do { \
- if( FD_UNLIKELY( off+sizeof(T)>buf_max ) ) { \
- FD_LOG_WARNING(( "ser %s: overflow (off %lu > buf_max: %lu)", #name, off, buf_max )); \
- return -1; \
- } \
- if( FD_LIKELY( ser->name ) ) { \
- FD_STORE( T, buf+off, *ser->name ); \
- ser->name = (T const *)fd_type_pun_const( buf+off ); \
- } \
- off += sizeof(T); \
- } while(0)
-
- #define OFF( T, name ) do { \
- if( FD_UNLIKELY( off+sizeof(T)>buf_max ) ) { \
- FD_LOG_WARNING(( "ser %s: overflow (off %lu > buf_max: %lu)", #name, off, buf_max )); \
- return -1; \
- } \
- ser->name = (T const *)fd_type_pun_const( buf+off ); \
- off += sizeof(T); \
- } while(0)
-
- ulong off = 0;
-
- /* SavedTower::Current */
-
- SER( uint, kind );
- OFF( fd_ed25519_sig_t, signature );
- OFF( ulong, data_sz );
- SER( fd_pubkey_t, node_pubkey );
- SER( ulong, threshold_depth );
- SER( double, threshold_size );
-
- /* VoteState1_14_11 */
-
- SER( fd_pubkey_t, vote_state.node_pubkey );
- SER( fd_pubkey_t, vote_state.authorized_withdrawer );
- SER( uchar, vote_state.commission );
- SER( ulong, vote_state.votes_cnt );
- for( ulong i=0; i < fd_ulong_min( *ser->vote_state.votes_cnt, 31 ); i++ ) {
- SER( ulong, vote_state.votes[i].slot );
- SER( uint, vote_state.votes[i].confirmation_count );
- }
- SER( uchar, vote_state.root_slot_option );
- if( FD_LIKELY( *ser->vote_state.root_slot_option ) ) {
- SER( ulong, vote_state.root_slot );
- }
- SER( ulong, vote_state.authorized_voters_cnt );
- for( ulong i = 0; i < fd_ulong_min( *ser->vote_state.authorized_voters_cnt, 32 ); i++ ) {
- SER( ulong, vote_state.authorized_voters[i].epoch );
- SER( fd_pubkey_t, vote_state.authorized_voters[i].pubkey );
- }
- for( ulong i = 0; i < 32; i++ ) {
- SER( fd_pubkey_t, vote_state.prior_voters.buf[i].pubkey );
- SER( ulong, vote_state.prior_voters.buf[i].start_epoch );
- SER( ulong, vote_state.prior_voters.buf[i].end_epoch );
- }
- SER( ulong, vote_state.prior_voters.idx );
- SER( uchar, vote_state.prior_voters.is_empty );
- SER( ulong, vote_state.epoch_credits_cnt );
- for( ulong i = 0; i < fd_ulong_min( *ser->vote_state.epoch_credits_cnt, 32 ); i++ ) {
- SER( ulong, vote_state.epoch_credits[i].epoch );
- SER( ulong, vote_state.epoch_credits[i].credits );
- SER( ulong, vote_state.epoch_credits[i].prev_credits );
- }
- SER( ulong, vote_state.last_timestamp.slot );
- SER( long, vote_state.last_timestamp.timestamp );
+ /* Case 5b: failed switch check, non-duplicate.
- /* VoteTransaction::TowerSync */
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L200 */
- SER( uint, last_vote_kind );
- SER( ulong, last_vote.root );
- off += ser_short_vec_cnt( buf+off, ser->last_vote.lockouts_cnt );
- for( ulong i = 0; i < fd_ulong_min( ser->last_vote.lockouts_cnt, 31 ); i++ ) {
- off += ser_varint( buf+off, ser->last_vote.lockouts[i].offset );
- SER( uchar, last_vote.lockouts[i].confirmation_count );
- }
- SER( fd_hash_t, last_vote.hash );
- SER( uchar, last_vote.timestamp_option );
- if( FD_LIKELY( *ser->last_vote.timestamp_option ) ) {
- SER( long, last_vote.timestamp );
+ else {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
+ reset_blk = fd_ghost_best( ghost, prev_vote_blk );
+ }
}
- SER( fd_hash_t, last_vote.block_id );
-
- /* BlockTimestamp */
-
- SER( ulong, last_timestamp.slot );
- SER( long, last_timestamp.timestamp );
- #undef SER
- #undef OFF
+ /* If there is a block to vote for, there are a few additional checks
+ to make sure we can actually vote for it.
- *buf_sz = off;
+ Specifically, we need to make sure we're not locked out, pass the
+ threshold check and that our previous leader block has propagated
+ (reached the prop threshold according to fd_notar).
- return 0;
-}
+ https://github.com/firedancer-io/agave/blob/master/core/src/consensus/fork_choice.rs#L382-L385
-static ulong
-de_short_vec_cnt( ushort * dst, uchar * src ) {
- if ( FD_LIKELY( !(0x80U & src[0]) ) ) { *dst = (ushort)src[0]; return 1; }
- else if( FD_LIKELY( !(0x80U & src[1]) ) ) { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)src[1])<<7)); return 2; }
- else { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)(src[1]&0x7FUL))<<7) + (((ulong)src[2])<<14)); return 3; }
-}
+ Agave uses the total stake on the fork being threshold checked
+ (vote_blk) for determining whether it meets the stake threshold. */
-static ulong
-de_varint( ulong * dst, uchar * src ) {
- *dst = 0;
- ulong off = 0;
- ulong bit = 0;
- while( FD_LIKELY( bit < 64 ) ) {
- uchar byte = *(uchar const *)(src+off);
- off += 1;
- *dst |= (byte & 0x7FUL) << bit;
- if( FD_LIKELY( (byte & 0x80U) == 0U ) ) {
- if( FD_UNLIKELY( (*dst>>bit) != byte ) ) FD_LOG_CRIT(( "de_varint" ));
- if( FD_UNLIKELY( byte==0U && (bit!=0U || *dst!=0UL) ) ) FD_LOG_CRIT(( "de_varint" ));
- return off;
+ if( FD_LIKELY( vote_blk ) ) {
+ if ( FD_UNLIKELY( !lockout_check( tower, forks, vote_blk->slot ) ) ) {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_LOCKOUT_FAIL );
+ vote_blk = NULL;
+ }
+ else if( FD_UNLIKELY( !threshold_check( tower, accts, vote_blk->total_stake, vote_blk->slot ) ) ) {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_THRESHOLD_FAIL );
+ vote_blk = NULL;
+ }
+ else if( FD_UNLIKELY( !propagated_check( notar, vote_blk->slot ) ) ) {
+ flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_PROPAGATED_FAIL );
+ vote_blk = NULL;
}
- bit += 7;
}
- FD_LOG_CRIT(( "de_varint" ));
-}
-int
-fd_tower_deserialize( uchar * buf,
- ulong buf_sz,
- fd_tower_file_serde_t * de ) {
-
- #define DE( T, name ) do { \
- if( FD_UNLIKELY( off+sizeof(T)>buf_sz ) ) { \
- FD_LOG_WARNING(( "de %s: overflow (off %lu > buf_sz: %lu)", #name, off, buf_sz )); \
- return -1; \
- } \
- de->name = (T const *)fd_type_pun_const( buf+off ); \
- off += sizeof(T); \
- } while(0)
-
- ulong off = 0;
-
- /* SavedTower::Current */
-
- DE( uint, kind );
- DE( fd_ed25519_sig_t, signature );
- DE( ulong, data_sz );
- DE( fd_pubkey_t, node_pubkey );
- DE( ulong, threshold_depth );
- DE( double, threshold_size );
-
- /* VoteState1_14_11 */
-
- DE( fd_pubkey_t, vote_state.node_pubkey );
- DE( fd_pubkey_t, vote_state.authorized_withdrawer );
- DE( uchar, vote_state.commission );
- DE( ulong, vote_state.votes_cnt );
- for( ulong i=0; i < fd_ulong_min( *de->vote_state.votes_cnt, 31 ); i++ ) {
- DE( ulong, vote_state.votes[i].slot );
- DE( uint, vote_state.votes[i].confirmation_count );
- }
- DE( uchar, vote_state.root_slot_option );
- if( FD_LIKELY( *de->vote_state.root_slot_option ) ) {
- DE( ulong, vote_state.root_slot );
- }
- DE( ulong, vote_state.authorized_voters_cnt );
- for( ulong i = 0; i < fd_ulong_min( *de->vote_state.authorized_voters_cnt, 32 ); i++ ) {
- DE( ulong, vote_state.authorized_voters[i].epoch );
- DE( fd_pubkey_t, vote_state.authorized_voters[i].pubkey );
- }
- for( ulong i = 0; i < 32; i++ ) {
- DE( fd_pubkey_t, vote_state.prior_voters.buf[i].pubkey );
- DE( ulong, vote_state.prior_voters.buf[i].start_epoch );
- DE( ulong, vote_state.prior_voters.buf[i].end_epoch );
- }
- DE( ulong, vote_state.prior_voters.idx );
- DE( uchar, vote_state.prior_voters.is_empty );
- DE( ulong, vote_state.epoch_credits_cnt );
- for( ulong i = 0; i < fd_ulong_min( *de->vote_state.epoch_credits_cnt, 32 ); i++ ) {
- DE( ulong, vote_state.epoch_credits[i].epoch );
- DE( ulong, vote_state.epoch_credits[i].credits );
- DE( ulong, vote_state.epoch_credits[i].prev_credits );
+ FD_TEST( reset_blk ); /* always a reset_blk */
+ fd_tower_out_t out = {
+ .flags = flags,
+ .reset_slot = reset_blk->slot,
+ .reset_block_id = reset_blk->id,
+ .vote_slot = ULONG_MAX,
+ .root_slot = ULONG_MAX
+ };
+
+ /* Finally, if our vote passed all the checks, we actually push the
+ vote onto the tower. */
+
+ if( FD_LIKELY( vote_blk ) ) {
+ out.vote_slot = vote_blk->slot;
+ out.vote_block_id = vote_blk->id;
+ out.root_slot = push_vote( tower, vote_blk->slot );
+
+ /* Query our tower fork for this slot we're voting for. Note this
+ can never be NULL because we record tower forks as we replay, and
+ we should never be voting on something we haven't replayed. */
+
+ fd_tower_forks_t * fork = fd_tower_forks_query( forks, vote_blk->slot, NULL );
+ fork->voted = 1;
+ fork->voted_block_id = vote_blk->id;
+
+ /* Query the root slot's block id from tower forks. This block id
+ may not necessarily be confirmed, because confirmation requires
+ votes on the block itself (vs. block and its descendants).
+
+ So if we have a confirmed block id, we return that. Otherwise
+ we return our own vote block id for that slot, which we assume
+ is the cluster confirmed version by the time it gets rooted.
+
+ The only way it is possible for us to root the wrong version of
+ a block (ie. not the one the cluster confirmed) is if there is
+ mass equivocation (>2/3 of threshold check stake has voted for
+ two versions of a block). This exceeds the equivocation safety
+ threshold and we would eventually detect this via a bank hash
+ mismatch and error out. */
+
+ if( FD_LIKELY( out.root_slot!=ULONG_MAX ) ) {
+ fd_tower_forks_t * root_fork = fd_tower_forks_query( forks, out.root_slot, NULL );
+ out.root_block_id = *fd_ptr_if( root_fork->confirmed, &root_fork->confirmed_block_id, &root_fork->voted_block_id );
+ }
}
- DE( ulong, vote_state.last_timestamp.slot );
- DE( long, vote_state.last_timestamp.timestamp );
- /* VoteTransaction::TowerSync */
+# if LOGGING
+ FD_LOG_NOTICE(( "[%s] code: %d. reset_slot: %lu. vote_slot: %lu. root_slot: %lu.", __func__, out.code, out.reset_slot, out.vote_slot, out.root_slot ));
+# endif
- DE( uint, last_vote_kind );
- DE( ulong, last_vote.root );
- off += de_short_vec_cnt( &de->last_vote.lockouts_cnt, buf+off );
- for( ulong i = 0; i < fd_ulong_min( de->last_vote.lockouts_cnt, 31 ); i++ ) {
- off += de_varint( &de->last_vote.lockouts[i].offset, buf+off );
- DE( uchar, last_vote.lockouts[i].confirmation_count );
- }
- DE( fd_hash_t, last_vote.hash );
- DE( uchar, last_vote.timestamp_option );
- if( FD_LIKELY( *de->last_vote.timestamp_option ) ) {
- DE( long, last_vote.timestamp );
- }
- DE( fd_hash_t, last_vote.block_id );
+ return out;
+}
- /* BlockTimestamp */
+void
+fd_tower_reconcile( fd_tower_t * tower,
+ ulong root,
+ uchar const * vote_account_data ) {
+ ulong on_chain_vote = fd_voter_vote_slot( vote_account_data );
+ ulong on_chain_root = fd_voter_root_slot( vote_account_data );
+
+ fd_tower_vote_t const * last_vote = fd_tower_peek_tail_const( tower );
+ ulong last_vote_slot = last_vote ? last_vote->slot : ULONG_MAX;
+
+ if( FD_UNLIKELY( ( on_chain_vote==ULONG_MAX && last_vote_slot==ULONG_MAX ) ) ) return;
+ if( FD_LIKELY ( ( on_chain_vote!=ULONG_MAX && last_vote_slot!=ULONG_MAX
+ && on_chain_vote <= last_vote_slot ) ) ) return;
+
+ /* At this point our local tower is too old, and we need to replace it
+ with our on-chain tower. However, it's possible our local root is
+ newer than the on-chain root (even though the tower is older). The
+ most likely reason this happens is because we just booted from a
+ snapshot and the snapshot slot > on-chain root.
+
+ So we need to filter out the stale votes < snapshot slot. This
+ mirrors the Agave logic:
+ https://github.com/firedancer-io/agave/blob/master/core/src/replay_stage.rs#L3690-L3719 */
+
+ if( FD_LIKELY( on_chain_root == ULONG_MAX || root > on_chain_root ) ) {
+ fd_tower_remove_all( tower );
+ fd_voter_t const * voter = (fd_voter_t const *)fd_type_pun_const( vote_account_data );
+ uint kind = fd_uint_load_4_fast( vote_account_data ); /* skip node_pubkey */
+ for( ulong i=0; ivotes_cnt; i++ ) {
+ if( FD_LIKELY( kind==FD_VOTER_V3 ) ) fd_tower_push_tail( tower, (fd_tower_t){ .slot = voter->votes_v3[i].slot, .conf = voter->votes_v3[i].conf } );
+ if( FD_LIKELY( kind==FD_VOTER_V2 ) ) fd_tower_push_tail( tower, (fd_tower_t){ .slot = voter->votes_v2[i].slot, .conf = voter->votes_v2[i].conf } );
+ }
- DE( ulong, last_timestamp.slot );
- DE( long, last_timestamp.timestamp );
+ /* Fast forward our tower to tower_root by retaining only votes >
+ local tower root. */
- #undef DE
+ while( FD_LIKELY( !fd_tower_empty( tower ) ) ) {
+ fd_tower_t const * vote = fd_tower_peek_head_const( tower );
+ if( FD_LIKELY( vote->slot > root ) ) break;
+ fd_tower_pop_head( tower );
+ }
+ }
+}
- return 0;
+void
+fd_tower_from_vote_acc( fd_tower_t * tower,
+ uchar const * vote_acc ) {
+ fd_voter_t const * voter = (fd_voter_t const *)fd_type_pun_const( vote_acc );
+ uint kind = fd_uint_load_4_fast( vote_acc ); /* skip node_pubkey */
+ for( ulong i=0; ivotes_cnt; i++ ) {
+ if( FD_LIKELY( kind==FD_VOTER_V3 ) ) fd_tower_push_tail( tower, (fd_tower_t){ .slot = voter->votes_v3[i].slot, .conf = voter->votes_v3[i].conf } );
+ if( FD_LIKELY( kind==FD_VOTER_V2 ) ) fd_tower_push_tail( tower, (fd_tower_t){ .slot = voter->votes_v2[i].slot, .conf = voter->votes_v2[i].conf } );
+ }
}
void
@@ -897,17 +631,17 @@ fd_tower_to_vote_txn( fd_tower_t const * tower,
fd_compact_vote_state_update_t tower_sync;
tower_sync.root = fd_ulong_if( root == ULONG_MAX, 0UL, root );
- tower_sync.lockouts_len = (ushort)fd_tower_votes_cnt( tower );
+ tower_sync.lockouts_len = (ushort)fd_tower_cnt( tower );
tower_sync.lockouts = lockouts_scratch;
tower_sync.timestamp = fd_log_wallclock() / (long)1e9; /* seconds */
tower_sync.has_timestamp = 1;
ulong prev = tower_sync.root;
ulong i = 0UL;
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
- !fd_tower_votes_iter_done( tower, iter );
- iter = fd_tower_votes_iter_next( tower, iter ) ) {
- fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
+ for( fd_tower_iter_t iter = fd_tower_iter_init( tower );
+ !fd_tower_iter_done( tower, iter );
+ iter = fd_tower_iter_next( tower, iter ) ) {
+ fd_tower_t const * vote = fd_tower_iter_ele_const( tower, iter );
tower_sync.lockouts[i].offset = vote->slot - prev;
tower_sync.lockouts[i].confirmation_count = (uchar)vote->conf;
prev = vote->slot;
@@ -925,16 +659,17 @@ fd_tower_to_vote_txn( fd_tower_t const * tower,
1: vote account address
2: vote program */
- fd_txn_accounts_t accts;
- accts.signature_cnt = 1;
- accts.readonly_signed_cnt = 0;
- accts.readonly_unsigned_cnt = 1;
- accts.acct_cnt = 3;
- accts.signers_w = validator_identity;
- accts.signers_r = NULL;
- accts.non_signers_w = vote_acc;
- accts.non_signers_r = &fd_solana_vote_program_id;
- FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
+ fd_txn_accounts_t votes;
+ votes.signature_cnt = 1;
+ votes.readonly_signed_cnt = 0;
+ votes.readonly_unsigned_cnt = 1;
+ votes.acct_cnt = 3;
+ votes.signers_w = validator_identity;
+ votes.signers_r = NULL;
+ votes.non_signers_w = vote_acc;
+ votes.non_signers_r = &fd_solana_vote_program_id;
+ FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
+
} else {
/* 0: validator identity
@@ -942,16 +677,16 @@ fd_tower_to_vote_txn( fd_tower_t const * tower,
2: vote account address
3: vote program */
- fd_txn_accounts_t accts;
- accts.signature_cnt = 2;
- accts.readonly_signed_cnt = 1;
- accts.readonly_unsigned_cnt = 1;
- accts.acct_cnt = 4;
- accts.signers_w = validator_identity;
- accts.signers_r = vote_authority;
- accts.non_signers_w = vote_acc;
- accts.non_signers_r = &fd_solana_vote_program_id;
- FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
+ fd_txn_accounts_t votes;
+ votes.signature_cnt = 2;
+ votes.readonly_signed_cnt = 1;
+ votes.readonly_unsigned_cnt = 1;
+ votes.acct_cnt = 4;
+ votes.signers_w = validator_identity;
+ votes.signers_r = vote_authority;
+ votes.non_signers_w = vote_acc;
+ votes.non_signers_r = &fd_solana_vote_program_id;
+ FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
}
/* Add the vote instruction to the transaction. */
@@ -979,11 +714,11 @@ fd_tower_to_vote_txn( fd_tower_t const * tower,
int
fd_tower_verify( fd_tower_t const * tower ) {
- fd_tower_vote_t const * prev = NULL;
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
- !fd_tower_votes_iter_done( tower, iter );
- iter = fd_tower_votes_iter_next( tower, iter ) ) {
- fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
+ fd_tower_t const * prev = NULL;
+ for( fd_tower_iter_t iter = fd_tower_iter_init( tower );
+ !fd_tower_iter_done( tower, iter );
+ iter = fd_tower_iter_next( tower, iter ) ) {
+ fd_tower_t const * vote = fd_tower_iter_ele_const( tower, iter );
if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) {
FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
return -1;
@@ -1002,11 +737,11 @@ fd_tower_print( fd_tower_t const * tower, ulong root ) {
/* Determine spacing. */
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
- !fd_tower_votes_iter_done_rev( tower, iter );
- iter = fd_tower_votes_iter_prev ( tower, iter ) ) {
+ for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower );
+ !fd_tower_iter_done_rev( tower, iter );
+ iter = fd_tower_iter_prev ( tower, iter ) ) {
- max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
+ max_slot = fd_ulong_max( max_slot, fd_tower_iter_ele_const( tower, iter )->slot );
}
/* Calculate the number of digits in the maximum slot value. */
@@ -1018,11 +753,11 @@ fd_tower_print( fd_tower_t const * tower, ulong root ) {
++digit_cnt;
} while( rem > 0 );
- /* Print the table header */
+ /* Print the column headers. */
printf( "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
- /* Print the divider line */
+ /* Print the divider line. */
for( int i = 0; i < digit_cnt; i++ ) {
printf( "-" );
@@ -1033,44 +768,20 @@ fd_tower_print( fd_tower_t const * tower, ulong root ) {
}
printf( "\n" );
- /* Print each record in the table */
+ /* Print each vote as a table. */
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
- !fd_tower_votes_iter_done_rev( tower, iter );
- iter = fd_tower_votes_iter_prev ( tower, iter ) ) {
+ for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower );
+ !fd_tower_iter_done_rev( tower, iter );
+ iter = fd_tower_iter_prev ( tower, iter ) ) {
- fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
+ fd_tower_t const * vote = fd_tower_iter_ele_const( tower, iter );
printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
- max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
+ max_slot = fd_ulong_max( max_slot, fd_tower_iter_ele_const( tower, iter )->slot );
}
- printf( "%*lu | root\n", digit_cnt, root );
- printf( "\n" );
-}
-
-void
-fd_tower_from_vote_acc_data( uchar const * data,
- fd_tower_t * tower_out ) {
-# if FD_TOWER_PARANOID
- FD_TEST( fd_tower_votes_empty( tower_out ) );
-# endif
-
- fd_voter_state_t const * state = (fd_voter_state_t const *)fd_type_pun_const( data );
-
- /* Push all the votes onto the tower. */
- for( ulong i = 0; i < fd_voter_state_cnt( state ); i++ ) {
- fd_tower_vote_t vote = { 0 };
- if( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_v0_23_5 ) ) {
- vote.slot = state->v0_23_5.votes[i].slot;
- vote.conf = state->v0_23_5.votes[i].conf;
- } else if( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_v1_14_11 ) ) {
- vote.slot = state->v1_14_11.votes[i].slot;
- vote.conf = state->v1_14_11.votes[i].conf;
- } else if ( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_current ) ) {
- vote.slot = state->votes[i].slot;
- vote.conf = state->votes[i].conf;
- } else {
- FD_LOG_CRIT(( "[%s] unknown vote state version. discriminant %u", __func__, state->kind ));
- }
- fd_tower_votes_push_tail( tower_out, vote );
+ if( FD_UNLIKELY( root==ULONG_MAX ) ) {
+ printf( "%*s | root\n", digit_cnt, "NULL" );
+ } else {
+ printf( "%*lu | root\n", digit_cnt, root );
}
+ printf( "\n" );
}
diff --git a/src/choreo/tower/fd_tower.h b/src/choreo/tower/fd_tower.h
index c8004bd3eaf..297a4653775 100644
--- a/src/choreo/tower/fd_tower.h
+++ b/src/choreo/tower/fd_tower.h
@@ -166,7 +166,7 @@
In the Tower rules, once a vote reaches a conf count of 32, it is
considered rooted and it is popped from the bottom of the tower. Here
- is an example where 1 got rooted and therefore popped from the bottom:
+ is an example where 1 got rooted and popped from the bottom:
(before) slot | conf
-----------
@@ -420,8 +420,10 @@
*/
#include "../fd_choreo_base.h"
-#include "../epoch/fd_epoch.h"
+#include "fd_tower_accts.h"
+#include "fd_tower_forks.h"
#include "../ghost/fd_ghost.h"
+#include "../notar/fd_notar.h"
#include "../../disco/pack/fd_microblock.h"
/* FD_TOWER_PARANOID: Define this to non-zero at compile time
@@ -433,462 +435,102 @@
#define FD_TOWER_VOTE_MAX (31UL)
+/* fd_tower is a representation of a validator's "vote tower" (described
+ in detail in the preamble at the top of this file). The votes in the
+ tower are stored in an fd_deque.c ordered from lowest to highest vote
+ slot (highest to lowest confirmation count) relative to the head and
+ tail. There can be at most FD_TOWER_VOTE_MAX votes in the tower. */
+
struct fd_tower_vote {
ulong slot; /* vote slot */
ulong conf; /* confirmation count */
};
typedef struct fd_tower_vote fd_tower_vote_t;
-#define DEQUE_NAME fd_tower_votes
+#define DEQUE_NAME fd_tower
#define DEQUE_T fd_tower_vote_t
#define DEQUE_MAX FD_TOWER_VOTE_MAX
#include "../../util/tmpl/fd_deque.c"
-/* fd_tower is a representation of a validator's "vote tower" (described
- in detail in the preamble at the top of this file). The votes in the
- tower are stored in an fd_deque ordered from lowest to highest vote
- slot (highest to lowest confirmation count) relative to the head and
- tail . There can be at most 31 votes in the tower. This invariant
- is upheld with every call to `fd_tower_vote`.
-
- The definition of `fd_tower_t` is a simple typedef alias for
- `fd_tower_vote_t` and is a transparent wrapper around the vote deque.
- Relatedly, the tower API takes a local pointer to the first vote in
- the deque (the result of `fd_deque_join`) as a parameter in all its
- function signatures. */
-
-typedef fd_tower_vote_t fd_tower_t;
-
-/* fd_tower_sync_serde describes a serialization / deserialization
- schema for a bincode-encoded TowerSync transaction. The serde is
- structured for zero-copy access ie. x-raying individual fields. */
-
-struct fd_tower_sync_serde /* CompactTowerSync */ {
- ulong const * root;
- struct /* short_vec */ {
- ushort lockouts_cnt; /* variable-length so copied (ShortU16) */
- struct /* Lockout */ {
- ulong offset; /* variable-length so copied (VarInt) */
- uchar const * confirmation_count;
- } lockouts[31];
- };
- fd_hash_t const * hash;
- struct /* Option */ {
- uchar const * timestamp_option;
- long const * timestamp;
- };
- fd_hash_t const * block_id;
-};
-typedef struct fd_tower_sync_serde fd_tower_sync_serde_t;
-
-/* fd_tower_file_serde describes a serialization / deserialization
- schema for checkpointing / restoring tower from a file. This
- corresponds exactly with the binary layout of a tower file that Agave
- uses during boot, set-identity, and voting.
-
- The serde is structured for zero-copy access ie. x-raying individual
- fields. */
-
-struct fd_tower_file_serde /* SavedTowerVersions::Current */ {
- uint const * kind;
- fd_ed25519_sig_t const * signature;
- ulong const * data_sz; /* serialized sz of data field below */
- struct /* Tower1_14_11 */ {
- fd_pubkey_t const * node_pubkey;
- ulong const * threshold_depth;
- double const * threshold_size;
- fd_voter_v2_serde_t vote_state;
- struct {
- uint const * last_vote_kind;
- fd_tower_sync_serde_t last_vote;
- };
- struct /* BlockTimestamp */ {
- ulong const * slot;
- long const * timestamp;
- } last_timestamp;
- } /* data */;
+typedef fd_tower_vote_t fd_tower_t; /* typedef for semantic clarity */
+
+/* FD_TOWER_{ALIGN,FOOTPRINT} provided for static declarations. */
+
+#define FD_TOWER_ALIGN (alignof(fd_tower_private_t))
+#define FD_TOWER_FOOTPRINT (sizeof (fd_tower_private_t))
+FD_STATIC_ASSERT( alignof(fd_tower_private_t)==8UL, FD_TOWER_ALIGN );
+FD_STATIC_ASSERT( sizeof (fd_tower_private_t)==512UL, FD_TOWER_FOOTPRINT );
+
+#define FD_TOWER_FLAG_ANCESTOR_ROLLBACK 0 /* rollback to an ancestor of our prev vote */
+#define FD_TOWER_FLAG_SIBLING_CONFIRMED 1 /* our prev vote was a duplicate and its sibling got confirmed */
+#define FD_TOWER_FLAG_SAME_FORK 2 /* prev vote is on the same fork */
+#define FD_TOWER_FLAG_SWITCH_PASS 3 /* successfully switched to a different fork */
+#define FD_TOWER_FLAG_SWITCH_FAIL 4 /* failed to switch to a different fork */
+#define FD_TOWER_FLAG_LOCKOUT_FAIL 5 /* failed lockout check */
+#define FD_TOWER_FLAG_THRESHOLD_FAIL 6 /* failed threshold check */
+#define FD_TOWER_FLAG_PROPAGATED_FAIL 7 /* failed propagated check */
+
+struct fd_tower_out {
+ uchar flags; /* one of FD_TOWER_{EMPTY,...} */
+ ulong reset_slot; /* slot to reset PoH to */
+ fd_hash_t reset_block_id; /* block ID to reset PoH to */
+ ulong vote_slot; /* slot to vote for (ULONG_MAX if no vote) */
+ fd_hash_t vote_block_id; /* block ID to vote for */
+ ulong root_slot; /* new tower root slot (ULONG_MAX if no new root) */
+ fd_hash_t root_block_id; /* new tower root block ID */
};
-typedef struct fd_tower_file_serde fd_tower_file_serde_t;
-
-/* fd_tower_sign_fn is the signing callback used for signing tower
- checkpoints after serialization. */
-
-typedef void (fd_tower_sign_fn)( void const * ctx, uchar * sig, uchar const * ser, ulong ser_sz );
-
-/* FD_TOWER_{ALIGN,FOOTPRINT} specify the alignment and footprint needed
- for tower. ALIGN is double x86 cache line to mitigate various kinds
- of false sharing (eg. ACLPF adjacent cache line prefetch). FOOTPRINT
- is the size of fd_deque including the private header's start and end
- and an exact multiple of ALIGN. These are provided to facilitate
- compile time tower declarations. */
-
-#define FD_TOWER_ALIGN (128UL)
-#define FD_TOWER_FOOTPRINT (512UL)
-FD_STATIC_ASSERT( FD_TOWER_FOOTPRINT==sizeof(fd_tower_votes_private_t), FD_TOWER_FOOTPRINT );
-
-/* fd_tower_{align,footprint} return the required alignment and
- footprint of a memory region suitable for use as a tower. align
- returns FD_TOWER_ALIGN. footprint returns FD_TOWER_FOOTPRINT. */
-
-FD_FN_CONST static inline ulong
-fd_tower_align( void ) {
- return FD_TOWER_ALIGN;
-}
-
-FD_FN_CONST static inline ulong
-fd_tower_footprint( void ) {
- return FD_TOWER_FOOTPRINT;
-}
-
-/* fd_tower_new formats an unused memory region for use as a tower. mem
- is a non-NULL pointer to this region in the local address space with
- the required footprint and alignment. */
-
-void *
-fd_tower_new( void * mem );
-
-/* fd_tower_join joins the caller to the tower. tower points to the
- first byte of the memory region backing the tower in the caller's
- address space.
-
- Returns a pointer in the local address space to tower on success. */
-
-fd_tower_t *
-fd_tower_join( void * tower );
-
-/* fd_tower_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 tower is NULL. */
-
-void *
-fd_tower_leave( fd_tower_t * tower );
-
-/* fd_tower_delete unformats a memory region used as a tower. Assumes
- only the local process is joined to the region. Returns a pointer to
- the underlying shared memory region or NULL if used obviously in
- error (e.g. tower is obviously not a tower ... logs details). The
- ownership of the memory region is transferred to the caller. */
-
-void *
-fd_tower_delete( void * tower );
-
-/* fd_tower_lockout_check checks if we are locked out from voting for
- the `slot`. Returns 1 if we can vote for `slot` without violating
- lockout, 0 otherwise. Assumes tower is non-empty.
-
- After voting for a slot n, we are locked out for 2^k slots, where k
- is the confirmation count of that vote. Once locked out, we cannot
- vote for a different fork until that previously-voted fork expires at
- slot n+2^k. This implies the earliest slot in which we can switch
- from the previously-voted fork is (n+2^k)+1. We use `ghost` to
- determine whether `slot` is on the same or different fork as previous
- vote slots.
-
- In the case of the tower, every vote has its own expiration slot
- depending on confirmations. The confirmation count is the max number
- of consecutive votes that have been pushed on top of the vote, and
- not necessarily its current height in the tower.
-
- For example, the following is a diagram of a tower pushing and
- popping with each vote:
-
-
- slot | confirmation count
- -----|-------------------
- 4 | 1 <- vote
- 3 | 2
- 2 | 3
- 1 | 4
-
-
- slot | confirmation count
- -----|-------------------
- 9 | 1 <- vote
- 2 | 3
- 1 | 4
-
-
- slot | confirmation count
- -----|-------------------
- 10 | 1 <- vote
- 9 | 2
- 2 | 3
- 1 | 4
-
-
- slot | confirmation count
- -----|-------------------
- 11 | 1 <- vote
- 10 | 2
- 9 | 3
- 2 | 4
- 1 | 5
-
-
- slot | confirmation count
- -----|-------------------
- 18 | 1 <- vote
- 2 | 4
- 1 | 5
-
-
- In the final tower, note the gap in confirmation counts between slot
- 18 and slot 2, even though slot 18 is directly above slot 2. */
-
-int
-fd_tower_lockout_check( fd_tower_t const * tower,
- fd_ghost_t const * ghost,
- ulong slot,
- fd_hash_t const * block_id );
-
-/* fd_tower_switch_check checks if we can switch to the fork of `slot`.
- Returns 1 if we can switch, 0 otherwise. Assumes tower is non-empty.
-
- There are two forks of interest: our last vote fork ("vote fork") and
- the fork we want to switch to ("switch fork"). The switch fork is the
- fork of `slot`.
-
- In order to switch, FD_TOWER_SWITCH_PCT of stake must have voted for
- a different descendant of the GCA of vote_fork and switch_fork, and
- also must be locked out from our last vote slot.
-
- Recall from the lockout check a validator is locked out from voting
- for our last vote slot when their last vote slot is on a different
- fork, and that vote's expiration slot > our last vote slot.
-
- The following pseudocode describes the algorithm:
-
- ```
- find the greatest common ancestor (gca) of vote_fork and switch_fork
- for all validators v
- if v's locked out[1] from voting for our latest vote slot
- add v's stake to switch stake
- return switch stake >= FD_TOWER_SWITCH_PCT
- ```
-
- The switch check is used to safeguard optimistic confirmation.
- Specifically: FD_TOWER_OPT_CONF_PCT + FD_TOWER_SWITCH_PCT >= 1. */
-
-int
-fd_tower_switch_check( fd_tower_t const * tower,
- fd_epoch_t const * epoch,
- fd_ghost_t const * ghost,
- ulong slot,
- fd_hash_t const * block_id );
-
-/* fd_tower_threshold_check checks if we pass the threshold required to
- vote for `slot`. This is only relevant after voting for (and
- confirming) the same fork ie. the tower is FD_TOWER_THRESHOLD_DEPTH
- deep. Returns 1 if we pass the threshold check, 0 otherwise.
-
- The following psuedocode describes the algorithm:
-
- ```
- for all vote accounts in the current epoch
-
- simulate that the validator has voted for `slot`
-
- pop all votes expired by that simulated vote
-
- if the validator's latest tower vote after expiry >= our threshold
- slot ie. our vote from FD_TOWER_THRESHOLD_DEPTH back (after
- simulating a vote on our own tower the same way), then add
- validator's stake to threshold_stake.
-
- return threshold_stake >= FD_TOWER_THRESHOLD_RATIO
- ```
-
- The threshold check simulates voting for the current slot to expire
- stale votes. This is to prevent validators that haven't voted in a
- long time from counting towards the threshold stake. */
-
-int
-fd_tower_threshold_check( fd_tower_t const * tower,
- fd_epoch_t * epoch,
- fd_pubkey_t * vote_keys,
- fd_tower_t * const * vote_towers,
- ulong vote_cnt,
- ulong slot );
-
-/* fd_tower_reset_slot returns the slot to reset PoH to when building
- the next leader block. Assumes tower and ghost are both valid local
- joins and in-sync ie. every vote slot in tower corresponds to a node
- in ghost. There is always a reset slot (ie. this function will never
- return ULONG_MAX). In general, we reset to the leaf of our last vote
- fork (which is usually also the ghost head).
- See the implementation for detailed documentation of each reset case.
- */
-
-ulong
-fd_tower_reset_slot( fd_tower_t const * tower,
- fd_epoch_t const * epoch,
- fd_ghost_t const * ghost );
-
-/* fd_tower_vote_slot returns the correct vote slot to pick given the
- ghost tree. Returns FD_SLOT_NULL if we cannot vote, because we are
- locked out, do not meet switch threshold, or fail the threshold
- check.
-
- Specifically, these are the two scenarios in which we can vote:
-
- 1. the ghost head is on the same fork as our last vote slot, and
- we pass the threshold check.
- 2. the ghost head is on a different fork than our last vote slot,
- but we pass both the lockout and switch checks so we can
- switch to the ghost head's fork. */
-
-ulong
-fd_tower_vote_slot( fd_tower_t const * tower,
- fd_epoch_t * epoch,
- fd_pubkey_t * voter_keys,
- fd_tower_t * const * voter_towers,
- ulong voter_len,
- fd_ghost_t const * ghost );
-
-/* fd_tower_simulate_vote simulates a vote on the vote tower for slot,
- returning the new height (cnt) for all the votes that would have been
- popped. Assumes tower is non-empty. */
-
-ulong
-fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot );
-
-/* Operations */
-
-/* fd_tower_vote votes for slot. Assumes caller has already performed
- the relevant tower checks (lockout_check, etc.) to ensure it is valid
- to vote for `slot`. Returns a new root if this vote results in the
- lowest vote slot in the tower reaching max lockout. The lowest vote
- will also be popped from the tower.
-
- Max lockout is equivalent to 1 << FD_TOWER_VOTE_MAX + 1 (which
- implies confirmation count is FD_TOWER_VOTE_MAX + 1). As a result,
- fd_tower_vote also maintains the invariant that the tower contains at
- most FD_TOWER_VOTE_MAX votes, because (in addition to vote expiry)
- there will always be a pop before reaching FD_TOWER_VOTE_MAX + 1. */
-
-ulong
-fd_tower_vote( fd_tower_t * tower, ulong slot );
+typedef struct fd_tower_out fd_tower_out_t;
+
+/* fd_tower_vote_and_reset selects both a block to vote for and block to
+ reset to. Returns a struct with a reason code (FD_TOWER_{EMPTY,...})
+ in addition to {reset,vote,root}_{slot,block_id}.
+
+ We can't always vote, so vote_slot may be ULONG_MAX which indicates
+ no vote should be cast and caller should ignore vote_block_id. New
+ roots result from votes, so the same applies for root_slot (there is
+ not always a new root). However there is always a reset block, so
+ reset_slot and reset_block_id will always be populated on return. The
+ implementation contains detailed documentation of the tower rules. */
+
+fd_tower_out_t
+fd_tower_vote_and_reset( fd_tower_t * tower,
+ fd_tower_accts_t * accts,
+ fd_tower_forks_t * forks,
+ fd_ghost_t * ghost,
+ fd_notar_t * notar );
/* Misc */
-/* fd_tower_sync_serde populates serde using the provided tower and args
- to create a zero-copy view of a TowerSync vote transaction payload
- ready for serialization. */
+/* fd_tower_reconcile reconciles our local tower with the on-chain tower
+ inside our vote account. Mirrors what Agave does. */
-fd_tower_sync_serde_t *
-fd_tower_to_tower_sync( fd_tower_t const * tower, ulong root, fd_hash_t * bank_hash, fd_hash_t * block_id, long ts, fd_tower_sync_serde_t * ser );
+void
+fd_tower_reconcile( fd_tower_t * tower,
+ ulong tower_root,
+ uchar const * vote_acc );
-/* fd_tower_sync_serde populates serde using the provided tower and args
- to create a zero-copy view of an Agave-compatible serialized Tower
- ready for serialization. */
+/* fd_tower_from_vote_acc deserializes the vote account into tower.
+ Assumes tower is a valid local join and currently empty. */
-fd_tower_file_serde_t *
-fd_tower_serde( fd_tower_t const * tower,
- ulong root,
- fd_tower_sync_serde_t * last_vote,
- uchar const pvtkey[static 32],
- uchar const pubkey[static 32],
- fd_tower_file_serde_t * ser );
+void
+fd_tower_from_vote_acc( fd_tower_t * tower,
+ uchar const * vote_acc );
/* fd_tower_to_vote_txn writes tower into a fd_tower_sync_t vote
instruction and serializes it into a Solana transaction. Assumes
tower is a valid local join. */
void
-fd_tower_to_vote_txn( fd_tower_t const * tower,
+fd_tower_to_vote_txn( fd_tower_t const * tower,
ulong root,
fd_lockout_offset_t * lockouts_scratch,
- fd_hash_t const * bank_hash,
- fd_hash_t const * recent_blockhash,
- fd_pubkey_t const * validator_identity,
- fd_pubkey_t const * vote_authority,
- fd_pubkey_t const * vote_acc,
+ fd_hash_t const * bank_hash,
+ fd_hash_t const * recent_blockhash,
+ fd_pubkey_t const * validator_identity,
+ fd_pubkey_t const * vote_authority,
+ fd_pubkey_t const * vote_account,
fd_txn_p_t * vote_txn );
-/* fd_tower_checkpt bincode-serializes tower into the provided file
- descriptor. Returns 0 on success meaning the above has been
- successfully written to fd, -1 if an error occurred during
- checkpointing. Assumes tower is non-empty and a valid local join of
- the current tower, root is the current tower root, last_vote is the
- serde of the last vote sent, pvtkey / pubkey is the validator
- identity keypair, fd is a valid open file descriptor to write to, buf
- is a buffer of at least buf_max bytes to serialize to and write from.
-
- The binary layout of the file is compatible with Agave and specified
- in bincode. fd_tower_serde_t describes the schema. Firedancer can
- restore towers checkpointed by Agave (>=2.3.7) and vice versa.
-
- IMPORTANT SAFETY TIP! No other process should be writing to either
- the memory pointed by tower or the file pointed to by path while
- fd_tower_checkpt is in progress. */
-
-int
-fd_tower_checkpt( fd_tower_t const * tower,
- ulong root,
- fd_tower_sync_serde_t * last_vote,
- uchar const pubkey[static 32],
- fd_tower_sign_fn * sign_fn,
- int fd,
- uchar * buf,
- ulong buf_max );
-
-/* fd_tower_restore restores tower from the bytes pointed to by restore.
- Returns 0 on success, -1 on error. Assumes tower is a valid local
- join of an empty tower, pubkey is the identity key of the validator
- associated with the tower, and fd is a valid open file descriptor to
- read from. On return, the state of the tower, tower root and tower
- timestamp as of the time the file was checkpointed will be restored
- into tower, root and ts respectively. Any errors encountered during
- restore are logged with as informative an error message as can be
- contextualized before returning -1.
-
- The binary layout of the file is compatible with Agave and specified
- in bincode. fd_tower_serde_t describes the schema. Firedancer can
- restore towers checkpointed by Agave (>=2.3.7) and vice versa.
-
- IMPORTANT SAFETY TIP! No other process should be writing to either
- the memory pointed by tower or the file pointed to by path while
- fd_tower_restore is in progress. */
-
-int
-fd_tower_restore( fd_tower_t * tower,
- ulong * root,
- long * ts,
- uchar const pubkey[static 32],
- int fd,
- uchar * buf,
- ulong buf_max,
- ulong * buf_sz );
-
-/* fd_tower_serialize serializes the provided serde into buf. Returns 0
- on success, -1 if an error is encountered during serialization.
- Assumes serde is populated with valid local pointers to values to
- serialize (NULL if a field should be skipped) and buf is at least as
- large as the serialized size of ser. Populates the number of bytes
- serialized in buf_sz.
-
- serde is a zero-copy view of the Agave-compatible bincode-encoding of
- a tower. See also the struct definition of fd_tower_serde_t. */
-
-int
-fd_tower_serialize( fd_tower_file_serde_t * ser,
- uchar * buf,
- ulong buf_max,
- ulong * buf_sz );
-
-/* fd_tower_deserialize deserializes buf into serde. Returns 0 on
- success, -1 if an error is encountered during deserialization. buf
- must be at least as large as is required during bincode-decoding of
- ser otherwise returns an error.
-
- serde is a zero-copy view of the Agave-compatible bincode-encoding of
- a tower. See also the struct definition of fd_tower_serde_t. */
-
-int
-fd_tower_deserialize( uchar * buf,
- ulong buf_sz,
- fd_tower_file_serde_t * de );
-
/* fd_tower_verify checks tower is in a valid state. Valid iff:
- cnt < FD_TOWER_VOTE_MAX
- vote slots and confirmation counts in the tower are monotonically
@@ -897,37 +539,21 @@ fd_tower_deserialize( uchar * buf,
int
fd_tower_verify( fd_tower_t const * tower );
-/* fd_tower_on_duplicate checks if the tower is on the same fork with an
- invalid ancestor. */
-
-int
-fd_tower_on_duplicate( fd_tower_t const * tower, fd_ghost_t const * ghost );
-
/* fd_tower_print pretty-prints tower as a formatted table.
Sample output:
slot | confirmation count
--------- | ------------------
- 279803918 | 1
- 279803917 | 2
- 279803916 | 3
- 279803915 | 4
- 279803914 | 5
- 279803913 | 6
- 279803912 | 7
+ 279803931 | 1
+ 279803930 | 2
+ ...
+ 279803901 | 31
+ 279803900 | root
*/
void
-fd_tower_print( fd_tower_t const * tower, ulong root );
-
-/* fd_tower_from_vote_acc_data reads into the given tower the given vote account data.
- The vote account data will be deserialized from the Solana vote account
- representation, and the tower will be populated with the votes.
-
- Assumes tower is a valid local join and currently empty. */
-void
-fd_tower_from_vote_acc_data( uchar const * data,
- fd_tower_t * tower_out );
+fd_tower_print( fd_tower_t const * tower,
+ ulong root );
#endif /* HEADER_fd_src_choreo_tower_fd_tower_h */
diff --git a/src/choreo/tower/fd_tower_accts.h b/src/choreo/tower/fd_tower_accts.h
new file mode 100644
index 00000000000..b6b1e53e03b
--- /dev/null
+++ b/src/choreo/tower/fd_tower_accts.h
@@ -0,0 +1,23 @@
+#ifndef HEADER_fd_src_choreo_tower_fd_tower_accts_h
+#define HEADER_fd_src_choreo_tower_fd_tower_accts_h
+
+#include "../fd_choreo_base.h"
+
+/* fd_tower_accts describes the set of vote accounts that feed into
+ TowerBFT rules. This is fixed for each epoch, and each acct is
+ associated with a 3-tuple of (vote account address, vote account
+ stake, and vote account data). All the accts in the deque are
+ intended to be as of the same slot. */
+
+struct fd_tower_accts {
+ fd_pubkey_t addr; /* vote account address */
+ ulong stake; /* vote account stake */
+ uchar data[3762]; /* vote account data (max 3762 bytes) */
+};
+typedef struct fd_tower_accts fd_tower_accts_t;
+
+#define DEQUE_NAME fd_tower_accts
+#define DEQUE_T fd_tower_accts_t
+#include "../../util/tmpl/fd_deque_dynamic.c"
+
+#endif /* HEADER_fd_src_choreo_tower_fd_tower_accts_h */
diff --git a/src/choreo/tower/fd_tower_forks.c b/src/choreo/tower/fd_tower_forks.c
new file mode 100644
index 00000000000..3c651dacac5
--- /dev/null
+++ b/src/choreo/tower/fd_tower_forks.c
@@ -0,0 +1,53 @@
+#include "fd_tower_forks.h"
+
+int
+is_ancestor( fd_tower_forks_t * forks,
+ ulong slot,
+ ulong ancestor_slot ) {
+ fd_tower_forks_t * anc = fd_tower_forks_query( forks, slot, NULL );
+ while( FD_LIKELY( anc ) ) {
+ if( FD_LIKELY( anc->parent_slot == ancestor_slot ) ) return 1;
+ anc = fd_tower_forks_query( forks, anc->parent_slot, NULL );
+ }
+ return 0;
+}
+
+int
+fd_tower_forks_is_slot_ancestor( fd_tower_forks_t * forks,
+ ulong descendant_slot,
+ ulong ancestor_slot ) {
+ return is_ancestor( forks, descendant_slot, ancestor_slot );
+}
+
+int
+fd_tower_forks_is_slot_descendant( fd_tower_forks_t * forks,
+ ulong ancestor_slot,
+ ulong descendant_slot ) {
+ return is_ancestor( forks, descendant_slot, ancestor_slot );
+}
+
+ulong
+fd_tower_forks_lowest_common_ancestor( fd_tower_forks_t * forks,
+ ulong slot1,
+ ulong slot2 ) {
+ fd_tower_forks_t * fork1 = fd_tower_forks_query( forks, slot1, NULL );
+ fd_tower_forks_t * fork2 = fd_tower_forks_query( forks, slot2, NULL );
+
+ while( FD_LIKELY( fork1 && fork2 ) ) {
+ if( FD_UNLIKELY( fork1->slot == fork2->slot ) ) return fork1->slot;
+ if( fork1->slot > fork2->slot ) fork1 = fd_tower_forks_query( forks, fork1->parent_slot, NULL );
+ else fork2 = fd_tower_forks_query( forks, fork2->parent_slot, NULL );
+ }
+ FD_LOG_CRIT(( "invalid forks" ));
+}
+
+fd_hash_t const *
+fd_tower_forks_canonical_block_id( fd_tower_forks_t * forks,
+ ulong slot ) {
+ fd_tower_forks_t * fork = fd_tower_forks_query( forks, slot, NULL );
+ if( FD_UNLIKELY( !fork ) ) return NULL;
+ if ( FD_LIKELY( fork->confirmed ) ) return &fork->confirmed_block_id;
+ else if( FD_LIKELY( fork->voted ) ) return &fork->voted_block_id;
+ else if( FD_LIKELY( fork->replayed ) ) return &fork->replayed_block_id;
+ else return NULL;
+}
diff --git a/src/choreo/tower/fd_tower_forks.h b/src/choreo/tower/fd_tower_forks.h
new file mode 100644
index 00000000000..d797641d53f
--- /dev/null
+++ b/src/choreo/tower/fd_tower_forks.h
@@ -0,0 +1,86 @@
+#ifndef HEADER_fd_src_choreo_tower_fd_tower_forks_h
+#define HEADER_fd_src_choreo_tower_fd_tower_forks_h
+
+#include "../fd_choreo_base.h"
+
+/* fd_tower_forks maintains fork information for tower, such as whether
+ slots are on the same or different forks. Importantly, parentage is
+ based purely on slot numbers as opposed to block ids, so in cases of
+ equivocation (duplicate blocks), tower will consider something an
+ ancestor or descendant even if the block ids do not chain. This is
+ different from fd_ghost and fd_notar, which both track block ids.
+
+ Instead, fd_tower_forks maintains two block_id fields on every slot.
+ The first is the block_id that we voted on for that slot. In case of
+ duplicates, this is the first version of the block we replayed and
+ voted on. The second is the block_id that was duplicate confirmed
+ (voted on by >=52% of stake). This may or may not equal the block_id
+ we voted on. It also may or may not be populated. It is possible
+ but highly unlikely for confirmed_block_id to never be populated
+ before the slot is pruned during rooting.
+
+ This behavior intentionally mirrors the Agave logic implemented in
+ `make_check_switch_threshold_decision`. Essentially, tower is unable
+ to distinguish duplicates because the vote account format (in which
+ towers are stored) only stores slot numbers and not block_ids. */
+
+struct fd_tower_forks {
+ ulong slot; /* map key */
+ ulong parent_slot; /* parent slot */
+ int confirmed; /* whether this slot has been duplicate confirmed */
+ fd_hash_t confirmed_block_id; /* the block_id that was duplicate confirmed */
+ int voted; /* whether we voted for this slot yet */
+ fd_hash_t voted_block_id; /* the block_id we voted on for this slot */
+ int replayed; /* whether we replayed this slot yet */
+ fd_hash_t replayed_block_id; /* the block_id we _first_ replayed for this slot */
+};
+typedef struct fd_tower_forks fd_tower_forks_t;
+
+#define MAP_NAME fd_tower_forks
+#define MAP_T fd_tower_forks_t
+#define MAP_KEY slot
+#define MAP_MEMOIZE 0
+#include "../../util/tmpl/fd_map_dynamic.c"
+
+FD_PROTOTYPES_BEGIN
+
+int
+fd_tower_forks_is_slot_ancestor( fd_tower_forks_t * forks,
+ ulong descendant_slot,
+ ulong ancestor_slot );
+
+int
+fd_tower_forks_is_slot_descendant( fd_tower_forks_t * forks,
+ ulong ancestor_slot,
+ ulong descendant_slot );
+
+/* fd_tower_forks_lowest_common_ancestor returns the lowest common
+ ancestor of slot1 and slot 2. There is always an LCA in a valid
+ tower_forks tree (the root). */
+
+ulong
+fd_tower_forks_lowest_common_ancestor( fd_tower_forks_t * forks,
+ ulong slot1,
+ ulong slot2 );
+
+/* fd_tower_forks_canonical_block_id returns what we think to be the
+ correct block id for a given slot, based on what we've observed.
+
+ We prioritize in-order:
+ 1. the confirmed block id
+ 2. our voted block id
+ 3. replayed block id
+
+ This is the canonical order because it reflects what we think is the
+ "true" block id given the information we have.
+
+ Agave behaves similarly, except they "purge" their replay bank hash
+ so they're always comparing the confirmed block id */
+
+fd_hash_t const *
+fd_tower_forks_canonical_block_id( fd_tower_forks_t * forks,
+ ulong slot );
+
+FD_PROTOTYPES_END
+
+#endif /* HEADER_fd_src_choreo_tower_fd_tower_forks_h */
diff --git a/src/choreo/tower/fd_tower_private.h b/src/choreo/tower/fd_tower_private.h
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/choreo/tower/fd_tower_serde.c b/src/choreo/tower/fd_tower_serde.c
new file mode 100644
index 00000000000..32fc1418ce7
--- /dev/null
+++ b/src/choreo/tower/fd_tower_serde.c
@@ -0,0 +1,63 @@
+#include "fd_tower_serde.h"
+#include "fd_tower.h"
+
+#define SHORTVEC 0
+
+#define DE( T, name ) do { \
+ if( FD_UNLIKELY( off+sizeof(T)>buf_sz ) ) { \
+ FD_LOG_WARNING(( "de %s: overflow (off %lu > buf_sz: %lu)", #name, off, buf_sz )); \
+ return -1; \
+ } \
+ serde->name = *(T const *)fd_type_pun_const( buf+off ); \
+ off += sizeof(T); \
+} while(0)
+
+static ulong
+de_short_u16( ushort * dst, uchar const * src ) {
+ if ( FD_LIKELY( !(0x80U & src[0]) ) ) { *dst = (ushort)src[0]; return 1; }
+ else if( FD_LIKELY( !(0x80U & src[1]) ) ) { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)src[1])<<7)); return 2; }
+ else { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)(src[1]&0x7FUL))<<7) + (((ulong)src[2])<<14)); return 3; }
+}
+
+static ulong
+de_var_int( ulong * dst, uchar const * src ) {
+ *dst = 0;
+ ulong off = 0;
+ ulong bit = 0;
+ while( FD_LIKELY( bit < 64 ) ) {
+ uchar byte = *(uchar const *)(src+off);
+ off += 1;
+ *dst |= (byte & 0x7FUL) << bit;
+ if( FD_LIKELY( (byte & 0x80U) == 0U ) ) {
+ if( FD_UNLIKELY( (*dst>>bit) != byte ) ) FD_LOG_CRIT(( "de_varint" ));
+ if( FD_UNLIKELY( byte==0U && (bit!=0U || *dst!=0UL) ) ) FD_LOG_CRIT(( "de_varint" ));
+ return off;
+ }
+ bit += 7;
+ }
+ FD_LOG_CRIT(( "de_varint" ));
+}
+
+int
+fd_compact_tower_sync_deserialize( fd_compact_tower_sync_serde_t * serde,
+ uchar const * buf,
+ ulong buf_sz ) {
+ ulong off = 0;
+ DE( ulong, root );
+ off += de_short_u16( &serde->lockouts_cnt, buf+off );
+ if( FD_UNLIKELY( serde->lockouts_cnt > FD_TOWER_VOTE_MAX ) ) {
+ FD_LOG_WARNING(( "lockouts_cnt > 31: %u", serde->lockouts_cnt ));
+ return -1;
+ }
+ for( ulong i = 0; i < fd_ulong_min( serde->lockouts_cnt, 31 ); i++ ) {
+ off += de_var_int( &serde->lockouts[i].offset, buf+off );
+ DE( uchar, lockouts[i].confirmation_count );
+ }
+ DE( fd_hash_t, hash );
+ DE( uchar, timestamp_option );
+ if( FD_LIKELY( serde->timestamp_option ) ) {
+ DE( long, timestamp );
+ }
+ DE( fd_hash_t, block_id );
+ return 0;
+}
diff --git a/src/choreo/tower/fd_tower_serde.h b/src/choreo/tower/fd_tower_serde.h
new file mode 100644
index 00000000000..81063beef73
--- /dev/null
+++ b/src/choreo/tower/fd_tower_serde.h
@@ -0,0 +1,38 @@
+#ifndef HEADER_fd_src_choreo_tower_fd_tower_serde_h
+#define HEADER_fd_src_choreo_tower_fd_tower_serde_h
+
+#include "../fd_choreo_base.h"
+#include "../voter/fd_voter_serde.h"
+
+#define FD_VOTE_IX_KIND_TOWER_SYNC (14)
+#define FD_VOTE_IX_KIND_TOWER_SYNC_SWITCH (15)
+
+/* fd_compact_tower_sync_serde describes the serialization /
+ deserialization schema of a CompactTowerSync vote instruction. There
+ are various legacy instructions for vote transactions, but current
+ mainnet votes are almost exclusively this instruction. */
+
+struct fd_compact_tower_sync_serde /* CompactTowerSync */ {
+ ulong root;
+ struct /* ShortVec */ {
+ ushort lockouts_cnt; /* ShortU16 */
+ struct /* Lockout */ {
+ ulong offset; /* VarInt */
+ uchar confirmation_count;
+ } lockouts[31];
+ };
+ fd_hash_t hash; /* bank hash */
+ struct /* Option */ {
+ uchar timestamp_option;
+ long timestamp; /* UnixTimestamp */
+ };
+ fd_hash_t block_id;
+};
+typedef struct fd_compact_tower_sync_serde fd_compact_tower_sync_serde_t;
+
+int
+fd_compact_tower_sync_deserialize( fd_compact_tower_sync_serde_t * serde,
+ uchar const * buf,
+ ulong buf_sz );
+
+#endif /* HEADER_fd_src_choreo_tower_fd_tower_serde_h */
diff --git a/src/choreo/tower/test_tower.c b/src/choreo/tower/test_tower.c
index a364b1ed60a..eeb53c7731b 100644
--- a/src/choreo/tower/test_tower.c
+++ b/src/choreo/tower/test_tower.c
@@ -1,232 +1,232 @@
#include "fd_tower.h"
-#include "test_tower.h"
-
-static uchar scratch[ FD_TOWER_FOOTPRINT ] __attribute__((aligned(FD_TOWER_ALIGN)));
-
-void
-test_vote( void ) {
- fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
- FD_TEST( tower );
-
- /* Add some votes to the tower
-
- (0, 31) expiration = 0 + 1<<31
- (1, 30) expiration = 1 + 1<<30
- (2, 29) expiration = 2 + 1<<29
- ..
- (28, 3) expiration = 28 + 1<<3 = 36
- (29, 2) expiration = 29 + 1<<2 = 33
- (30, 1) expiration = 30 + 1<<1 = 32 */
-
- for( ulong i = 0; i < 31; i++ ) {
- fd_tower_vote( tower, i );
- FD_TEST( fd_tower_votes_cnt( tower ) == i + 1 );
- }
- for( ulong i = 0; i < 31; i++ ) {
- fd_tower_vote_t expected_vote = { .slot = i, .conf = 31 - i };
- fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i );
- FD_TEST( expected_vote.slot == actual_vote->slot );
- FD_TEST( expected_vote.conf == actual_vote->conf );
- }
-
- /* CASE 1: NEW VOTE WHICH REPLACES EXPIRED VOTE */
-
- /* Test expiration
-
- A vote for 33 should make the vote for 30 expire.
- A full tower has 31 votes. One expired vote => 30 remaining. */
-
- ulong new_vote_expiry = 33;
- ulong vote_cnt = fd_tower_simulate_vote( tower, new_vote_expiry );
- FD_TEST( vote_cnt == 30 );
-
- /* Test slots 1 through 30 are unchanged after voting */
-
- fd_tower_vote( tower, new_vote_expiry );
- for( ulong i = 0; i < 30; i++ ) {
- fd_tower_vote_t expected_vote = { .slot = i, .conf = 31 - i };
- fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i );
- FD_TEST( expected_vote.slot == actual_vote->slot );
- FD_TEST( expected_vote.conf == actual_vote->conf );
- }
-
- /* Check new vote */
-
- fd_tower_vote_t expected_vote = { .slot = new_vote_expiry, .conf = 1 };
- fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, 30 );
- FD_TEST( expected_vote.slot == actual_vote->slot );
- FD_TEST( expected_vote.conf == actual_vote->conf );
-
- /* CASE 2: NEW VOTE WHICH PRODUCES NEW ROOT */
-
- ulong new_vote_root = 34;
- FD_TEST( fd_tower_vote( tower, new_vote_root ) == 0 );
-
- /* Check all existing votes were repositioned one index lower and one
- confirmation higher. */
-
- for( ulong i = 0; i < 29 /* one of the original slots was rooted */; i++ ) {
- fd_tower_vote_t expected_vote = { .slot = i + 1, .conf = 31 - i };
- fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i );
- FD_TEST( expected_vote.slot == actual_vote->slot );
- FD_TEST( expected_vote.conf == actual_vote->conf );
- }
-
- /* Check new vote in the tower. */
-
- fd_tower_vote_t expected_vote_root = { .slot = new_vote_root, .conf = 1 };
- fd_tower_vote_t * actual_vote_root = fd_tower_votes_peek_index( tower, 30 );
- FD_TEST( expected_vote_root.slot == actual_vote_root->slot );
- FD_TEST( expected_vote_root.conf == actual_vote_root->conf );
-
- fd_tower_delete( fd_tower_leave( tower ) );
-}
-
-
-void
-test_tower_from_vote_acc_data_v1_14_11( void ) {
- fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
- FD_TEST( tower );
-
- fd_tower_from_vote_acc_data( v1_14_11, tower );
-
- fd_tower_vote_t expected_votes[31] = {
- { 159175525, 31 },
- { 159175526, 30 },
- { 159175527, 29 },
- { 159175528, 28 },
- { 159175529, 27 },
- { 159175530, 26 },
- { 159175531, 25 },
- { 159175532, 24 },
- { 159175533, 23 },
- { 159175534, 22 },
- { 159175535, 21 },
- { 159175536, 20 },
- { 159175537, 19 },
- { 159175538, 18 },
- { 159175539, 17 },
- { 159175540, 16 },
- { 159175541, 15 },
- { 159175542, 14 },
- { 159175543, 13 },
- { 159175544, 12 },
- { 159175545, 11 },
- { 159175546, 10 },
- { 159175547, 9 },
- { 159175548, 8 },
- { 159175549, 7 },
- { 159175550, 6 },
- { 159175551, 5 },
- { 159175552, 4 },
- { 159175553, 3 },
- { 159175554, 2 },
- { 159175555, 1 },
- };
-
- FD_TEST( fd_tower_votes_cnt( tower ) == 31UL );
- ulong expected_idx = 0UL;
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
- !fd_tower_votes_iter_done( tower, iter );
- iter = fd_tower_votes_iter_next( tower, iter ) ) {
- fd_tower_vote_t * actual_vote = fd_tower_votes_iter_ele( tower, iter );
- fd_tower_vote_t * expected_vote = &expected_votes[ expected_idx++ ];
- FD_TEST( expected_vote->slot == actual_vote->slot );
- FD_TEST( expected_vote->conf == actual_vote->conf );
- }
-}
-
-void
-test_tower_from_vote_acc_data_current( void ) {
- fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
- FD_TEST( tower );
-
- fd_tower_from_vote_acc_data( current, tower );
-
- fd_tower_vote_t expected_votes[31] = {
- { 285373759, 31 },
- { 285373760, 30 },
- { 285373761, 29 },
- { 285373762, 28 },
- { 285373763, 27 },
- { 285373764, 26 },
- { 285373765, 25 },
- { 285373766, 24 },
- { 285373767, 23 },
- { 285373768, 22 },
- { 285373769, 21 },
- { 285373770, 20 },
- { 285373771, 19 },
- { 285373772, 18 },
- { 285373773, 17 },
- { 285373780, 16 },
- { 285373781, 15 },
- { 285373782, 14 },
- { 285373783, 13 },
- { 285373784, 12 },
- { 285373785, 11 },
- { 285373786, 10 },
- { 285373787, 9 },
- { 285373788, 8 },
- { 285373789, 7 },
- { 285373790, 6 },
- { 285373791, 5 },
- { 285373792, 4 },
- { 285373793, 3 },
- { 285373794, 2 },
- { 285373795, 1 },
- };
-
- FD_TEST( fd_tower_votes_cnt( tower ) == 31UL );
- ulong expected_idx = 0UL;
- for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
- !fd_tower_votes_iter_done( tower, iter );
- iter = fd_tower_votes_iter_next( tower, iter ) ) {
- fd_tower_vote_t * actual_vote = fd_tower_votes_iter_ele( tower, iter );
- fd_tower_vote_t * expected_vote = &expected_votes[ expected_idx++ ];
- FD_TEST( expected_vote->slot == actual_vote->slot );
- FD_TEST( expected_vote->conf == actual_vote->conf );
- }
-}
-
-void
-test_tower_checkpt( void ) {
- fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
- FD_TEST( tower );
- // ulong root;
- // uchar const pubkey[32] = { 0x32, 0x73, 0x61, 0x45, 0x02, 0x2d, 0x33, 0x72, 0x48, 0x01, 0x79, 0x11, 0x0d, 0x30, 0x71, 0x7e, 0xef, 0xf4, 0xf2, 0x84, 0xca, 0xe7, 0x6a, 0xbe, 0x4c, 0xaa, 0x77, 0x38, 0xda, 0xad, 0x06, 0x2b };
-
- // fd_tower_restore( tower, &root, &vote_state, &last_vote, &last_timestamp, pubkey, checkpt, sizeof(checkpt) );
-}
-
-void
-test_serde( void ) {
- fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
- FD_TEST( tower );
-
- // uchar const pubkey[32] = { 0x32, 0x73, 0x61, 0x45, 0x02, 0x2d, 0x33, 0x72, 0x48, 0x01, 0x79, 0x11, 0x0d, 0x30, 0x71, 0x7e, 0xef, 0xf4, 0xf2, 0x84, 0xca, 0xe7, 0x6a, 0xbe, 0x4c, 0xaa, 0x77, 0x38, 0xda, 0xad, 0x06, 0x2b };
-
- fd_tower_file_serde_t serde = { 0 };
- fd_tower_deserialize( restore, sizeof(restore), &serde );
-
- uchar checkpt[sizeof(restore)];
- ulong checkpt_sz;
- fd_tower_serialize( &serde, checkpt, sizeof(checkpt), &checkpt_sz );
-
- FD_TEST( sizeof(restore) == checkpt_sz );
- FD_TEST( fd_uint_load_4( restore ) == fd_uint_load_4( checkpt ) );
-
- ulong off = sizeof(uint) + FD_ED25519_SIG_SZ + sizeof(ulong);
- FD_TEST( fd_uint_load_4_fast( restore )==fd_uint_load_4_fast( checkpt ) ); /* kind */
- /* skip comparing sig and data_sz (populated outside serialize) */
- FD_TEST( 0==memcmp( restore + off, checkpt + off, sizeof(restore) - off ) );
-}
+// #include "test_tower.h"
+
+// static uchar scratch[ FD_TOWER_FOOTPRINT ] __attribute__((aligned(FD_TOWER_ALIGN)));
+
+// void
+// test_vote( void ) {
+// fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
+// FD_TEST( tower );
+
+// /* Add some votes to the tower
+
+// (0, 31) expiration = 0 + 1<<31
+// (1, 30) expiration = 1 + 1<<30
+// (2, 29) expiration = 2 + 1<<29
+// ..
+// (28, 3) expiration = 28 + 1<<3 = 36
+// (29, 2) expiration = 29 + 1<<2 = 33
+// (30, 1) expiration = 30 + 1<<1 = 32 */
+
+// for( ulong i = 0; i < 31; i++ ) {
+// fd_tower_push_vote( tower, i );
+// FD_TEST( fd_tower_votes_cnt( tower ) == i + 1 );
+// }
+// for( ulong i = 0; i < 31; i++ ) {
+// fd_tower_vote_t expected_vote = { .slot = i, .conf = 31 - i };
+// fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i );
+// FD_TEST( expected_vote.slot == actual_vote->slot );
+// FD_TEST( expected_vote.conf == actual_vote->conf );
+// }
+
+// /* CASE 1: NEW VOTE WHICH REPLACES EXPIRED VOTE */
+
+// /* Test expiration
+
+// A vote for 33 should make the vote for 30 expire.
+// A full tower has 31 votes. One expired vote => 30 remaining. */
+
+// ulong new_vote_expiry = 33;
+// ulong vote_cnt = fd_tower_simulate_vote( tower, new_vote_expiry );
+// FD_TEST( vote_cnt == 30 );
+
+// /* Test slots 1 through 30 are unchanged after voting */
+
+// fd_tower_push_vote( tower, new_vote_expiry );
+// for( ulong i = 0; i < 30; i++ ) {
+// fd_tower_vote_t expected_vote = { .slot = i, .conf = 31 - i };
+// fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i );
+// FD_TEST( expected_vote.slot == actual_vote->slot );
+// FD_TEST( expected_vote.conf == actual_vote->conf );
+// }
+
+// /* Check new vote */
+
+// fd_tower_vote_t expected_vote = { .slot = new_vote_expiry, .conf = 1 };
+// fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, 30 );
+// FD_TEST( expected_vote.slot == actual_vote->slot );
+// FD_TEST( expected_vote.conf == actual_vote->conf );
+
+// /* CASE 2: NEW VOTE WHICH PRODUCES NEW ROOT */
+
+// ulong new_vote_root = 34;
+// FD_TEST( fd_tower_push_vote( tower, new_vote_root ) == 0 );
+
+// /* Check all existing votes were repositioned one index lower and one
+// confirmation higher. */
+
+// for( ulong i = 0; i < 29 /* one of the original slots was rooted */; i++ ) {
+// fd_tower_vote_t expected_vote = { .slot = i + 1, .conf = 31 - i };
+// fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i );
+// FD_TEST( expected_vote.slot == actual_vote->slot );
+// FD_TEST( expected_vote.conf == actual_vote->conf );
+// }
+
+// /* Check new vote in the tower. */
+
+// fd_tower_vote_t expected_vote_root = { .slot = new_vote_root, .conf = 1 };
+// fd_tower_vote_t * actual_vote_root = fd_tower_votes_peek_index( tower, 30 );
+// FD_TEST( expected_vote_root.slot == actual_vote_root->slot );
+// FD_TEST( expected_vote_root.conf == actual_vote_root->conf );
+
+// fd_tower_delete( fd_tower_leave( tower ) );
+// }
+
+
+// void
+// test_tower_from_vote_acc_data_v1_14_11( void ) {
+// fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
+// FD_TEST( tower );
+
+// fd_tower_from_vote_acc( v1_14_11, tower );
+
+// fd_tower_vote_t expected_votes[31] = {
+// { 159175525, 31 },
+// { 159175526, 30 },
+// { 159175527, 29 },
+// { 159175528, 28 },
+// { 159175529, 27 },
+// { 159175530, 26 },
+// { 159175531, 25 },
+// { 159175532, 24 },
+// { 159175533, 23 },
+// { 159175534, 22 },
+// { 159175535, 21 },
+// { 159175536, 20 },
+// { 159175537, 19 },
+// { 159175538, 18 },
+// { 159175539, 17 },
+// { 159175540, 16 },
+// { 159175541, 15 },
+// { 159175542, 14 },
+// { 159175543, 13 },
+// { 159175544, 12 },
+// { 159175545, 11 },
+// { 159175546, 10 },
+// { 159175547, 9 },
+// { 159175548, 8 },
+// { 159175549, 7 },
+// { 159175550, 6 },
+// { 159175551, 5 },
+// { 159175552, 4 },
+// { 159175553, 3 },
+// { 159175554, 2 },
+// { 159175555, 1 },
+// };
+
+// FD_TEST( fd_tower_votes_cnt( tower ) == 31UL );
+// ulong expected_idx = 0UL;
+// for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
+// !fd_tower_votes_iter_done( tower, iter );
+// iter = fd_tower_votes_iter_next( tower, iter ) ) {
+// fd_tower_vote_t * actual_vote = fd_tower_votes_iter_ele( tower, iter );
+// fd_tower_vote_t * expected_vote = &expected_votes[ expected_idx++ ];
+// FD_TEST( expected_vote->slot == actual_vote->slot );
+// FD_TEST( expected_vote->conf == actual_vote->conf );
+// }
+// }
+
+// void
+// test_tower_from_vote_acc_data_current( void ) {
+// fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
+// FD_TEST( tower );
+
+// fd_tower_from_vote_acc( current, tower );
+
+// fd_tower_vote_t expected_votes[31] = {
+// { 285373759, 31 },
+// { 285373760, 30 },
+// { 285373761, 29 },
+// { 285373762, 28 },
+// { 285373763, 27 },
+// { 285373764, 26 },
+// { 285373765, 25 },
+// { 285373766, 24 },
+// { 285373767, 23 },
+// { 285373768, 22 },
+// { 285373769, 21 },
+// { 285373770, 20 },
+// { 285373771, 19 },
+// { 285373772, 18 },
+// { 285373773, 17 },
+// { 285373780, 16 },
+// { 285373781, 15 },
+// { 285373782, 14 },
+// { 285373783, 13 },
+// { 285373784, 12 },
+// { 285373785, 11 },
+// { 285373786, 10 },
+// { 285373787, 9 },
+// { 285373788, 8 },
+// { 285373789, 7 },
+// { 285373790, 6 },
+// { 285373791, 5 },
+// { 285373792, 4 },
+// { 285373793, 3 },
+// { 285373794, 2 },
+// { 285373795, 1 },
+// };
+
+// FD_TEST( fd_tower_votes_cnt( tower ) == 31UL );
+// ulong expected_idx = 0UL;
+// for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
+// !fd_tower_votes_iter_done( tower, iter );
+// iter = fd_tower_votes_iter_next( tower, iter ) ) {
+// fd_tower_vote_t * actual_vote = fd_tower_votes_iter_ele( tower, iter );
+// fd_tower_vote_t * expected_vote = &expected_votes[ expected_idx++ ];
+// FD_TEST( expected_vote->slot == actual_vote->slot );
+// FD_TEST( expected_vote->conf == actual_vote->conf );
+// }
+// }
+
+// void
+// test_tower_checkpt( void ) {
+// fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
+// FD_TEST( tower );
+// // ulong root;
+// // uchar const pubkey[32] = { 0x32, 0x73, 0x61, 0x45, 0x02, 0x2d, 0x33, 0x72, 0x48, 0x01, 0x79, 0x11, 0x0d, 0x30, 0x71, 0x7e, 0xef, 0xf4, 0xf2, 0x84, 0xca, 0xe7, 0x6a, 0xbe, 0x4c, 0xaa, 0x77, 0x38, 0xda, 0xad, 0x06, 0x2b };
+
+// // fd_tower_restore( tower, &root, &vote_state, &last_vote, &last_timestamp, pubkey, checkpt, sizeof(checkpt) );
+// }
+
+// void
+// test_serde( void ) {
+// fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) );
+// FD_TEST( tower );
+
+// // uchar const pubkey[32] = { 0x32, 0x73, 0x61, 0x45, 0x02, 0x2d, 0x33, 0x72, 0x48, 0x01, 0x79, 0x11, 0x0d, 0x30, 0x71, 0x7e, 0xef, 0xf4, 0xf2, 0x84, 0xca, 0xe7, 0x6a, 0xbe, 0x4c, 0xaa, 0x77, 0x38, 0xda, 0xad, 0x06, 0x2b };
+
+// // fd_tower_file_serde_t serde = { 0 };
+// // fd_tower_deserialize( restore, sizeof(restore), &serde );
+
+// // uchar checkpt[sizeof(restore)];
+// // ulong checkpt_sz;
+// // fd_tower_serialize( &serde, checkpt, sizeof(checkpt), &checkpt_sz );
+
+// // FD_TEST( sizeof(restore) == checkpt_sz );
+// // FD_TEST( fd_uint_load_4( restore ) == fd_uint_load_4( checkpt ) );
+
+// // ulong off = sizeof(uint) + FD_ED25519_SIG_SZ + sizeof(ulong);
+// // FD_TEST( fd_uint_load_4_fast( restore )==fd_uint_load_4_fast( checkpt ) ); /* kind */
+// // /* skip comparing sig and data_sz (populated outside serialize) */
+// // FD_TEST( 0==memcmp( restore + off, checkpt + off, sizeof(restore) - off ) );
+// }
int
main( int argc, char ** argv ) {
fd_boot( &argc, &argv );
- test_serde();
+ // test_serde();
// fd_tower_restore( NULL, pubkey, );
// test_tower_vote();
// test_tower_from_vote_acc_data_v1_14_11();
diff --git a/src/choreo/tower/test_tower.h b/src/choreo/tower/test_tower.h
index f25b7abe2be..34887a86425 100644
--- a/src/choreo/tower/test_tower.h
+++ b/src/choreo/tower/test_tower.h
@@ -2,4 +2,4 @@
static uchar v1_14_11[3731] = { 0x01, 0x00, 0x00, 0x00, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x64, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x66, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x67, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x68, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x69, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x6a, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x6b, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x6c, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x6d, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x6f, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x70, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x71, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x72, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x73, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x74, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x75, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x76, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x77, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x78, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x79, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x7a, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x7b, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x7c, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x7d, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7e, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x7f, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x80, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x81, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x82, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x83, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x64, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xd1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x49, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xd1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xee, 0x56, 0x63 };
static uchar current [3762] = { 0x02, 0x00, 0x00, 0x00, 0x44, 0x0a, 0xe1, 0x3c, 0x0e, 0xea, 0x91, 0x74, 0xc2, 0x76, 0xc3, 0xfd,0xb1, 0x10, 0x62, 0x2f, 0xc6, 0x70, 0xae, 0xbe, 0x45, 0xda, 0x79, 0xd4, 0x85, 0x5d, 0xfd, 0x5b,0x57, 0x26, 0x0a, 0x6e, 0x8b, 0x0b, 0x5b, 0x3e, 0x52, 0x1b, 0x1d, 0xa5, 0x59, 0x98, 0x40, 0xbc,0xf0, 0x0f, 0x64, 0xe2, 0x22, 0xb6, 0x27, 0xfb, 0x55, 0x7a, 0x38, 0x4a, 0x43, 0xf9, 0x65, 0x0d,0x84, 0xcd, 0x41, 0x61, 0x64, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x75,0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x40, 0x75, 0x02, 0x11, 0x00,0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x41, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00,0x1d, 0x00, 0x00, 0x00, 0x00, 0x42, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00,0x00, 0x00, 0x43, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x44,0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x45, 0x75, 0x02, 0x11,0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x46, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00,0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x47, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00,0x00, 0x00, 0x00, 0x48, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00,0x49, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x75, 0x02,0x11, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x75, 0x02, 0x11, 0x00, 0x00,0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x12,0x00, 0x00, 0x00, 0x00, 0x4d, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,0x00, 0x54, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x55, 0x75,0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x75, 0x02, 0x11, 0x00,0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x57, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00,0x0d, 0x00, 0x00, 0x00, 0x00, 0x58, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,0x00, 0x00, 0x59, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x5a,0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x75, 0x02, 0x11,0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00,0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,0x00, 0x00, 0x00, 0x5e, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,0x5f, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x60, 0x75, 0x02,0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x61, 0x75, 0x02, 0x11, 0x00, 0x00,0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x62, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x02,0x00, 0x00, 0x00, 0x00, 0x63, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,0x01, 0x3e, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0xa1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x0a, 0xe1, 0x3c, 0x0e, 0xea, 0x91,0x74, 0xc2, 0x76, 0xc3, 0xfd, 0xb1, 0x10, 0x62, 0x2f, 0xc6, 0x70, 0xae, 0xbe, 0x45, 0xda, 0x79,0xd4, 0x85, 0x5d, 0xfd, 0x5b, 0x57, 0x26, 0x0a, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xfc, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x9b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x50, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xfc, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xab, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x50, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xaa, 0x01, 0x00, 0x00, 0x00,0x00, 0x00, 0xab, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x0c, 0x1e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xaa, 0x01, 0x00, 0x00, 0x00,0x00, 0x00, 0x63, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x70, 0x37, 0x52, 0xc7, 0xd9, 0x56,0xe7, 0x17 };
-static uchar restore [2319] = { 0x1, 0x0, 0x0, 0x0, 0xa9, 0xe1, 0xf5, 0xa, 0xf0, 0x6e, 0x5c, 0xad, 0x6d, 0x5d, 0x9d, 0x4c, 0xc2, 0x4a, 0x19, 0x81, 0x5c, 0xaf, 0x22, 0xa7, 0x2a, 0x74, 0x67, 0xe6, 0xa3, 0x5f, 0x94, 0xb9, 0x8c, 0x67, 0x63, 0x67, 0x31, 0x62, 0xbb, 0xeb, 0xd4, 0xd0, 0x43, 0xfd, 0x72, 0x25, 0x27, 0xa5, 0xfb, 0xd5, 0x13, 0x57, 0x34, 0x7, 0x9b, 0xb4, 0x8c, 0x62, 0x3, 0xb3, 0x8c, 0x7b, 0x9c, 0xf0, 0xb6, 0xe1, 0xea, 0x4, 0xc3, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0x73, 0x61, 0x45, 0x2, 0x2d, 0x33, 0x72, 0x48, 0x1, 0x79, 0x11, 0xd, 0x30, 0x71, 0x7e, 0xef, 0xf4, 0xf2, 0x84, 0xca, 0xe7, 0x6a, 0xbe, 0x4c, 0xaa, 0x77, 0x38, 0xda, 0xad, 0x6, 0x2b, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xe5, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x7c, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x0, 0x0, 0x0, 0x7d, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1d, 0x0, 0x0, 0x0, 0x7e, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x7f, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x0, 0x0, 0x80, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1a, 0x0, 0x0, 0x0, 0x81, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x19, 0x0, 0x0, 0x0, 0x82, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0, 0x83, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x84, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x85, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0x86, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x87, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x88, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x89, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x11, 0x0, 0x0, 0x0, 0x8a, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x8b, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x8c, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xe, 0x0, 0x0, 0x0, 0x8d, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x8e, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x8f, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x90, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x91, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x92, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x93, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x94, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x95, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x96, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x97, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x98, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x99, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7a, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x7a, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1, 0x1f, 0x1, 0x1e, 0x1, 0x1d, 0x1, 0x1c, 0x1, 0x1b, 0x1, 0x1a, 0x1, 0x19, 0x1, 0x18, 0x1, 0x17, 0x1, 0x16, 0x1, 0x15, 0x1, 0x14, 0x1, 0x13, 0x1, 0x12, 0x1, 0x11, 0x1, 0x10, 0x1, 0xf, 0x1, 0xe, 0x1, 0xd, 0x1, 0xc, 0x1, 0xb, 0x1, 0xa, 0x1, 0x9, 0x1, 0x8, 0x1, 0x7, 0x1, 0x6, 0x1, 0x5, 0x1, 0x4, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x62, 0x8a, 0xa1, 0x8e, 0x40, 0xc1, 0x9b, 0x35, 0x23, 0xbb, 0x12, 0x37, 0x1, 0xb6, 0xc3, 0x41, 0xfe, 0x42, 0x10, 0x90, 0xa5, 0x5e, 0x66, 0x6b, 0xc2, 0xfb, 0x7b, 0x44, 0x12, 0x7c, 0xb9, 0x78, 0x1, 0x69, 0xbd, 0xac, 0x68, 0x0, 0x0, 0x0, 0x0, 0x24, 0xcd, 0x77, 0xb0, 0xe1, 0xff, 0x92, 0xf7, 0x91, 0xf4, 0xce, 0xc7, 0xb8, 0xab, 0x69, 0xf5, 0x37, 0xdb, 0xed, 0xdd, 0xf6, 0xd1, 0xf0, 0x35, 0xe8, 0xe2, 0x56, 0xa5, 0xe7, 0xd1, 0x41, 0x9, 0x98, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x69, 0xbd, 0xac, 0x68, 0x0, 0x0, 0x0, 0x0 };
+FD_PARAM_UNUSED static uchar restore [2319] = { 0x1, 0x0, 0x0, 0x0, 0xa9, 0xe1, 0xf5, 0xa, 0xf0, 0x6e, 0x5c, 0xad, 0x6d, 0x5d, 0x9d, 0x4c, 0xc2, 0x4a, 0x19, 0x81, 0x5c, 0xaf, 0x22, 0xa7, 0x2a, 0x74, 0x67, 0xe6, 0xa3, 0x5f, 0x94, 0xb9, 0x8c, 0x67, 0x63, 0x67, 0x31, 0x62, 0xbb, 0xeb, 0xd4, 0xd0, 0x43, 0xfd, 0x72, 0x25, 0x27, 0xa5, 0xfb, 0xd5, 0x13, 0x57, 0x34, 0x7, 0x9b, 0xb4, 0x8c, 0x62, 0x3, 0xb3, 0x8c, 0x7b, 0x9c, 0xf0, 0xb6, 0xe1, 0xea, 0x4, 0xc3, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0x73, 0x61, 0x45, 0x2, 0x2d, 0x33, 0x72, 0x48, 0x1, 0x79, 0x11, 0xd, 0x30, 0x71, 0x7e, 0xef, 0xf4, 0xf2, 0x84, 0xca, 0xe7, 0x6a, 0xbe, 0x4c, 0xaa, 0x77, 0x38, 0xda, 0xad, 0x6, 0x2b, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xe5, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x7c, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x0, 0x0, 0x0, 0x7d, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1d, 0x0, 0x0, 0x0, 0x7e, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x7f, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x0, 0x0, 0x80, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1a, 0x0, 0x0, 0x0, 0x81, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x19, 0x0, 0x0, 0x0, 0x82, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0, 0x83, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x84, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x85, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0x86, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x87, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x88, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x89, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x11, 0x0, 0x0, 0x0, 0x8a, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x8b, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x8c, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xe, 0x0, 0x0, 0x0, 0x8d, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x8e, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x8f, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x90, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x91, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x92, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x93, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x94, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x95, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x96, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x97, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x98, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x99, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7a, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x7a, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1, 0x1f, 0x1, 0x1e, 0x1, 0x1d, 0x1, 0x1c, 0x1, 0x1b, 0x1, 0x1a, 0x1, 0x19, 0x1, 0x18, 0x1, 0x17, 0x1, 0x16, 0x1, 0x15, 0x1, 0x14, 0x1, 0x13, 0x1, 0x12, 0x1, 0x11, 0x1, 0x10, 0x1, 0xf, 0x1, 0xe, 0x1, 0xd, 0x1, 0xc, 0x1, 0xb, 0x1, 0xa, 0x1, 0x9, 0x1, 0x8, 0x1, 0x7, 0x1, 0x6, 0x1, 0x5, 0x1, 0x4, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x62, 0x8a, 0xa1, 0x8e, 0x40, 0xc1, 0x9b, 0x35, 0x23, 0xbb, 0x12, 0x37, 0x1, 0xb6, 0xc3, 0x41, 0xfe, 0x42, 0x10, 0x90, 0xa5, 0x5e, 0x66, 0x6b, 0xc2, 0xfb, 0x7b, 0x44, 0x12, 0x7c, 0xb9, 0x78, 0x1, 0x69, 0xbd, 0xac, 0x68, 0x0, 0x0, 0x0, 0x0, 0x24, 0xcd, 0x77, 0xb0, 0xe1, 0xff, 0x92, 0xf7, 0x91, 0xf4, 0xce, 0xc7, 0xb8, 0xab, 0x69, 0xf5, 0x37, 0xdb, 0xed, 0xdd, 0xf6, 0xd1, 0xf0, 0x35, 0xe8, 0xe2, 0x56, 0xa5, 0xe7, 0xd1, 0x41, 0x9, 0x98, 0x93, 0x14, 0x15, 0x0, 0x0, 0x0, 0x0, 0x69, 0xbd, 0xac, 0x68, 0x0, 0x0, 0x0, 0x0 };
diff --git a/src/choreo/tower/test_tower_serde.c b/src/choreo/tower/test_tower_serde.c
new file mode 100644
index 00000000000..e2f5eee452f
--- /dev/null
+++ b/src/choreo/tower/test_tower_serde.c
@@ -0,0 +1,50 @@
+#include "fd_tower.h"
+#include "fd_tower_serde.h"
+
+void
+test_serde( void ) {
+ // fd_tower_sync_serde_t serde;
+ // serde.root = 359716198;
+ // serde.lockouts_cnt = 31;
+ // for( ushort i=0; i<31; i++ ) {
+ // serde.lockouts[i].offset = 1;
+ // serde.lockouts[i].confirmation_count = (uchar)(31-i);
+ // }
+ // serde.hash = (fd_hash_t){{42}};
+ // serde.timestamp_option = 1;
+ // serde.timestamp = 1758832061;
+ // serde.block_id = (fd_hash_t){{42}};
+ // FD_LOG_NOTICE(( "serde->timestamp_option %u", serde.timestamp_option ));
+
+ // uchar ser[2048];
+ // ulong sz; fd_tower_sync_serialize( &serde, ser, sizeof(ser), &sz );
+ // FD_LOG_NOTICE(( "sz %lu", sz ));
+ // /* 136 bytes */
+
+ // // uchar const pubkey[32] = { 0x32, 0x73, 0x61, 0x45, 0x02, 0x2d, 0x33, 0x72, 0x48, 0x01, 0x79, 0x11, 0x0d, 0x30, 0x71, 0x7e, 0xef, 0xf4, 0xf2, 0x84, 0xca, 0xe7, 0x6a, 0xbe, 0x4c, 0xaa, 0x77, 0x38, 0xda, 0xad, 0x06, 0x2b };
+
+ // // fd_tower_file_serde_t serde = { 0 };
+ // // fd_tower_deserialize( restore, sizeof(restore), &serde );
+
+ // // uchar checkpt[sizeof(restore)];
+ // // ulong checkpt_sz;
+ // // fd_tower_serialize( &serde, checkpt, sizeof(checkpt), &checkpt_sz );
+
+ // // FD_TEST( sizeof(restore) == checkpt_sz );
+ // // FD_TEST( fd_uint_load_4( restore ) == fd_uint_load_4( checkpt ) );
+
+ // // ulong off = sizeof(uint) + FD_ED25519_SIG_SZ + sizeof(ulong);
+ // // FD_TEST( fd_uint_load_4_fast( restore )==fd_uint_load_4_fast( checkpt ) ); /* kind */
+ // // /* skip comparing sig and data_sz (populated outside serialize) */
+ // // FD_TEST( 0==memcmp( restore + off, checkpt + off, sizeof(restore) - off ) );
+}
+
+int
+main( int argc, char ** argv ) {
+ fd_boot( &argc, &argv );
+
+ test_serde();
+
+ fd_halt();
+ return 0;
+}
diff --git a/src/choreo/voter/fd_voter.c b/src/choreo/voter/fd_voter.c
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/choreo/voter/fd_voter.h b/src/choreo/voter/fd_voter.h
index 60e03a6c9c0..f1b82d4a1ff 100644
--- a/src/choreo/voter/fd_voter.h
+++ b/src/choreo/voter/fd_voter.h
@@ -1,13 +1,11 @@
#ifndef HEADER_fd_src_choreo_voter_fd_voter_h
#define HEADER_fd_src_choreo_voter_fd_voter_h
-/* fd_voter is an API for accessing voters' on-chain accounts, known as
- "vote states". The accounts are in bincode-serialized form and voter
- intentionally X-rays the accounts ie. interprets the bytes without
- deserializing. */
+/* fd_voter provides APIs for zero-copy serializing and deserializing
+ on-chain vote accounts. Vote accounts contain "vote states" which
+ store a voter's metadata and tower. */
#include "../fd_choreo_base.h"
-#include "../../funk/fd_funk_rec.h"
/* FD_VOTER_USE_HANDHOLDING: Define this to non-zero at compile time
to turn on additional runtime checks and logging. */
@@ -16,331 +14,75 @@
#define FD_VOTER_USE_HANDHOLDING 1
#endif
-#define FD_VOTER_STATE_V0_23_5 (0)
-#define FD_VOTER_STATE_V1_14_11 (1)
-#define FD_VOTER_STATE_CURRENT (2)
-FD_STATIC_ASSERT(FD_VOTER_STATE_V0_23_5 ==fd_vote_state_versioned_enum_v0_23_5, FD_VOTER_STATE_V0_23_5 );
-FD_STATIC_ASSERT(FD_VOTER_STATE_V1_14_11==fd_vote_state_versioned_enum_v1_14_11, FD_VOTER_STATE_V1_14_11);
-FD_STATIC_ASSERT(FD_VOTER_STATE_CURRENT ==fd_vote_state_versioned_enum_current, FD_VOTER_STATE_CURRENT );
+#define FD_VOTER_V2 (1)
+#define FD_VOTER_V3 (2)
+FD_STATIC_ASSERT( FD_VOTER_V2==fd_vote_state_versioned_enum_v1_14_11, FD_VOTER_V2 );
+FD_STATIC_ASSERT( FD_VOTER_V3==fd_vote_state_versioned_enum_current, FD_VOTER_V3 );
-/* fd_voter_v2_serde defines a serialization / deserialization schema
- for a bincode-encoded vote account v2. This corresponds exactly with
- the binary layout of a an Agave VoteState1_14_11.
+/* fd_voter describes the layout of a vote state stored in a vote
+ account. These structs are used to support zero-copy access (direct
+ casts) of byte arrays containing the vote account data.
- The serde is structured for zero-copy access ie. x-raying individual
- fields
+ fd_voter is versioned, and the serialized formats differ depending on
+ this. They correspond to Agave's VoteState0_23_5, VoteState1_14_11
+ and VoteState structs.
- Agave schema: https://github.com/anza-xyz/agave/blob/v2.3.7/vote/src/vote_state_view.rs#L182 */
+ VoteStatev0_23_5 is deprecated and there are no longer vote accounts
+ of that version on testnet / mainnet. VoteState1_14_11 corresponds
+ to FD_VOTER_V2 and VoteState corresponds to FD_VOTER_V3. The only
+ difference between the two is the votes in V3 contain an additional
+ uchar field `latency`.
-struct fd_voter_v2_serde {
- fd_pubkey_t const * node_pubkey;
- fd_pubkey_t const * authorized_withdrawer;
- uchar const * commission;
-
- struct /* VecDeque */ {
- ulong const * votes_cnt;
- struct {
- ulong const * slot;
- uint const * confirmation_count;
- } votes[31]; /* idx >= votes_cnt are invalid */
- };
-
- struct /* Option */ {
- uchar const * root_slot_option;
- ulong const * root_slot;
- };
-
- struct /* AuthorizedVoters */ {
- ulong const * authorized_voters_cnt;
- struct {
- ulong const * epoch;
- fd_pubkey_t const * pubkey;
- } authorized_voters[32]; /* idx >= authorized_voters_cnt are invalid */
- };
-
- struct /* CircBuf */ {
- struct {
- fd_pubkey_t const * pubkey;
- ulong const * start_epoch;
- ulong const * end_epoch;
- } buf[32];
- ulong const * idx;
- uchar const * is_empty;
- } prior_voters;
-
- struct /* Vec */ {
- ulong const * epoch_credits_cnt;
- struct {
- ulong const * epoch;
- ulong const * credits;
- ulong const * prev_credits;
- } epoch_credits[32]; /* idx >= epoch_credits_cnt are invalid */
- };
-
- struct /* BlockTimestamp */ {
- ulong const * slot;
- long const * timestamp;
- } last_timestamp;
-};
-typedef struct fd_voter_v2_serde fd_voter_v2_serde_t;
-
-/* fd_voter_v3_serde defines a serialization / deserialization schema
- for a bincode-encoded vote account v3. This corresponds exactly with
- the binary layout of a an Agave VoteState (also known as
- VoteStateVersioned::Current).
-
- The serde is structured for zero-copy access ie. x-raying individual
- fields. */
-
-struct fd_voter_v3_serde {
- fd_pubkey_t const * node_pubkey;
- fd_pubkey_t const * authorized_withdrawer;
- uchar const * commission;
-
- struct /* VecDeque */ {
- ulong const * votes_cnt;
- struct {
- uchar const * latency;
- ulong const * slot;
- uint const * confirmation_count;
- } votes[31]; /* idx >= votes_cnt are invalid */
- };
-
- struct /* Option */ {
- uchar const * root_slot_option;
- ulong const * root_slot;
- };
-
- struct /* AuthorizedVoters */ {
- ulong const * authorized_voters_cnt;
- struct {
- ulong const * epoch;
- fd_pubkey_t const * pubkey;
- } authorized_voters[32]; /* idx >= authorized_voters_cnt are invalid */
- };
-
- struct /* CircBuf */ {
- struct {
- fd_pubkey_t const * pubkey;
- ulong const * start_epoch;
- ulong const * end_epoch;
- } buf[32];
- ulong const * idx;
- uchar const * is_empty;
- } prior_voters;
-
- struct /* Vec */ {
- ulong const * epoch_credits_cnt;
- struct {
- ulong const * epoch;
- ulong const * credits;
- ulong const * prev_credits;
- } epoch_credits[32]; /* idx >= epoch_credits_cnt are invalid */
- };
-
- struct /* BlockTimestamp */ {
- ulong const * slot;
- long const * timestamp;
- } last_timestamp;
-};
-typedef struct fd_voter_v3_serde fd_voter_v3_serde_t;
-
-/* Useful to keep both the block_id and slot in the vote record,
- for handling equivocation cases. Potentially re-evaluate removing the
- slot altogether.*/
-
-struct fd_vote_record {
- ulong slot;
- fd_hash_t hash;
-};
-typedef struct fd_vote_record fd_vote_record_t;
-
-/* A fd_voter_t describes a voter. The voter is generic to the context
- in which it is used, eg. it might be a voter in a slot-level context
- in which its stake value may be different from the same voter in an
- epoch-level context which in turn is different from the same voter in
- the prior epoch's context.
-
- The voter is used by various choreo APIs including fd_epoch which
- tracks all the voters in a given epoch, fd_forks which performs
- choreo-related fork updates after replaying a slot, and ghost and
- tower which both require bookkeeping the epoch voters. */
-
-struct fd_voter {
- fd_pubkey_t key; /* vote account address */
- uint hash; /* reserved for fd_map_dynamic.c */
-
- /* IMPORTANT! The values below should only be modified by fd_epoch and
- fd_ghost. */
-
- ulong stake; /* voter's stake */
- fd_vote_record_t replay_vote; /* cached read of last tower vote via replay */
- fd_vote_record_t gossip_vote; /* cached read of last tower vote via gossip */
- fd_vote_record_t rooted_vote; /* cached read of last tower root via replay */
-};
-typedef struct fd_voter fd_voter_t;
-
-/* fd_voter_{vote_old, vote, meta, meta_old, state} are struct
- representations of the bincode-serialized layout of a voter's state
- stored in a vote account. These structs are used to support zero-copy
- reads of the vote account.
-
- The voter's state is versioned, and the serialized formats differ
- depending on this. Currently, the only version that differs from the
- others that is relevant here is v0.23.5. Thus v0.23.5 has its own
- dedicated struct definition with a different set of fields that
- precede the votes than the other versions. Furthermore, v0.23.5
- contains votes of type `fd_vote_lockout_t` vs. the other versions
- which are of type `fd_landed_vote_t`. The only difference between
- `fd_vote_lockout_t` and `fd_landed_vote_t` is there is an additional
- uchar field `latency`, so that is we include an offset of 0 or 1
- depending on which vote state type it is.
-
- The layout begins with a set of fields providing important metadata
- about the voter. Immediately following these fields is the tower
- itself. The tower layout begins with the number of votes currently in
- the tower ie. `cnt`. Then the votes themselves follow. The format of
- the votes varies depending on the version. Finally the layout
- concludes with the tower's root slot.
-
- --------
- metadata <- sizeof(fd_voter_meta_t) or sizeof(fd_voter_meta_old_t)
- --------
- votes <- {sizeof(vote) or sizeof(vote_old)} * cnt
- --------
- root <- 5 or 1 byte(s). bincode-serialized Option
- --------
-*/
-
-struct __attribute__((packed)) fd_voter_vote_old {
- ulong slot;
- uint conf;
-};
-typedef struct fd_voter_vote_old fd_voter_vote_old_t;
-
-struct __attribute__((packed)) fd_voter_vote {
- uchar latency;
- ulong slot;
- uint conf;
-};
-typedef struct fd_voter_vote fd_voter_vote_t;
-
-struct __attribute__((packed)) fd_voter_meta_old {
- fd_pubkey_t node_pubkey;
- fd_pubkey_t authorized_voter;
- ulong authorized_voter_epoch;
- uchar prior_voters[ (32*56+sizeof(ulong)) /* serialized bincode sz */ ];
- fd_pubkey_t authorized_withdrawer;
- uchar commission;
-};
-typedef struct fd_voter_meta_old fd_voter_meta_old_t;
-
-struct __attribute__((packed)) fd_voter_meta {
+ The binary layout begins with metadata in the vote account, followed by the voter's votes (tower), and terminates with the root. */
+struct __attribute__((packed)) fd_voter {
+ uint kind;
fd_pubkey_t node_pubkey;
fd_pubkey_t authorized_withdrawer;
uchar commission;
-};
-typedef struct fd_voter_meta fd_voter_meta_t;
-
-struct __attribute__((packed)) fd_voter_state {
- uint kind;
+ ulong votes_cnt;
union {
struct __attribute__((packed)) {
- fd_voter_meta_old_t meta;
- ulong cnt;
- fd_voter_vote_old_t votes[31];
- } v0_23_5;
-
- struct __attribute__((packed)) {
- fd_voter_meta_t meta;
- ulong cnt;
- fd_voter_vote_old_t votes[31];
- } v1_14_11;
-
+ ulong slot;
+ uint conf;
+ } votes_v2[31]; /* variable-length */
struct __attribute__((packed)) {
- fd_voter_meta_t meta;
- ulong cnt;
- fd_voter_vote_t votes[31];
- };
-
- /* The voter's root (a bincode-serialized Option) follows
- votes. Because the preceding votes are variable-length in
- serialized form, we cannot encode the root directly inside the
- struct. */
+ uchar latency;
+ ulong slot;
+ uint conf;
+ } votes_v3[31]; /* variable-length */
+ /* uchar root_option */
+ /* ulong root */
};
};
-typedef struct fd_voter_state fd_voter_state_t;
-
-struct __attribute__((packed)) fd_voter_tower {
- ulong cnt;
- fd_voter_vote_old_t votes[31];
-};
-typedef struct fd_voter_tower fd_voter_tower_t;
-
-struct __attribute__((packed)) fd_voter_footer {
- uchar some; /* 0 = None, 1 = Some */
- ulong root;
-};
-
-/* fd_voter_state_cnt returns the number of votes in the voter's tower.
- Assumes `state` is a valid fd_voter_state_t. */
-
-FD_FN_PURE static inline ulong
-fd_voter_state_cnt( fd_voter_state_t const * state ) {
- if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V0_23_5 ) ) return state->v0_23_5.cnt;
- if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V1_14_11 ) ) return state->v1_14_11.cnt;
- return state->cnt;
-}
-
-/* fd_voter_root_laddr returns a pointer to the voter's root by x-raying
- the bincode-serialized vote state. */
-
-static inline uchar *
-fd_voter_root_laddr( fd_voter_state_t const * state ) {
- ulong cnt = fd_voter_state_cnt( state );
- if( FD_UNLIKELY( !cnt ) ) return NULL;
- uchar * root = NULL;
- if ( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V0_23_5 ) ) root = (uchar *)&state->v0_23_5.votes[cnt];
- else if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V1_14_11 ) ) root = (uchar *)&state->v1_14_11.votes[cnt];
- else root = (uchar *)&state->votes[cnt];
- FD_TEST( root );
- return root;
-}
-
-/* fd_voter_state queries funk for the record in the provided `txn` and
- `key`. Returns a pointer to the start of the voter's state. Assumes
- `key` is a vote account address and the record is a voter's state
- (fd_voter_state_t). U.B. if `key` does not point to a valid vote
- account.
-
- It will update the given Funk query with the version at the point of
- querying. fd_funk_rec_query_test must be called after usage to check
- that the record has not been modified. */
-
-fd_voter_state_t const *
-fd_voter_state( fd_funk_t const * funk, fd_funk_rec_t const * rec );
-
-/* fd_voter_state_vote returns the voter's most recent vote (ie. the
- last vote of the tower in the voter's state). Assumes `state` is a
- valid fd_voter_state_t. */
+typedef struct fd_voter fd_voter_t;
-FD_FN_PURE static inline ulong
-fd_voter_state_vote( fd_voter_state_t const * state ) {
- ulong cnt = fd_voter_state_cnt( state );
- if( FD_UNLIKELY( !cnt ) ) return FD_SLOT_NULL;
+/* fd_voter_vote_slot takes a voter's vote account data and returns the
+ voter's most recent vote slot in the tower. Returns ULONG_MAX if
+ they have an empty tower. */
- if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V0_23_5 ) ) return state->v0_23_5.votes[cnt - 1].slot;
- if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V1_14_11 ) ) return state->v1_14_11.votes[cnt - 1].slot;
- return state->votes[cnt - 1].slot;
+static inline ulong
+fd_voter_vote_slot( uchar const * vote_account_data ) {
+ fd_voter_t const * voter = (fd_voter_t const *)fd_type_pun_const( vote_account_data );
+ ulong cnt = voter->votes_cnt;
+ switch( voter->kind ) {
+ case FD_VOTER_V3: return cnt ? voter->votes_v3[cnt-1].slot : ULONG_MAX;
+ case FD_VOTER_V2: return cnt ? voter->votes_v2[cnt-1].slot : ULONG_MAX;
+ default: FD_LOG_HEXDUMP_CRIT(( "bad voter %u", vote_account_data, 3762 ));
+ }
}
-/* fd_voter_root_slot returns the voter's root slot. Assumes `state`
- is a valid fd_voter_state_t. */
+/* fd_voter_root_slot takes a voter's vote account data and returns the
+ voter's root slot. Returns ULONG_MAX if they don't have a root. */
static inline ulong
-fd_voter_root_slot( fd_voter_state_t const * state ) {
- uchar * root = fd_voter_root_laddr( state );
- return *(uchar *)root ? *(ulong *)(root+sizeof(uchar)) /* Some(root) */ : FD_SLOT_NULL /* None */;
+fd_voter_root_slot( uchar const * vote_account_data ) {
+ fd_voter_t const * voter = (fd_voter_t const *)fd_type_pun_const( vote_account_data );
+ ulong cnt = voter->votes_cnt;
+ switch( voter->kind ) {
+ case FD_VOTER_V3: { uchar root_option = fd_uchar_load_1_fast( (uchar *)&voter->votes_v3[cnt] ); return root_option ? fd_ulong_load_8_fast( (uchar *)&voter->votes_v3[cnt] ) : ULONG_MAX; }
+ case FD_VOTER_V2: { uchar root_option = fd_uchar_load_1_fast( (uchar *)&voter->votes_v2[cnt] ); return root_option ? fd_ulong_load_8_fast( (uchar *)&voter->votes_v2[cnt] ) : ULONG_MAX; }
+ default: FD_LOG_CRIT(( "unhandled kind %u", voter->kind ));
+ }
}
#endif /* HEADER_fd_src_choreo_voter_fd_voter_h */
diff --git a/src/choreo/voter/fd_voter_serde.h b/src/choreo/voter/fd_voter_serde.h
new file mode 100644
index 00000000000..8aa31f34a75
--- /dev/null
+++ b/src/choreo/voter/fd_voter_serde.h
@@ -0,0 +1,123 @@
+#include "fd_voter.h"
+
+/* fd_voter_v2_serde defines a serialization / deserialization schema
+ for a bincode-encoded vote account v2. This corresponds exactly with
+ the binary layout of a an Agave VoteState1_14_11.
+
+ The serde is structured for zero-copy access ie. x-raying individual
+ fields
+
+ Agave schema: https://github.com/anza-xyz/agave/blob/v2.3.7/vote/src/vote_state_view.rs#L182 */
+
+struct fd_voter_v2_serde {
+ fd_pubkey_t const * node_pubkey;
+ fd_pubkey_t const * authorized_withdrawer;
+ uchar const * commission;
+
+ struct /* VecDeque */ {
+ ulong const * votes_cnt;
+ struct {
+ ulong const * slot;
+ uint const * confirmation_count;
+ } votes[31]; /* idx >= votes_cnt are invalid */
+ };
+
+ struct /* Option */ {
+ uchar const * root_slot_option;
+ ulong const * root_slot;
+ };
+
+ struct /* AuthorizedVoters */ {
+ ulong const * authorized_voters_cnt;
+ struct {
+ ulong const * epoch;
+ fd_pubkey_t const * pubkey;
+ } authorized_voters[32]; /* idx >= authorized_voters_cnt are invalid */
+ };
+
+ struct /* CircBuf */ {
+ struct {
+ fd_pubkey_t const * pubkey;
+ ulong const * start_epoch;
+ ulong const * end_epoch;
+ } buf[32];
+ ulong const * idx;
+ uchar const * is_empty;
+ } prior_voters;
+
+ struct /* Vec */ {
+ ulong const * epoch_credits_cnt;
+ struct {
+ ulong const * epoch;
+ ulong const * credits;
+ ulong const * prev_credits;
+ } epoch_credits[32]; /* idx >= epoch_credits_cnt are invalid */
+ };
+
+ struct /* BlockTimestamp */ {
+ ulong const * slot;
+ long const * timestamp;
+ } last_timestamp;
+};
+typedef struct fd_voter_v2_serde fd_voter_v2_serde_t;
+
+/* fd_voter_v3_serde defines a serialization / deserialization schema
+ for a bincode-encoded vote account v3. This corresponds exactly with
+ the binary layout of a an Agave VoteState (also known as
+ VoteStateVersioned::Current).
+
+ The serde is structured for zero-copy access ie. x-raying individual
+ fields. */
+
+struct fd_voter_v3_serde {
+ fd_pubkey_t const * node_pubkey;
+ fd_pubkey_t const * authorized_withdrawer;
+ uchar const * commission;
+
+ struct /* VecDeque */ {
+ ulong const * votes_cnt;
+ struct {
+ uchar const * latency;
+ ulong const * slot;
+ uint const * confirmation_count;
+ } votes[31]; /* idx >= votes_cnt are invalid */
+ };
+
+ struct /* Option */ {
+ uchar const * root_slot_option;
+ ulong const * root_slot;
+ };
+
+ struct /* AuthorizedVoters */ {
+ ulong const * authorized_voters_cnt;
+ struct {
+ ulong const * epoch;
+ fd_pubkey_t const * pubkey;
+ } authorized_voters[32]; /* idx >= authorized_voters_cnt are invalid */
+ };
+
+ struct /* CircBuf */ {
+ struct {
+ fd_pubkey_t const * pubkey;
+ ulong const * start_epoch;
+ ulong const * end_epoch;
+ } buf[32];
+ ulong const * idx;
+ uchar const * is_empty;
+ } prior_voters;
+
+ struct /* Vec */ {
+ ulong const * epoch_credits_cnt;
+ struct {
+ ulong const * epoch;
+ ulong const * credits;
+ ulong const * prev_credits;
+ } epoch_credits[32]; /* idx >= epoch_credits_cnt are invalid */
+ };
+
+ struct /* BlockTimestamp */ {
+ ulong const * slot;
+ long const * timestamp;
+ } last_timestamp;
+};
+typedef struct fd_voter_v3_serde fd_voter_v3_serde_t;
diff --git a/src/choreo/voter/test_voter.c b/src/choreo/voter/test_voter.c
index ea75b1daf33..f7f57d3d874 100644
--- a/src/choreo/voter/test_voter.c
+++ b/src/choreo/voter/test_voter.c
@@ -1,79 +1,80 @@
#include "fd_voter.h"
-#include "../../ballet/ed25519/fd_ed25519.h"
-#include "../../util/fd_util.h"
-#include /* malloc */
-#include
-
-#define TEST_VOTE_TXN_MAGIC ( 0x7e58UL )
-
-static uchar v1_14_11[3731] = { 0x01, 0x00, 0x00, 0x00, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x64, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x66, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x67, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x68, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x69, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x6a, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x6b, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x6c, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x6d, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x6f, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x70, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x71, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x72, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x73, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x74, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x75, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x76, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x77, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x78, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x79, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x7a, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x7b, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x7c, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x7d, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7e, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x7f, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x80, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x81, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x82, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x83, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x64, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xd1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x49, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xd1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xee, 0x56, 0x63 };
-static uchar current[3762] = {0x02, 0x00, 0x00, 0x00, 0x44, 0x0a, 0xe1, 0x3c, 0x0e, 0xea, 0x91, 0x74, 0xc2, 0x76, 0xc3, 0xfd,0xb1, 0x10, 0x62, 0x2f, 0xc6, 0x70, 0xae, 0xbe, 0x45, 0xda, 0x79, 0xd4, 0x85, 0x5d, 0xfd, 0x5b,0x57, 0x26, 0x0a, 0x6e, 0x8b, 0x0b, 0x5b, 0x3e, 0x52, 0x1b, 0x1d, 0xa5, 0x59, 0x98, 0x40, 0xbc,0xf0, 0x0f, 0x64, 0xe2, 0x22, 0xb6, 0x27, 0xfb, 0x55, 0x7a, 0x38, 0x4a, 0x43, 0xf9, 0x65, 0x0d,0x84, 0xcd, 0x41, 0x61, 0x64, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x75,0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x40, 0x75, 0x02, 0x11, 0x00,0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x41, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00,0x1d, 0x00, 0x00, 0x00, 0x00, 0x42, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00,0x00, 0x00, 0x43, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x44,0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x45, 0x75, 0x02, 0x11,0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x46, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00,0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x47, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00,0x00, 0x00, 0x00, 0x48, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00,0x49, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x75, 0x02,0x11, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x75, 0x02, 0x11, 0x00, 0x00,0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x12,0x00, 0x00, 0x00, 0x00, 0x4d, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,0x00, 0x54, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x55, 0x75,0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x75, 0x02, 0x11, 0x00,0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x57, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00,0x0d, 0x00, 0x00, 0x00, 0x00, 0x58, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,0x00, 0x00, 0x59, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x5a,0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x75, 0x02, 0x11,0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00,0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,0x00, 0x00, 0x00, 0x5e, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,0x5f, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x60, 0x75, 0x02,0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x61, 0x75, 0x02, 0x11, 0x00, 0x00,0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x62, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x02,0x00, 0x00, 0x00, 0x00, 0x63, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,0x01, 0x3e, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0xa1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x0a, 0xe1, 0x3c, 0x0e, 0xea, 0x91,0x74, 0xc2, 0x76, 0xc3, 0xfd, 0xb1, 0x10, 0x62, 0x2f, 0xc6, 0x70, 0xae, 0xbe, 0x45, 0xda, 0x79,0xd4, 0x85, 0x5d, 0xfd, 0x5b, 0x57, 0x26, 0x0a, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xfc, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x9b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x50, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xfc, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xab, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x50, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xaa, 0x01, 0x00, 0x00, 0x00,0x00, 0x00, 0xab, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x0c, 0x1e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xaa, 0x01, 0x00, 0x00, 0x00,0x00, 0x00, 0x63, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x70, 0x37, 0x52, 0xc7, 0xd9, 0x56,0xe7, 0x17 };
-
-void
-vote_txn_signer( void * keypair, uchar signature[static 64], uchar const * buffer, ulong len ) {
- fd_sha512_t sha;
- uchar * validator_identity_keypair = (uchar *)fd_type_pun( keypair );
- fd_ed25519_sign( /* sig */ signature,
- /* msg */ buffer,
- /* sz */ len,
- /* public_key */ validator_identity_keypair + 32UL,
- /* private_key */ validator_identity_keypair,
- &sha );
-}
-
-void
-test_voter_v1_14_11( void ) {
- fd_voter_state_t * state = (fd_voter_state_t *)fd_type_pun( v1_14_11 );
-
- fd_bincode_decode_ctx_t ctx = {
- .data = v1_14_11,
- .dataend = v1_14_11 + sizeof(v1_14_11)
- };
-
- ulong total_sz = 0UL;
- int err = fd_vote_state_versioned_decode_footprint( &ctx, &total_sz );
- FD_TEST( err == FD_BINCODE_SUCCESS );
-
- uchar * mem = malloc( total_sz );
- FD_TEST( mem );
-
- fd_vote_state_versioned_t * versioned = fd_vote_state_versioned_decode( mem, &ctx );
-
- FD_TEST( state->kind == versioned->discriminant );
- FD_TEST( fd_voter_state_cnt( state ) > 0 );
- FD_TEST( fd_voter_state_cnt( state ) == deq_fd_vote_lockout_t_cnt( versioned->inner.v1_14_11.votes ) );
- FD_TEST( fd_voter_state_vote( state ) == deq_fd_vote_lockout_t_peek_tail( versioned->inner.v1_14_11.votes )->slot );
- FD_TEST( fd_voter_root_slot( state ) == versioned->inner.v1_14_11.root_slot );
-}
-
-void
-test_voter_current( void ) {
- fd_voter_state_t * state = (fd_voter_state_t *)fd_type_pun( current );
-
- fd_bincode_decode_ctx_t ctx;
- ctx.data = current;
- ctx.dataend = current + sizeof(current);
-
- ulong total_sz = 0UL;
- int err = fd_vote_state_versioned_decode_footprint( &ctx, &total_sz );
- FD_TEST( err == FD_BINCODE_SUCCESS );
-
- uchar * mem = malloc( total_sz );
- FD_TEST( mem );
-
- fd_vote_state_versioned_t * versioned = fd_vote_state_versioned_decode( mem, &ctx );
-
- FD_TEST( state->kind == versioned->discriminant );
- FD_TEST( fd_voter_state_cnt( state ) > 0 );
- FD_TEST( fd_voter_state_cnt( state ) == deq_fd_landed_vote_t_cnt( versioned->inner.current.votes ) );
- FD_TEST( fd_voter_state_vote( state ) == deq_fd_landed_vote_t_peek_tail( versioned->inner.current.votes )->lockout.slot );
- FD_TEST( fd_voter_root_slot( state ) == versioned->inner.current.root_slot );
-}
+// #include "../../ballet/ed25519/fd_ed25519.h"
+// #include "../../util/fd_util.h"
+// #include /* malloc */
+// #include
+
+// #define TEST_VOTE_TXN_MAGIC ( 0x7e58UL )
+
+// static uchar v1_14_11[3731] = { 0x01, 0x00, 0x00, 0x00, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x64, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x66, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x67, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x68, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x69, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x6a, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x6b, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x6c, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x6d, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x6f, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x70, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x71, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x72, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x73, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x74, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x75, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x76, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x77, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x78, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x79, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x7a, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x7b, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x7c, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x7d, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7e, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x7f, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x80, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x81, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x82, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x83, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x64, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x16, 0x70, 0x4d, 0x7d, 0x8a, 0x84, 0x33, 0xb2, 0xad, 0x50, 0x9e, 0x8b, 0x5c, 0x22, 0xfd, 0x7d, 0xf9, 0x9e, 0x3a, 0xef, 0xbd, 0xac, 0x0c, 0x63, 0x8c, 0x36, 0x7d, 0xd0, 0x79, 0xa5, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xd1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x49, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xd1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xd3, 0x7c, 0x09, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xee, 0x56, 0x63 };
+// static uchar current[3762] = {0x02, 0x00, 0x00, 0x00, 0x44, 0x0a, 0xe1, 0x3c, 0x0e, 0xea, 0x91, 0x74, 0xc2, 0x76, 0xc3, 0xfd,0xb1, 0x10, 0x62, 0x2f, 0xc6, 0x70, 0xae, 0xbe, 0x45, 0xda, 0x79, 0xd4, 0x85, 0x5d, 0xfd, 0x5b,0x57, 0x26, 0x0a, 0x6e, 0x8b, 0x0b, 0x5b, 0x3e, 0x52, 0x1b, 0x1d, 0xa5, 0x59, 0x98, 0x40, 0xbc,0xf0, 0x0f, 0x64, 0xe2, 0x22, 0xb6, 0x27, 0xfb, 0x55, 0x7a, 0x38, 0x4a, 0x43, 0xf9, 0x65, 0x0d,0x84, 0xcd, 0x41, 0x61, 0x64, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x75,0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x40, 0x75, 0x02, 0x11, 0x00,0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x41, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00,0x1d, 0x00, 0x00, 0x00, 0x00, 0x42, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00,0x00, 0x00, 0x43, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x44,0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x45, 0x75, 0x02, 0x11,0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x46, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00,0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x47, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00,0x00, 0x00, 0x00, 0x48, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00,0x49, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x75, 0x02,0x11, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x75, 0x02, 0x11, 0x00, 0x00,0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x12,0x00, 0x00, 0x00, 0x00, 0x4d, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,0x00, 0x54, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x55, 0x75,0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x75, 0x02, 0x11, 0x00,0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x57, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00,0x0d, 0x00, 0x00, 0x00, 0x00, 0x58, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,0x00, 0x00, 0x59, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x5a,0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x75, 0x02, 0x11,0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00,0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,0x00, 0x00, 0x00, 0x5e, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,0x5f, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x60, 0x75, 0x02,0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x61, 0x75, 0x02, 0x11, 0x00, 0x00,0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x62, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x02,0x00, 0x00, 0x00, 0x00, 0x63, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,0x01, 0x3e, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0xa1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x0a, 0xe1, 0x3c, 0x0e, 0xea, 0x91,0x74, 0xc2, 0x76, 0xc3, 0xfd, 0xb1, 0x10, 0x62, 0x2f, 0xc6, 0x70, 0xae, 0xbe, 0x45, 0xda, 0x79,0xd4, 0x85, 0x5d, 0xfd, 0x5b, 0x57, 0x26, 0x0a, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xfc, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x9b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x50, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xfc, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xab, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x50, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xaa, 0x01, 0x00, 0x00, 0x00,0x00, 0x00, 0xab, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x02, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x0c, 0x1e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xaa, 0x01, 0x00, 0x00, 0x00,0x00, 0x00, 0x63, 0x75, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x70, 0x37, 0x52, 0xc7, 0xd9, 0x56,0xe7, 0x17 };
+
+// void
+// vote_txn_signer( void * keypair, uchar signature[static 64], uchar const * buffer, ulong len ) {
+// fd_sha512_t sha;
+// uchar * validator_identity_keypair = (uchar *)fd_type_pun( keypair );
+// fd_ed25519_sign( /* sig */ signature,
+// /* msg */ buffer,
+// /* sz */ len,
+// /* public_key */ validator_identity_keypair + 32UL,
+// /* private_key */ validator_identity_keypair,
+// &sha );
+// }
+
+// void
+// test_voter_v1_14_11( void ) {
+// fd_voter_state_t * state = (fd_voter_state_t *)fd_type_pun( v1_14_11 );
+
+// fd_bincode_decode_ctx_t ctx = {
+// .data = v1_14_11,
+// .dataend = v1_14_11 + sizeof(v1_14_11)
+// };
+
+// ulong total_sz = 0UL;
+// int err = fd_vote_state_versioned_decode_footprint( &ctx, &total_sz );
+// FD_TEST( err == FD_BINCODE_SUCCESS );
+
+// uchar * mem = malloc( total_sz );
+// FD_TEST( mem );
+
+// fd_vote_state_versioned_t * versioned = fd_vote_state_versioned_decode( mem, &ctx );
+
+// FD_TEST( state->kind == versioned->discriminant );
+// FD_TEST( fd_voter_state_cnt( state ) > 0 );
+// FD_TEST( fd_voter_state_cnt( state ) == deq_fd_vote_lockout_t_cnt( versioned->inner.v1_14_11.votes ) );
+// FD_TEST( fd_voter_state_vote( state ) == deq_fd_vote_lockout_t_peek_tail( versioned->inner.v1_14_11.votes )->slot );
+// FD_TEST( fd_voter_root_slot( state ) == versioned->inner.v1_14_11.root_slot );
+// }
+
+// void
+// test_voter_current( void ) {
+// fd_voter_state_t * state = (fd_voter_state_t *)fd_type_pun( current );
+
+// fd_bincode_decode_ctx_t ctx;
+// ctx.data = current;
+// ctx.dataend = current + sizeof(current);
+
+// ulong total_sz = 0UL;
+// int err = fd_vote_state_versioned_decode_footprint( &ctx, &total_sz );
+// FD_TEST( err == FD_BINCODE_SUCCESS );
+
+// uchar * mem = malloc( total_sz );
+// FD_TEST( mem );
+
+// fd_vote_state_versioned_t * versioned = fd_vote_state_versioned_decode( mem, &ctx );
+
+// FD_TEST( state->kind == versioned->discriminant );
+// FD_TEST( fd_voter_state_cnt( state ) > 0 );
+// FD_TEST( fd_voter_state_cnt( state ) == deq_fd_landed_vote_t_cnt( versioned->inner.current.votes ) );
+// FD_TEST( fd_voter_state_vote( state ) == deq_fd_landed_vote_t_peek_tail( versioned->inner.current.votes )->lockout.slot );
+// FD_TEST( fd_voter_root_slot( state ) == versioned->inner.current.root_slot );
+// }
int
main( int argc, char ** argv ) {
fd_boot( &argc, &argv );
- test_voter_v1_14_11();
- test_voter_current();
+ // test_voter_v1_14_11();
+ // test_voter_current();
+ fd_halt();
}
diff --git a/src/disco/fd_txn_m.h b/src/disco/fd_txn_m.h
index 7bb704d9fd6..2152abef63d 100644
--- a/src/disco/fd_txn_m.h
+++ b/src/disco/fd_txn_m.h
@@ -72,7 +72,7 @@ typedef struct fd_txn_m fd_txn_m_t;
static FD_FN_CONST inline ulong
fd_txn_m_align( void ) {
- return alignof( fd_txn_m_t );
+ return alignof(fd_txn_m_t);
}
static inline ulong
diff --git a/src/disco/gui/fd_gui.c b/src/disco/gui/fd_gui.c
index e7f655278e0..8d44b5adc40 100644
--- a/src/disco/gui/fd_gui.c
+++ b/src/disco/gui/fd_gui.c
@@ -2425,27 +2425,23 @@ fd_gui_handle_rooted_slot( fd_gui_t * gui, ulong root_slot ) {
fd_http_server_ws_broadcast( gui->http );
}
-/* fd_gui_handle_tower_update handles updates from the tower tile, which
- manages consensus related fork switching, rooting, slot confirmation. */
+/* fd_gui_handle_tower_slot_done handles slot_done frags from the tower
+ tile, which manages consensus related fork switching, rooting, slot
+ confirmation. */
void
-fd_gui_handle_tower_update( fd_gui_t * gui,
- fd_tower_slot_done_t const * tower,
- long now ) {
+fd_gui_handle_tower_slot_done( fd_gui_t * gui,
+ fd_tower_slot_done_t const * slot_done,
+ long now ) {
(void)now;
- /* handle new root */
- if( FD_LIKELY( tower->new_root && gui->summary.slot_rooted!=tower->root_slot ) ) {
- fd_gui_handle_rooted_slot( gui, tower->root_slot );
- }
-
- if( FD_UNLIKELY( gui->summary.vote_distance!=tower->reset_slot-tower->vote_slot ) ) {
- gui->summary.vote_distance = tower->reset_slot-tower->vote_slot;
+ if( FD_UNLIKELY( gui->summary.vote_distance!=slot_done->reset_slot-slot_done->vote_slot ) ) {
+ gui->summary.vote_distance = slot_done->reset_slot-slot_done->vote_slot;
fd_gui_printf_vote_distance( gui );
fd_http_server_ws_broadcast( gui->http );
}
if( FD_LIKELY( gui->summary.vote_state!=FD_GUI_VOTE_STATE_NON_VOTING ) ) {
- if( FD_UNLIKELY( tower->vote_slot==ULONG_MAX || (tower->vote_slot+150UL)reset_slot ) ) {
+ if( FD_UNLIKELY( slot_done->vote_slot==ULONG_MAX || (slot_done->vote_slot+150UL)reset_slot ) ) {
if( FD_UNLIKELY( gui->summary.vote_state!=FD_GUI_VOTE_STATE_DELINQUENT ) ) {
gui->summary.vote_state = FD_GUI_VOTE_STATE_DELINQUENT;
fd_gui_printf_vote_state( gui );
@@ -2463,6 +2459,24 @@ fd_gui_handle_tower_update( fd_gui_t * gui,
/* todo ... optimistic confirmation, waiting on fd_ghost / fd_notar */
}
+/* fd_gui_handle_tower_update handles slot_confirmed frags from the
+ tower tile, which indicate when slots are optimistically confirmed or
+ rooted. */
+
+void
+fd_gui_handle_tower_slot_confirmed( fd_gui_t * gui,
+ fd_tower_slot_confirmed_t const * slot_confirmed,
+ long now ) {
+ (void)now;
+
+ /* handle new root */
+ if( FD_LIKELY( slot_confirmed->kind==FD_TOWER_SLOT_CONFIRMED_ROOTED && gui->summary.slot_rooted!=slot_confirmed->slot ) ) {
+ fd_gui_handle_rooted_slot( gui, slot_confirmed->slot );
+ }
+
+ /* todo ... optimistic confirmation, waiting on fd_ghost / fd_notar */
+}
+
void
fd_gui_handle_replay_update( fd_gui_t * gui,
fd_gui_slot_completed_t * slot_completed,
diff --git a/src/disco/gui/fd_gui.h b/src/disco/gui/fd_gui.h
index 29398a5c2c6..53ee14d6a3b 100644
--- a/src/disco/gui/fd_gui.h
+++ b/src/disco/gui/fd_gui.h
@@ -826,7 +826,7 @@ fd_gui_handle_leader_schedule( fd_gui_t * gui,
long now );
void
-fd_gui_handle_tower_update( fd_gui_t * gui,
+fd_gui_handle_tower_slot_done( fd_gui_t * gui,
fd_tower_slot_done_t const * msg,
long now );
diff --git a/src/disco/gui/fd_gui_tile.c b/src/disco/gui/fd_gui_tile.c
index e3824ae2788..f83a35f5826 100644
--- a/src/disco/gui/fd_gui_tile.c
+++ b/src/disco/gui/fd_gui_tile.c
@@ -382,8 +382,13 @@ after_frag( fd_gui_ctx_t * ctx,
}
case IN_KIND_TOWER_OUT: {
FD_TEST( ctx->is_full_client );
- fd_tower_slot_done_t const * tower = (fd_tower_slot_done_t const *)ctx->buf;
- fd_gui_handle_tower_update( ctx->gui, tower, fd_clock_now( ctx->clock ) );
+ if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_DONE )) {
+ fd_tower_slot_done_t const * slot_done = (fd_tower_slot_done_t const *)ctx->buf;
+ fd_gui_handle_tower_slot_done( ctx->gui, slot_done, fd_clock_now( ctx->clock ) );
+ } else if ( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_CONFIRMED ) ) {
+ fd_tower_slot_confirmed_t const * slot_confirmed = (fd_tower_slot_confirmed_t const *)ctx->buf;
+ fd_gui_handle_tower_slot_confirmed( ctx->gui, slot_confirmed, fd_clock_now( ctx->clock ) );
+ }
break;
}
case IN_KIND_SHRED_OUT: {
diff --git a/src/disco/metrics/generate/types.py b/src/disco/metrics/generate/types.py
index 8d12f9fb45b..5212b2fc100 100644
--- a/src/disco/metrics/generate/types.py
+++ b/src/disco/metrics/generate/types.py
@@ -38,6 +38,7 @@ class Tile(Enum):
BACKT = 32
EXEC = 33
SNAPWR = 34
+ TOWER = 35
class MetricType(Enum):
COUNTER = 0
diff --git a/src/disco/metrics/generated/fd_metrics_all.c b/src/disco/metrics/generated/fd_metrics_all.c
index 600fb9134a1..15ee4f32586 100644
--- a/src/disco/metrics/generated/fd_metrics_all.c
+++ b/src/disco/metrics/generated/fd_metrics_all.c
@@ -67,6 +67,7 @@ const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT] = {
"backt",
"exec",
"snapwr",
+ "tower",
};
const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT] = {
@@ -101,6 +102,7 @@ const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT] = {
FD_METRICS_BACKT_TOTAL,
FD_METRICS_EXEC_TOTAL,
FD_METRICS_SNAPWR_TOTAL,
+ FD_METRICS_TOWER_TOTAL,
};
const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT] = {
FD_METRICS_NET,
@@ -134,4 +136,5 @@ const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT]
FD_METRICS_BACKT,
FD_METRICS_EXEC,
FD_METRICS_SNAPWR,
+ FD_METRICS_TOWER,
};
diff --git a/src/disco/metrics/generated/fd_metrics_all.h b/src/disco/metrics/generated/fd_metrics_all.h
index ab6d8463580..84e0f5f5e44 100644
--- a/src/disco/metrics/generated/fd_metrics_all.h
+++ b/src/disco/metrics/generated/fd_metrics_all.h
@@ -36,6 +36,7 @@
#include "fd_metrics_ipecho.h"
#include "fd_metrics_backt.h"
#include "fd_metrics_exec.h"
+#include "fd_metrics_tower.h"
/* Start of LINK OUT metrics */
#define FD_METRICS_COUNTER_LINK_SLOW_COUNT_OFF (0UL)
@@ -172,7 +173,7 @@ extern const fd_metrics_meta_t FD_METRICS_ALL_LINK_OUT[FD_METRICS_ALL_LINK_OUT_T
#define FD_METRICS_TOTAL_SZ (8UL*253UL)
-#define FD_METRICS_TILE_KIND_CNT 31
+#define FD_METRICS_TILE_KIND_CNT 32
extern const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT];
extern const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT];
extern const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT];
diff --git a/src/disco/metrics/generated/fd_metrics_tower.c b/src/disco/metrics/generated/fd_metrics_tower.c
new file mode 100644
index 00000000000..f3b48f76a96
--- /dev/null
+++ b/src/disco/metrics/generated/fd_metrics_tower.c
@@ -0,0 +1,13 @@
+/* THIS FILE IS GENERATED BY gen_metrics.py. DO NOT HAND EDIT. */
+#include "fd_metrics_tower.h"
+
+const fd_metrics_meta_t FD_METRICS_TOWER[FD_METRICS_TOWER_TOTAL] = {
+ DECLARE_METRIC( TOWER_ANCESTOR_ROLLBACK, COUNTER ),
+ DECLARE_METRIC( TOWER_SIBLING_CONFIRMED, COUNTER ),
+ DECLARE_METRIC( TOWER_SAME_FORK, COUNTER ),
+ DECLARE_METRIC( TOWER_SWITCH_PASS, COUNTER ),
+ DECLARE_METRIC( TOWER_SWITCH_FAIL, COUNTER ),
+ DECLARE_METRIC( TOWER_LOCKOUT_FAIL, COUNTER ),
+ DECLARE_METRIC( TOWER_THRESHOLD_FAIL, COUNTER ),
+ DECLARE_METRIC( TOWER_PROPAGATED_FAIL, COUNTER ),
+};
diff --git a/src/disco/metrics/generated/fd_metrics_tower.h b/src/disco/metrics/generated/fd_metrics_tower.h
new file mode 100644
index 00000000000..784e83a6d58
--- /dev/null
+++ b/src/disco/metrics/generated/fd_metrics_tower.h
@@ -0,0 +1,60 @@
+#ifndef HEADER_fd_src_disco_metrics_generated_fd_metrics_tower_h
+#define HEADER_fd_src_disco_metrics_generated_fd_metrics_tower_h
+
+/* THIS FILE IS GENERATED BY gen_metrics.py. DO NOT HAND EDIT. */
+
+#include "../fd_metrics_base.h"
+#include "fd_metrics_enums.h"
+
+#define FD_METRICS_COUNTER_TOWER_ANCESTOR_ROLLBACK_OFF (16UL)
+#define FD_METRICS_COUNTER_TOWER_ANCESTOR_ROLLBACK_NAME "tower_ancestor_rollback"
+#define FD_METRICS_COUNTER_TOWER_ANCESTOR_ROLLBACK_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_TOWER_ANCESTOR_ROLLBACK_DESC "Rollback to an ancestor of our prev vote (can't vote)"
+#define FD_METRICS_COUNTER_TOWER_ANCESTOR_ROLLBACK_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_TOWER_SIBLING_CONFIRMED_OFF (17UL)
+#define FD_METRICS_COUNTER_TOWER_SIBLING_CONFIRMED_NAME "tower_sibling_confirmed"
+#define FD_METRICS_COUNTER_TOWER_SIBLING_CONFIRMED_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_TOWER_SIBLING_CONFIRMED_DESC "Duplicate sibling got confirmed (can't vote)"
+#define FD_METRICS_COUNTER_TOWER_SIBLING_CONFIRMED_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_TOWER_SAME_FORK_OFF (18UL)
+#define FD_METRICS_COUNTER_TOWER_SAME_FORK_NAME "tower_same_fork"
+#define FD_METRICS_COUNTER_TOWER_SAME_FORK_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_TOWER_SAME_FORK_DESC "Same fork as prev vote (can vote)"
+#define FD_METRICS_COUNTER_TOWER_SAME_FORK_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_TOWER_SWITCH_PASS_OFF (19UL)
+#define FD_METRICS_COUNTER_TOWER_SWITCH_PASS_NAME "tower_switch_pass"
+#define FD_METRICS_COUNTER_TOWER_SWITCH_PASS_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_TOWER_SWITCH_PASS_DESC "Prev vote was on a different fork, but we are allowed to switch (can vote)"
+#define FD_METRICS_COUNTER_TOWER_SWITCH_PASS_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_TOWER_SWITCH_FAIL_OFF (20UL)
+#define FD_METRICS_COUNTER_TOWER_SWITCH_FAIL_NAME "tower_switch_fail"
+#define FD_METRICS_COUNTER_TOWER_SWITCH_FAIL_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_TOWER_SWITCH_FAIL_DESC "Prev vote was on a different fork, and we are not allowed to switch (can't vote)"
+#define FD_METRICS_COUNTER_TOWER_SWITCH_FAIL_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_TOWER_LOCKOUT_FAIL_OFF (21UL)
+#define FD_METRICS_COUNTER_TOWER_LOCKOUT_FAIL_NAME "tower_lockout_fail"
+#define FD_METRICS_COUNTER_TOWER_LOCKOUT_FAIL_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_TOWER_LOCKOUT_FAIL_DESC "Locked out (can't vote)"
+#define FD_METRICS_COUNTER_TOWER_LOCKOUT_FAIL_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_TOWER_THRESHOLD_FAIL_OFF (22UL)
+#define FD_METRICS_COUNTER_TOWER_THRESHOLD_FAIL_NAME "tower_threshold_fail"
+#define FD_METRICS_COUNTER_TOWER_THRESHOLD_FAIL_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_TOWER_THRESHOLD_FAIL_DESC "Did not pass threshold check (can't vote)"
+#define FD_METRICS_COUNTER_TOWER_THRESHOLD_FAIL_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_COUNTER_TOWER_PROPAGATED_FAIL_OFF (23UL)
+#define FD_METRICS_COUNTER_TOWER_PROPAGATED_FAIL_NAME "tower_propagated_fail"
+#define FD_METRICS_COUNTER_TOWER_PROPAGATED_FAIL_TYPE (FD_METRICS_TYPE_COUNTER)
+#define FD_METRICS_COUNTER_TOWER_PROPAGATED_FAIL_DESC "Prev leader block did not propagate (can't vote)"
+#define FD_METRICS_COUNTER_TOWER_PROPAGATED_FAIL_CVT (FD_METRICS_CONVERTER_NONE)
+
+#define FD_METRICS_TOWER_TOTAL (8UL)
+extern const fd_metrics_meta_t FD_METRICS_TOWER[FD_METRICS_TOWER_TOTAL];
+
+#endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_tower_h */
diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml
index 0405b08ee73..4b5b79c2434 100644
--- a/src/disco/metrics/metrics.xml
+++ b/src/disco/metrics/metrics.xml
@@ -1084,4 +1084,15 @@ metric introduced.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/disco/store/fd_store.h b/src/disco/store/fd_store.h
index 3f763a1c90e..7eb6e798510 100644
--- a/src/disco/store/fd_store.h
+++ b/src/disco/store/fd_store.h
@@ -185,7 +185,7 @@ struct __attribute__((aligned(FD_STORE_ALIGN))) fd_store_fec {
/* Keys */
fd_store_key_t key; /* map key, merkle root of the FEC set + a partition index */
- fd_hash_t cmr; /* parent's map key, chained merkle root of the FEC set */
+ fd_hash_t cmr; /* parent's map key, chained merkle root of the FEC set */
/* Pointers. These are internal to the store and callers should not
interface with them directly. */
diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h
index edfe65dde69..9608519a74a 100644
--- a/src/disco/topo/fd_topo.h
+++ b/src/disco/topo/fd_topo.h
@@ -476,11 +476,12 @@ struct fd_topo_tile {
} archiver;
struct {
- ulong funk_obj_id;
- char identity_key_path[ PATH_MAX ];
- char vote_acc_path[ PATH_MAX ];
- char ledger_path[PATH_MAX];
+ ulong slot_max;
+ char identity_key[ PATH_MAX ];
+ char vote_account[ PATH_MAX ];
+ char base_path[PATH_MAX];
} tower;
+
struct {
char folder_path[ PATH_MAX ];
ushort repair_intake_listen_port;
diff --git a/src/discof/backtest/fd_backtest_tile.c b/src/discof/backtest/fd_backtest_tile.c
index 0b604537e47..63fb36bc4ed 100644
--- a/src/discof/backtest/fd_backtest_tile.c
+++ b/src/discof/backtest/fd_backtest_tile.c
@@ -305,11 +305,11 @@ returnable_frag( fd_backt_tile_t * ctx,
}
fd_tower_slot_done_t * dst = fd_chunk_to_laddr( ctx->tower_out->mem, ctx->tower_out->chunk );
- dst->new_root = 1;
- dst->root_slot = msg->slot;
- dst->root_block_id = msg->block_id;
- dst->reset_block_id = msg->block_id;
- dst->reset_slot = msg->slot;
+ dst->vote_slot = msg->slot;
+ dst->reset_slot = msg->slot;
+ dst->reset_block_id = msg->block_id;
+ dst->root_slot = msg->slot;
+ dst->root_block_id = msg->block_id;
fd_stem_publish( stem, ctx->tower_out->idx, 0UL, ctx->tower_out->chunk, sizeof(fd_tower_slot_done_t), 0UL, tspub, fd_frag_meta_ts_comp( fd_tickcount() ) );
ctx->tower_out->chunk = fd_dcache_compact_next( ctx->tower_out->chunk, sizeof(fd_tower_slot_done_t), ctx->tower_out->chunk0, ctx->tower_out->wmark );
diff --git a/src/discof/reasm/fd_reasm.c b/src/discof/reasm/fd_reasm.c
index b66dcf2bda4..9dd79635b6a 100644
--- a/src/discof/reasm/fd_reasm.c
+++ b/src/discof/reasm/fd_reasm.c
@@ -8,7 +8,7 @@ fd_reasm_align( void ) {
return alignof(fd_reasm_t);
}
-ulong
+FD_FN_CONST ulong
fd_reasm_footprint( ulong fec_max ) {
int lgf_max = fd_ulong_find_msb( fd_ulong_pow2_up( fec_max ) );
return FD_LAYOUT_FINI(
@@ -35,7 +35,9 @@ fd_reasm_footprint( ulong fec_max ) {
}
void *
-fd_reasm_new( void * shmem, ulong fec_max, ulong seed ) {
+fd_reasm_new( void * shmem,
+ ulong fec_max,
+ ulong seed ) {
if( FD_UNLIKELY( !shmem ) ) {
FD_LOG_WARNING(( "NULL mem" ));
@@ -208,7 +210,9 @@ link( fd_reasm_t * reasm,
fd_reasm_fec_t * curr = pool_ele( reasm->pool, parent->child );
while( curr->sibling != pool_idx_null( reasm->pool ) ) curr = pool_ele( reasm->pool, curr->sibling );
curr->sibling = pool_idx( reasm->pool, child ); /* set as right-sibling. */
- if( FD_UNLIKELY( !parent->slot_complete ) ) child->eqvoc = 1; /* set to equivocating */
+ if( FD_UNLIKELY( !parent->slot_complete ) ) child->eqvoc = 1; /* only the last FEC set in a slot
+ can have multiple children and
+ be non-equivocating */
}
}
@@ -303,10 +307,11 @@ fd_reasm_insert( fd_reasm_t * reasm,
so we need to check that. */
fd_reasm_fec_t * parent = NULL;
- if( FD_LIKELY( parent = ancestry_ele_query ( ancestry, &fec->cmr, NULL, pool ) ) ) { /* parent is connected non-leaf */
- frontier_ele_insert( frontier, fec, pool );
- out_push_tail( out, pool_idx( pool, fec ) );
- } else if( FD_LIKELY( parent = frontier_ele_remove( frontier, &fec->cmr, NULL, pool ) ) ) { /* parent is connected leaf */
+ ulong idx = pool_idx( pool, fec );
+ if( FD_LIKELY ( parent = ancestry_ele_query ( ancestry, &fec->cmr, NULL, pool ) ) ) { /* parent is connected non-leaf */
+ frontier_ele_insert( frontier, fec, pool );
+ out_push_tail ( out, idx );
+ } else if( FD_LIKELY ( parent = frontier_ele_remove( frontier, &fec->cmr, NULL, pool ) ) ) { /* parent is connected leaf */
ancestry_ele_insert( ancestry, parent, pool );
frontier_ele_insert( frontier, fec, pool );
out_push_tail( out, pool_idx( pool, fec ) );
@@ -334,9 +339,7 @@ fd_reasm_insert( fd_reasm_t * reasm,
iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) {
bfs_push_tail( bfs, subtreel_iter_idx( iter, subtreel, pool ) );
}
-
- /* connects subtrees to the new FEC */
- while( FD_LIKELY( !bfs_empty( bfs ) ) ) {
+ while( FD_LIKELY( !bfs_empty( bfs ) ) ) { /* link orphan subtrees to the new FEC */
fd_reasm_fec_t * orphan_root = pool_ele( reasm->pool, bfs_pop_head( bfs ) );
overwrite_invalid_cmr( reasm, orphan_root ); /* handle receiving child before parent */
if( FD_LIKELY( orphan_root && 0==memcmp( orphan_root->cmr.uc, fec->key.uc, sizeof(fd_hash_t) ) ) ) { /* this orphan_root is a direct child of fec */
@@ -347,26 +350,22 @@ fd_reasm_insert( fd_reasm_t * reasm,
}
}
- /* At this point we are in a state where:
-
- ele < in frontier/subtrees/orphaned >
- |
- children < all in orphaned >
-
- if ele is in orphaned/subtrees, we are done and this state is
- correct.
- if ele is an frontier, then we need to extend the
- frontier from this ele. (make ele and all its children the ancestry,
- except the leaf, which needs to be added to the frontier).
- it's not possible for ele to be in ancestry at this point! */
-
- /* Third, we advance the frontier beginning from this FEC, if it was
- connected. By definition if this FEC was connected then its parent
- is connected, so by induction this new FEC extends the frontier.
- However, even though we have already inserted this new FEC into the
- frontier it is not necessarily a leaf, as it may have connected to
- orphaned children. So we BFS the from the new FEC outward until we
- reach the leaves. */
+ /* Third, we advance the frontier outward beginning from fec as we may
+ have connected orphaned descendants to fec in the above step. This
+ does a BFS outward from fec until it reaches leaves, moving fec and
+ its non-leaf descendants into ancestry and leaves into frontier.
+
+ parent (ancestry) orphan root (subtrees)
+ | |
+ fec (frontier) orphan child (orphaned)
+
+ parent
+ |
+ fec <- frontier is here
+ |
+ orphan root
+ |
+ orphan child <- advance to here */
if( FD_LIKELY( frontier_ele_query( frontier, &fec->key, NULL, pool ) ) ) bfs_push_tail( bfs, pool_idx( pool, fec ) );
while( FD_LIKELY( !bfs_empty( bfs ) ) ) {
diff --git a/src/discof/reasm/fd_reasm.h b/src/discof/reasm/fd_reasm.h
index f6f6a5cc57a..15f010d325e 100644
--- a/src/discof/reasm/fd_reasm.h
+++ b/src/discof/reasm/fd_reasm.h
@@ -137,29 +137,22 @@ struct __attribute__((aligned(128UL))) fd_reasm_fec {
ulong dlist_prev;
ulong dlist_next;
- /* Data */
-
- ulong slot; /* The slot of the FEC set */
- uint fec_set_idx; /* The index of first shred in the FEC set */
- ushort parent_off; /* The offset for the parent slot of the FEC set */
- ushort data_cnt; /* The number of data shreds in the FEC set */
- int free; /* Whether the FEC set is a valid pool member */
- int data_complete; /* Whether the FEC set completes an entry batch */
- int slot_complete; /* Whether the FEC set completes the slot */
- int leader; /* Whether the FEC set corresponds to FECs produced during a leader slot */
- int eqvoc; /* Whether the FEC set is equivocating. Note,
- this doesn't track all types of equivocations
- (i.e. equivocations not on a slot boundary
- and malformed FEC indices).
- TODO: this will change with fix-32. */
-
- /* Metadata (set by caller)
-
- parent_bank_idx and bank_idx are used to track downstream
- consumers of the reasm_fecs from reasm_next(). parent_bank_idx and
- bank_idx is set externally in the replay tile. */
- ulong parent_bank_idx;
+ /* Data (set on insert) */
+
+ ulong slot; /* slot of the FEC set */
+ uint fec_set_idx; /* index of first shred in the FEC set */
+ ushort parent_off; /* offset for the parent slot of the FEC set */
+ ushort data_cnt; /* number of data shreds in the FEC set */
+ int free; /* Whether this FEC is currently in the pool */
+ int data_complete; /* whether this FEC completes an entry batch */
+ int slot_complete; /* whether this FEC completes the slot */
+ int leader; /* whether this FEC was produced by us as leader */
+ int eqvoc; /* whether this FEC equivocates */
+
+ /* Data (set by caller) */
+
ulong bank_idx;
+ ulong parent_bank_idx;
};
typedef struct fd_reasm_fec fd_reasm_fec_t;
@@ -182,7 +175,9 @@ fd_reasm_footprint( ulong fec_max );
address space with the required footprint and alignment. */
void *
-fd_reasm_new( void * shmem, ulong fec_max, ulong seed );
+fd_reasm_new( void * shmem,
+ ulong fec_max,
+ ulong seed );
/* fd_reasm_join joins the caller to the reasm. reasm points
to the first byte of the memory region backing the reasm in the
@@ -214,7 +209,8 @@ fd_reasm_delete( void * reasm );
found, NULL otherwise. */
fd_reasm_fec_t *
-fd_reasm_query( fd_reasm_t const * reasm, fd_hash_t const * merkle_root );
+fd_reasm_query( fd_reasm_t const * reasm,
+ fd_hash_t const * merkle_root );
/* fd_reasm_{root,parent,child,sibling} returns a pointer in the
caller's address space to the {root,parent,left-child,right-sibling}.
@@ -288,7 +284,7 @@ fd_reasm_insert( fd_reasm_t * reasm,
this new root. */
fd_reasm_fec_t *
-fd_reasm_publish( fd_reasm_t * reasm,
+fd_reasm_publish( fd_reasm_t * reasm,
fd_hash_t const * merkle_root );
void
diff --git a/src/discof/reasm/fd_reasm_private.h b/src/discof/reasm/fd_reasm_private.h
index 4808bd796c2..8b0119a2f99 100644
--- a/src/discof/reasm/fd_reasm_private.h
+++ b/src/discof/reasm/fd_reasm_private.h
@@ -11,28 +11,28 @@
#define MAP_ELE_T fd_reasm_fec_t
#define MAP_KEY_T fd_hash_t
#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_hash_t)))
-#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t))) /* FIXME keyspace partitioning */
+#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
#include "../../util/tmpl/fd_map_chain.c"
#define MAP_NAME frontier
#define MAP_ELE_T fd_reasm_fec_t
#define MAP_KEY_T fd_hash_t
#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_hash_t)))
-#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t))) /* FIXME keyspace partitioning */
+#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
#include "../../util/tmpl/fd_map_chain.c"
#define MAP_NAME orphaned
#define MAP_ELE_T fd_reasm_fec_t
#define MAP_KEY_T fd_hash_t
#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_hash_t)))
-#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t))) /* FIXME keyspace partitioning */
+#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
#include "../../util/tmpl/fd_map_chain.c"
#define MAP_NAME subtrees
#define MAP_ELE_T fd_reasm_fec_t
#define MAP_KEY_T fd_hash_t
#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_hash_t)))
-#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t))) /* FIXME keyspace partitioning */
+#define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
#include "../../util/tmpl/fd_map_chain.c"
#define DLIST_NAME subtreel
diff --git a/src/discof/repair/fd_repair_tile.c b/src/discof/repair/fd_repair_tile.c
index b991491d9eb..a344c691ee6 100644
--- a/src/discof/repair/fd_repair_tile.c
+++ b/src/discof/repair/fd_repair_tile.c
@@ -767,10 +767,15 @@ after_frag( ctx_t * ctx,
}
if( FD_UNLIKELY( in_kind==IN_KIND_TOWER ) ) {
- fd_tower_slot_done_t const * msg = (fd_tower_slot_done_t const *)fd_type_pun_const( ctx->buffer );
- if( FD_LIKELY( msg->new_root ) ) {
- fd_forest_publish( ctx->forest, msg->root_slot );
- fd_policy_reset ( ctx->policy, ctx->forest );
+ if( FD_UNLIKELY( sig==FD_TOWER_SIG_SLOT_DONE ) ) {
+ fd_tower_slot_done_t const * msg = (fd_tower_slot_done_t const *)fd_type_pun_const( ctx->buffer );
+ if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) {
+ fd_forest_publish( ctx->forest, msg->root_slot );
+ fd_policy_reset ( ctx->policy, ctx->forest );
+ }
+ } else if( FD_LIKELY( sig == FD_TOWER_SIG_DUPLICATE_CONFIRMED ) ) {
+ fd_tower_slot_confirmed_t const * msg = (fd_tower_slot_confirmed_t const *)fd_type_pun_const( ctx->buffer );
+ (void)msg;
}
return;
}
diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c
index 8d9ebd819ae..dd4f3cc2f50 100644
--- a/src/discof/replay/fd_replay_tile.c
+++ b/src/discof/replay/fd_replay_tile.c
@@ -730,7 +730,8 @@ static void
publish_slot_completed( fd_replay_tile_t * ctx,
fd_stem_context_t * stem,
fd_bank_t * bank,
- int is_initial ) {
+ int is_initial,
+ int is_leader ) {
ulong slot = fd_bank_slot_get( bank );
@@ -789,6 +790,8 @@ publish_slot_completed( fd_replay_tile_t * ctx,
slot_info->parent_bank_idx = parent_bank->idx;
}
+ slot_info->is_leader = is_leader;
+
fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_SLOT_COMPLETED, ctx->replay_out->chunk, sizeof(fd_replay_slot_completed_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_slot_completed_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
}
@@ -863,7 +866,7 @@ replay_block_finalize( fd_replay_tile_t * ctx,
/* Must be last so we can measure completion time correctly, even
though we could technically do this before the hash cmp and vote
tower stuff. */
- publish_slot_completed( ctx, stem, bank, 0 );
+ publish_slot_completed( ctx, stem, bank, 0, 0 /* is_leader */ );
/* If enabled, dump the block to a file and reset the dumping
context state */
@@ -1014,7 +1017,7 @@ fini_leader_bank( fd_replay_tile_t * ctx,
fd_bank_hash_cmp_unlock( bank_hash_cmp );
- publish_slot_completed( ctx, stem, ctx->leader_bank, 0 );
+ publish_slot_completed( ctx, stem, ctx->leader_bank, 0, 1 /* is_leader */ );
/* Copy the vote tower of all the vote accounts into the buffer,
which will be published in after_credit. */
@@ -1418,7 +1421,7 @@ boot_genesis( fd_replay_tile_t * ctx,
FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
- publish_slot_completed( ctx, stem, bank, 1 );
+ publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */ );
publish_root_advanced( ctx, stem );
publish_reset( ctx, stem, bank );
}
@@ -1498,7 +1501,7 @@ on_snapshot_message( fd_replay_tile_t * ctx,
slot_bank needed in blockstore_init. */
init_after_snapshot( ctx );
- publish_slot_completed( ctx, stem, bank, 1 );
+ publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */ );
publish_root_advanced( ctx, stem );
fd_reasm_fec_t * fec = fd_reasm_insert( ctx->reasm, &manifest_block_id, NULL, snapshot_slot, 0, 0, 0, 0, 1, 0 ); /* FIXME manifest block_id */
@@ -2046,7 +2049,7 @@ process_tower_update( fd_replay_tile_t * ctx,
FD_LOG_CRIT(( "invariant violation: bank not found for bank index %lu", reset_bank_idx ));
}
- if( FD_LIKELY( msg->new_root ) ) FD_TEST( msg->root_slot<=msg->reset_slot );
+ if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) FD_TEST( msg->root_slot<=msg->reset_slot );
ctx->reset_bank = bank;
if( FD_LIKELY( ctx->replay_out->idx!=ULONG_MAX ) ) {
@@ -2079,10 +2082,10 @@ process_tower_update( fd_replay_tile_t * ctx,
ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_poh_reset_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
}
- FD_LOG_INFO(( "tower_update(reset_slot=%lu, next_leader_slot=%lu, vote_slot=%lu, new_root=%d, root_slot=%lu, root_block_id=%s)", msg->reset_slot, ctx->next_leader_slot, msg->vote_slot, msg->new_root, msg->root_slot, FD_BASE58_ENC_32_ALLOCA( &msg->root_block_id ) ));
+ FD_LOG_INFO(( "tower_update(reset_slot=%lu, next_leader_slot=%lu, vote_slot=%lu, root_slot=%lu, root_block_id=%s)", msg->reset_slot, ctx->next_leader_slot, msg->vote_slot, msg->root_slot, FD_BASE58_ENC_32_ALLOCA( &msg->root_block_id ) ));
maybe_become_leader( ctx, stem );
- if( FD_LIKELY( msg->new_root ) ) {
+ if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) {
FD_TEST( msg->root_slot>=ctx->consensus_root_slot );
fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &msg->root_block_id, NULL, ctx->block_id_arr );
@@ -2262,7 +2265,7 @@ returnable_frag( fd_replay_tile_t * ctx,
break;
}
case IN_KIND_TOWER: {
- process_tower_update( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
+ if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_DONE ) ) process_tower_update( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
break;
}
case IN_KIND_SHRED: {
diff --git a/src/discof/replay/fd_replay_tile.h b/src/discof/replay/fd_replay_tile.h
index f292681d07b..3dc1fb9406f 100644
--- a/src/discof/replay/fd_replay_tile.h
+++ b/src/discof/replay/fd_replay_tile.h
@@ -37,6 +37,8 @@ struct fd_replay_slot_completed {
long first_transaction_scheduled_nanos; /* timestamp when replay first sent a transaction to be executed */
long last_transaction_finished_nanos; /* timestamp when replay received the last execution completion */
long completion_time_nanos; /* timestamp when replay completed finalizing the slot and notified tower */
+
+ int is_leader; /* whether we were leader for this slot */
};
typedef struct fd_replay_slot_completed fd_replay_slot_completed_t;
@@ -69,11 +71,11 @@ struct fd_replay_tower {
typedef struct fd_replay_tower fd_replay_tower_t;
union fd_replay_message {
- fd_replay_slot_completed_t slot_completed;
- fd_replay_root_advanced_t root_advanced;
- fd_poh_reset_t reset;
- fd_became_leader_t became_leader;
- fd_replay_tower_t tower;
+ fd_replay_slot_completed_t slot_completed;
+ fd_replay_root_advanced_t root_advanced;
+ fd_poh_reset_t reset;
+ fd_became_leader_t became_leader;
+ fd_replay_tower_t tower;
};
typedef union fd_replay_message fd_replay_message_t;
diff --git a/src/discof/send/fd_send_tile.c b/src/discof/send/fd_send_tile.c
index dbc559d8ef6..a13bd74b82e 100644
--- a/src/discof/send/fd_send_tile.c
+++ b/src/discof/send/fd_send_tile.c
@@ -564,18 +564,20 @@ during_frag( fd_send_tile_ctx_t * ctx,
}
if( FD_UNLIKELY( kind==IN_KIND_TOWER ) ) {
- FD_TEST( sz==sizeof(fd_tower_slot_done_t) );
+ if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_DONE ) ) {
+ FD_TEST( sz==sizeof(fd_tower_slot_done_t) );
- fd_tower_slot_done_t const * slot_done = fd_type_pun_const( dcache_entry );
+ fd_tower_slot_done_t const * slot_done = fd_type_pun_const( dcache_entry );
- ulong const vote_slot = slot_done->vote_slot;
- ulong const vote_txn_sz = slot_done->vote_txn_sz;
- if( FD_UNLIKELY( vote_slot==ULONG_MAX ) ) return; /* no new vote to send */
+ ulong const vote_slot = slot_done->vote_slot;
+ ulong const vote_txn_sz = slot_done->vote_txn_sz;
+ if( FD_UNLIKELY( vote_slot==ULONG_MAX ) ) return; /* no new vote to send */
- uchar vote_txn[ FD_TPU_MTU ];
- fd_memcpy( vote_txn, slot_done->vote_txn, vote_txn_sz );
+ uchar vote_txn[ FD_TPU_MTU ];
+ fd_memcpy( vote_txn, slot_done->vote_txn, vote_txn_sz );
- handle_vote_msg( ctx, vote_slot, vote_txn, vote_txn_sz );
+ handle_vote_msg( ctx, vote_slot, vote_txn, vote_txn_sz );
+ }
}
}
diff --git a/src/discof/tower/fd_tower_tile.c b/src/discof/tower/fd_tower_tile.c
index e482cc5de8c..c4adebfe0ac 100644
--- a/src/discof/tower/fd_tower_tile.c
+++ b/src/discof/tower/fd_tower_tile.c
@@ -2,73 +2,127 @@
#include "generated/fd_tower_tile_seccomp.h"
#include "../genesis/fd_genesi_tile.h"
-#include "../replay/fd_replay_tile.h"
#include "../../choreo/ghost/fd_ghost.h"
+#include "../../choreo/notar/fd_notar.h"
#include "../../choreo/tower/fd_tower.h"
-#include "../../choreo/voter/fd_voter.h"
+#include "../../choreo/tower/fd_tower_accts.h"
+#include "../../choreo/tower/fd_tower_forks.h"
+#include "../../choreo/tower/fd_tower_serde.h"
#include "../../disco/keyguard/fd_keyload.h"
+#include "../../disco/metrics/fd_metrics.h"
#include "../../disco/topo/fd_topo.h"
+#include "../../disco/fd_txn_m.h"
#include "../../discof/restore/utils/fd_ssmsg.h"
-#include "../../ballet/lthash/fd_lthash.h"
+#include "../../discof/replay/fd_replay_tile.h"
#include "../../flamenco/fd_flamenco_base.h"
-#include "../../flamenco/runtime/fd_system_ids.h"
+#include "../../flamenco/gossip/fd_gossip_types.h"
#include
#include
+/* The tower tile is responsible for two things:
+
+ 1. running the fork choice (fd_ghost) and TowerBFT (fd_tower) rules
+ after replaying a block.
+ 2. listening to gossip (duplicate shred and vote messages) and
+ monitoring for duplicate or duplicate confirmed blocks (fd_notar).
+
+ Tower signals to other tiles about events that occur as a result of
+ those two above events, such as what block to vote on, what block to
+ reset onto as leader, what block got rooted, what blocks are
+ duplicates and what blocks are confirmed.
+
+ In general, tower uses the block_id as the identifier for blocks. The
+ block_id is the merkle root of the last FEC set for a block. This is
+ guaranteed to be unique for a given block and is the canonical
+ identifier over the slot number because unlike slot numbers, if a
+ leader equivocates (produces multiple blocks for the same slot) the
+ block_id can disambiguate the blocks.
+
+ However, the block_id was only introduced into the Solana protocol
+ recently, and TowerBFT still uses the "legacy" identifier of slot
+ numbers for blocks. So the tile (and relevant modules) will use
+ block_id when possible to interface with the protocol but otherwise
+ falling back to slot number when block_id is unsupported. */
+
#define LOGGING 0
#define IN_KIND_GENESIS (0)
-#define IN_KIND_SNAP (1)
+#define IN_KIND_GOSSIP (1)
#define IN_KIND_REPLAY (2)
+#define IN_KIND_SNAP (3)
+
+#define VOTE_TXN_SIG_MAX (2UL) /* validator identity and vote authority */
+
+#define DEQUE_NAME slots
+#define DEQUE_T ulong
+#include "../../util/tmpl/fd_deque_dynamic.c"
-struct fd_tower_tile_in {
+static const fd_hash_t manifest_block_id = { .ul = { 0xf17eda2ce7b1d } }; /* FIXME manifest_block_id */
+
+typedef struct {
fd_wksp_t * mem;
ulong chunk0;
ulong wmark;
ulong mtu;
-};
+} in_ctx_t;
-typedef struct fd_tower_tile_in fd_tower_tile_in_t;
-
-struct fd_tower_tile {
+typedef struct {
+ ulong seed; /* map seed */
+ ulong root; /* most recent tower root slot */
+ ulong slot0; /* snapshot or genesis slot */
fd_pubkey_t identity_key[1];
- fd_pubkey_t vote_acc[1];
-
- int initialized;
+ fd_pubkey_t vote_account[1];
+ int checkpt_fd;
+ int restore_fd;
- int checkpt_fd;
- int restore_fd;
+ /* structures owned by tower tile */
- fd_epoch_t * epoch;
- fd_ghost_t * ghost;
- fd_tower_t * scratch;
- fd_tower_t * tower;
- uchar * voters;
+ fd_ghost_t * ghost;
+ fd_notar_t * notar;
+ fd_tower_t * tower;
+ fd_tower_accts_t * tower_accts;
+ fd_tower_forks_t * tower_forks;
+ fd_tower_t * tower_spare; /* spare tower used during processing */
+ ulong * slots;
- long ts; /* tower timestamp */
+ /* frag-related structures (consume and publish) */
- fd_snapshot_manifest_t manifest;
- fd_replay_slot_completed_t replay_slot_info;
+ uchar gossip_vote_txn[FD_TPU_PARSED_MTU];
+ fd_sha512_t * gossip_vote_sha[VOTE_TXN_SIG_MAX];
+ fd_compact_tower_sync_serde_t compact_tower_sync_serde;
+ fd_snapshot_manifest_t manifest;
+ fd_replay_slot_completed_t replay_slot_completed;
- ulong replay_towers_cnt;
- fd_replay_tower_t replay_towers[ FD_REPLAY_TOWER_VOTE_ACC_MAX ];
+ /* watermarks corresponding to each confirmation kind for the last
+ slot for which we've published a frag */
- fd_tower_t * vote_towers[ FD_REPLAY_TOWER_VOTE_ACC_MAX ];
- fd_pubkey_t vote_keys[ FD_REPLAY_TOWER_VOTE_ACC_MAX ];
+ ulong optimistic_wmark;
+ ulong rooted_wmark;
- uchar vote_state[ FD_REPLAY_TOWER_VOTE_ACC_MAX ]; /* our vote state */
+ /* in/out link setup */
- int in_kind[ 64UL ];
- fd_tower_tile_in_t in[ 64UL ];
+ int in_kind[ 64UL ];
+ in_ctx_t in [ 64UL ];
fd_wksp_t * out_mem;
ulong out_chunk0;
ulong out_wmark;
ulong out_chunk;
-};
-typedef struct fd_tower_tile fd_tower_tile_t;
+ /* metrics */
+
+ struct {
+ ulong ancestor_rollback;
+ ulong sibling_confirmed;
+ ulong same_fork;
+ ulong switch_pass;
+ ulong switch_fail;
+ ulong lockout_fail;
+ ulong threshold_fail;
+ ulong propagated_fail;
+ } metrics;
+} ctx_t;
FD_FN_CONST static inline ulong
scratch_align( void ) {
@@ -76,301 +130,453 @@ scratch_align( void ) {
}
FD_FN_PURE static inline ulong
-scratch_footprint( fd_topo_tile_t const * tile ) {
- (void)tile;
-
+scratch_footprint( FD_PARAM_UNUSED fd_topo_tile_t const * tile ) {
+ ulong slot_max = tile->tower.slot_max;
+ int lg_slot_max = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max ) ) + 1;
ulong l = FD_LAYOUT_INIT;
- l = FD_LAYOUT_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
- l = FD_LAYOUT_APPEND( l, fd_epoch_align(), fd_epoch_footprint( FD_REPLAY_TOWER_VOTE_ACC_MAX ) );
- l = FD_LAYOUT_APPEND( l, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ) );
- l = FD_LAYOUT_APPEND( l, fd_tower_align(), fd_tower_footprint() );
- l = FD_LAYOUT_APPEND( l, fd_tower_align(), fd_tower_footprint()*FD_REPLAY_TOWER_VOTE_ACC_MAX );
+ l = FD_LAYOUT_APPEND( l, alignof(ctx_t), sizeof(ctx_t) );
+ l = FD_LAYOUT_APPEND( l, fd_ghost_align(), fd_ghost_footprint( 2*slot_max, FD_VOTER_MAX ) );
+ l = FD_LAYOUT_APPEND( l, fd_notar_align(), fd_notar_footprint( slot_max ) );
+ l = FD_LAYOUT_APPEND( l, fd_tower_align(), fd_tower_footprint() );
+ l = FD_LAYOUT_APPEND( l, fd_tower_accts_align(), fd_tower_accts_footprint( FD_VOTER_MAX ) );
+ l = FD_LAYOUT_APPEND( l, fd_tower_forks_align(), fd_tower_forks_footprint( lg_slot_max ) );
+ l = FD_LAYOUT_APPEND( l, fd_tower_align(), fd_tower_footprint() );
+ l = FD_LAYOUT_APPEND( l, slots_align(), slots_footprint( slot_max ) );
return FD_LAYOUT_FINI( l, scratch_align() );
}
+static inline void
+metrics_write( ctx_t * ctx ) {
+ FD_MCNT_SET( TOWER, ANCESTOR_ROLLBACK, ctx->metrics.ancestor_rollback );
+ FD_MCNT_SET( TOWER, SIBLING_CONFIRMED, ctx->metrics.sibling_confirmed );
+ FD_MCNT_SET( TOWER, SAME_FORK, ctx->metrics.same_fork );
+ FD_MCNT_SET( TOWER, SWITCH_PASS, ctx->metrics.switch_pass );
+ FD_MCNT_SET( TOWER, SWITCH_FAIL, ctx->metrics.switch_fail );
+ FD_MCNT_SET( TOWER, LOCKOUT_FAIL, ctx->metrics.lockout_fail );
+ FD_MCNT_SET( TOWER, THRESHOLD_FAIL, ctx->metrics.threshold_fail );
+ FD_MCNT_SET( TOWER, PROPAGATED_FAIL, ctx->metrics.propagated_fail );
+}
+
+static void
+publish_slot_confirmed( ctx_t * ctx,
+ fd_stem_context_t * stem,
+ ulong tsorig,
+ ulong slot,
+ fd_hash_t const * block_id,
+ int kind ) {
+ fd_tower_slot_confirmed_t * msg = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
+ msg->slot = slot;
+ msg->block_id = *block_id;
+ msg->kind = kind;
+ fd_stem_publish( stem, 0UL, FD_TOWER_SIG_SLOT_CONFIRMED, ctx->out_chunk, sizeof(fd_tower_slot_confirmed_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
+ ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_confirmed_t), ctx->out_chunk0, ctx->out_wmark );
+}
+
+static void
+duplicate_confirm( ctx_t * ctx,
+ fd_stem_context_t * stem,
+ ulong tsorig,
+ ulong slot,
+ fd_hash_t const * block_id ) {
+ fd_tower_forks_t * fork = fd_tower_forks_query( ctx->tower_forks, slot, NULL ); /* ensure fork exists */
+ if( FD_UNLIKELY( !fork ) ) {
+ fork = fd_tower_forks_insert( ctx->tower_forks, slot );
+ fork->parent_slot = ULONG_MAX;
+ fork->voted = 0;
+ }
+ fork->confirmed = 1;
+ fork->confirmed_block_id = *block_id;
+ publish_slot_confirmed( ctx, stem, tsorig, slot, block_id, FD_TOWER_SLOT_CONFIRMED_DUPLICATE );
+}
+
+static void
+optimistic_or_rooted_confirm( ctx_t * ctx,
+ fd_stem_context_t * stem,
+ ulong tsorig,
+ ulong slot,
+ ulong wmark,
+ int kind ) {
+
+ /* For optimistic and rooted confirmations, confirming a slot means
+ all ancestors are confirmed too, so we need to publish any skipped
+ ancestors (confirmations can be out-of-order and roots can be
+ skipped due to lockout). */
+
+ ulong ancestor = slot;
+ while( FD_UNLIKELY( ancestor > wmark ) ) {
+ slots_push_tail( ctx->slots, ancestor );
+ fd_tower_forks_t * fork = fd_tower_forks_query( ctx->tower_forks, ancestor, NULL );
+ ancestor = fork->parent_slot;
+ }
+ while( FD_LIKELY( !slots_empty( ctx->slots ) ) ) {
+ ulong ancestor = slots_pop_tail( ctx->slots );
+ fd_hash_t const * block_id = fd_tower_forks_canonical_block_id( ctx->tower_forks, ancestor );
+ if( FD_UNLIKELY( !block_id ) ) FD_LOG_CRIT(( "missing block id for ancestor %lu", ancestor ));
+ publish_slot_confirmed( ctx, stem, tsorig, ancestor, block_id, kind );
+ }
+}
+
static void
-update_ghost( fd_tower_tile_t * ctx ) {
- fd_voter_t * epoch_voters = fd_epoch_voters( ctx->epoch );
- for( ulong i=0UL; ireplay_towers_cnt; i++ ) {
- fd_replay_tower_t const * replay_tower = &ctx->replay_towers[ i ];
- fd_pubkey_t const * pubkey = &replay_tower->key;
- fd_voter_state_t const * voter_state = (fd_voter_state_t const *)fd_type_pun_const( replay_tower->acc );
- fd_tower_t * tower = ctx->vote_towers[ i ];
-
- /* Look up the voter for this vote account */
- fd_voter_t * voter = fd_epoch_voters_query( epoch_voters, *pubkey, NULL );
- if( FD_UNLIKELY( !voter ) ) {
- /* This means that the cached list of epoch voters is not in sync
- with the list passed through from replay. This likely means
- that we have crossed an epoch boundary and the epoch_voter list
- has not been updated.
-
- TODO: update the set of account in epoch_voter's to match the
- list received from replay, so that epoch_voters is
- correct across epoch boundaries. */
- FD_LOG_CRIT(( "voter %s was not in epoch voters", FD_BASE58_ENC_32_ALLOCA( pubkey ) ));
+notar_blk_confirm( ctx_t * ctx,
+ fd_stem_context_t * stem,
+ ulong tsorig,
+ fd_notar_blk_t * notar_blk ) {
+
+ /* Record any confirmations in our tower forks structure and also
+ publish slot_confirmed frags indicating confirmations to consumers.
+
+ See documentation in fd_tower_tile.h for guarantees. */
+
+ if( FD_LIKELY( notar_blk->dup_conf ) ) duplicate_confirm( ctx, stem, tsorig, notar_blk->slot, ¬ar_blk->block_id );
+ if( FD_LIKELY( notar_blk->opt_conf ) ) {
+ publish_slot_confirmed( ctx, stem, tsorig, notar_blk->slot, ¬ar_blk->block_id, FD_TOWER_SLOT_CONFIRMED_CLUSTER );
+ fd_tower_forks_t * fork = fd_tower_forks_query( ctx->tower_forks, notar_blk->slot, NULL );
+ if( FD_UNLIKELY( fork && fork->replayed && notar_blk->slot > ctx->optimistic_wmark ) ) {
+ optimistic_or_rooted_confirm( ctx, stem, tsorig, notar_blk->slot, ctx->optimistic_wmark, FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC );
+ ctx->optimistic_wmark = notar_blk->slot;
}
+ }
+}
- voter->stake = replay_tower->stake; /* update the voters stake */
+static void
+gossip_vote( ctx_t * ctx,
+ fd_gossip_vote_t const * vote,
+ ulong tsorig,
+ fd_stem_context_t * stem ) {
+
+ /* Parse the gossip vote txn and check it's a vote txn. */
+
+ uchar const * payload = vote->txn;
+ ulong payload_sz = vote->txn_sz;
+ ulong sz = fd_txn_parse_core( vote->txn, vote->txn_sz, ctx->gossip_vote_txn, NULL, NULL );
+ if( FD_UNLIKELY( sz==0 ) ) return;
+ fd_txn_t * txn = (fd_txn_t *)ctx->gossip_vote_txn;
+ if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, vote->txn ) ) ) return; /* TODO Agave doesn't have the same validation of <=2 signatures */
+
+ /* Filter any non-tower sync votes. */
+
+ fd_txn_instr_t * instr = &txn->instr[0];
+ uchar const * instr_data = vote->txn + instr->data_off;
+ uint kind = fd_uint_load_4_fast( instr_data );
+ if( FD_UNLIKELY( kind != FD_VOTE_IX_KIND_TOWER_SYNC && kind != FD_VOTE_IX_KIND_TOWER_SYNC_SWITCH ) ) return;
+
+ /* Sigverify the vote txn. */
+
+ uchar const * msg = payload + txn->message_off;
+ ulong msg_sz = (ulong)payload_sz - txn->message_off;
+ uchar const * sigs = payload + txn->signature_off;
+ uchar const * accts = payload + txn->acct_addr_off;
+ if( FD_UNLIKELY( txn->signature_cnt == 0 ) ) return;
+ int err = fd_ed25519_verify_batch_single_msg( msg, msg_sz, sigs, accts, ctx->gossip_vote_sha, txn->signature_cnt );
+ if( FD_UNLIKELY( err != FD_ED25519_SUCCESS ) ) return;
+
+ /* Deserialize the CompactTowerSync. */
+
+ err = fd_compact_tower_sync_deserialize( &ctx->compact_tower_sync_serde, instr_data + sizeof(uint), instr->data_sz - sizeof(uint) ); /* FIXME validate */
+ if( FD_UNLIKELY( err == -1 ) ) return;
+ ulong slot = ctx->compact_tower_sync_serde.root;
+ fd_tower_remove_all( ctx->tower_spare );
+ for( ulong i = 0; i < ctx->compact_tower_sync_serde.lockouts_cnt; i++ ) {
+ slot += ctx->compact_tower_sync_serde.lockouts[i].offset;
+ fd_tower_push_tail( ctx->tower_spare, (fd_tower_vote_t){ .slot = slot, .conf = ctx->compact_tower_sync_serde.lockouts[i].confirmation_count } );
+ }
+ if( FD_UNLIKELY( 0==memcmp( &ctx->compact_tower_sync_serde.block_id, &hash_null, sizeof(fd_hash_t) ) ) ) return;
- if( FD_UNLIKELY( !fd_voter_state_cnt( voter_state ) ) ) continue; /* skip voters with no votes */
+ fd_pubkey_t const * addrs = (fd_pubkey_t const *)fd_type_pun_const( accts );
+ fd_pubkey_t const * addr = NULL;
+ if( FD_UNLIKELY( txn->signature_cnt==1 ) ) addr = (fd_pubkey_t const *)fd_type_pun_const( &addrs[1] ); /* account idx 1 is the vote account address */
+ else addr = (fd_pubkey_t const *)fd_type_pun_const( &addrs[2] ); /* account idx 2 is the vote account address */
- ulong vote = fd_tower_votes_peek_tail( tower )->slot; /* peek last vote from the tower */
- ulong root = fd_voter_root_slot( voter_state );
+ /* Return early if their tower is empty. */
- /* Only process votes for slots >= root. */
- if( FD_LIKELY( vote != FD_SLOT_NULL && vote >= fd_ghost_root( ctx->ghost )->slot ) ) {
- fd_ghost_ele_t const * ele = fd_ghost_query_const( ctx->ghost, fd_ghost_hash( ctx->ghost, vote ) );
+ if( FD_UNLIKELY( fd_tower_empty( ctx->tower_spare ) ) ) return;
- /* It is an invariant violation if the vote slot is not in ghost.
- These votes come from replay ie. on-chain towers stored in vote
- accounts which implies every vote slot must have been processed
- by the vote program (ie. replayed) and therefore in ghost. */
+ /* The vote txn contains a block id and bank hash for their last vote
+ slot in the tower. Agave always counts the last vote.
- if( FD_UNLIKELY( !ele ) ) FD_LOG_CRIT(( "voter %s's vote slot %lu was not in ghost", FD_BASE58_ENC_32_ALLOCA( &voter->key ), vote ));
- fd_ghost_replay_vote( ctx->ghost, voter, &ele->key );
- }
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L476-L487 */
- /* Check if this voter's root >= ghost root. We can't process roots
- before our own root because it was already pruned. */
+ fd_tower_vote_t const * their_last_vote = fd_tower_peek_tail_const( ctx->tower_spare );
+ fd_hash_t const * their_block_id = &ctx->compact_tower_sync_serde.block_id;
- if( FD_LIKELY( root!=FD_SLOT_NULL && root>=fd_ghost_root( ctx->ghost )->slot ) ) {
- fd_ghost_ele_t const * ele = fd_ghost_query( ctx->ghost, fd_ghost_hash( ctx->ghost, root ) );
+ ulong total_stake = fd_ghost_root( ctx->ghost )->total_stake;
+ fd_notar_blk_t * notar_blk = fd_notar_count_vote( ctx->notar, total_stake, addr, their_last_vote->slot, their_block_id );
+ if( FD_LIKELY( notar_blk ) ) notar_blk_confirm( ctx, stem, tsorig, notar_blk );
- /* Error if the node's root slot is not in ghost. This is an
- invariant violation, because we know their tower must be on the
- same fork as this current one that we're processing, and so by
- definition their root slot must be in our ghost (ie. we can't
- have rooted past it or be on a different fork). */
+ fd_hash_t const * our_block_id = fd_tower_forks_canonical_block_id( ctx->tower_forks, their_last_vote->slot );
+ if( FD_UNLIKELY( !our_block_id || 0!=memcmp( our_block_id, their_block_id, sizeof(fd_hash_t) ) ) ) return;
- if( FD_UNLIKELY( !ele ) ) FD_LOG_CRIT(( "voter %s's root slot %lu was not in ghost", FD_BASE58_ENC_32_ALLOCA( &voter->key ), root ));
+ /* Agave decides to count intermediate vote slots in the tower only if
+ 1. they've replayed the slot and 2. their replay bank hash matches
+ the vote's bank hash. We do the same thing, but using block_ids.
- fd_ghost_rooted_vote( ctx->ghost, voter, root );
- }
+ It's possible we haven't yet replayed this slot being voted on
+ because gossip votes can be ahead of our replay.
+
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L483-L487 */
+
+ int skipped_last_vote = 0;
+ for( fd_tower_iter_t iter = fd_tower_iter_init_rev( ctx->tower_spare );
+ !fd_tower_iter_done_rev( ctx->tower_spare, iter );
+ iter = fd_tower_iter_prev ( ctx->tower_spare, iter ) ) {
+ if( FD_UNLIKELY( !skipped_last_vote ) ) { skipped_last_vote = 1; continue; }
+ fd_tower_vote_t const * their_intermediate_vote = fd_tower_iter_ele_const( ctx->tower_spare, iter );
+
+ /* If we don't recognize an intermediate vote slot in their tower,
+ it means their tower either:
+
+ 1. Contains intermediate vote slots that are too old (older than
+ our root) so we already pruned them for tower_forks. Normally
+ if the descendant (last vote slot) is in tower forks, then all
+ of its ancestors should be in there too.
+
+ 2. Is invalid. Even though at this point we have successfully
+ sigverified and deserialized their vote txn, the tower itself
+ might still be invalid because unlike TPU vote txns, we have
+ not plumbed through the vote program, but obviously gossip
+ votes do not so we need to do some light validation here.
+
+ We could throwaway this voter's tower, but we handle it the same
+ way as Agave which is to just skip this intermediate vote slot:
+
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L513-L518 */
+
+ fd_hash_t const * our_block_id = fd_tower_forks_canonical_block_id( ctx->tower_forks, their_intermediate_vote->slot );
+ if( FD_UNLIKELY( !our_block_id ) ) continue;
+
+ /* Otherwise, we count the vote using our own block id for that slot
+ (again, mirroring what Agave does albeit with bank hashes).
+
+ Agave uses the current root bank's total stake when counting
+ vote txns from gossip / replay:
+
+ https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L500 */
+
+
+ fd_notar_blk_t * notar_blk = fd_notar_count_vote( ctx->notar, total_stake, addr, their_last_vote->slot, their_block_id );
+ if( FD_LIKELY( notar_blk ) ) notar_blk_confirm( ctx, stem, tsorig, notar_blk );
}
}
static void
-replay_slot_completed( fd_tower_tile_t * ctx,
+replay_slot_completed( ctx_t * ctx,
fd_replay_slot_completed_t * slot_info,
ulong tsorig,
fd_stem_context_t * stem ) {
- /* If we have not received any votes, something is wrong. */
- if( FD_UNLIKELY( !ctx->replay_towers_cnt ) ) {
- /* TODO: This is not correct. It is fine and valid to receive a
- block with no votes, we don't want to return here as we still
- want to vote on the block. */
- FD_LOG_WARNING(( "No vote states received from replay. No votes will be sent"));
- return;
- }
- /* Parse the replay vote towers */
- for( ulong i=0UL; ireplay_towers_cnt; i++ ) {
- fd_tower_votes_remove_all( ctx->vote_towers[ i ] );
- fd_tower_from_vote_acc_data( ctx->replay_towers[ i ].acc, ctx->vote_towers[ i ] );
- ctx->vote_keys[ i ] = ctx->replay_towers[ i ].key;
+ /* fd_notar requires some bookkeeping when there is a new epoch. */
+
+ if( FD_UNLIKELY( ctx->notar->epoch==ULONG_MAX || slot_info->epoch > ctx->notar->epoch ) ) {
+ fd_notar_advance_epoch( ctx->notar, ctx->tower_accts, slot_info->epoch );
+ }
- if( FD_UNLIKELY( 0==memcmp( &ctx->vote_keys[ i ], ctx->vote_acc, sizeof(fd_pubkey_t) ) ) ) {
+ /* Insert the just replayed block into ghost. */
- /* If this is our vote account, and our tower has not been
- initialized, initialize it with our vote state */
+ fd_hash_t const * parent_block_id = &slot_info->parent_block_id;
+ if( FD_UNLIKELY( slot_info->parent_slot==ctx->slot0 ) ) parent_block_id = &manifest_block_id;
+ fd_ghost_blk_t * ghost_blk = fd_ghost_insert( ctx->ghost, &slot_info->block_id, parent_block_id, slot_info->slot );
- if( FD_UNLIKELY( fd_tower_votes_empty( ctx->tower ) ) ) {
- fd_tower_from_vote_acc_data( ctx->replay_towers[i].acc, ctx->tower );
- }
+ /* Insert the just replayed block into tower forks. */
- /* Copy in our voter state */
- memcpy( &ctx->vote_state, ctx->replay_towers[ i ].acc, ctx->replay_towers[ i ].acc_sz );
- }
+ fd_tower_forks_t * fork = fd_tower_forks_query( ctx->tower_forks, slot_info->slot, NULL ); /* ensure fork exists */
+ if( FD_UNLIKELY( !fork ) ) {
+ fork = fd_tower_forks_insert( ctx->tower_forks, slot_info->slot );
+ fork->confirmed = 0;
+ fork->voted = 0;
}
+ fork->replayed = 1;
+ fork->replayed_block_id = slot_info->block_id;
+ fork->parent_slot = slot_info->parent_slot;
- /* Update ghost with the vote account states received from replay. */
+ /* Iterate all the vote accounts to count votes towards fork choice
+ (fd_ghost) and confirmation (fd_notar) and also reconcile our local
+ tower with our on-chain one (fd_tower_reconcile).
- fd_ghost_ele_t const * ghost_ele = fd_ghost_insert( ctx->ghost, &slot_info->parent_block_id, slot_info->slot, &slot_info->block_id, ctx->epoch->total_stake );
- FD_TEST( ghost_ele );
- update_ghost( ctx );
+ TODO replace with direct funk query */
- /* Populate the out frag. */
+ ulong total_stake = 0;
+ for( fd_tower_accts_iter_t iter = fd_tower_accts_iter_init( ctx->tower_accts );
+ !fd_tower_accts_iter_done( ctx->tower_accts, iter );
+ iter = fd_tower_accts_iter_next( ctx->tower_accts, iter ) ) {
+ fd_tower_accts_t const * acct = fd_tower_accts_iter_ele( ctx->tower_accts, iter );
- fd_tower_slot_done_t * msg = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
+ total_stake += acct->stake;
- /* 1. Determine next slot to vote for, if one exists. */
+ /* If this is our vote acc, reconcile with our local tower. */
- msg->vote_slot = fd_tower_vote_slot( ctx->tower, ctx->epoch, ctx->vote_keys, ctx->vote_towers, ctx->replay_towers_cnt, ctx->ghost );
- msg->new_root = 0UL;
+ if( FD_UNLIKELY( 0==memcmp( &acct->addr, ctx->vote_account, sizeof(fd_pubkey_t) ) ) ) fd_tower_reconcile( ctx->tower, ctx->root, acct->data );
- /* 2. Determine new root, if there is one. A new vote slot can result
- in a new root but not always. */
+ /* Deserialize the last vote slot from this vote account's tower. */
- if( FD_LIKELY( msg->vote_slot!=FD_SLOT_NULL ) ) {
- msg->root_slot = fd_tower_vote( ctx->tower, msg->vote_slot );
- fd_hash_t const * root_block_id = fd_ghost_hash( ctx->ghost, msg->root_slot );
- if( FD_LIKELY( root_block_id ) ) {
- msg->root_block_id = *root_block_id;
- msg->new_root = 1;
- fd_ghost_publish( ctx->ghost, &msg->root_block_id );
- }
- }
+ ulong vote_slot = fd_voter_vote_slot( acct->data );
+ if( FD_UNLIKELY( vote_slot==ULONG_MAX ) ) continue; /* hasn't voted */
+ if( FD_UNLIKELY( vote_slot < fd_ghost_root( ctx->ghost )->slot ) ) continue; /* vote too old */
- /* 3. Populate vote_txn with the current tower (regardless of whether
- there was a new vote slot or not). */
+ /* We search up the ghost ancestry to find the ghost block for this
+ vote slot. In Agave, they look this value up using a hashmap of
+ slot->block_id ("fork progress"), but that approach only works
+ because they dump and repair (so there's only ever one canonical
+ block id). We retain multiple block ids, both the original and
+ confirmed one. */
- fd_lockout_offset_t lockouts[ FD_TOWER_VOTE_MAX ];
- fd_txn_p_t txn[1];
- fd_tower_to_vote_txn( ctx->tower, msg->root_slot, lockouts, &slot_info->bank_hash, &slot_info->block_hash, ctx->identity_key, ctx->identity_key, ctx->vote_acc, txn );
- FD_TEST( !fd_tower_votes_empty( ctx->tower ) );
- FD_TEST( txn->payload_sz && txn->payload_sz<=FD_TPU_MTU );
- fd_memcpy( msg->vote_txn, txn->payload, txn->payload_sz );
- msg->vote_txn_sz = txn->payload_sz;
+ fd_ghost_blk_t * ancestor_blk = fd_ghost_slot_ancestor( ctx->ghost, ghost_blk, vote_slot ); /* FIXME potentially slow */
- /* 4. Determine next slot to reset leader pipeline to. */
+ /* It is impossible for ancestor to be missing, because these are
+ vote accounts on a given fork, not vote txns across forks. So we
+ know these towers must contain slots we know about (as long as
+ they are >= root, which we checked above). */
- msg->reset_slot = fd_tower_reset_slot( ctx->tower, ctx->epoch, ctx->ghost );
- msg->reset_block_id = *fd_ghost_hash( ctx->ghost, msg->reset_slot ); /* FIXME fd_ghost_hash is a naive lookup but reset_slot only ever refers to the confirmed duplicate */
+ if( FD_UNLIKELY( !ancestor_blk ) ) FD_LOG_CRIT(( "missing ancestor. replay slot %lu vote slot %lu voter %s", slot_info->slot, vote_slot, FD_BASE58_ENC_32_ALLOCA( &acct->addr ) ));
- /* Publish the frag */
+ /* Count the vote toward ghost, notar and total_stake. */
- fd_stem_publish( stem, 0UL, 0UL, ctx->out_chunk, sizeof(fd_tower_slot_done_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
- ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_done_t), ctx->out_chunk0, ctx->out_wmark );
+ fd_ghost_count_vote( ctx->ghost, ancestor_blk, &acct->addr, acct->stake, vote_slot );
-# if LOGGING
- fd_ghost_print( ctx->ghost, ctx->epoch->total_stake, fd_ghost_root( ctx->ghost ) );
- fd_tower_print( ctx->tower, msg->root_slot );
-# endif
-}
+ /* TODO count TPU vote txns towards notar */
+ }
+ if( FD_UNLIKELY( fd_ghost_root( ctx->ghost )->total_stake==0 ) ) fd_ghost_root( ctx->ghost )->total_stake = total_stake;
+ ghost_blk->total_stake = total_stake;
-static void
-init_genesis( fd_tower_tile_t * ctx,
- fd_genesis_solana_global_t const * genesis ) {
- fd_hash_t manifest_block_id = { .ul = { 0xf17eda2ce7b1d } }; /* FIXME manifest_block_id */
- fd_ghost_init( ctx->ghost, 0UL, &manifest_block_id );
+ /* Determine reset, vote, and root slots. There may not be a vote or
+ root slot but there is always a reset slot. */
- fd_voter_t * epoch_voters = fd_epoch_voters( ctx->epoch );
+ fd_tower_slot_done_t * msg = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
+ fd_tower_out_t out = fd_tower_vote_and_reset( ctx->tower, ctx->tower_accts, ctx->tower_forks, ctx->ghost, ctx->notar );
+ msg->vote_slot = out.vote_slot;
+ msg->reset_slot = out.reset_slot;
+ msg->reset_block_id = out.reset_block_id;
- fd_pubkey_account_pair_global_t const * accounts = fd_genesis_solana_accounts_join( genesis );
- for( ulong i=0UL; iaccounts_len; i++ ) {
- fd_solana_account_global_t const * account = &accounts[ i ].account;
- if( FD_LIKELY( memcmp( account->owner.key, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) ) continue;
+ if( FD_LIKELY( out.root_slot!=ULONG_MAX ) ) {
- uchar const * acc_data = fd_solana_account_data_join( account );
+ }
- fd_stake_state_v2_t stake_state;
- if( FD_UNLIKELY( !fd_bincode_decode_static( stake_state_v2, &stake_state, acc_data, account->data_len, NULL ) ) ) {
- FD_LOG_ERR(( "Failed to deserialize genesis stake account %s", FD_BASE58_ENC_32_ALLOCA( accounts[ i ].key.uc ) ));
- }
+ /* Write out metrics for vote / reset reasons. */
+
+ ctx->metrics.ancestor_rollback += (ulong)fd_uchar_extract_bit( out.flags, 0 );
+ ctx->metrics.sibling_confirmed += (ulong)fd_uchar_extract_bit( out.flags, 1 );
+ ctx->metrics.same_fork += (ulong)fd_uchar_extract_bit( out.flags, 2 );
+ ctx->metrics.lockout_fail += (ulong)fd_uchar_extract_bit( out.flags, 3 );
+ ctx->metrics.switch_pass += (ulong)fd_uchar_extract_bit( out.flags, 4 );
+ ctx->metrics.switch_fail += (ulong)fd_uchar_extract_bit( out.flags, 5 );
+ ctx->metrics.threshold_fail += (ulong)fd_uchar_extract_bit( out.flags, 6 );
+ ctx->metrics.propagated_fail += (ulong)fd_uchar_extract_bit( out.flags, 7 );
+
+ /* Create a vote_txn with the current tower (regardless of whether
+ there was a new vote slot or not). */
- if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) continue;
- if( FD_UNLIKELY( !stake_state.inner.stake.stake.delegation.stake ) ) continue;
+ /* TODO only do this on refresh_last_vote? */
- fd_pubkey_t const * pubkey = &stake_state.inner.stake.stake.delegation.voter_pubkey;
+ fd_lockout_offset_t lockouts[ FD_TOWER_VOTE_MAX ];
+ fd_txn_p_t txn[1];
+ fd_tower_to_vote_txn( ctx->tower, out.root_slot, lockouts, &slot_info->bank_hash, &slot_info->block_hash, ctx->identity_key, ctx->identity_key, ctx->vote_account, txn );
+ FD_TEST( !fd_tower_empty( ctx->tower ) );
+ FD_TEST( txn->payload_sz && txn->payload_sz<=FD_TPU_MTU );
+ fd_memcpy( msg->vote_txn, txn->payload, txn->payload_sz );
+ msg->vote_txn_sz = txn->payload_sz;
+ msg->vote_slot = out.vote_slot;
+
+ fd_stem_publish( stem, 0UL, FD_TOWER_SIG_SLOT_DONE, ctx->out_chunk, sizeof(fd_tower_slot_done_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
+ ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_done_t), ctx->out_chunk0, ctx->out_wmark );
- fd_voter_t * voter = fd_epoch_voters_insert( epoch_voters, *pubkey );
+ /* Publish according structures if there is a root */
- voter->stake = stake_state.inner.stake.stake.delegation.stake;
- ctx->epoch->total_stake += voter->stake;
+ if( FD_UNLIKELY( out.root_slot!=ULONG_MAX ) ) {
+ fd_ghost_blk_t * newr = fd_ghost_query( ctx->ghost, &out.root_block_id );
+ FD_TEST( newr );
+ fd_ghost_publish( ctx->ghost, newr );
+ fd_notar_advance_wmark( ctx->notar, out.root_slot );
+ for(ulong slot = ctx->root; slot < out.root_slot; slot++ ) {
+ fd_tower_forks_t * fork = fd_tower_forks_query( ctx->tower_forks, slot, NULL );
+ if( FD_LIKELY( fork ) ) fd_tower_forks_remove( ctx->tower_forks, fork );
+ }
+ ctx->root = out.root_slot;
}
+
+# if LOGGING
+ fd_ghost_print( ctx->ghost, fd_ghost_root( ctx->ghost ) );
+ fd_tower_print( ctx->tower, fd_ghost_root( ctx->ghost )->slot );
+# endif
+}
+
+static void
+init_genesis( ctx_t * ctx ) {
+ fd_ghost_insert( ctx->ghost, &manifest_block_id, NULL, 0 );
+ fd_tower_forks_t * fork = fd_tower_forks_insert( ctx->tower_forks, 0 );
+ fork->confirmed = 1;
+ fork->confirmed_block_id = manifest_block_id;
+ ctx->root = 0;
+ ctx->slot0 = 0;
}
static void
-snapshot_done( fd_tower_tile_t * ctx,
+snapshot_done( ctx_t * ctx,
fd_snapshot_manifest_t const * manifest ) {
- fd_hash_t manifest_block_id = { .ul = { 0xf17eda2ce7b1d } }; /* FIXME manifest_block_id */
- fd_ghost_init( ctx->ghost, manifest->slot, &manifest_block_id );
-
- /* As per the documentation in fd_ssmsg.h, we use
- manifest->epoch_stakes[1] to initialize the epoch voters. These
- correspond to the amount staked to each vote account at the
- beginning of the current epoch:
-
- manifest->epoch_stakes[0] represents the stakes used to generate
- the leader schedule at the end of the current epoch. These are the
- stakes as of the end of two epochs ago.
-
- manifest->epoch_stakes[1] represents the stakes used to generate
- the leader schedule at the end of the next epoch. These are the
- stakes as of the end of the previous epoch, so these will be the
- stakes throughout the current epoch. */
- fd_voter_t * epoch_voters = fd_epoch_voters( ctx->epoch );
- fd_snapshot_manifest_epoch_stakes_t const * epoch_stakes = &manifest->epoch_stakes[ 1 ];
- for( ulong i=0UL; ivote_stakes_len; i++ ) {
- if( FD_UNLIKELY( !epoch_stakes->vote_stakes[ i ].stake ) ) continue;
-
- fd_pubkey_t const * pubkey = (fd_pubkey_t const *)epoch_stakes->vote_stakes[ i ].vote;
-
-#if FD_EPOCH_USE_HANDHOLDING
- FD_TEST( !fd_epoch_voters_query( epoch_voters, *pubkey, NULL ) );
- FD_TEST( fd_epoch_voters_key_cnt( epoch_voters ) < fd_epoch_voters_key_max( epoch_voters ) );
-#endif
-
- fd_voter_t * voter = fd_epoch_voters_insert( epoch_voters, *pubkey );
-
-#if FD_EPOCH_USE_HANDHOLDING
- FD_TEST( 0==memcmp( voter->key.uc, pubkey->uc, sizeof(fd_pubkey_t) ) );
- FD_TEST( fd_epoch_voters_query( epoch_voters, voter->key, NULL ) );
-#endif
-
- voter->stake = epoch_stakes->vote_stakes[ i ].stake;
- voter->replay_vote.slot = FD_SLOT_NULL;
- voter->gossip_vote.slot = FD_SLOT_NULL;
- voter->rooted_vote.slot = FD_SLOT_NULL;
- ctx->epoch->total_stake += voter->stake;
- }
+ fd_ghost_insert( ctx->ghost, &manifest_block_id, NULL, manifest->slot );
+ fd_tower_forks_t * fork = fd_tower_forks_insert( ctx->tower_forks, manifest->slot );
+ fork->confirmed = 1;
+ fork->confirmed_block_id = manifest_block_id;
+ ctx->root = manifest->slot;
+ ctx->slot0 = manifest->slot;
}
static inline int
-returnable_frag( fd_tower_tile_t * ctx,
+returnable_frag( ctx_t * ctx,
ulong in_idx,
- ulong seq,
+ ulong seq FD_PARAM_UNUSED,
ulong sig,
ulong chunk,
ulong sz,
ulong ctl,
ulong tsorig,
- ulong tspub,
+ ulong tspub FD_PARAM_UNUSED,
fd_stem_context_t * stem ) {
- (void)seq;
- (void)tspub;
- if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GENESIS ) ) {
- if( FD_LIKELY( sig==GENESI_SIG_BOOTSTRAP_COMPLETED ) ) {
- init_genesis( ctx, fd_type_pun( (uchar*)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk )+sizeof(fd_lthash_value_t)+sizeof(fd_hash_t) ) );
- ctx->initialized = 1;
- }
- } else if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_SNAP ) ) {
- if( FD_UNLIKELY( fd_ssmsg_sig_message( sig )==FD_SSMSG_DONE ) ) {
- snapshot_done( ctx, &ctx->manifest );
- ctx->initialized = 1;
- } else {
+
+ switch( ctx->in_kind[ in_idx ] ) {
+ case IN_KIND_GENESIS: {
+ if( FD_LIKELY( sig==GENESI_SIG_BOOTSTRAP_COMPLETED ) ) init_genesis( ctx );
+ return 0;
+ }
+ case IN_KIND_SNAP: {
+ if( FD_UNLIKELY( fd_ssmsg_sig_message( sig )==FD_SSMSG_DONE ) ) snapshot_done( ctx, &ctx->manifest );
+ else {
if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) )
FD_LOG_ERR(( "chunk %lu %lu from in %d corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
fd_memcpy( &ctx->manifest, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), sizeof(fd_snapshot_manifest_t) );
}
- } else if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) ) {
+ return 0;
+ }
+ case IN_KIND_GOSSIP: {
if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) )
FD_LOG_ERR(( "chunk %lu %lu from in %d corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
-
- /* If we haven't initialized from either genesis or a snapshot yet,
- we cannot process any frags from replay as it's a race condition,
- just wait until we initialize and then process. */
- if( FD_UNLIKELY( !ctx->initialized ) ) return 1;
-
+ if( FD_UNLIKELY( ctx->root==ULONG_MAX ) ) return 1;
+ if( FD_UNLIKELY( sig==FD_GOSSIP_UPDATE_TAG_VOTE ) ) {
+ fd_gossip_vote_t const * vote = &((fd_gossip_update_message_t const *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ))->vote;
+ gossip_vote( ctx, vote, tsorig, stem );
+ }
+ return 0;
+ }
+ case IN_KIND_REPLAY: {
+ if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) )
+ FD_LOG_ERR(( "chunk %lu %lu from in %d corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
+ if( FD_UNLIKELY( ctx->root==ULONG_MAX ) ) return 1;
if( FD_LIKELY( sig==REPLAY_SIG_SLOT_COMPLETED ) ) {
- fd_memcpy( &ctx->replay_slot_info, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), sizeof(fd_replay_slot_completed_t) );
+ fd_memcpy( &ctx->replay_slot_completed, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), sizeof(fd_replay_slot_completed_t) );
} else if( FD_LIKELY( sig==REPLAY_SIG_VOTE_STATE ) ) {
- if( FD_UNLIKELY( fd_frag_meta_ctl_som( ctl ) ) ) ctx->replay_towers_cnt = 0;
-
- if( FD_UNLIKELY( ctx->replay_towers_cnt>=FD_REPLAY_TOWER_VOTE_ACC_MAX ) ) FD_LOG_ERR(( "tower received more vote states than expected" ));
- memcpy( &ctx->replay_towers[ ctx->replay_towers_cnt ], fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), sizeof(fd_replay_tower_t) );
- ctx->replay_towers_cnt++;
-
- if( FD_UNLIKELY( fd_frag_meta_ctl_eom( ctl ) ) ) replay_slot_completed( ctx, &ctx->replay_slot_info, tsorig, stem );
+ if( FD_UNLIKELY( fd_frag_meta_ctl_som( ctl ) ) ) fd_tower_accts_remove_all( ctx->tower_accts );
+ fd_replay_tower_t * vote_state = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
+ fd_tower_accts_t acct = { .addr = vote_state->key, .stake = vote_state->stake };
+ fd_memcpy( acct.data, vote_state->acc, vote_state->acc_sz );
+ fd_tower_accts_push_tail( ctx->tower_accts, acct );
+ if( FD_UNLIKELY( fd_frag_meta_ctl_eom( ctl ) ) ) replay_slot_completed( ctx, &ctx->replay_slot_completed /* FIXME this seems racy */, tsorig, stem );
}
- } else {
+ return 0;
+ }
+ default: {
FD_LOG_ERR(( "unexpected input kind %d", ctx->in_kind[ in_idx ] ));
}
-
- return 0;
+ }
}
static void
@@ -378,72 +584,82 @@ privileged_init( fd_topo_t * topo,
fd_topo_tile_t * tile ) {
void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
FD_SCRATCH_ALLOC_INIT( l, scratch );
- fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
+ ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) );
+ FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
- if( FD_UNLIKELY( !strcmp( tile->tower.identity_key_path, "" ) ) )
- FD_LOG_ERR(( "identity_key_path not set" ));
+ FD_TEST( fd_rng_secure( &ctx->seed, 8 ) );
- ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.identity_key_path, /* pubkey only: */ 1 ) );
+ if( FD_UNLIKELY( !strcmp( tile->tower.identity_key, "" ) ) ) FD_LOG_ERR(( "identity_key_path not set" ));
+ ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.identity_key, /* pubkey only: */ 1 ) );
/* The vote key can be specified either directly as a base58 encoded
- pubkey, or as a file path. We first try to decode as a pubkey.
+ pubkey, or as a file path. We first try to decode as a pubkey. */
- TODO: The "vote_acc_path" should be renamed, as it might not be
- a path. */
- uchar * vote_key = fd_base58_decode_32( tile->tower.vote_acc_path, ctx->vote_acc->uc );
- if( FD_UNLIKELY( !vote_key ) ) {
- ctx->vote_acc[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.vote_acc_path, /* pubkey only: */ 1 ) );
- }
+ uchar * vote_key = fd_base58_decode_32( tile->tower.vote_account, ctx->vote_account->uc );
+ if( FD_UNLIKELY( !vote_key ) ) ctx->vote_account[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.vote_account, /* pubkey only: */ 1 ) );
+
+ /* The tower file is used to checkpt and restore the state of the
+ local tower. */
char path[ PATH_MAX ];
- FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin.new", tile->tower.ledger_path, FD_BASE58_ENC_32_ALLOCA( ctx->identity_key->uc ) ) );
+ FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin.new", tile->tower.base_path, FD_BASE58_ENC_32_ALLOCA( ctx->identity_key->uc ) ) );
ctx->checkpt_fd = open( path, O_WRONLY|O_CREAT|O_TRUNC, 0600 );
if( FD_UNLIKELY( -1==ctx->checkpt_fd ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
- FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin", tile->tower.ledger_path, FD_BASE58_ENC_32_ALLOCA( ctx->identity_key->uc ) ) );
+ FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin", tile->tower.base_path, FD_BASE58_ENC_32_ALLOCA( ctx->identity_key->uc ) ) );
ctx->restore_fd = open( path, O_RDONLY );
- if( FD_UNLIKELY( -1==ctx->restore_fd && errno!=ENOENT ) ) FD_LOG_WARNING(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
+ if( FD_UNLIKELY( -1==ctx->restore_fd && errno!=ENOENT ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
}
static void
unprivileged_init( fd_topo_t * topo,
fd_topo_tile_t * tile ) {
- void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
-
+ void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
+ ulong slot_max = tile->tower.slot_max;
+ int lg_slot_max = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max ) ) + 1;
FD_SCRATCH_ALLOC_INIT( l, scratch );
- fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
- void * _epoch = FD_SCRATCH_ALLOC_APPEND( l, fd_epoch_align(), fd_epoch_footprint( FD_REPLAY_TOWER_VOTE_ACC_MAX ) );
- void * _ghost = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ) );
- void * _tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint() );
- void * _vote_towers = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint()*FD_REPLAY_TOWER_VOTE_ACC_MAX );
-
- ctx->epoch = fd_epoch_join( fd_epoch_new( _epoch, FD_REPLAY_TOWER_VOTE_ACC_MAX ) );
- FD_TEST( ctx->epoch );
-
- ctx->ghost = fd_ghost_join( fd_ghost_new( _ghost, FD_BLOCK_MAX, 42UL ) );
+ ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) );
+ void * ghost = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(), fd_ghost_footprint( 2*slot_max, FD_VOTER_MAX ) );
+ void * notar = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_align(), fd_notar_footprint( slot_max ) );
+ void * tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint() );
+ void * accts = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_accts_align(), fd_tower_accts_footprint( FD_VOTER_MAX ) );
+ void * forks = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_forks_align(), fd_tower_forks_footprint( lg_slot_max ) );
+ void * spare = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint() );
+ void * sqmem = FD_SCRATCH_ALLOC_APPEND( l, slots_align(), slots_footprint( slot_max ) );
+ FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
+
+ ctx->ghost = fd_ghost_join ( fd_ghost_new ( ghost, 2*slot_max, FD_VOTER_MAX, 42UL ) ); /* FIXME seed */
+ ctx->notar = fd_notar_join ( fd_notar_new ( notar, slot_max ) );
+ ctx->tower = fd_tower_join ( fd_tower_new ( tower ) );
+ ctx->tower_accts = fd_tower_accts_join( fd_tower_accts_new( accts, FD_VOTER_MAX ) );
+ ctx->tower_forks = fd_tower_forks_join( fd_tower_forks_new( forks, lg_slot_max ) );
+ ctx->tower_spare = fd_tower_join ( fd_tower_new ( spare ) );
+ ctx->slots = slots_join ( slots_new ( sqmem, slot_max ) );
FD_TEST( ctx->ghost );
-
- ctx->tower = fd_tower_join( fd_tower_new( _tower ) );
+ FD_TEST( ctx->notar );
FD_TEST( ctx->tower );
-
- for( ulong i=0UL; ivote_towers[ i ] = fd_tower_join( fd_tower_new( (uchar*)_vote_towers+(i*fd_tower_footprint() ) ) );
- FD_TEST( ctx->vote_towers[ i ] );
+ FD_TEST( ctx->tower_accts );
+ FD_TEST( ctx->tower_forks );
+ FD_TEST( ctx->tower_spare );
+
+ for( ulong i = 0; igossip_vote_sha[i] = sha;
}
- ctx->initialized = 0;
-
- ctx->replay_towers_cnt = 0UL;
+ ctx->root = ULONG_MAX;
FD_TEST( tile->in_cntin_kind)/sizeof(ctx->in_kind[0]) );
for( ulong i=0UL; iin_cnt; i++ ) {
fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ];
fd_topo_wksp_t * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
- if( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESIS;
- else if( FD_LIKELY( !strcmp( link->name, "snapin_manif" ) ) ) ctx->in_kind[ i ] = IN_KIND_SNAP;
+ if ( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESIS;
+ else if( FD_LIKELY( !strcmp( link->name, "gossip_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP;
else if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
- else FD_LOG_ERR(( "tower tile has unexpected input link %lu %s", i, link->name ));
+ else if( FD_LIKELY( !strcmp( link->name, "snapin_manif" ) ) ) ctx->in_kind[ i ] = IN_KIND_SNAP;
+ else FD_LOG_ERR(( "tower tile has unexpected input link %lu %s", i, link->name ));
ctx->in[ i ].mem = link_wksp->wksp;
ctx->in[ i ].mtu = link->mtu;
@@ -464,7 +680,7 @@ populate_allowed_seccomp( fd_topo_t const * topo,
struct sock_filter * out ) {
void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
FD_SCRATCH_ALLOC_INIT( l, scratch );
- fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
+ ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) );
populate_sock_filter_policy_fd_tower_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)ctx->checkpt_fd, (uint)ctx->restore_fd );
return sock_filter_policy_fd_tower_tile_instr_cnt;
@@ -477,7 +693,7 @@ populate_allowed_fds( fd_topo_t const * topo,
int * out_fds ) {
void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
FD_SCRATCH_ALLOC_INIT( l, scratch );
- fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
+ ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) );
if( FD_UNLIKELY( out_fds_cnt<4UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
@@ -490,10 +706,11 @@ populate_allowed_fds( fd_topo_t const * topo,
return out_cnt;
}
-#define STEM_BURST (1UL)
+#define STEM_BURST (4UL)
-#define STEM_CALLBACK_CONTEXT_TYPE fd_tower_tile_t
-#define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_tower_tile_t)
+#define STEM_CALLBACK_CONTEXT_TYPE ctx_t
+#define STEM_CALLBACK_CONTEXT_ALIGN alignof(ctx_t)
+#define STEM_CALLBACK_METRICS_WRITE metrics_write
#define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
#include "../../disco/stem/fd_stem.c"
diff --git a/src/discof/tower/fd_tower_tile.h b/src/discof/tower/fd_tower_tile.h
index 214c652226d..76ef69a3610 100644
--- a/src/discof/tower/fd_tower_tile.h
+++ b/src/discof/tower/fd_tower_tile.h
@@ -3,9 +3,12 @@
#include "../../disco/topo/fd_topo.h"
+#define FD_TOWER_SIG_SLOT_DONE (0)
+#define FD_TOWER_SIG_SLOT_CONFIRMED (1)
+
/* In response to finishing replay of a slot, the tower tile will
- generate an update to the vote state, and potentially advance
- the root. */
+ produce both a block to vote for and block to reset to, and
+ potentially advance the root. */
struct fd_tower_slot_done {
@@ -15,17 +18,16 @@ struct fd_tower_slot_done {
by the vote sending tile to do some internal book-keeping related
to leader targeting. */
- ulong vote_slot;
+ ulong vote_slot;
- /* Sometimes, finshing replay of a slot may cause a new slot to be
- rooted. If this happens, new root will be 1 and both root_slot and
- root_block_id will be set to the new root values accordingly.
- Otherwise, `new_root` will be 0 and root_slot will be ULONG_MAX and
- root_block_id will be all 32-bytes of all 0s (Base58 111...). */
+ /* The slot to reset leader pipeline to. Unlike vote slot, the reset
+ slot is always set and represents the consensus fork to build on.
+ It may be unchanged since the last slot done. reset_block_id is
+ a unique identifier in case there are multiple blocks for the reset
+ slot due to equivocation. */
- ulong root_slot;
- fd_hash_t root_block_id;
- int new_root;
+ ulong reset_slot;
+ fd_hash_t reset_block_id;
/* This always contains a vote transaction with our current tower,
regardless of whether there is a new vote slot or not (ie. vote
@@ -38,17 +40,61 @@ struct fd_tower_slot_done {
ulong vote_txn_sz;
uchar vote_txn[ FD_TPU_MTU ];
+};
+typedef struct fd_tower_slot_done fd_tower_slot_done_t;
- /* The slot to reset leader pipeline to. Unlike vote slot, the reset
- slot is always set and represents the consensus fork to build on.
- It may be unchanged since the last slot done. */
-
- ulong reset_slot;
- fd_hash_t reset_block_id;
-
+/* fd_tower_slot_confirmed provides confirmed notifications of different
+ Solana confirmation levels. The levels are:
+
+ - duplicate: a block is duplicate confirmed if it has received votes
+ from at least 52% of stake in the cluster.
+
+ - optimistic: a block is optimistically confirmed if it has received
+ votes from at least 2/3 of stake in the cluster.
+
+ - cluster: same as optimistic, but may not have replayed / can be
+ delivered out of order.
+
+ - rooted: a block is rooted if it or any of its descendants reach max
+ lockout per TowerBFT rules.
+
+ For optimistic and rooted confirmations, the tower tile guarantees
+ that we have already replayed the block. This is not the case for
+ duplicate and cluster confirmations (a block can get duplicate or
+ cluster confirmed before it has been replayed). Optimistic and
+ rooted confirmations are also guaranteed to be delivered in-order
+ with no gaps from tower. That is, if we receive a rooted frag for
+ slot N, we will have already received rooted frags for any ancestor
+ slots N - 1, N - 2, ... (if they are not skipped / on a different
+ fork) and likewise for optimistic.
+
+ Note even if tower never actually voted on a slot (and therefore the
+ slot never became a tower root), tower will still send a rooted
+ confirmation for that slot if a descendant is voted on and eventually
+ rooted.
+
+ The reason both optimistic and cluster confirmed exist is "cluster"
+ is intended to be consumed by the Solana RPC protocol, whereas
+ optimistic is intended for Firedancer-specific APIs (hence in-order
+ and no gap guarantees) */
+
+#define FD_TOWER_SLOT_CONFIRMED_DUPLICATE 0
+#define FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC 1
+#define FD_TOWER_SLOT_CONFIRMED_CLUSTER 2
+#define FD_TOWER_SLOT_CONFIRMED_ROOTED 3
+
+struct fd_tower_slot_confirmed {
+ ulong slot;
+ fd_hash_t block_id;
+ int kind;
};
+typedef struct fd_tower_slot_confirmed fd_tower_slot_confirmed_t;
-typedef struct fd_tower_slot_done fd_tower_slot_done_t;
+union fd_tower_msg {
+ fd_tower_slot_done_t slot_done;
+ fd_tower_slot_confirmed_t slot_confirmed;
+};
+typedef union fd_tower_msg fd_tower_msg_t;
extern fd_topo_run_tile_t fd_tile_tower;