Line data Source code
1 : #ifndef HEADER_fd_src_choreo_ghost_fd_ghost_h
2 : #define HEADER_fd_src_choreo_ghost_fd_ghost_h
3 :
4 : /* fd_ghost implements Solana's LMD-GHOST ("latest message-driven greedy
5 : heaviest-observed subtree") fork choice rule.
6 :
7 : Protocol details:
8 :
9 : - LMD is an acronym for "latest message-driven". It describes how
10 : votes are counted when picking the best fork. In this scheme, only
11 : a validator's latest vote counts. So if a validator votes for slot
12 : 3 and then slot 5, the vote for 5 overwrites the vote for 3.
13 :
14 : - GHOST is an acronym for "greedy heaviest-observed subtree":
15 :
16 : greedy: for each depth of the tree, pick the locally optimal
17 : child / subtree / fork. This will result in the global
18 : optimal choice.
19 :
20 : heaviest: pick based on the highest stake weight.
21 :
22 : observed: this is the validator's local view, and other validators
23 : may have differing views because they've observed
24 : different votes.
25 :
26 : subtree: pick based on the weight of an entire subtree, not just
27 : an individual ele. For example, if slot 3 has 10 stake
28 : and slot 5 has 5 stake, but slot 5 has two children 6
29 : and 7 that each have 3 stake, our weights are
30 :
31 : slot 3 subtree [3] = 10
32 : slot 5 subtree [5, 6, 7] = 11 (5+3+3)
33 :
34 : Therefore slot 5 would be the heaviest.
35 :
36 : In-memory representation:
37 :
38 : There are two maps, both indexing the same pool of tree elements.
39 :
40 : - One map is keyed by slot number, and one map is keyed by hash_id
41 : (currently the block id, which is the merkle root of the last FEC
42 : set in the slot).
43 :
44 : - The elements in the slot map are a subset of the elements in the
45 : hash_id map.
46 :
47 : - Each tree ele tracks the amount of stake (`stake`) that has voted
48 : for its slot, as well as the recursive sum of stake for the subtree
49 : rooted at that ele (`weight`).
50 :
51 : The map keyed by slot is the "happy tree." i.e. the first version of
52 : a block we see and replay is going to be the version visible in the
53 : slot map. This is also the version of the slot that our tower is
54 : referring to. The map keyed by hash_id maintains every block that
55 : we've seen evidence of, including the equivocating blocks.
56 :
57 : both equivocating blocks seen only one block seen
58 :
59 : 0 0
60 : / \ /
61 : 1 1' (both invalid) 1' (valid)
62 : / \ /
63 : 2 3 3
64 :
65 : The first version of block 1 we see / replay is going to be the
66 : version visible in the slot map. Block 1' will be available in the
67 : hash-keyed tree, but not in the slot map. Block 3 will also be
68 : available in the slot-keyed tree, despite being a descendant of
69 : something not existing in the slot map.
70 :
71 : Thus in ghost,
72 :
73 : both equivocating blocks seen only one block seen
74 : slot_map: [0, 1, 2, 3] slot_map: [0, 1', 3]
75 : hash_map: [1'] hash_map: []
76 :
77 : Whatever is in the slot map is the slot referenced to by tower. Tower
78 : is *pure* and has no notion of duplicitness. So tower just needs to
79 : worry about querying ghost_slot_map for the proper hash_id.
80 :
81 : Slot 4 arrives, chained off of 1. In the right case where we didn't
82 : see block 1, block 4 now provides evidence for 1. We mark 1' as
83 : invalid for fork choice, and we repair the parent of 4 (getting 1').
84 : Then we replay down 1' and then replay down 4. Now the maps in this
85 : case look like:
86 :
87 : both equivocating blocks seen only one block seen
88 :
89 : 0 0
90 : / \ / \
91 : 1 1' (both invalid) 1' 1 (both invalid)
92 : / \ \ / \
93 : 2 4 3 3 4
94 :
95 : both equivocating blocks seen only one block seen
96 : slot_map: [0, 1, 2, 3, 4] slot_map: [0, 1', 3, 4]
97 : hash_map: [1'] hash_map: [1']
98 :
99 : Let's say 1' get's duplicate confirmed. Then the left case needs to
100 : switch forks in tower. Then ghost will need to itself swap the
101 : corresponding ghost hash_id in the slot_map to the hash_map and vice
102 : versa.
103 :
104 : both equivocating blocks seen only one block seen
105 : slot_map: [0, 1', 2, 3, 4] slot_map: [0, 1', 3, 4]
106 : hash_map: [1] hash_map: [1']
107 : (no change)
108 :
109 : 1' is marked as valid for fork choice. 1 remains invalid.
110 :
111 : Link to original GHOST paper: https://eprint.iacr.org/2013/881.pdf.
112 : This is simply a reference for those curious about the etymology, and
113 : not prerequisite reading for understanding this implementation. */
114 :
115 : #include "../fd_choreo_base.h"
116 : #include "../epoch/fd_epoch.h"
117 : #include "../../tango/fseq/fd_fseq.h"
118 :
119 : /* FD_GHOST_USE_HANDHOLDING: Define this to non-zero at compile time
120 : to turn on additional runtime checks and logging. */
121 :
122 : #ifndef FD_GHOST_USE_HANDHOLDING
123 : #define FD_GHOST_USE_HANDHOLDING 1
124 : #endif
125 :
126 : /* fd_ghost_ele_t implements a left-child, right-sibling n-ary tree.
127 : Each ele maintains the `pool` index of its left-most child
128 : (`child_idx`), its immediate-right sibling (`sibling_idx`), and its
129 : parent (`parent_idx`).
130 :
131 : This tree structure is gaddr-safe and supports accesses and
132 : operations from processes with separate local ghost joins. */
133 :
134 : struct __attribute__((aligned(128UL))) fd_ghost_ele {
135 : fd_hash_t key; /* hash_id (merkle root of the last FEC set in the slot) */
136 : ulong slot; /* slot this ele is tracking */
137 : ulong next; /* reserved for internal use by fd_pool, hash_map fd_map_chain and fd_ghost_publish */
138 : ulong nexts; /* reserved for internal use by slot_map fd_map_chain */
139 : ulong eqvoc; /* pool idx of a duplicate of this slot */
140 : ulong parent; /* pool idx of the parent */
141 : ulong child; /* pool idx of the left-child */
142 : ulong sibling; /* pool idx of the right-sibling */
143 : ulong weight; /* total stake from replay votes for this slot or any of its descendants */
144 : ulong replay_stake; /* total stake from replay votes for this slot */
145 : ulong gossip_stake; /* total stake from gossip votes for this slot */
146 : ulong rooted_stake; /* replay stake that has rooted this slot */
147 : int valid; /* whether this ele is valid for fork choice */
148 : };
149 : typedef struct fd_ghost_ele fd_ghost_ele_t;
150 :
151 : #define POOL_NAME fd_ghost_pool
152 114 : #define POOL_T fd_ghost_ele_t
153 : #include "../../util/tmpl/fd_pool.c"
154 :
155 : #define MAP_NAME fd_ghost_hash_map
156 : #define MAP_ELE_T fd_ghost_ele_t
157 : #define MAP_KEY_T fd_hash_t
158 2112 : #define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_hash_t)))
159 3225 : #define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
160 1929 : #define MAP_NEXT next
161 : #include "../../util/tmpl/fd_map_chain.c"
162 :
163 : #define MAP_NAME fd_ghost_slot_map
164 : #define MAP_ELE_T fd_ghost_ele_t
165 321 : #define MAP_KEY slot
166 561 : #define MAP_NEXT nexts
167 : #include "../../util/tmpl/fd_map_chain.c"
168 :
169 : struct fd_dup_seen {
170 : ulong slot;
171 : };
172 : typedef struct fd_dup_seen fd_dup_seen_t;
173 :
174 : #define MAP_NAME fd_dup_seen_map
175 426 : #define MAP_T fd_dup_seen_t
176 1104 : #define MAP_KEY slot
177 : #define MAP_MEMOIZE 0
178 : #include "../../util/tmpl/fd_map_dynamic.c"
179 :
180 : /* fd_ghost_t is the top-level structure that holds the root of the
181 : tree, as well as the memory pools and map structures for tracking
182 : ghost eles and votes.
183 :
184 : These structures are bump-allocated and laid out contiguously in
185 : memory from the fd_ghost_t * pointer which points to the beginning of
186 : the memory region.
187 :
188 : ---------------------- <- fd_ghost_t *
189 : | metadata |
190 : ----------------------
191 : | pool |
192 : ----------------------
193 : | map |
194 : ----------------------
195 :
196 : A valid, initialized ghost is always non-empty. After
197 : `fd_ghost_init` the ghost will always have a root ele unless
198 : modified improperly out of ghost's API. */
199 :
200 57 : #define FD_GHOST_MAGIC (0xf17eda2ce7940570UL) /* firedancer ghost version 0 */
201 :
202 : struct __attribute__((aligned(128UL))) fd_ghost {
203 :
204 : /* Metadata */
205 :
206 : ulong magic; /* ==FD_GHOST_MAGIC */
207 : ulong ghost_gaddr; /* wksp gaddr of this in the backing wksp, non-zero gaddr */
208 : ulong seed; /* seed for various hashing function used under the hood, arbitrary */
209 : ulong root; /* pool idx of the root */
210 : ulong pool_gaddr; /* wksp gaddr of the pool backing this ghost, non-zero gaddr */
211 : ulong hash_map_gaddr; /* wksp gaddr of the map (for fast O(1) querying by hash) backing this ghost, non-zero gaddr */
212 : ulong slot_map_gaddr; /* wksp gaddr of the map (for fast O(1) querying by slot) backing this ghost, non-zero gaddr */
213 :
214 : ulong dup_map_gaddr; /* wksp gaddr of the map (for fast O(1) querying, non-zero gaddr */
215 : /* version fseq. query pre & post read. if value is ULONG_MAX, ghost
216 : is uninitialized or invalid.
217 :
218 : odd: if either pre or post is odd, discard read.
219 : even: if pre == post, read is consistent. */
220 :
221 : ulong ver_gaddr;
222 : };
223 : typedef struct fd_ghost fd_ghost_t;
224 :
225 : FD_PROTOTYPES_BEGIN
226 :
227 : /* Constructors */
228 :
229 : /* fd_ghost_{align,footprint} return the required alignment and
230 : footprint of a memory region suitable for use as ghost with up to
231 : ele_max eles and vote_max votes. */
232 :
233 : FD_FN_CONST static inline ulong
234 735 : fd_ghost_align( void ) {
235 735 : return alignof(fd_ghost_t);
236 735 : }
237 :
238 : FD_FN_CONST static inline ulong
239 114 : fd_ghost_footprint( ulong ele_max ) {
240 114 : int lg_ele_max = fd_ulong_find_msb( fd_ulong_pow2_up( ele_max ) );
241 114 : return FD_LAYOUT_FINI(
242 114 : FD_LAYOUT_APPEND(
243 114 : FD_LAYOUT_APPEND(
244 114 : FD_LAYOUT_APPEND(
245 114 : FD_LAYOUT_APPEND(
246 114 : FD_LAYOUT_APPEND(
247 114 : FD_LAYOUT_APPEND(
248 114 : FD_LAYOUT_INIT,
249 114 : alignof(fd_ghost_t), sizeof(fd_ghost_t) ),
250 114 : fd_fseq_align(), fd_fseq_footprint() ),
251 114 : fd_ghost_pool_align(), fd_ghost_pool_footprint ( ele_max ) ),
252 114 : fd_ghost_hash_map_align(), fd_ghost_hash_map_footprint( ele_max ) ),
253 114 : fd_ghost_slot_map_align(), fd_ghost_slot_map_footprint( ele_max ) ),
254 114 : fd_dup_seen_map_align(), fd_dup_seen_map_footprint ( lg_ele_max ) ),
255 114 : fd_ghost_align() );
256 114 : }
257 :
258 : /* fd_ghost_new formats an unused memory region for use as a ghost.
259 : mem is a non-NULL pointer to this region in the local address space
260 : with the required footprint and alignment. */
261 :
262 : void *
263 : fd_ghost_new( void * shmem, ulong ele_max, ulong seed );
264 :
265 : /* fd_ghost_join joins the caller to the ghost. ghost points to the
266 : first byte of the memory region backing the ghost in the caller's
267 : address space.
268 :
269 : Returns a pointer in the local address space to ghost on success. */
270 :
271 : fd_ghost_t *
272 : fd_ghost_join( void * ghost );
273 :
274 : /* fd_ghost_leave leaves a current local join. Returns a pointer to the
275 : underlying shared memory region on success and NULL on failure (logs
276 : details). Reasons for failure include ghost is NULL. */
277 :
278 : void *
279 : fd_ghost_leave( fd_ghost_t const * ghost );
280 :
281 : /* fd_ghost_delete unformats a memory region used as a ghost.
282 : Assumes only the nobody is joined to the region. Returns a
283 : pointer to the underlying shared memory region or NULL if used
284 : obviously in error (e.g. ghost is obviously not a ghost ... logs
285 : details). The ownership of the memory region is transferred to the
286 : caller. */
287 :
288 : void *
289 : fd_ghost_delete( void * ghost );
290 :
291 : /* fd_ghost_init initializes a ghost. Assumes ghost is a valid local
292 : join and no one else is joined. root is the initial root ghost will
293 : use. This is the snapshot slot if booting from a snapshot, 0 if the
294 : genesis slot. hash is the hash_id of the initial root.
295 :
296 : In general, this should be called by the same process that formatted
297 : ghost's memory, ie. the caller of fd_ghost_new. */
298 :
299 : void
300 : fd_ghost_init( fd_ghost_t * ghost, ulong root, fd_hash_t * hash );
301 :
302 : /* Accessors */
303 :
304 : /* fd_ghost_wksp returns the local join to the wksp backing the ghost.
305 : The lifetime of the returned pointer is at least as long as the
306 : lifetime of the local join. Assumes ghost is a current local
307 : join. */
308 :
309 : FD_FN_PURE static inline fd_wksp_t *
310 8880 : fd_ghost_wksp( fd_ghost_t const * ghost ) {
311 8880 : return (fd_wksp_t *)( ( (ulong)ghost ) - ghost->ghost_gaddr );
312 8880 : }
313 :
314 : /* fd_ghost_{ver,pool,map,root} returns a pointer in the caller's
315 : address space to the corresponding ghost field. const versions for
316 : each are also provided. */
317 :
318 579 : FD_FN_PURE static inline ulong * fd_ghost_ver ( fd_ghost_t * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->ver_gaddr ); }
319 102 : FD_FN_PURE static inline ulong const * fd_ghost_ver_const ( fd_ghost_t const * ghost ) { return fd_wksp_laddr_fast( fd_ghost_wksp( ghost ), ghost->ver_gaddr ); }
320 3555 : 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 ); }
321 1725 : 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 ); }
322 1653 : 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 ); }
323 462 : 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 ); }
324 405 : 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 ); }
325 87 : 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 ); }
326 312 : 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 ); }
327 0 : 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 ); }
328 756 : 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 ); }
329 114 : 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 ); }
330 :
331 : /* fd_ghost_{parent,child,sibling} returns a pointer in the caller's
332 : address space to the corresponding {parent,left-child,right-sibling}
333 : of fec. Assumes ghost is a current local join and fec is a valid
334 : pointer to a pool element inside ghost. const versions for each are
335 : also provided. */
336 :
337 540 : 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 ); }
338 45 : 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 ); }
339 6 : 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 ); }
340 237 : 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 ); }
341 0 : 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 ); }
342 225 : 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 ); }
343 :
344 : /* fd_ghost_{query,query_const} returns the ele keyed by `hash_id`,
345 : NULL if not found. */
346 :
347 : FD_FN_PURE static inline fd_ghost_ele_t *
348 1251 : fd_ghost_query( fd_ghost_t * ghost, fd_hash_t const * hash ) {
349 1251 : if( FD_UNLIKELY( !hash ) ) { return NULL; }
350 1251 : fd_ghost_hash_map_t * map = fd_ghost_hash_map( ghost );
351 1251 : fd_ghost_ele_t * pool = fd_ghost_pool( ghost );
352 1251 : return fd_ghost_hash_map_ele_query( map, hash, NULL, pool );
353 1251 : }
354 :
355 : FD_FN_PURE static inline fd_ghost_ele_t const *
356 360 : fd_ghost_query_const( fd_ghost_t const * ghost, fd_hash_t const * hash ) {
357 360 : if( FD_UNLIKELY( !hash ) ) { return NULL; }
358 360 : fd_ghost_hash_map_t const * map = fd_ghost_hash_map_const ( ghost );
359 360 : fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
360 360 : return fd_ghost_hash_map_ele_query_const( map, hash, NULL, pool );
361 360 : }
362 :
363 : /* fd_ghost_hash returns the hash_id of the ele keyed by `slot`.
364 : NULL if the slot is not found. */
365 :
366 : FD_FN_PURE static inline fd_hash_t const *
367 87 : fd_ghost_hash( fd_ghost_t const * ghost, ulong slot ) {
368 87 : fd_ghost_slot_map_t const * maps = fd_ghost_slot_map_const( ghost );
369 87 : fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
370 87 : fd_ghost_ele_t const * ele = fd_ghost_slot_map_ele_query_const( maps, &slot, NULL, pool );
371 87 : return ele ? &ele->key : NULL;
372 87 : }
373 :
374 : /* fd_ghost_head greedily traverses the ghost beginning from `root` (not
375 : necessarily the root of the ghost tree) and returns the heaviest leaf
376 : of the traversal (see top-level documentation for traversal details).
377 : Assumes ghost is a current local join and has been initialized with
378 : fd_ghost_init and is therefore non-empty. */
379 :
380 : fd_ghost_ele_t const *
381 : fd_ghost_head( fd_ghost_t const * ghost, fd_ghost_ele_t const * root );
382 :
383 : /* fd_ghost_gca returns the greatest common ancestor of block1, block2
384 : in ghost. Assumes block1 or block2 are present in ghost (warns and
385 : returns NULL with handholding enabled). This is guaranteed to be
386 : non-NULL if block1 and block2 are both present. */
387 :
388 : fd_ghost_ele_t const *
389 : fd_ghost_gca( fd_ghost_t const * ghost, fd_hash_t const * bid1, fd_hash_t const * bid2 );
390 :
391 : /* fd_ghost_is_ancestor returns 1 if `ancestor` is `slot`'s ancestor, 0
392 : otherwise. Also returns 0 if either `ancestor` or `slot` are not in
393 : ghost. */
394 :
395 : int
396 : fd_ghost_is_ancestor( fd_ghost_t const * ghost, fd_hash_t const * ancestor, fd_hash_t const * slot );
397 :
398 : /* Operations */
399 :
400 : /* fd_ghost_insert inserts a new ele keyed by `hash_id`, for the slot
401 : `slot` into the ghost. Inserts an ele keyed by `slot` into the slot
402 : map if one doesn't already exist as well. Assumes slot >= ghost->smr,
403 : parent_hash_id is already in ghost, and the ele pool has a free
404 : element (if handholding is enabled, explicitly checks and errors).
405 : Returns the inserted ghost ele. */
406 :
407 : /* FIXME: total_stake as an arg is a little unwieldy. is there a better
408 : way to design this API? ghost->total_stake runs the risk of forgetting
409 : to update*/
410 :
411 : fd_ghost_ele_t *
412 : fd_ghost_insert( fd_ghost_t * ghost, fd_hash_t const * parent_hash, ulong slot, fd_hash_t const * hash_id, ulong total_stake );
413 :
414 : /* fd_ghost_replay_vote votes for hash_id, adding pubkey's stake to the
415 : `stake` field for slot and to the `weight` field for both slot and
416 : slot's ancestors. If pubkey has previously voted, pubkey's stake is
417 : also subtracted from `weight` for its previous vote slot and its
418 : ancestors.
419 :
420 : Assumes slot is present in ghost (if handholding is enabled,
421 : explicitly checks and errors).
422 :
423 : TODO the implementation can be made more efficient by
424 : short-circuiting and doing fewer traversals. Currently this is
425 : bounded to O(h), where h is the height of ghost. */
426 :
427 : void
428 : fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, fd_hash_t const * hash_id );
429 :
430 : /* fd_ghost_gossip_vote adds stake amount to the gossip_stake field of
431 : slot.
432 :
433 : Assumes slot is present in ghost (if handholding is enabled,
434 : explicitly checks and errors). Returns the ghost ele keyed by slot.
435 :
436 : Unlike fd_ghost_replay_vote, this stake is not propagated to
437 : the weight field for slot and slot's ancestors. It is only counted
438 : towards slot itself, as gossip votes are only used for optimistic
439 : confirmation and not fork choice. */
440 :
441 : void
442 : fd_ghost_gossip_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot );
443 :
444 : /* fd_ghost_rooted_vote adds stake amount to the rooted_stake field of
445 : slot.
446 :
447 : Assumes slot is present in ghost (if handholding is enabled,
448 : explicitly checks and errors). Returns the ghost ele keyed by slot.
449 :
450 : Note rooting a slot implies rooting its ancestor, but ghost does not
451 : explicitly track this. */
452 :
453 : void
454 : fd_ghost_rooted_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong root );
455 :
456 : /* fd_ghost_publish publishes slot as the new ghost root, setting the
457 : subtree beginning from slot as the new ghost tree (ie. slot and all
458 : its descendants). Prunes all eles not in slot's ancestry. Assumes
459 : slot is present in ghost. Returns the new root. */
460 :
461 : fd_ghost_ele_t const *
462 : fd_ghost_publish( fd_ghost_t * ghost, fd_hash_t const * hash_id );
463 :
464 : /* Misc */
465 :
466 : /* fd_ghost_verify checks the ghost is not obviously corrupt, as well as
467 : that ghost invariants are being preserved ie. the weight of every
468 : ele is >= the sum of weights of its direct children. Returns 0 if
469 : verify succeeds, -1 otherwise. */
470 :
471 : int
472 : fd_ghost_verify( fd_ghost_t const * ghost );
473 :
474 : /* fd_ghost_print pretty-prints a formatted ghost tree. Printing begins
475 : from `ele` (it will appear as the root in the print output).
476 :
477 : The most straightforward and commonly used printing pattern is:
478 : `fd_ghost_print( ghost, fd_ghost_root( ghost ) )`
479 :
480 : This would print ghost beginning from the root.
481 :
482 : Alternatively, caller can print a more localized view, for example
483 : starting from the grandparent of the most recently executed slot:
484 :
485 : ```
486 : fd_ghost_ele_t const * ele = fd_ghost_query( slot );
487 : fd_ghost_print( ghost, fd_ghost_parent( fd_ghost_parent( ele ) ) )
488 : ```
489 :
490 : Callers should add null-checks as appropriate in actual usage. */
491 :
492 : void
493 : fd_ghost_print( fd_ghost_t const * ghost, ulong total_stake, fd_ghost_ele_t const * ele );
494 :
495 : static int FD_FN_UNUSED
496 48 : is_duplicate_confirmed( fd_ghost_t * ghost, fd_hash_t const * hash, ulong total_stake ) {
497 48 : fd_ghost_ele_t const * ele = fd_ghost_query( ghost, hash );
498 48 : if( FD_UNLIKELY( !ele ) ) {
499 0 : FD_LOG_WARNING(( "[%s] slot %s was not in ghost", __func__, FD_BASE58_ENC_32_ALLOCA(hash) ));
500 0 : return 0;
501 0 : }
502 48 : double pct = (double)( ele->weight + ele->gossip_stake ) / (double)total_stake; /* TODO make gossip weight a field as well */
503 48 : return pct > FD_EQVOCSAFE_PCT;
504 48 : }
505 :
506 : /* Duplicate confirmed signal */
507 :
508 : void
509 : process_duplicate_confirmed( fd_ghost_t * ghost, fd_hash_t const * hash, ulong slot );
510 :
511 : void
512 : process_duplicate( fd_ghost_t * ghost, ulong slot, ulong total_stake );
513 :
514 :
515 : FD_PROTOTYPES_END
516 :
517 : #endif /* HEADER_fd_src_choreo_ghost_fd_ghost_h */
|