Skip to content

Commit 1081349

Browse files
committed
flamenco: vote states v4
1 parent 600acf2 commit 1081349

24 files changed

+3013
-1298
lines changed

src/flamenco/features/fd_features_generated.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,12 @@ fd_feature_id_t const ids[] = {
16911691
.name = "poseidon_enforce_padding",
16921692
.cleaned_up = {UINT_MAX, UINT_MAX, UINT_MAX} },
16931693

1694+
{ .index = offsetof(fd_features_t, vote_state_v4)>>3,
1695+
.id = {"\xec\xfa\x3a\xf2\xab\xa3\x21\x89\x19\xf4\xc3\x4c\x05\xdf\x88\xf0\x79\x57\x48\xf1\x3e\x35\x12\x0b\x7b\x56\xaa\xae\x3f\xe9\x98\x58"},
1696+
/* Gx4XFcrVMt4HUvPzTpTSVkdDVgcDSjKhDN1RqRS6KDuZ */
1697+
.name = "vote_state_v4",
1698+
.cleaned_up = {UINT_MAX, UINT_MAX, UINT_MAX} },
1699+
16941700
{ .index = ULONG_MAX }
16951701
};
16961702
/* TODO replace this with fd_map_perfect */
@@ -1944,6 +1950,7 @@ fd_feature_id_query( ulong prefix ) {
19441950
case 0x520c5e674243fab5: return &ids[ 244 ];
19451951
case 0xf08a42c3c040e908: return &ids[ 245 ];
19461952
case 0x8c7bee4552d93e0c: return &ids[ 246 ];
1953+
case 0x8921a3abf23afaec: return &ids[ 247 ];
19471954
default: break;
19481955
}
19491956
return NULL;
@@ -2196,4 +2203,5 @@ FD_STATIC_ASSERT( offsetof( fd_features_t, stricter_abi_and_runtime_constraints
21962203
FD_STATIC_ASSERT( offsetof( fd_features_t, account_data_direct_mapping )>>3==244UL, layout );
21972204
FD_STATIC_ASSERT( offsetof( fd_features_t, fix_alt_bn128_pairing_length_check )>>3==245UL, layout );
21982205
FD_STATIC_ASSERT( offsetof( fd_features_t, poseidon_enforce_padding )>>3==246UL, layout );
2206+
FD_STATIC_ASSERT( offsetof( fd_features_t, vote_state_v4 )>>3==247UL, layout );
21992207
FD_STATIC_ASSERT( sizeof( fd_features_t )>>3==FD_FEATURE_ID_CNT, layout );

src/flamenco/features/fd_features_generated.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
#endif
99

1010
/* FEATURE_ID_CNT is the number of features in ids */
11-
#define FD_FEATURE_ID_CNT (247UL)
11+
#define FD_FEATURE_ID_CNT (248UL)
1212

1313
/* Feature set ID calculated from all feature names */
14-
#define FD_FEATURE_SET_ID (2146234083U)
14+
#define FD_FEATURE_SET_ID (1260307723U)
1515

1616
union fd_features {
1717
ulong f[ FD_FEATURE_ID_CNT ];
@@ -263,5 +263,6 @@ union fd_features {
263263
/* 0x520c5e674243fab5 */ ulong account_data_direct_mapping;
264264
/* 0xf08a42c3c040e908 */ ulong fix_alt_bn128_pairing_length_check;
265265
/* 0x8c7bee4552d93e0c */ ulong poseidon_enforce_padding;
266+
/* 0x8921a3abf23afaec */ ulong vote_state_v4;
266267
};
267268
};

src/flamenco/features/feature_map.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,5 +245,6 @@
245245
{"name":"stricter_abi_and_runtime_constraints","pubkey":"sD3uVpaavUXQRvDXrMFCQ2CqLqnbz5mK8ttWNXbtD3r","old":"CxeBn9PVeeXbmjbNwLv6U4C6svNxnC4JX6mfkvgeMocM"},
246246
{"name":"account_data_direct_mapping","pubkey":"DFN8MyKpQqFW31qczcahgnnxcAHQc6P94wtTEX5EP1RA","old":"9s3RKimHWS44rJcJ9P1rwCmn2TvMqtZQBmz815ZUUHqJ"},
247247
{"name":"fix_alt_bn128_pairing_length_check","pubkey":"bnYzodLwmybj7e1HAe98yZrdJTd7we69eMMLgCXqKZm"},
248-
{"name":"poseidon_enforce_padding","pubkey":"poUdAqRXXsNmfqAZ6UqpjbeYgwBygbfQLEvWSqVhSnb"}
248+
{"name":"poseidon_enforce_padding","pubkey":"poUdAqRXXsNmfqAZ6UqpjbeYgwBygbfQLEvWSqVhSnb"},
249+
{"name":"vote_state_v4","pubkey":"Gx4XFcrVMt4HUvPzTpTSVkdDVgcDSjKhDN1RqRS6KDuZ"}
249250
]

src/flamenco/runtime/program/fd_vote_program.c

Lines changed: 719 additions & 1292 deletions
Large diffs are not rendered by default.

src/flamenco/runtime/program/fd_vote_program.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,87 @@
1212
#include "../context/fd_exec_instr_ctx.h"
1313
#include "../fd_bank.h"
1414

15+
/* Some vote instruction types are dynamically sized:
16+
- tower_sync_switch (contains deque of fd_vote_lockout_t)
17+
- tower_sync (contains deque of fd_vote_lockout_t)
18+
- compact_vote_state_update_switch (vector of fd_lockout_offset_t)
19+
- compact_vote_state_update (vector of fd_lockout_offset_t)
20+
- authorize_checked_with_seed (char vector of current_authority_derived_key_seed)
21+
- authorize_with_seed (char vector of current_authority_derived_key_seed)
22+
- update_vote_state_switch (contains deque of fd_vote_lockout_t)
23+
- update_vote_state (contains deque of fd_vote_lockout_t)
24+
- vote_switch (deque of slot numbers)
25+
- vote (deque of slot numbers)
26+
All other vote instruction types are statically sized.
27+
28+
A loose bound on the max amount of encoded fd_vote_lockout_t
29+
possible is 1232 bytes/(12 bytes/per lockout) = 102 lockouts. So
30+
the worst case bound for the deque of fd_vote_lockout is
31+
32 + (102 * sizeof(fd_vote_lockout_t)) = 1644 bytes.
32+
33+
The worst case vector of fd_lockout_offset_t is one where each
34+
encoded element is 2 bytes. This means that we can have 1232/2 =
35+
616 elements. They are represented as being 16 bytes each, so the
36+
total footprint would be 9856 bytes.
37+
38+
The deque of slot numbers is a vector of ulong, which is 8 bytes.
39+
So the worst case is 1232 bytes/8 bytes = 154 elements. So, the
40+
total footprint is 32 + (154 * 8 bytes) = 1264 bytes.
41+
42+
The worst case char vector is 1232 bytes as each element is 1 byte
43+
up to the txn MTU.
44+
45+
With this, that means that the compact_vote_state_update_switch
46+
can have the largest worst case footprint where the struct is
47+
104 bytes (sizeof(fd_compact_vote_state_update_switch_t) + the
48+
worst case lockout vector of 616 elements. */
49+
#define FD_LOCKOUT_OFFSET_FOOTPRINT (9856UL)
50+
#define FD_VOTE_INSTRUCTION_FOOTPRINT (sizeof(fd_vote_instruction_t) + FD_LOCKOUT_OFFSET_FOOTPRINT)
51+
52+
/* TODO: This is the value as generated by fd_types bincode decoding of
53+
fd_vote_state_versioned_t. This should eventually be replaced. */
54+
#define FD_VOTE_STATE_VERSIONED_FOOTPRINT (9248UL)
55+
56+
/* The footprint of a fd_vote_authorized_voters_t struct is defined as a
57+
fd_vote_authorized_voters_t followed by a pool and then a treap. */
58+
#define FD_AUTHORIZED_VOTERS_ALIGN (128UL)
59+
#define FD_AUTHORIZED_VOTERS_FOOTPRINT (4888UL)
60+
61+
/* TODO: These footprints are currently overprovisioned due to test
62+
fixtures which currently violate protocol invariants. */
63+
64+
/* The footprint of the landed votes is determined by a deque with max
65+
cnt of 31. The footprint is as follows:
66+
alignof(DEQUE_T) == alignof(fd_landed_vote_t) == 8
67+
sizeof(DEQUE_T) == sizeof(fd_landed_vote_t) == 24
68+
return fd_ulong_align_up( fd_ulong_align_up( 32UL, alignof(DEQUE_T) ) + sizeof(DEQUE_T)*max, alignof(DEQUE_(private_t)) );
69+
return fd_ulong_align_up( fd_ulong_align_up( 32UL, 8UL ) ) + 24UL*31UL, 8UL );
70+
return fd_ulong_align_up( 32UL + 744, 8UL ) == 776 */
71+
#define FD_LANDED_VOTES_ALIGN (32UL)
72+
#define FD_LANDED_VOTES_FOOTPRINT (FD_VOTE_STATE_VERSIONED_FOOTPRINT)
73+
74+
/* The calculation for the landed votes footprint is the same as the
75+
calculation for the landed votes but the sizeof(fd_vote_lockout_t)
76+
is 16 bytes:
77+
return fd_ulong_align_up( 32UL + 16UL * 31UL, 8UL ) == 528UL */
78+
#define FD_VOTE_LOCKOUTS_ALIGN (32UL)
79+
#define FD_VOTE_LOCKOUTS_FOOTPRINT (FD_VOTE_STATE_VERSIONED_FOOTPRINT)
80+
81+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L35
82+
#define MAX_LOCKOUT_HISTORY 31UL
83+
84+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L36
85+
#define MAX_EPOCH_CREDITS_HISTORY 64UL
86+
87+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L48
88+
#define VOTE_CREDITS_MAXIMUM_PER_SLOT 16
89+
90+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L45
91+
#define VOTE_CREDITS_GRACE_SLOTS 2
92+
93+
/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L597-L598 */
94+
#define DEFAULT_BLOCK_REVENUE_COMMISSION_BPS (10000UL)
95+
1596
/* Vote program custom error codes */
1697

1798
#define FD_VOTE_ERR_VOTE_TOO_OLD ( 0)
@@ -37,6 +118,12 @@
37118

38119
#define FD_VOTE_STATE_V2_SZ (3731UL)
39120
#define FD_VOTE_STATE_V3_SZ (3762UL)
121+
#define FD_VOTE_STATE_V4_SZ (3762UL)
122+
123+
/* Target vote state versions
124+
https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L639-L645 */
125+
#define VOTE_STATE_TARGET_VERSION_V3 (0)
126+
#define VOTE_STATE_TARGET_VERSION_V4 (1)
40127

41128
FD_PROTOTYPES_BEGIN
42129

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
$(call add-hdrs,fd_authorized_voters.h)
2+
$(call add-objs,fd_authorized_voters,fd_flamenco)
3+
4+
$(call add-hdrs,fd_vote_common.h)
5+
$(call add-objs,fd_vote_common,fd_flamenco)
6+
7+
$(call add-hdrs,fd_vote_lockout.h)
8+
$(call add-objs,fd_vote_lockout,fd_flamenco)
9+
10+
$(call add-hdrs,fd_vote_state_versioned.h)
11+
$(call add-objs,fd_vote_state_versioned,fd_flamenco)
12+
13+
$(call add-hdrs,fd_vote_state_v3.h)
14+
$(call add-objs,fd_vote_state_v3,fd_flamenco)
15+
16+
$(call add-hdrs,fd_vote_state_v4.h)
17+
$(call add-objs,fd_vote_state_v4,fd_flamenco)
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#include "fd_authorized_voters.h"
2+
#include "fd_vote_state_v3.h"
3+
#include "fd_vote_state_v4.h"
4+
5+
/**********************************************************************/
6+
/* impl AuthorizedVoters */
7+
/**********************************************************************/
8+
9+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L17
10+
fd_vote_authorized_voters_t *
11+
fd_authorized_voters_new( ulong epoch,
12+
fd_pubkey_t const * pubkey,
13+
uchar * mem ) {
14+
15+
FD_SCRATCH_ALLOC_INIT( l, mem );
16+
fd_vote_authorized_voters_t * authorized_voters = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_align(), sizeof(fd_vote_authorized_voters_t) );
17+
void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_pool_align(), fd_vote_authorized_voters_pool_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
18+
void * treap_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_treap_align(), fd_vote_authorized_voters_treap_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
19+
20+
authorized_voters->pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
21+
authorized_voters->treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
22+
if( 0 == fd_vote_authorized_voters_pool_free( authorized_voters->pool ) ) {
23+
FD_LOG_ERR(( "Authorized_voter pool is empty" ));
24+
}
25+
fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_pool_ele_acquire( authorized_voters->pool );
26+
ele->epoch = epoch;
27+
ele->pubkey = *pubkey;
28+
ele->prio = (ulong)&ele->pubkey;
29+
fd_vote_authorized_voters_treap_ele_insert( authorized_voters->treap, ele, authorized_voters->pool );
30+
return authorized_voters;
31+
}
32+
33+
// Helper to create an empty AuthorizedVoters structure (for default/uninitialized states)
34+
fd_vote_authorized_voters_t *
35+
fd_authorized_voters_new_empty( uchar * mem ) {
36+
FD_SCRATCH_ALLOC_INIT( l, mem );
37+
fd_vote_authorized_voters_t * authorized_voters = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_align(), sizeof(fd_vote_authorized_voters_t) );
38+
void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_pool_align(), fd_vote_authorized_voters_pool_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
39+
void * treap_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_treap_align(), fd_vote_authorized_voters_treap_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
40+
41+
authorized_voters->pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
42+
authorized_voters->treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
43+
return authorized_voters;
44+
}
45+
46+
int
47+
fd_authorized_voters_is_empty( fd_vote_authorized_voters_t * self ) {
48+
return fd_vote_authorized_voters_treap_ele_cnt( self->treap ) == 0;
49+
}
50+
51+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L80
52+
int
53+
fd_authorized_voters_contains( fd_vote_authorized_voters_t * self, ulong epoch ) {
54+
return !!fd_vote_authorized_voters_treap_ele_query( self->treap, epoch, self->pool );
55+
}
56+
57+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L72
58+
fd_vote_authorized_voter_t *
59+
fd_authorized_voters_last( fd_vote_authorized_voters_t * self ) {
60+
fd_vote_authorized_voters_treap_rev_iter_t iter =
61+
fd_vote_authorized_voters_treap_rev_iter_init( self->treap, self->pool );
62+
return fd_vote_authorized_voters_treap_rev_iter_ele( iter, self->pool );
63+
}
64+
65+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L43
66+
void
67+
fd_authorized_voters_purge_authorized_voters( fd_vote_authorized_voters_t * self,
68+
ulong current_epoch ) {
69+
70+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L46
71+
ulong expired_keys[ FD_VOTE_AUTHORIZED_VOTERS_MIN ];
72+
ulong key_cnt = 0;
73+
for( fd_vote_authorized_voters_treap_fwd_iter_t iter =
74+
fd_vote_authorized_voters_treap_fwd_iter_init( self->treap, self->pool );
75+
!fd_vote_authorized_voters_treap_fwd_iter_done( iter );
76+
iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, self->pool ) ) {
77+
fd_vote_authorized_voter_t * ele =
78+
fd_vote_authorized_voters_treap_fwd_iter_ele( iter, self->pool );
79+
if( ele->epoch < current_epoch ) expired_keys[key_cnt++] = ele->epoch;
80+
}
81+
82+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L52
83+
for( ulong i = 0; i < key_cnt; i++ ) {
84+
fd_vote_authorized_voter_t * ele =
85+
fd_vote_authorized_voters_treap_ele_query( self->treap, expired_keys[i], self->pool );
86+
fd_vote_authorized_voters_treap_ele_remove( self->treap, ele, self->pool );
87+
fd_vote_authorized_voters_pool_ele_release( self->pool, ele );
88+
}
89+
90+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L60
91+
FD_TEST( !fd_authorized_voters_is_empty( self ) );
92+
93+
}
94+
95+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L91
96+
fd_vote_authorized_voter_t *
97+
fd_authorized_voters_get_or_calculate_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self,
98+
ulong epoch,
99+
int * existed ) {
100+
*existed = 0;
101+
ulong latest_epoch = 0;
102+
fd_vote_authorized_voter_t * res =
103+
fd_vote_authorized_voters_treap_ele_query( self->treap, epoch, self->pool );
104+
// "predecessor" would be more big-O optimal here, but mirroring labs logic
105+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L93
106+
if( FD_UNLIKELY( !res ) ) {
107+
for( fd_vote_authorized_voters_treap_fwd_iter_t iter =
108+
fd_vote_authorized_voters_treap_fwd_iter_init( self->treap, self->pool );
109+
!fd_vote_authorized_voters_treap_fwd_iter_done( iter );
110+
iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, self->pool ) ) {
111+
fd_vote_authorized_voter_t * ele =
112+
fd_vote_authorized_voters_treap_fwd_iter_ele( iter, self->pool );
113+
if( ele->epoch < epoch && ( latest_epoch == 0 || ele->epoch > latest_epoch ) ) {
114+
latest_epoch = ele->epoch;
115+
res = ele;
116+
}
117+
}
118+
*existed = 0;
119+
return res;
120+
} else {
121+
*existed = 1;
122+
return res;
123+
}
124+
return res;
125+
}
126+
127+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L28
128+
fd_vote_authorized_voter_t *
129+
fd_authorized_voters_get_and_cache_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self,
130+
ulong epoch ) {
131+
int existed = 0;
132+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L29
133+
fd_vote_authorized_voter_t * res =
134+
fd_authorized_voters_get_or_calculate_authorized_voter_for_epoch( self, epoch, &existed );
135+
if( !res ) return NULL;
136+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L32
137+
if( !existed ) {
138+
/* insert cannot fail because !existed */
139+
if( 0 == fd_vote_authorized_voters_pool_free( self->pool) ) {
140+
FD_LOG_ERR(( "Authorized_voter pool is empty" ));
141+
}
142+
fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_pool_ele_acquire( self->pool );
143+
ele->epoch = epoch;
144+
ele->pubkey = res->pubkey;
145+
ele->prio = (ulong)&res->pubkey;
146+
// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L33
147+
fd_vote_authorized_voters_treap_ele_insert( self->treap, ele, self->pool );
148+
}
149+
return res;
150+
}
151+
152+
int
153+
fd_authorized_voters_get_and_update_authorized_voter( fd_vote_state_versioned_t * self,
154+
ulong current_epoch,
155+
fd_pubkey_t ** pubkey /* out */ ) {
156+
switch( self->discriminant ) {
157+
case fd_vote_state_versioned_enum_current:
158+
return fd_vote_state_v3_get_and_update_authorized_voter( &self->inner.current, current_epoch, pubkey );
159+
case fd_vote_state_versioned_enum_v4:
160+
return fd_vote_state_v4_get_and_update_authorized_voter( &self->inner.v4, current_epoch, pubkey );
161+
default:
162+
__builtin_unreachable();
163+
}
164+
}

0 commit comments

Comments
 (0)