Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_gossip_fd_crds_h
2 : #define HEADER_fd_src_flamenco_gossip_fd_crds_h
3 :
4 : #include "../fd_gossip_private.h"
5 : #include "../fd_gossip_out.h"
6 :
7 : #include "../../../disco/metrics/generated/fd_metrics_gossip.h"
8 : #include "../../../ballet/sha256/fd_sha256.h"
9 :
10 : struct fd_crds_entry_private;
11 : typedef struct fd_crds_entry_private fd_crds_entry_t;
12 :
13 : struct fd_crds_private;
14 : typedef struct fd_crds_private fd_crds_t;
15 :
16 : struct fd_crds_mask_iter_private;
17 : typedef struct fd_crds_mask_iter_private fd_crds_mask_iter_t;
18 :
19 9 : #define FD_CRDS_ALIGN 128UL
20 :
21 3 : #define FD_CRDS_MAGIC (0xf17eda2c37c7d50UL) /* firedancer crds version 0*/
22 :
23 0 : #define FD_CRDS_UPSERT_CHECK_UPSERTS ( 0)
24 0 : #define FD_CRDS_UPSERT_CHECK_FAILS (-1)
25 :
26 2654520 : #define CRDS_MAX_CONTACT_INFO (1<<15) /* 32768 */
27 :
28 : struct fd_crds_metrics {
29 : ulong count[ FD_METRICS_ENUM_CRDS_VALUE_CNT ];
30 : ulong expired_cnt;
31 : ulong evicted_cnt;
32 :
33 : ulong peer_staked_cnt;
34 : ulong peer_unstaked_cnt;
35 : ulong peer_visible_stake;
36 : ulong peer_evicted_cnt;
37 :
38 : ulong purged_cnt;
39 : ulong purged_expired_cnt;
40 : ulong purged_evicted_cnt;
41 : };
42 :
43 : typedef struct fd_crds_metrics fd_crds_metrics_t;
44 :
45 : FD_PROTOTYPES_BEGIN
46 :
47 : FD_FN_CONST ulong
48 : fd_crds_align( void );
49 :
50 : FD_FN_CONST ulong
51 : fd_crds_footprint( ulong ele_max,
52 : ulong purged_max );
53 :
54 : void *
55 : fd_crds_new( void * shmem,
56 : fd_rng_t * rng,
57 : ulong ele_max,
58 : ulong purged_max,
59 : fd_gossip_out_ctx_t * gossip_update_out );
60 :
61 : fd_crds_t *
62 : fd_crds_join( void * shcrds );
63 :
64 : fd_crds_metrics_t const *
65 : fd_crds_metrics( fd_crds_t const * crds );
66 :
67 : /* fd_crds_advance performs housekeeping operations and should be run
68 : as a part of a gossip advance loop. The following operations are
69 : performed:
70 : - expire: removes stale entries from the replicated data store.
71 : CRDS values from staked nodes expire roughly an epoch after they
72 : are created, and values from non-staked nodes expire after 15
73 : seconds. Removed contact info entries are also published as gossip
74 : updates via stem.
75 : - re-weigh: peers are downsampled in the peer sampler if they have
76 : not been refreshed in <60s.
77 :
78 : There is one exception, when the node is first bootstrapping, and
79 : has not yet seen any staked nodes, values do not expire at all. */
80 :
81 : void
82 : fd_crds_advance( fd_crds_t * crds,
83 : long now,
84 : fd_stem_context_t * stem );
85 :
86 : /* fd_crds_len returns the number of entries in the CRDS table. This
87 : does not include purged entries, which have a separate queue tracking
88 : them. See fd_crds_purged_* APIs below. */
89 :
90 : ulong
91 : fd_crds_len( fd_crds_t const * crds );
92 :
93 : /* fd_crds maintains a table of purged CRDS entries. A CRDS entry is
94 : purged when it is overriden by a newer form of the entry, or it is
95 : expired. Such entries are no longer propagated by the node, but are
96 : still tracked in order to avoid re-receiving them via pull responses
97 : by including them in the pull request filters we generate. This
98 : means we only need to hold the hash of the entry and the wallclock
99 : time when it was purged.
100 :
101 : Agave's gossip client maintains two such tables: one labeled "purged"
102 : and another "failed_inserts". They function the same, the only
103 : difference lies in the conditions that trigger the insertion and the
104 : expiry windows.
105 :
106 : - purged, kept for 60s
107 :
108 : A CRDS value is roughly considered "purged" when it is removed
109 : from the gossip table due to an incoming CRDS value replacing it.
110 :
111 : - failed, kept for 20s
112 :
113 : A CRDS value is failed when it is incoming and does not upsert the
114 : table, or it is too old to be inserted.
115 : */
116 :
117 : ulong
118 : fd_crds_purged_len( fd_crds_t const * crds );
119 :
120 : void
121 : fd_crds_generate_hash( fd_sha256_t * sha,
122 : uchar const * crds_value,
123 : ulong crds_value_sz,
124 : uchar out_hash[ static 32UL ] );
125 :
126 : void
127 : fd_crds_insert_failed_insert( fd_crds_t * crds,
128 : uchar const * hash,
129 : long now );
130 :
131 : /* fd_crds_checks_fast checks if inserting a CRDS value would fail on
132 : specific conditions. Updates the CRDS purged table depending on the checks
133 : that failed.
134 :
135 : This isn't an exhaustive check, but that does not matter since
136 : fd_crds_insert will perform the full check. This avoids expensive operations
137 : like sigverify and hashing* if a CRDS value fails these fast checks.
138 :
139 : Returns FD_CRDS_UPSERT_CHECK_UPSERTS if the value passes the fast checks.
140 : Returns >0 if the value is a duplicate, with the return value denoting the
141 : number of duplicates seen at this point (including current). Returns
142 : FD_CRDS_UPSERT_CHECK_UNDETERMINED if further checks are needed
143 : (e.g. hash comparison). Returns FD_CRDS_UPSERT_CHECK_FAILS for other
144 : failures (e.g. too old). This will result in the candidate being purged.
145 :
146 : Note that this function is not idempotent as duplicate counts are tracked by
147 : the CRDS table.
148 :
149 : *Hashing is performed if a failure condition warrants a purge insert. */
150 :
151 : int
152 : fd_crds_checks_fast( fd_crds_t * crds,
153 : fd_gossip_view_crds_value_t const * candidate,
154 : uchar const * payload,
155 : uchar from_push_msg );
156 :
157 : /* fd_crds_insert inserts and indexes a CRDS value into the data store
158 : as a CRDS entry, so that it can be returned by future queries. This
159 : function should not be called if the result of fd_crds_checks_fast is
160 : not FD_CRDS_UPSERT_CHECK_UPSERTS.
161 :
162 : On top of inserting the CRDS entry, this function also updates the sidetable
163 : of ContactInfo entries and the peer samplers if the entry is a ContactInfo.
164 : is_from_me indicates the CRDS entry originates from this node. We exclude our
165 : own entries from peer samplers. origin_stake is used to weigh the peer in the
166 : samplers.
167 :
168 : stem is used to publish updates to {ContactInfo, Vote, LowestSlot} entries.
169 :
170 : Returns a pointer to the newly created CRDS entry. Lifetime is guaranteed
171 : until the next call to the following functions:
172 : - fd_crds_insert
173 : - fd_crds_expire
174 : Returns NULL if the insertion fails for any reason. */
175 :
176 : fd_crds_entry_t const *
177 : fd_crds_insert( fd_crds_t * crds,
178 : fd_gossip_view_crds_value_t const * candidate_view,
179 : uchar const * payload,
180 : ulong origin_stake,
181 : uchar is_from_me,
182 : long now,
183 : fd_stem_context_t * stem );
184 :
185 : /* fd_crds_has_staked_node returns true if any node in the cluster is
186 : observed to have stake. This is used in timeout calculations, which
187 : default to an extended expiry window when we have yet to observe
188 : any staked peers. */
189 : int
190 : fd_crds_has_staked_node( fd_crds_t const * crds );
191 :
192 : void
193 : fd_crds_entry_value( fd_crds_entry_t const * entry,
194 : uchar const ** value_bytes,
195 : ulong * value_sz );
196 :
197 : uchar const *
198 : fd_crds_entry_pubkey( fd_crds_entry_t const * entry );
199 :
200 : /* fd_crds_entry_hash returns a pointer to the 32b sha256 hash of the
201 : entry's value hash. This is used for constructing a bloom filter. */
202 : uchar const *
203 : fd_crds_entry_hash( fd_crds_entry_t const * entry );
204 :
205 : /* fd_crds_entry_is_contact_info returns 1 if entry holds a Contact
206 : Info CRDS value. Assumes entry was populated with either
207 : fd_crds_populate_{preflight,full} */
208 : int
209 : fd_crds_entry_is_contact_info( fd_crds_entry_t const * entry );
210 :
211 : /* fd_crds_contact_info returns a pointer to the contact info
212 : structure in the entry. This is used to access the contact info
213 : fields in the entry, such as the pubkey, shred version, and
214 : socket address.
215 :
216 : Assumes crds entry is a contact info (check with
217 : fd_crds_entry_is_contact_info) */
218 : fd_contact_info_t *
219 : fd_crds_entry_contact_info( fd_crds_entry_t const * entry );
220 :
221 : /* fd_crds tracks Contact Info entries with a sidetable that holds the
222 : fully decoded contact info of a */
223 :
224 : /* fd_crds_contact_info_lookup returns a pointer to the contact info
225 : structure corresponding to pubkey. returns NULL if there is no such
226 : entry. */
227 :
228 : fd_contact_info_t const *
229 : fd_crds_contact_info_lookup( fd_crds_t const * crds,
230 : uchar const * pubkey );
231 :
232 : /* fd_crds_peer_count returns the number of Contact Info entries
233 : present in the sidetable. The lifetime of a Contact Info entry
234 : tracks the lifetime of the corresponding CRDS entry. */
235 : ulong
236 : fd_crds_peer_count( fd_crds_t const * crds );
237 :
238 : /* The CRDS table tracks whether a peer is active or not to determine
239 : whether it should be sampled (see sample APIs).
240 : fd_crds_peer_{active,inactive} provide a way to manage this state
241 : for a given peer.
242 :
243 : A peer's active state is typicallly determined by its ping/pong status. */
244 : void
245 : fd_crds_peer_active( fd_crds_t * crds,
246 : uchar const * peer_pubkey,
247 : long now );
248 :
249 : void
250 : fd_crds_peer_inactive( fd_crds_t * crds,
251 : uchar const * peer_pubkey,
252 : long now );
253 :
254 : /* The CRDS Table also maintains a set of peer samplers for use in various
255 : Gossip tx cases. Namely
256 : - Rotating the active push set (bucket_samplers)
257 : - Selecting a pull request target (pr_sampler) */
258 :
259 :
260 : /* fd_crds_bucket_* sample APIs are meant to be used by fd_active_set.
261 : Each bucket has a unique sampler. */
262 : fd_contact_info_t const *
263 : fd_crds_bucket_sample_and_remove( fd_crds_t * crds,
264 : fd_rng_t * rng,
265 : ulong bucket );
266 :
267 : /* fd_crds_bucket adds back in a peer that was previously
268 : sampled with fd_crds_bucket_sample_and_remove. */
269 : void
270 : fd_crds_bucket_add( fd_crds_t * crds,
271 : ulong bucket,
272 : uchar const * pubkey );
273 :
274 :
275 : /* fd_crds_sample_peer randomly selects a peer node from the CRDS based
276 : weighted by stake. Peers with a ContactInfo that hasn't been
277 : refreshed in more than 60 seconds are considered offline, and are
278 : downweighted in the selection by a factor of 100. They are still
279 : included to mitigate eclipse attacks. Peers with no ContactInfo in
280 : the CRDS are not included in the selection. The current node is
281 : also excluded from the selection. Low stake peers which are not
282 : active in the ping tracker, because they aren't responding to pings
283 : are also excluded from the sampling. Peers with a different shred
284 : version than us, or with an invalid gossip socket address are also
285 : excluded from the sampling.
286 :
287 : If no valid peer can be found, the returned fd_contact_info_t will be
288 : NULL. The caller should check for this case and handle it
289 : appropriately. On success, the returned fd_contact_info_t is a
290 : contact info suitable for sending a gossip pull request. */
291 :
292 : fd_contact_info_t const *
293 : fd_crds_peer_sample( fd_crds_t const * crds,
294 : fd_rng_t * rng );
295 :
296 : /* fd_crds_mask_iter_{init,next,done,entry} provide an API to
297 : iterate over the CRDS values in the table that whose hashes match
298 : a given mask. In the Gossip CRDS filter, the mask is applied on
299 : the most significant 8 bytes of the CRDS value's hash.
300 :
301 : The Gossip CRDS filter encodes the mask in two values: `mask` and
302 : `mask_bits`. For example, if we set `mask_bits` to 5 and 0b01010 as
303 : `mask`, we get the following 64-bit bitmask:
304 : 01010 11111111111.....
305 :
306 : Therefore, we can frame a mask match as a CRDS value's hash whose
307 : most significant `mask_bits` is `mask`. We can trivially define
308 : the range of matching hash values by setting the non-mask bits to
309 : all 0s or 1s to get the start and end values respectively. */
310 :
311 : fd_crds_mask_iter_t *
312 : fd_crds_mask_iter_init( fd_crds_t const * crds,
313 : ulong mask,
314 : uint mask_bits,
315 : uchar iter_mem[ static 16UL ] );
316 :
317 : fd_crds_mask_iter_t *
318 : fd_crds_mask_iter_next( fd_crds_mask_iter_t * it,
319 : fd_crds_t const * crds );
320 :
321 : int
322 : fd_crds_mask_iter_done( fd_crds_mask_iter_t * it,
323 : fd_crds_t const * crds );
324 :
325 : fd_crds_entry_t const *
326 : fd_crds_mask_iter_entry( fd_crds_mask_iter_t * it,
327 : fd_crds_t const * crds );
328 :
329 : /* fd_crds_purged_mask_iter_{init,next,done} mirrors the fd_crds_mask_*
330 : APIs for the purged table. This includes purged and failed_inserts
331 : entries for the specified mask range.
332 :
333 : Mixing APIs (e.g., using crds init and purged next/done/hash) is UB.*/
334 :
335 : fd_crds_mask_iter_t *
336 : fd_crds_purged_mask_iter_init( fd_crds_t const * crds,
337 : ulong mask,
338 : uint mask_bits,
339 : uchar iter_mem[ static 16UL ] );
340 :
341 : fd_crds_mask_iter_t *
342 : fd_crds_purged_mask_iter_next( fd_crds_mask_iter_t * it,
343 : fd_crds_t const * crds );
344 :
345 : int
346 : fd_crds_purged_mask_iter_done( fd_crds_mask_iter_t * it,
347 : fd_crds_t const * crds );
348 :
349 : /* fd_crds_purged_mask_iter_hash returns the hash of the current
350 : entry in the purged mask iterator. */
351 : uchar const *
352 : fd_crds_purged_mask_iter_hash( fd_crds_mask_iter_t * it,
353 : fd_crds_t const * crds );
354 :
355 : FD_PROTOTYPES_END
356 :
357 : #endif /* HEADER_fd_src_flamenco_gossip_fd_crds_h */
|