Skip to content

Commit 8cf8dc3

Browse files
committed
runtime: rewrite blockhash queue
1 parent eda6db8 commit 8cf8dc3

24 files changed

+499
-633
lines changed

src/discof/replay/fd_replay_tile.c

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,9 @@ publish_slot_notifications( fd_replay_tile_ctx_t * ctx,
602602

603603
msg->slot_exec.bank_hash = fd_bank_bank_hash_get( ctx->slot_ctx->bank );
604604

605-
fd_block_hash_queue_global_t const * block_hash_queue = fd_bank_block_hash_queue_query( ctx->slot_ctx->bank );
606-
fd_hash_t * last_hash = fd_block_hash_queue_last_hash_join( block_hash_queue );
605+
fd_blockhashes_t const * block_hash_queue = fd_bank_block_hash_queue_query( ctx->slot_ctx->bank );
606+
fd_hash_t const * last_hash = fd_blockhashes_peek_last( block_hash_queue );
607+
FD_TEST( last_hash );
607608
msg->slot_exec.block_hash = *last_hash;
608609

609610
memcpy( &msg->slot_exec.identity, ctx->validator_identity_pubkey, sizeof( fd_pubkey_t ) );
@@ -923,12 +924,12 @@ init_poh( fd_replay_tile_ctx_t * ctx ) {
923924
msg->ticks_per_slot = fd_bank_ticks_per_slot_get( ctx->slot_ctx->bank );
924925
msg->tick_duration_ns = (ulong)(fd_bank_ns_per_slot_get( ctx->slot_ctx->bank )) / fd_bank_ticks_per_slot_get( ctx->slot_ctx->bank );
925926

926-
fd_block_hash_queue_global_t * bhq = (fd_block_hash_queue_global_t *)&ctx->slot_ctx->bank->block_hash_queue[0];
927-
fd_hash_t * last_hash = fd_block_hash_queue_last_hash_join( bhq );
927+
fd_blockhashes_t const * bhq = fd_bank_block_hash_queue_query( ctx->slot_ctx->bank );
928+
fd_hash_t const * last_hash = fd_blockhashes_peek_last( bhq );
928929
if( last_hash ) {
929-
memcpy(msg->last_entry_hash, last_hash, sizeof(fd_hash_t));
930+
memcpy( msg->last_entry_hash, last_hash, sizeof(fd_hash_t) );
930931
} else {
931-
memset(msg->last_entry_hash, 0UL, sizeof(fd_hash_t));
932+
memset( msg->last_entry_hash, 0UL, sizeof(fd_hash_t) );
932933
}
933934
msg->tick_height = fd_bank_slot_get( ctx->slot_ctx->bank ) * msg->ticks_per_slot;
934935

@@ -1426,11 +1427,9 @@ exec_slice_fini_slot( fd_replay_tile_ctx_t * ctx, fd_stem_context_t * stem ) {
14261427
if( FD_LIKELY( ctx->tower_out_idx!=ULONG_MAX && !ctx->read_only ) ) {
14271428
uchar * chunk_laddr = fd_chunk_to_laddr( ctx->tower_out_mem, ctx->tower_out_chunk );
14281429
fd_hash_t const * bank_hash = fd_bank_bank_hash_query( ctx->slot_ctx->bank );
1429-
fd_block_hash_queue_global_t * block_hash_queue = (fd_block_hash_queue_global_t *)&ctx->slot_ctx->bank->block_hash_queue[0];
1430-
fd_hash_t * last_hash = fd_block_hash_queue_last_hash_join( block_hash_queue );
1431-
1430+
fd_blockhashes_t const * blockhashes = fd_bank_block_hash_queue_query( ctx->slot_ctx->bank );
14321431
memcpy( chunk_laddr, bank_hash, sizeof(fd_hash_t) );
1433-
memcpy( chunk_laddr+sizeof(fd_hash_t), last_hash, sizeof(fd_hash_t) );
1432+
memcpy( chunk_laddr+sizeof(fd_hash_t), fd_blockhashes_peek_last( blockhashes ), sizeof(fd_hash_t) );
14341433
fd_stem_publish( stem, ctx->tower_out_idx, fd_bank_slot_get( ctx->slot_ctx->bank ) << 32UL | fd_bank_parent_slot_get( ctx->slot_ctx->bank ), ctx->tower_out_chunk, sizeof(fd_hash_t) * 2, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
14351434
}
14361435

src/flamenco/runtime/Local.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ $(call add-objs,fd_txn_account,fd_flamenco)
88
$(call add-hdrs,fd_bank_hash_cmp.h fd_rwseq_lock.h)
99
$(call add-objs,fd_bank_hash_cmp,fd_flamenco)
1010

11+
$(call add-hdrs,fd_blockhashes.h)
12+
$(call add-objs,fd_blockhashes,fd_flamenco)
13+
$(call make-unit-test,test_blockhashes,test_blockhashes,fd_flamenco fd_ballet fd_util)
14+
$(call run-unit-test,test_blockhashes)
15+
1116
$(call add-hdrs,fd_blockstore.h fd_rwseq_lock.h)
1217
$(call add-objs,fd_blockstore,fd_flamenco)
1318
$(call make-unit-test,test_blockstore,test_blockstore, fd_flamenco fd_util fd_ballet,$(SECP256K1_LIBS))

src/flamenco/runtime/context/fd_exec_slot_ctx.c

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -187,38 +187,15 @@ fd_exec_slot_ctx_recover( fd_exec_slot_ctx_t * slot_ctx,
187187
/* Index vote accounts */
188188

189189
/* Block Hash Queue */
190-
191-
fd_block_hash_queue_global_t * bhq = (fd_block_hash_queue_global_t *)&slot_ctx->bank->block_hash_queue[0];
192-
uchar * last_hash_mem = (uchar *)fd_ulong_align_up( (ulong)bhq + sizeof(fd_block_hash_queue_global_t), alignof(fd_hash_t) );
193-
uchar * ages_pool_mem = (uchar *)fd_ulong_align_up( (ulong)last_hash_mem + sizeof(fd_hash_t), fd_hash_hash_age_pair_t_map_align() );
194-
195-
fd_hash_hash_age_pair_t_mapnode_t * ages_pool = fd_hash_hash_age_pair_t_map_join( fd_hash_hash_age_pair_t_map_new( ages_pool_mem, 301 ) );
196-
fd_hash_hash_age_pair_t_mapnode_t * ages_root = NULL;
197-
198-
bhq->last_hash_index = old_bank->blockhash_queue.last_hash_index;
199-
200-
fd_hash_t const * last_hash = fd_block_hash_vec_last_hash_join( &old_bank->blockhash_queue );
201-
202-
if( last_hash ) {
203-
fd_memcpy( last_hash_mem, last_hash, sizeof(fd_hash_t) );
204-
} else {
205-
fd_memset( last_hash_mem, 0, sizeof(fd_hash_t) );
190+
{
191+
fd_blockhashes_t * bhq = fd_bank_block_hash_queue_modify( slot_ctx->bank );
192+
ulong seed; FD_TEST( fd_rng_secure( &seed, sizeof(ulong) ) );
193+
FD_TEST( fd_blockhashes_recover(
194+
bhq,
195+
fd_block_hash_vec_ages_join( &old_bank->blockhash_queue ),
196+
old_bank->blockhash_queue.ages_len,
197+
seed ) );
206198
}
207-
bhq->last_hash_offset = (ulong)last_hash_mem - (ulong)bhq;
208-
209-
fd_hash_hash_age_pair_t const * ages = fd_block_hash_vec_ages_join( &old_bank->blockhash_queue );
210-
211-
for( ulong i=0UL; i<old_bank->blockhash_queue.ages_len; i++ ) {
212-
fd_hash_hash_age_pair_t const * elem = &ages[i];
213-
fd_hash_hash_age_pair_t_mapnode_t * node = fd_hash_hash_age_pair_t_map_acquire( ages_pool );
214-
node->elem = *elem;
215-
fd_hash_hash_age_pair_t_map_insert( ages_pool, &ages_root, node );
216-
}
217-
218-
fd_block_hash_queue_ages_pool_update( bhq, ages_pool );
219-
fd_block_hash_queue_ages_root_update( bhq, ages_root );
220-
221-
bhq->max_age = old_bank->blockhash_queue.max_age;
222199

223200
/* Bank Hash */
224201

@@ -309,8 +286,10 @@ fd_exec_slot_ctx_recover( fd_exec_slot_ctx_t * slot_ctx,
309286

310287
/* PoH */
311288

312-
if( last_hash ) {
313-
fd_bank_poh_set( slot_ctx->bank, *last_hash );
289+
{
290+
fd_blockhashes_t const * bhq = fd_bank_block_hash_queue_query( slot_ctx->bank );
291+
fd_hash_t const * last_hash = fd_blockhashes_peek_last( bhq );
292+
if( last_hash ) fd_bank_poh_set( slot_ctx->bank, *last_hash );
314293
}
315294

316295
/* Prev Bank Hash */

src/flamenco/runtime/fd_bank.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../leaders/fd_leaders.h"
66
#include "../features/fd_features.h"
77
#include "../fd_rwlock.h"
8+
#include "fd_blockhashes.h"
89

910
FD_PROTOTYPES_BEGIN
1011

@@ -132,7 +133,7 @@ FD_PROTOTYPES_BEGIN
132133
X(fd_clock_timestamp_votes_global_t, clock_timestamp_votes, 5000000UL, 128UL, 1, 1 ) /* TODO: This needs to get sized out */ \
133134
X(fd_account_keys_global_t, stake_account_keys, 100000000UL, 128UL, 1, 1 ) /* Supports roughly 3M stake accounts */ \
134135
X(fd_account_keys_global_t, vote_account_keys, 3200000UL, 128UL, 1, 1 ) /* Supports roughly 100k vote accounts */ \
135-
X(fd_block_hash_queue_global_t, block_hash_queue, 50000UL, 128UL, 0, 0 ) /* Block hash queue */ \
136+
X(fd_blockhashes_t, block_hash_queue, sizeof(fd_blockhashes_t), alignof(fd_blockhashes_t), 0, 0 ) /* Block hash queue */ \
136137
X(fd_fee_rate_governor_t, fee_rate_governor, sizeof(fd_fee_rate_governor_t), alignof(fd_fee_rate_governor_t), 0, 0 ) /* Fee rate governor */ \
137138
X(ulong, capitalization, sizeof(ulong), alignof(ulong), 0, 0 ) /* Capitalization */ \
138139
X(ulong, lamports_per_signature, sizeof(ulong), alignof(ulong), 0, 0 ) /* Lamports per signature */ \

src/flamenco/runtime/fd_blockhashes.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#include "fd_blockhashes.h"
2+
3+
fd_blockhashes_t *
4+
fd_blockhashes_init( fd_blockhashes_t * mem,
5+
ulong seed ) {
6+
if( FD_UNLIKELY( !mem ) ) {
7+
FD_LOG_WARNING(( "NULL mem" ));
8+
return NULL;
9+
}
10+
FD_TEST( fd_blockhash_deq_join( fd_blockhash_deq_new( &mem->d ) ) );
11+
memset( mem->d.deque, 0x5a, sizeof(fd_blockhash_info_t) * FD_BLOCKHASHES_MAX );
12+
FD_TEST( fd_blockhash_map_join( fd_blockhash_map_new( mem, FD_BLOCKHASH_MAP_CHAIN_MAX, seed ) ) );
13+
return mem;
14+
}
15+
16+
fd_blockhashes_t *
17+
fd_blockhashes_recover( fd_blockhashes_t * blockhashes,
18+
fd_hash_hash_age_pair_t const * ages,
19+
ulong age_cnt,
20+
ulong seed ) {
21+
FD_TEST( fd_blockhashes_init( blockhashes, seed ) );
22+
if( FD_UNLIKELY( !age_cnt || age_cnt>FD_BLOCKHASHES_MAX ) ) {
23+
FD_LOG_WARNING(( "Corrupt snapshot: blockhash queue age count %lu is out of range [1,%d)", age_cnt, FD_BLOCKHASHES_MAX ));
24+
}
25+
26+
/* For depressing reasons, the ages array is not sorted when ingested
27+
from a snapshot. The hash_index field is also not validated.
28+
Firedancer assumes that the sequence of hash_index numbers is
29+
gapless and does not wrap around. */
30+
31+
ulong seq_min = ULONG_MAX-1;
32+
for( ulong i=0UL; i<age_cnt; i++ ) {
33+
seq_min = fd_ulong_min( seq_min, ages[i].val.hash_index );
34+
}
35+
ulong seq_max;
36+
if( FD_UNLIKELY( __builtin_uaddl_overflow( seq_min, age_cnt, &seq_max ) ) ) {
37+
FD_LOG_WARNING(( "Corrupt snapshot: blockhash queue sequence number wraparound (seq_min=%lu age_cnt=%lu)", seq_min, age_cnt ));
38+
return NULL;
39+
}
40+
41+
/* Reset */
42+
43+
for( ulong i=0UL; i<age_cnt; i++ ) {
44+
fd_blockhash_info_t * ele = fd_blockhash_deq_push_tail_nocopy( blockhashes->d.deque );
45+
memset( ele, 0, sizeof(fd_blockhash_info_t) );
46+
}
47+
48+
/* Load hashes */
49+
50+
for( ulong i=0UL; i<age_cnt; i++ ) {
51+
fd_hash_hash_age_pair_t const * elem = &ages[i];
52+
ulong idx;
53+
if( FD_UNLIKELY( __builtin_usubl_overflow( elem->val.hash_index, seq_min, &idx ) ) ) {
54+
FD_LOG_WARNING(( "Corrupt snapshot: gap in blockhash queue (seq=[%lu,%lu) idx=%lu)",
55+
seq_min, seq_max, elem->val.hash_index ));
56+
return NULL;
57+
}
58+
fd_blockhash_info_t * info = &blockhashes->d.deque[ idx ];
59+
if( FD_UNLIKELY( info->exists ) ) {
60+
FD_LOG_HEXDUMP_NOTICE(( "info", info, sizeof(fd_blockhash_info_t) ));
61+
FD_LOG_WARNING(( "Corrupt snapshot: duplicate blockhash queue index %lu", idx ));
62+
return NULL;
63+
}
64+
info->exists = 1;
65+
info->hash = elem->key;
66+
info->fee_calculator = elem->val.fee_calculator;
67+
fd_blockhash_map_idx_insert( blockhashes->map, idx, blockhashes->d.deque );
68+
}
69+
70+
return blockhashes;
71+
}
72+
73+
static void
74+
fd_blockhashes_pop_old( fd_blockhashes_t * blockhashes ) {
75+
if( FD_UNLIKELY( fd_blockhash_deq_empty( blockhashes->d.deque ) ) ) return;
76+
fd_blockhash_info_t * info = fd_blockhash_deq_pop_head_nocopy( blockhashes->d.deque );
77+
info->exists = 0;
78+
fd_blockhash_map_ele_remove( blockhashes->map, &info->hash, NULL, blockhashes->d.deque );
79+
}
80+
81+
void
82+
fd_blockhashes_pop_new( fd_blockhashes_t * blockhashes ) {
83+
if( FD_UNLIKELY( fd_blockhash_deq_empty( blockhashes->d.deque ) ) ) return;
84+
fd_blockhash_info_t * info = fd_blockhash_deq_pop_tail_nocopy( blockhashes->d.deque );
85+
info->exists = 0;
86+
fd_blockhash_map_ele_remove( blockhashes->map, &info->hash, NULL, blockhashes->d.deque );
87+
}
88+
89+
fd_blockhash_info_t *
90+
fd_blockhashes_push_new( fd_blockhashes_t * blockhashes,
91+
fd_hash_t const * hash ) {
92+
if( FD_UNLIKELY( fd_blockhash_deq_full( blockhashes->d.deque ) ) ) {
93+
fd_blockhashes_pop_old( blockhashes );
94+
}
95+
if( FD_UNLIKELY( fd_blockhash_map_idx_query( blockhashes->map, hash, ULONG_MAX, blockhashes->d.deque )!=ULONG_MAX ) ) {
96+
char bh_cstr[ FD_BASE58_ENCODED_32_SZ ]; fd_base58_encode_32( hash->uc, NULL, bh_cstr );
97+
FD_LOG_CRIT(( "Attempted to register duplicate blockhash %s", bh_cstr ));
98+
}
99+
100+
fd_blockhash_info_t * info = fd_blockhash_deq_push_tail_nocopy( blockhashes->d.deque );
101+
*info = (fd_blockhash_info_t) { .hash = *hash, .exists = 1 };
102+
103+
fd_blockhash_map_ele_insert( blockhashes->map, info, blockhashes->d.deque );
104+
105+
return info;
106+
}
107+
108+
fd_blockhash_info_t *
109+
fd_blockhashes_push_old( fd_blockhashes_t * blockhashes,
110+
fd_hash_t const * hash ) {
111+
if( FD_UNLIKELY( fd_blockhash_deq_full( blockhashes->d.deque ) ) ) {
112+
return NULL;
113+
}
114+
if( FD_UNLIKELY( fd_blockhash_map_idx_query( blockhashes->map, hash, ULONG_MAX, blockhashes->d.deque )!=ULONG_MAX ) ) {
115+
char bh_cstr[ FD_BASE58_ENCODED_32_SZ ]; fd_base58_encode_32( hash->uc, NULL, bh_cstr );
116+
FD_LOG_CRIT(( "Attempted to register duplicate blockhash %s", bh_cstr ));
117+
}
118+
119+
fd_blockhash_info_t * info = fd_blockhash_deq_push_head_nocopy( blockhashes->d.deque );
120+
*info = (fd_blockhash_info_t) { .hash = *hash, .exists = 1 };
121+
122+
fd_blockhash_map_ele_insert( blockhashes->map, info, blockhashes->d.deque );
123+
124+
return info;
125+
}
126+
127+
128+
FD_FN_PURE int
129+
fd_blockhashes_check_age( fd_blockhashes_t const * blockhashes,
130+
fd_hash_t const * blockhash,
131+
ulong max_age ) {
132+
ulong const idx = fd_blockhash_map_idx_query_const( blockhashes->map, blockhash, ULONG_MAX, blockhashes->d.deque );
133+
if( FD_UNLIKELY( idx==ULONG_MAX ) ) return 0;
134+
/* Derive distance from tail (end) */
135+
ulong const max = fd_blockhash_deq_max( blockhashes->d.deque );
136+
ulong const end = (blockhashes->d.end - 1) & (max-1);
137+
ulong const age = end + fd_ulong_if( idx<=end, 0UL, max ) - idx;
138+
return age<=max_age;
139+
}

src/flamenco/runtime/fd_blockhashes.h

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#ifndef HEADER_fd_src_flamenco_runtime_fd_blockhashes_h
2+
#define HEADER_fd_src_flamenco_runtime_fd_blockhashes_h
3+
4+
#include "../types/fd_types.h"
5+
#include "../../funk/fd_funk_base.h" /* fd_funk_rec_key_hash1 */
6+
7+
/* fd_blockhashes.h provides a "blockhash queue" API. The blockhash
8+
queue is a consensus-relevant data structure that is part of the slot
9+
bank.
10+
11+
See solana_accounts_db::blockhash_queue::BlockhashQueue. */
12+
13+
#define FD_BLOCKHASHES_MAX 301
14+
15+
/* See solana_accounts_db::blockhash_queue::HashInfo. */
16+
17+
struct fd_blockhash_info {
18+
fd_hash_t hash;
19+
fd_fee_calculator_t fee_calculator;
20+
ushort next;
21+
ushort exists : 1;
22+
};
23+
24+
typedef struct fd_blockhash_info fd_blockhash_info_t;
25+
26+
/* Declare a static size deque for the blockhash queue. */
27+
28+
#define DEQUE_NAME fd_blockhash_deq
29+
#define DEQUE_T fd_blockhash_info_t
30+
#define DEQUE_MAX 512 /* must be a power of 2 */
31+
#include "../../util/tmpl/fd_deque.c"
32+
33+
/* Declare a separately chained hash map over the blockhash queue. */
34+
35+
#define FD_BLOCKHASH_MAP_CHAIN_MAX (512UL)
36+
#define FD_BLOCKHASH_MAP_FOOTPRINT (1048UL)
37+
38+
#define MAP_NAME fd_blockhash_map
39+
#define MAP_ELE_T fd_blockhash_info_t
40+
#define MAP_KEY_T fd_hash_t
41+
#define MAP_KEY hash
42+
#define MAP_IDX_T ushort
43+
#define MAP_NEXT next
44+
#define MAP_KEY_EQ(k0,k1) fd_hash_eq( (k0), (k1) )
45+
#define MAP_KEY_HASH(k,s) fd_funk_rec_key_hash1( (k->uc), 0, (s) )
46+
#include "../../util/tmpl/fd_map_chain.c"
47+
48+
/* fd_blockhashes_t is the class representing a blockhash queue.
49+
50+
It is a static size container housing sub-structures as plain old
51+
struct members. Safe to declare as a local variable assuming the
52+
stack is sufficiently sized. Entirely self-contained and position-
53+
independent (safe to clone via fd_memcpy and safe to map into another
54+
address space).
55+
56+
Under the hood it is an array-backed double-ended queue, and a
57+
separately-chained hash index on top. New entries are inserted to
58+
the **tail** of the queue. */
59+
60+
struct fd_blockhashes {
61+
62+
union {
63+
fd_blockhash_map_t map[1];
64+
uchar map_mem[ FD_BLOCKHASH_MAP_FOOTPRINT ];
65+
};
66+
67+
fd_blockhash_deq_private_t d;
68+
69+
};
70+
71+
typedef struct fd_blockhashes fd_blockhashes_t;
72+
73+
FD_PROTOTYPES_BEGIN
74+
75+
fd_blockhashes_t *
76+
fd_blockhashes_init( fd_blockhashes_t * mem,
77+
ulong seed );
78+
79+
fd_blockhashes_t *
80+
fd_blockhashes_recover( fd_blockhashes_t * blockhashes,
81+
fd_hash_hash_age_pair_t const * ages,
82+
ulong age_cnt,
83+
ulong seed );
84+
85+
/* fd_blockhashes_push_new adds a new slot to the blockhash queue.
86+
The caller fills the returned pointer with blockhash queue info
87+
(currently only lamports_per_signature). Called as part of regular
88+
runtime processing. Evicts the oldest entry if the queue is full
89+
(practically always the case except for the first few blocks after
90+
genesis). Always returns a valid pointer. */
91+
92+
fd_blockhash_info_t *
93+
fd_blockhashes_push_new( fd_blockhashes_t * blockhashes,
94+
fd_hash_t const * hash );
95+
96+
/* fd_blockhashes_push_old behaves like the above, but adding a new
97+
oldest entry instead. Returns NULL if there is no more space.
98+
Useful for testing. */
99+
100+
fd_blockhash_info_t *
101+
fd_blockhashes_push_old( fd_blockhashes_t * blockhashes,
102+
fd_hash_t const * hash );
103+
104+
/* fd_blockhashes_pop_new removes the newest blockhash queue entry. */
105+
106+
void
107+
fd_blockhashes_pop_new( fd_blockhashes_t * blockhashes );
108+
109+
FD_FN_PURE int
110+
fd_blockhashes_check_age( fd_blockhashes_t const * blockhashes,
111+
fd_hash_t const * blockhash,
112+
ulong max_age );
113+
114+
FD_FN_PURE static inline fd_hash_t const *
115+
fd_blockhashes_peek_last( fd_blockhashes_t const * blockhashes ) {
116+
if( FD_UNLIKELY( fd_blockhash_deq_empty( blockhashes->d.deque ) ) ) return 0;
117+
return &fd_blockhash_deq_peek_tail_const( blockhashes->d.deque )->hash;
118+
}
119+
120+
FD_PROTOTYPES_END
121+
122+
#endif /* HEADER_fd_src_flamenco_runtime_fd_blockhashes_h */

0 commit comments

Comments
 (0)