Line data Source code
1 : #include "../replay/fd_replay_tile.h"
2 : #include "../genesis/fd_genesi_tile.h"
3 : #include "../votor/fd_votor_tile.h"
4 : #include "../../choreo/votor/ag_cert.h"
5 :
6 : #include "../../ballet/base64/fd_base64.h"
7 : #include "../../ballet/json/fd_jtok.h"
8 : #include "../../disco/topo/fd_topo.h"
9 : #include "../../util/pod/fd_pod_format.h"
10 : #include "../../disco/fd_clock_tile.h"
11 : #include "../../disco/metrics/fd_metrics.h"
12 : #include "../../disco/keyguard/fd_keyload.h"
13 : #include "../../disco/keyguard/fd_keyswitch.h"
14 : #include "../../disco/waker/fd_waker.h"
15 : #include "../../disco/metrics/fd_metrics.h"
16 : #include "../../flamenco/features/fd_features.h"
17 : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
18 : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
19 : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
20 : #include "../../flamenco/runtime/fd_runtime_const.h"
21 : #include "../../flamenco/accdb/fd_accdb.h"
22 : #include "../../flamenco/alpenglow/fd_alpenglow.h"
23 : #include "../../flamenco/accdb/fd_accdb_shmem.h"
24 : #include "../../tango/fseq/fd_fseq.h"
25 : #include "../../flamenco/gossip/fd_gossip_message.h"
26 : #include "../../flamenco/runtime/program/vote/fd_vote_codec_tmpl.h"
27 : #include "../../util/net/fd_ip4.h"
28 : #include "../../waltz/http/fd_http_server.h"
29 : #include "../../waltz/http/fd_http_server_private.h"
30 : #include "../../waltz/http/fd_url.h"
31 :
32 : #include <stddef.h>
33 : #include <poll.h>
34 : #include <sys/socket.h>
35 : #include <string.h>
36 :
37 : #define ZSTD_STATIC_LINKING_ONLY
38 : #include <zstd.h>
39 :
40 : #include "../../util/archive/fd_tar.h"
41 : #include "../../third_party/bzip2/bzlib.h"
42 :
43 : #include <linux/futex.h>
44 : #include "generated/fd_rpc_tile_seccomp.h"
45 :
46 0 : #define FD_RPC_AGAVE_API_VERSION "4.3.0-rc.0"
47 :
48 0 : #define FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN 8192UL
49 :
50 : /* FD_RPC_ID_MAX_SZ is the longest JSON-RPC request id (as raw JSON
51 : text) that is echoed back in responses. */
52 : #define FD_RPC_ID_MAX_SZ (256UL)
53 0 : #define FD_HTTP_SERVER_RPC_MAX_WS_SEND_FRAME_CNT 128UL
54 :
55 0 : #define IN_KIND_REPLAY (0)
56 0 : #define IN_KIND_GENESI (1)
57 0 : #define IN_KIND_GOSSIP_OUT (2)
58 0 : #define IN_KIND_EPOCH (5)
59 0 : #define IN_KIND_VOTOR (6)
60 :
61 : /* From bzip2 docs:
62 : To guarantee that the compressed data will fit in its buffer,
63 : allocate an output buffer of size 1% larger than the uncompressed
64 : data, plus six hundred extra bytes.
65 : */
66 : FD_FN_CONST static inline ulong
67 0 : fd_rpc_genesis_tar_max_sz( ulong max_message_size ) {
68 0 : return max_message_size + 4UL*FD_TAR_BLOCK_SZ;
69 0 : }
70 :
71 : FD_FN_CONST static inline ulong
72 0 : fd_rpc_genesis_tar_bz_max_sz( ulong max_message_size ) {
73 0 : ulong tar_max_sz = fd_rpc_genesis_tar_max_sz( max_message_size );
74 0 : return tar_max_sz + ((tar_max_sz + 100UL - 1UL) / 100UL) + 600UL;
75 0 : }
76 :
77 0 : #define FD_RPC_BASE58_ENCODED_128_LEN (175UL) /* ceil(128*log58(256)) */
78 0 : #define FD_RPC_ZSTD_LEVEL 1
79 : /* levels 4-15 are compiled out of libfd_zstd (third_party/zstd/Local.mk) */
80 : FD_STATIC_ASSERT( FD_RPC_ZSTD_LEVEL<=3 || FD_RPC_ZSTD_LEVEL>=16, zstd_level );
81 :
82 : #define FD_RPC_COMMITMENT_PROCESSED (0)
83 : #define FD_RPC_COMMITMENT_CONFIRMED (1)
84 : #define FD_RPC_COMMITMENT_FINALIZED (2)
85 :
86 : #define FD_RPC_ENCODING_BASE58 (0)
87 : #define FD_RPC_ENCODING_BASE64 (1)
88 : #define FD_RPC_ENCODING_BASE64_ZSTD (2)
89 : #define FD_RPC_ENCODING_BINARY (3)
90 : #define FD_RPC_ENCODING_JSON_PARSED (4)
91 :
92 0 : #define FD_RPC_HEALTH_STATUS_OK (0)
93 0 : #define FD_RPC_HEALTH_STATUS_BEHIND (1)
94 0 : #define FD_RPC_HEALTH_STATUS_UNKNOWN (2)
95 :
96 : /* Matches Agave's default health_check_slot_distance. */
97 : #define FD_RPC_HEALTH_CHECK_SLOT_DISTANCE (128UL)
98 :
99 : #define FD_RPC_METHOD_GET_ACCOUNT_INFO ( 0)
100 : #define FD_RPC_METHOD_GET_BALANCE ( 1)
101 : #define FD_RPC_METHOD_GET_BLOCK ( 2)
102 : #define FD_RPC_METHOD_GET_BLOCK_COMMITMENT ( 3)
103 : #define FD_RPC_METHOD_GET_BLOCK_HEIGHT ( 4)
104 : #define FD_RPC_METHOD_GET_BLOCK_PRODUCTION ( 5)
105 : #define FD_RPC_METHOD_GET_BLOCKS ( 6)
106 : #define FD_RPC_METHOD_GET_BLOCKS_WITH_LIMIT ( 7)
107 : #define FD_RPC_METHOD_GET_BLOCK_TIME ( 8)
108 : #define FD_RPC_METHOD_GET_CLUSTER_NODES ( 9)
109 : #define FD_RPC_METHOD_GET_EPOCH_INFO (10)
110 : #define FD_RPC_METHOD_GET_EPOCH_SCHEDULE (11)
111 : #define FD_RPC_METHOD_GET_FEE_FOR_MESSAGE (12)
112 : #define FD_RPC_METHOD_GET_FIRST_AVAILABLE_BLOCK (13)
113 : #define FD_RPC_METHOD_GET_GENESIS_HASH (14)
114 : #define FD_RPC_METHOD_GET_HEALTH (15)
115 : #define FD_RPC_METHOD_GET_HIGHEST_SNAPSHOT_SLOT (16)
116 : #define FD_RPC_METHOD_GET_IDENTITY (17)
117 : #define FD_RPC_METHOD_GET_INFLATION_GOVERNOR (18)
118 : #define FD_RPC_METHOD_GET_INFLATION_RATE (19)
119 : #define FD_RPC_METHOD_GET_INFLATION_REWARD (20)
120 : #define FD_RPC_METHOD_GET_LARGEST_ACCOUNTS (21)
121 : #define FD_RPC_METHOD_GET_LATEST_BLOCKHASH (22)
122 : #define FD_RPC_METHOD_GET_LEADER_SCHEDULE (23)
123 : #define FD_RPC_METHOD_GET_MAX_RETRANSMIT_SLOT (24)
124 : #define FD_RPC_METHOD_GET_MAX_SHRED_INSERT_SLOT (25)
125 : #define FD_RPC_METHOD_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION (26)
126 : #define FD_RPC_METHOD_GET_MULTIPLE_ACCOUNTS (27)
127 : #define FD_RPC_METHOD_GET_PROGRAM_ACCOUNTS (28)
128 : #define FD_RPC_METHOD_GET_RECENT_PERFORMANCE_SAMPLES (29)
129 : #define FD_RPC_METHOD_GET_RECENT_PRIORITIZATION_FEES (30)
130 : #define FD_RPC_METHOD_GET_SIGNATURES_FOR_ADDRESS (31)
131 : #define FD_RPC_METHOD_GET_SIGNATURE_STATUSES (32)
132 : #define FD_RPC_METHOD_GET_SLOT (33)
133 : #define FD_RPC_METHOD_GET_SLOT_LEADER (34)
134 : #define FD_RPC_METHOD_GET_SLOT_LEADERS (35)
135 : #define FD_RPC_METHOD_GET_STAKE_MINIMUM_DELEGATION (36)
136 : #define FD_RPC_METHOD_GET_SUPPLY (37)
137 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNT_BALANCE (38)
138 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_DELEGATE (39)
139 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_OWNER (40)
140 : #define FD_RPC_METHOD_GET_TOKEN_LARGEST_ACCOUNTS (41)
141 : #define FD_RPC_METHOD_GET_TOKEN_SUPPLY (42)
142 : #define FD_RPC_METHOD_GET_TRANSACTION (43)
143 : #define FD_RPC_METHOD_GET_TRANSACTION_COUNT (44)
144 : #define FD_RPC_METHOD_GET_VERSION (45)
145 : #define FD_RPC_METHOD_GET_VOTE_ACCOUNTS (46)
146 : #define FD_RPC_METHOD_IS_BLOCKHASH_VALID (47)
147 : #define FD_RPC_METHOD_MINIMUM_LEDGER_SLOT (48)
148 : #define FD_RPC_METHOD_REQUEST_AIRDROP (49)
149 : #define FD_RPC_METHOD_SEND_TRANSACTION (50)
150 : #define FD_RPC_METHOD_SIMULATE_TRANSACTION (51)
151 :
152 : // Keep in sync with https://github.com/solana-labs/solana-web3.js/blob/master/src/errors.ts
153 : // and https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/custom_error.rs
154 : #define FD_RPC_ERROR_BLOCK_CLEANED_UP (-32001)
155 : #define FD_RPC_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE (-32002)
156 : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE (-32003)
157 : #define FD_RPC_ERROR_BLOCK_NOT_AVAILABLE (-32004)
158 : #define FD_RPC_ERROR_NODE_UNHEALTHY (-32005)
159 : #define FD_RPC_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE (-32006)
160 : #define FD_RPC_ERROR_SLOT_SKIPPED (-32007)
161 : #define FD_RPC_ERROR_NO_SNAPSHOT (-32008)
162 : #define FD_RPC_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED (-32009)
163 : #define FD_RPC_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX (-32010)
164 : #define FD_RPC_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE (-32011)
165 : #define FD_RPC_ROR (-32012)
166 : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH (-32013)
167 : #define FD_RPC_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET (-32014)
168 : #define FD_RPC_ERROR_UNSUPPORTED_TRANSACTION_VERSION (-32015)
169 : #define FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED (-32016)
170 : #define FD_RPC_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE (-32017)
171 : #define FD_RPC_ERROR_SLOT_NOT_EPOCH_BOUNDARY (-32018)
172 : #define FD_RPC_ERROR_LONG_TERM_STORAGE_UNREACHABLE (-32019)
173 :
174 : /* fd_rpc_val_t is a JSON value captured from a request as a raw input
175 : slice plus its kind. Kinds mirror the serde names Agave uses in its
176 : error messages. INT is a number literal that fits in a ulong or a
177 : long, any other number literal is FLOAT. NONE denotes an absent
178 : value (missing member or array element). */
179 :
180 0 : #define FD_RPC_VAL_NONE (0)
181 0 : #define FD_RPC_VAL_STR (1)
182 0 : #define FD_RPC_VAL_MAP (2)
183 0 : #define FD_RPC_VAL_SEQ (3)
184 0 : #define FD_RPC_VAL_BOOL (4)
185 0 : #define FD_RPC_VAL_FLOAT (5)
186 0 : #define FD_RPC_VAL_INT (6)
187 0 : #define FD_RPC_VAL_NULL (7)
188 :
189 : struct fd_rpc_val {
190 : int kind;
191 : char const * raw;
192 : ulong raw_sz;
193 : fd_jtok_str_t str; /* STR only: view of the contents between the quotes */
194 : int neg; /* INT only: value is negative */
195 : ulong u; /* INT only: value if !neg */
196 : };
197 :
198 : typedef struct fd_rpc_val fd_rpc_val_t;
199 :
200 : static void
201 : fd_rpc_val_read( fd_jtok_t * j,
202 0 : fd_rpc_val_t * v ) {
203 0 : *v = (fd_rpc_val_t){ .kind = FD_RPC_VAL_NONE };
204 0 : int kind = fd_jtok_peek( j );
205 0 : char const * raw;
206 0 : ulong raw_sz;
207 0 : fd_jtok_raw( j, &raw, &raw_sz );
208 0 : if( FD_UNLIKELY( fd_jtok_err( j ) ) ) return;
209 0 : v->raw = raw;
210 0 : v->raw_sz = raw_sz;
211 0 : switch( kind ) {
212 0 : case FD_JTOK_OBJ: v->kind = FD_RPC_VAL_MAP; break;
213 0 : case FD_JTOK_ARR: v->kind = FD_RPC_VAL_SEQ; break;
214 0 : case FD_JTOK_BOOL: v->kind = FD_RPC_VAL_BOOL; break;
215 0 : case FD_JTOK_NULL: v->kind = FD_RPC_VAL_NULL; break;
216 0 : case FD_JTOK_NUM: v->kind = FD_RPC_VAL_FLOAT; break;
217 0 : case FD_JTOK_STR:
218 0 : v->kind = FD_RPC_VAL_STR;
219 0 : v->str = (fd_jtok_str_t){ .ptr = raw+1, .sz = raw_sz-2UL }; /* raw is the token including both quotes */
220 0 : break;
221 0 : case FD_JTOK_INT: {
222 0 : fd_jtok_t n[1];
223 0 : ulong u;
224 0 : fd_jtok_ulong( fd_jtok_init( n, raw, raw_sz ), &u );
225 0 : if( FD_LIKELY( !fd_jtok_err( n ) ) ) { v->kind = FD_RPC_VAL_INT; v->u = u; break; }
226 0 : long l;
227 0 : fd_jtok_long( fd_jtok_init( n, raw, raw_sz ), &l );
228 0 : if( FD_LIKELY( !fd_jtok_err( n ) ) ) { v->kind = FD_RPC_VAL_INT; v->neg = l<0L; v->u = l<0L ? 0UL : (ulong)l; break; }
229 0 : v->kind = FD_RPC_VAL_FLOAT;
230 0 : break;
231 0 : }
232 0 : default: break;
233 0 : }
234 0 : }
235 :
236 : /* fd_rpc_val_cstr decodes a STR value into the out_sz byte buffer at
237 : out. Returns 1 on success, 0 if v is not a string or does not fit
238 : (out is then an empty string). */
239 :
240 : static int
241 : fd_rpc_val_cstr( fd_rpc_val_t const * v,
242 : char * out,
243 0 : ulong out_sz ) {
244 0 : out[0] = '\0';
245 0 : if( v->kind!=FD_RPC_VAL_STR ) return 0;
246 0 : fd_jtok_t j[1];
247 0 : fd_jtok_cstr( fd_jtok_init( j, v->raw, v->raw_sz ), out, out_sz );
248 0 : return !fd_jtok_err( j );
249 0 : }
250 :
251 : /* fd_rpc_params_t is the "params" member of a request. kind is the
252 : kind of the params value itself. For a SEQ, cnt is the element
253 : count and v holds the first FD_RPC_PARAM_MAX elements (NONE beyond
254 : cnt). */
255 :
256 0 : #define FD_RPC_PARAM_MAX (2UL)
257 :
258 : struct fd_rpc_params {
259 : int kind;
260 : ulong cnt;
261 : fd_rpc_val_t v[ FD_RPC_PARAM_MAX ];
262 : };
263 :
264 : typedef struct fd_rpc_params fd_rpc_params_t;
265 :
266 : static void
267 : fd_rpc_params_read( fd_rpc_val_t const * params,
268 0 : fd_rpc_params_t * out ) {
269 0 : out->kind = params->kind;
270 0 : out->cnt = 0UL;
271 0 : for( ulong i=0UL; i<FD_RPC_PARAM_MAX; i++ ) out->v[ i ].kind = FD_RPC_VAL_NONE;
272 0 : if( params->kind!=FD_RPC_VAL_SEQ ) return;
273 0 : fd_jtok_t j[1];
274 0 : fd_jtok_arr_enter( fd_jtok_init( j, params->raw, params->raw_sz ) );
275 0 : while( fd_jtok_arr_next( j ) ) {
276 0 : if( out->cnt<FD_RPC_PARAM_MAX ) fd_rpc_val_read( j, &out->v[ out->cnt ] );
277 0 : out->cnt++;
278 0 : }
279 0 : }
280 :
281 : /* fd_rpc_config_t holds the members of a config object that any
282 : handler looks at. For duplicate members the first occurrence wins,
283 : unknown members are skipped. */
284 :
285 : struct fd_rpc_config {
286 : fd_rpc_val_t commitment;
287 : fd_rpc_val_t encoding;
288 : fd_rpc_val_t data_slice;
289 : fd_rpc_val_t min_context_slot;
290 : fd_rpc_val_t identity;
291 : };
292 :
293 : typedef struct fd_rpc_config fd_rpc_config_t;
294 :
295 : static void
296 : fd_rpc_config_read( fd_rpc_val_t const * config,
297 0 : fd_rpc_config_t * out ) {
298 0 : out->commitment.kind = FD_RPC_VAL_NONE;
299 0 : out->encoding.kind = FD_RPC_VAL_NONE;
300 0 : out->data_slice.kind = FD_RPC_VAL_NONE;
301 0 : out->min_context_slot.kind = FD_RPC_VAL_NONE;
302 0 : out->identity.kind = FD_RPC_VAL_NONE;
303 0 : if( config->kind!=FD_RPC_VAL_MAP ) return;
304 0 : fd_jtok_t j[1];
305 0 : fd_jtok_str_t key;
306 0 : fd_jtok_obj_enter( fd_jtok_init( j, config->raw, config->raw_sz ) );
307 0 : while( fd_jtok_obj_next( j, &key ) ) {
308 0 : fd_rpc_val_t * member;
309 0 : if( fd_jtok_str_eq( &key, "commitment" ) ) member = &out->commitment;
310 0 : else if( fd_jtok_str_eq( &key, "encoding" ) ) member = &out->encoding;
311 0 : else if( fd_jtok_str_eq( &key, "dataSlice" ) ) member = &out->data_slice;
312 0 : else if( fd_jtok_str_eq( &key, "minContextSlot" ) ) member = &out->min_context_slot;
313 0 : else if( fd_jtok_str_eq( &key, "identity" ) ) member = &out->identity;
314 0 : else continue;
315 0 : if( member->kind==FD_RPC_VAL_NONE ) fd_rpc_val_read( j, member );
316 0 : }
317 0 : }
318 :
319 : static fd_http_server_params_t
320 0 : derive_http_params( fd_topo_tile_t const * tile ) {
321 0 : return (fd_http_server_params_t) {
322 0 : .max_connection_cnt = tile->rpc.max_http_connections,
323 0 : .max_ws_connection_cnt = tile->rpc.max_websocket_connections,
324 0 : .max_request_len = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
325 0 : .max_ws_recv_frame_len = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
326 0 : .max_ws_send_frame_cnt = FD_HTTP_SERVER_RPC_MAX_WS_SEND_FRAME_CNT,
327 0 : .outgoing_buffer_sz = tile->rpc.send_buffer_size_mb * (1UL<<20UL),
328 0 : .compress_websocket = 0,
329 0 : };
330 0 : }
331 :
332 : struct fd_rpc_in {
333 : fd_wksp_t * mem;
334 : ulong chunk0;
335 : ulong wmark;
336 : ulong mtu;
337 : };
338 :
339 : typedef struct fd_rpc_in fd_rpc_in_t;
340 :
341 : struct fd_rpc_out {
342 : ulong idx;
343 : fd_wksp_t * mem;
344 : ulong chunk0;
345 : ulong wmark;
346 : ulong chunk;
347 : };
348 :
349 : typedef struct fd_rpc_out fd_rpc_out_t;
350 :
351 : struct bank_info {
352 : ulong slot; /* default ULONG_MAX */
353 : ulong bank_idx;
354 : fd_accdb_fork_id_t accdb_fork_id;
355 : ulong epoch;
356 : ulong slot_in_epoch;
357 : ulong slots_per_epoch;
358 :
359 : ulong transaction_count;
360 : uchar block_hash[ 32 ];
361 : ulong block_height;
362 :
363 : struct {
364 : double initial;
365 : double terminal;
366 : double taper;
367 : double foundation;
368 : double foundation_term;
369 : } inflation;
370 :
371 : struct {
372 : ulong lamports_per_uint8_year;
373 : double exemption_threshold;
374 : uchar burn_percent;
375 : } rent;
376 : };
377 :
378 : typedef struct bank_info bank_info_t;
379 :
380 : struct fd_rpc_cluster_node {
381 : int valid;
382 : fd_pubkey_t identity;
383 : fd_gossip_contact_info_t ci[ 1 ];
384 :
385 : struct { ulong prev, next; } dlist;
386 : };
387 :
388 : typedef struct fd_rpc_cluster_node fd_rpc_cluster_node_t;
389 :
390 : #define DLIST_NAME fd_rpc_cluster_node_dlist
391 : #define DLIST_ELE_T fd_rpc_cluster_node_t
392 0 : #define DLIST_PREV dlist.prev
393 0 : #define DLIST_NEXT dlist.next
394 : #include "../../util/tmpl/fd_dlist.c"
395 :
396 : /* fd_rpc_gls_pair_t associates a leader-schedule rotation index with the
397 : identity pubkey that leads it, used to group getLeaderSchedule's
398 : result by identity (see the handler). */
399 : struct fd_rpc_gls_pair {
400 : fd_pubkey_t id_key; /* leader identity */
401 : uint rot; /* rotation index into the epoch's leader schedule */
402 : };
403 : typedef struct fd_rpc_gls_pair fd_rpc_gls_pair_t;
404 :
405 0 : #define FD_RPC_MLEADERS_CNT (5UL)
406 0 : #define FD_RPC_MLEADERS_EPOCH_CNT (FD_RPC_MLEADERS_CNT*MULTI_EPOCH_LEADERS_EPOCH_CNT)
407 :
408 : static inline void
409 0 : fd_rpc_mleaders_init( fd_multi_epoch_leaders_t * mleaders ) {
410 0 : for( ulong i=0UL; i<FD_RPC_MLEADERS_CNT; i++ ) FD_TEST( fd_multi_epoch_leaders_new( &mleaders[ i ] ) );
411 0 : }
412 :
413 : static inline void
414 : fd_rpc_mleaders_ingest( fd_multi_epoch_leaders_t * mleaders,
415 0 : fd_epoch_info_msg_t const * msg ) {
416 0 : ulong idx = (msg->epoch / MULTI_EPOCH_LEADERS_EPOCH_CNT) % FD_RPC_MLEADERS_CNT;
417 0 : fd_multi_epoch_leaders_epoch_msg_init( &mleaders[ idx ], msg );
418 0 : fd_multi_epoch_leaders_epoch_msg_fini( &mleaders[ idx ] );
419 0 : }
420 :
421 : FD_FN_PURE static inline fd_epoch_leaders_t const *
422 : fd_rpc_mleaders_get_lsched_for_slot( fd_multi_epoch_leaders_t const * mleaders,
423 0 : ulong slot ) {
424 0 : for( ulong i=0UL; i<FD_RPC_MLEADERS_CNT; i++ ) {
425 0 : fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( &mleaders[ i ], slot );
426 0 : if( FD_LIKELY( lsched ) ) return lsched;
427 0 : }
428 0 : return NULL;
429 0 : }
430 :
431 : FD_FN_PURE static inline fd_pubkey_t const *
432 : fd_rpc_mleaders_get_leader_for_slot( fd_multi_epoch_leaders_t const * mleaders,
433 0 : ulong slot ) {
434 0 : fd_epoch_leaders_t const * lsched = fd_rpc_mleaders_get_lsched_for_slot( mleaders, slot );
435 0 : if( FD_UNLIKELY( !lsched ) ) return NULL;
436 0 : return fd_epoch_leaders_get( lsched, slot );
437 0 : }
438 :
439 : struct fd_rpc_tile {
440 : int delay_startup;
441 : int alpenglow;
442 : int waker_rearm_pending;
443 :
444 : fd_http_server_t * http;
445 :
446 : ulong * ws_subscribers_vote;
447 : ulong ws_subscribers_vote_cnt;
448 : ulong * ws_subscribers_slot;
449 : ulong ws_subscribers_slot_cnt;
450 :
451 : fd_rpc_cluster_node_dlist_t * cluster_nodes_dlist;
452 : fd_rpc_cluster_node_t cluster_nodes[ FD_CONTACT_INFO_TABLE_SIZE ];
453 :
454 : bank_info_t * banks;
455 : ulong max_live_slots;
456 :
457 : fd_accdb_t * accdb;
458 :
459 : ulong cluster_confirmed_slot;
460 :
461 : /* Highest slot relayed to turbine (Agave MaxSlots::retransmit),
462 : one fseq per shred tile, written by shred */
463 : ulong shred_rtx_cnt;
464 : ulong const * shred_rtx[ FD_TOPO_MAX_TILES ];
465 :
466 : /* Address of the account holding the alpenglow genesis certificate,
467 : for getAgGenesisCert. */
468 : fd_pubkey_t ag_genesis_cert_addr[ 1 ];
469 :
470 : ulong processed_idx;
471 : ulong confirmed_idx;
472 : ulong finalized_idx;
473 :
474 : int has_genesis_hash;
475 : fd_hash_t genesis_hash[ 1 ];
476 :
477 : ulong genesis_tar_bz_sz;
478 : ulong genesis_max_message_size;
479 : ulong genesis_tar_max_sz;
480 : ulong genesis_tar_bz_max_sz;
481 : uchar * genesis_tar;
482 : uchar * genesis_tar_bz;
483 :
484 : fd_alloc_t * bz2_alloc;
485 :
486 : fd_clock_tile_t clock[1];
487 : ulong in_cnt;
488 : ulong idle_cnt;
489 :
490 : ulong waker_client_idx;
491 : ulong * waker_fseq;
492 :
493 : fd_keyswitch_t * keyswitch;
494 : uchar identity_pubkey[ 32UL ];
495 :
496 : int in_kind[ 64UL ];
497 : fd_rpc_in_t in[ 64UL ];
498 :
499 : fd_rpc_out_t replay_out[1];
500 :
501 : fd_histf_t request_duration[ 1 ];
502 :
503 : fd_multi_epoch_leaders_t mleaders[ FD_RPC_MLEADERS_CNT ];
504 : fd_epoch_schedule_t epoch_schedule;
505 : int has_epoch_schedule;
506 :
507 : ZSTD_CCtx * zstd_cctx;
508 : uchar compress_buf[ ZSTD_COMPRESSBOUND( FD_RUNTIME_ACC_SZ_MAX ) ];
509 :
510 : /* Redirect to snapshot server */
511 : int snapshot_server_enabled;
512 : char snapshot_server_url[ FD_URL_MAX ];
513 :
514 : union {
515 : /* Scratch used by getLeaderSchedule to group the epoch's leader
516 : schedule by identity. */
517 : fd_rpc_gls_pair_t gls_pairs[ (MAX_SLOTS_PER_EPOCH + FD_EPOCH_SLOTS_PER_ROTATION - 1UL) / FD_EPOCH_SLOTS_PER_ROTATION ];
518 :
519 : /* Scratch buffer for fd_accdb_read_one_nocache: holds the account
520 : data bytes returned by the readonly accdb path. Sized to the
521 : runtime account data maximum. Must not be in accdb shmem. */
522 : uchar accdb_data_buf[ FD_RUNTIME_ACC_SZ_MAX ];
523 : } scratch;
524 : };
525 :
526 : typedef struct fd_rpc_tile fd_rpc_tile_t;
527 :
528 : static void
529 : fd_rpc_ws_subscriber_vote_add( fd_rpc_tile_t * ctx,
530 0 : ulong ws_conn_id ) {
531 0 : for( ulong i=0UL; i<ctx->ws_subscribers_vote_cnt; i++ )
532 0 : if( FD_UNLIKELY( ctx->ws_subscribers_vote[ i ]==ws_conn_id ) ) return;
533 :
534 0 : FD_TEST( ctx->ws_subscribers_vote_cnt<ctx->http->max_ws_conns );
535 0 : ctx->ws_subscribers_vote[ ctx->ws_subscribers_vote_cnt++ ] = ws_conn_id;
536 0 : }
537 :
538 : static int
539 : fd_rpc_ws_subscriber_vote_remove( fd_rpc_tile_t * ctx,
540 0 : ulong ws_conn_id ) {
541 0 : for( ulong idx=0UL; idx<ctx->ws_subscribers_vote_cnt; idx++ ) {
542 0 : if( FD_LIKELY( ctx->ws_subscribers_vote[ idx ]!=ws_conn_id ) ) continue;
543 :
544 0 : ctx->ws_subscribers_vote_cnt--;
545 0 : ctx->ws_subscribers_vote[ idx ] = ctx->ws_subscribers_vote[ ctx->ws_subscribers_vote_cnt ];
546 0 : return 1;
547 0 : }
548 :
549 0 : return 0;
550 0 : }
551 :
552 : static void
553 : fd_rpc_ws_subscriber_slot_add( fd_rpc_tile_t * ctx,
554 0 : ulong ws_conn_id ) {
555 0 : for( ulong i=0UL; i<ctx->ws_subscribers_slot_cnt; i++ )
556 0 : if( FD_UNLIKELY( ctx->ws_subscribers_slot[ i ]==ws_conn_id ) ) return;
557 :
558 0 : FD_TEST( ctx->ws_subscribers_slot_cnt<ctx->http->max_ws_conns );
559 0 : ctx->ws_subscribers_slot[ ctx->ws_subscribers_slot_cnt++ ] = ws_conn_id;
560 0 : }
561 :
562 : static int
563 : fd_rpc_ws_subscriber_slot_remove( fd_rpc_tile_t * ctx,
564 0 : ulong ws_conn_id ) {
565 0 : for( ulong idx=0UL; idx<ctx->ws_subscribers_slot_cnt; idx++ ) {
566 0 : if( FD_LIKELY( ctx->ws_subscribers_slot[ idx ]!=ws_conn_id ) ) continue;
567 :
568 0 : ctx->ws_subscribers_slot_cnt--;
569 0 : ctx->ws_subscribers_slot[ idx ] = ctx->ws_subscribers_slot[ ctx->ws_subscribers_slot_cnt ];
570 0 : return 1;
571 0 : }
572 :
573 0 : return 0;
574 0 : }
575 :
576 : static void *
577 : bz2_malloc( void * opaque,
578 : int items,
579 0 : int size ) {
580 0 : fd_alloc_t * alloc = (fd_alloc_t *)opaque;
581 :
582 0 : if( FD_UNLIKELY( items<=0 || size<=0 ) ) {
583 0 : FD_LOG_WARNING(( "bz2_malloc: invalid items=%d or size=%d", items, size ));
584 0 : return NULL;
585 0 : }
586 0 : void * result = fd_alloc_malloc( alloc, alignof(max_align_t), (ulong)items*(ulong)size );
587 0 : if( FD_UNLIKELY( !result ) ) return NULL;
588 0 : return result;
589 0 : }
590 :
591 : static void
592 : bz2_free( void * opaque,
593 0 : void * addr ) {
594 0 : fd_alloc_t * alloc = (fd_alloc_t *)opaque;
595 :
596 0 : if( FD_UNLIKELY( !addr ) ) return;
597 0 : fd_alloc_free( alloc, addr );
598 0 : }
599 :
600 : static inline ulong
601 : fd_rpc_file_as_tarball( fd_rpc_tile_t * ctx,
602 : char const * filename_cstr,
603 : uchar const * data,
604 : ulong data_sz,
605 : uchar * scratch,
606 : ulong scratch_sz,
607 : uchar * out,
608 0 : ulong out_sz ) {
609 0 : ulong padding_sz = 2UL*FD_TAR_BLOCK_SZ;
610 0 : if( FD_LIKELY( data_sz % FD_TAR_BLOCK_SZ ) ) padding_sz += FD_TAR_BLOCK_SZ - (data_sz % FD_TAR_BLOCK_SZ);
611 :
612 0 : if( FD_UNLIKELY( data_sz>ctx->genesis_max_message_size ) ) {
613 0 : FD_LOG_ERR(( "Genesis data exceeds maximum size (data_sz=%lu max=%lu)", data_sz, ctx->genesis_max_message_size ));
614 0 : }
615 0 : ulong tar_sz = sizeof(fd_tar_meta_t) + data_sz + padding_sz;
616 0 : if( FD_UNLIKELY( tar_sz>scratch_sz ) ) {
617 0 : FD_LOG_WARNING(( "tar_sz exceeds scratch_sz (tar_sz=%lu scratch_sz=%lu)", tar_sz, scratch_sz ));
618 0 : return ULONG_MAX;
619 0 : }
620 :
621 0 : fd_tar_meta_init_file_default( (fd_tar_meta_t *)scratch, filename_cstr, data_sz, fd_log_wallclock() );
622 0 : fd_memcpy( scratch+sizeof(fd_tar_meta_t), data, data_sz );
623 0 : memset( scratch+sizeof(fd_tar_meta_t)+data_sz, 0, padding_sz );
624 :
625 : /* NOTE: Agave's genesis.tar also contains a `rocksdb` folder */
626 :
627 0 : bz_stream bzstrm = {0};
628 0 : bzstrm.bzalloc = bz2_malloc;
629 0 : bzstrm.bzfree = bz2_free;
630 0 : bzstrm.opaque = ctx->bz2_alloc;
631 0 : int bzerr = BZ2_bzCompressInit( &bzstrm, 1, 0, 0 );
632 0 : if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzCompressInit() failed (%d)", bzerr ));
633 :
634 0 : ulong tar_bz_sz = out_sz;
635 :
636 0 : bzstrm.next_in = (char *)scratch;
637 0 : bzstrm.avail_in = (uint)tar_sz;
638 0 : bzstrm.next_out = (char *)out;
639 0 : bzstrm.avail_out = (uint)tar_bz_sz;
640 :
641 0 : for(;;) {
642 0 : bzerr = BZ2_bzCompress( &bzstrm, BZ_FINISH );
643 0 : if( FD_LIKELY( bzerr==BZ_STREAM_END ) ) break;
644 0 : if( FD_UNLIKELY( bzerr>=0 ) ) continue;
645 0 : FD_LOG_ERR(( "BZ2_bzCompress(_, BZ_FINISH) failed (%d)", bzerr ));
646 0 : }
647 :
648 0 : tar_bz_sz -= (ulong)bzstrm.avail_out;
649 :
650 0 : bzerr = BZ2_bzCompressEnd( &bzstrm );
651 0 : if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzCompressEnd() failed (%d)", bzerr ));
652 :
653 0 : return tar_bz_sz;
654 0 : }
655 :
656 : FD_FN_CONST static inline ulong
657 0 : scratch_align( void ) {
658 0 : ulong a = alignof( fd_rpc_tile_t );
659 0 : a = fd_ulong_max( a, fd_http_server_align() );
660 0 : a = fd_ulong_max( a, fd_alloc_align() );
661 0 : a = fd_ulong_max( a, alignof(bank_info_t) );
662 0 : a = fd_ulong_max( a, fd_rpc_cluster_node_dlist_align() );
663 0 : a = fd_ulong_max( a, fd_accdb_align() );
664 0 : return a;
665 0 : }
666 :
667 : FD_FN_CONST static inline ulong
668 0 : loose_footprint( fd_topo_tile_t const * tile ) {
669 0 : (void)tile;
670 : /* Leftover space for bzip2 allocations */
671 0 : return 1UL<<26; /* 64 MiB */
672 0 : }
673 :
674 : static inline ulong
675 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
676 0 : fd_http_server_params_t http_params = derive_http_params( tile );
677 0 : ulong http_fp = fd_http_server_footprint( http_params );
678 0 : if( FD_UNLIKELY( !http_fp ) ) FD_LOG_ERR(( "Invalid [tiles.rpc] config parameters" ));
679 :
680 0 : ulong l = FD_LAYOUT_INIT;
681 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_rpc_tile_t), sizeof(fd_rpc_tile_t) );
682 0 : l = FD_LAYOUT_APPEND( l, fd_http_server_align(), http_fp );
683 0 : l = FD_LAYOUT_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
684 0 : l = FD_LAYOUT_APPEND( l, alignof(bank_info_t), tile->rpc.max_live_slots*sizeof(bank_info_t) );
685 0 : l = FD_LAYOUT_APPEND( l, fd_rpc_cluster_node_dlist_align(), fd_rpc_cluster_node_dlist_footprint() );
686 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->rpc.max_live_slots, 0 ) );
687 0 : l = FD_LAYOUT_APPEND( l, alignof(ulong), http_params.max_ws_connection_cnt*sizeof(ulong) );
688 0 : l = FD_LAYOUT_APPEND( l, alignof(ulong), http_params.max_ws_connection_cnt*sizeof(ulong) );
689 0 : l = FD_LAYOUT_APPEND( l, alignof(uchar), fd_rpc_genesis_tar_max_sz( tile->rpc.genesis_max_message_size ) );
690 0 : l = FD_LAYOUT_APPEND( l, alignof(uchar), fd_rpc_genesis_tar_bz_max_sz( tile->rpc.genesis_max_message_size ) );
691 0 : l = FD_LAYOUT_APPEND( l, 16UL, ZSTD_estimateCCtxSize( FD_RPC_ZSTD_LEVEL ) );
692 0 : return FD_LAYOUT_FINI( l, scratch_align() );
693 0 : }
694 :
695 : static inline void
696 0 : during_housekeeping( fd_rpc_tile_t * ctx ) {
697 0 : if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) {
698 0 : fd_clock_tile_recal( ctx->clock );
699 0 : }
700 :
701 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
702 0 : fd_memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
703 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
704 0 : }
705 0 : }
706 :
707 : static inline void
708 0 : metrics_write( fd_rpc_tile_t * ctx ) {
709 0 : FD_MHIST_COPY( RPC, REQUEST_DURATION_NANOS, ctx->request_duration );
710 0 : FD_MGAUGE_SET( RPC, CONN_OPEN, ctx->http->metrics.connection_cnt );
711 0 : FD_MGAUGE_SET( RPC, WEBSOCKET_CONN_OPEN, ctx->http->metrics.ws_connection_cnt );
712 0 : FD_MGAUGE_SET( RPC, WEBSOCKET_SUBSCRIBER_COUNT_VOTE, ctx->ws_subscribers_vote_cnt );
713 0 : FD_MGAUGE_SET( RPC, WEBSOCKET_SUBSCRIBER_COUNT_SLOT, ctx->ws_subscribers_slot_cnt );
714 0 : FD_ACCDB_METRICS_WRITE_RO( RPC, fd_accdb_metrics( ctx->accdb ) );
715 0 : }
716 :
717 : static void
718 : before_credit( fd_rpc_tile_t * ctx,
719 : fd_stem_context_t * stem,
720 0 : int * charge_busy ) {
721 0 : (void)stem;
722 :
723 0 : ctx->idle_cnt++;
724 0 : if( FD_LIKELY( ctx->idle_cnt<2UL*ctx->in_cnt ) ) return;
725 0 : ctx->idle_cnt = 0UL;
726 :
727 0 : int replay_ready = ctx->confirmed_idx!=ULONG_MAX && ctx->processed_idx!=ULONG_MAX && ctx->finalized_idx!=ULONG_MAX;
728 0 : if( FD_UNLIKELY( ctx->delay_startup && !replay_ready ) ) {
729 : /* Consume readiness so an early connection does not veto every
730 : park, but do not rearm: the listen fd stays readable and would
731 : refire on every pass. */
732 0 : if( FD_UNLIKELY( fd_fseq_query( ctx->waker_fseq )==1UL ) ) {
733 0 : fd_fseq_update( ctx->waker_fseq, 0UL );
734 0 : ctx->waker_rearm_pending = 1;
735 0 : }
736 0 : return;
737 0 : }
738 :
739 0 : if( FD_UNLIKELY( ctx->waker_rearm_pending ) ) {
740 0 : ctx->waker_rearm_pending = 0;
741 0 : fd_waker_client_rearm( ctx->waker_client_idx );
742 0 : }
743 :
744 0 : if( FD_UNLIKELY( fd_fseq_query( ctx->waker_fseq )==1UL ) ) {
745 0 : fd_fseq_update( ctx->waker_fseq, 0UL );
746 0 : *charge_busy = fd_http_server_epoll_poll( ctx->http, 1UL );
747 0 : fd_waker_client_rearm( ctx->waker_client_idx );
748 0 : }
749 0 : }
750 :
751 : static int
752 : before_frag( fd_rpc_tile_t * ctx,
753 : ulong in_idx,
754 : ulong seq FD_PARAM_UNUSED,
755 0 : ulong sig ) {
756 0 : if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) ) {
757 : /* To match Agave, voteSubscribe shouldn't work under Alpenglow. */
758 0 : if( sig==FD_GOSSIP_UPDATE_TAG_VOTE ) return ctx->alpenglow || !ctx->ws_subscribers_vote_cnt;
759 0 : return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO &&
760 0 : sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE;
761 0 : }
762 :
763 0 : if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) ) {
764 0 : return sig!=REPLAY_SIG_SLOT_COMPLETED && sig!=REPLAY_SIG_OC_ADVANCED &&
765 0 : sig!=REPLAY_SIG_ROOT_ADVANCED && sig!=REPLAY_SIG_DROP_BANK_REF;
766 0 : }
767 :
768 0 : if( ctx->in_kind[ in_idx ]==IN_KIND_VOTOR ) return sig!=FD_VOTOR_SIG_CERTED;
769 :
770 0 : return 0;
771 0 : }
772 :
773 : static int
774 : fd_rpc_extract_vote_notification( fd_gossip_vote_t const * vote,
775 : fd_pubkey_t * vote_pubkey,
776 : fd_hash_t * hash,
777 : long * timestamp,
778 : uchar * has_timestamp,
779 : fd_signature_t * signature,
780 : ulong * slots,
781 0 : ulong * slots_cnt ) {
782 0 : uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t))));
783 0 : ulong txn_sz = fd_txn_parse( vote->transaction, vote->transaction_len, txn_mem, NULL );
784 0 : if( FD_UNLIKELY( !txn_sz ) ) return 0;
785 :
786 0 : fd_txn_t const * txn = (fd_txn_t const *)txn_mem;
787 0 : if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, vote->transaction ) ) ) return 0;
788 0 : if( FD_UNLIKELY( txn->instr_cnt!=1UL || txn->signature_cnt<1UL ) ) return 0;
789 :
790 0 : fd_txn_instr_t const * instr = &txn->instr[ 0 ];
791 0 : if( FD_UNLIKELY( !instr->acct_cnt ) ) return 0;
792 0 : uchar const * instr_accts = fd_txn_get_instr_accts( instr, vote->transaction );
793 0 : uchar vote_pubkey_idx = instr_accts[ 0 ];
794 0 : if( FD_UNLIKELY( vote_pubkey_idx>=txn->acct_addr_cnt ) ) return 0;
795 :
796 0 : fd_pubkey_t const * acct_addrs = (fd_pubkey_t const *)fd_type_pun_const( vote->transaction + txn->acct_addr_off );
797 0 : *vote_pubkey = acct_addrs[ vote_pubkey_idx ];
798 :
799 0 : fd_vote_instruction_t ix[1];
800 0 : if( FD_UNLIKELY( !fd_vote_instruction_deserialize( ix, fd_txn_get_instr_data( instr, vote->transaction ), instr->data_sz ) ) ) return 0;
801 :
802 0 : *slots_cnt = 0UL;
803 0 : *has_timestamp = 0;
804 0 : *timestamp = 0L;
805 :
806 0 : switch( ix->discriminant ) {
807 0 : case fd_vote_instruction_enum_tower_sync: {
808 0 : fd_tower_sync_t const * v = &ix->tower_sync;
809 0 : for( ulong i=0UL; i<v->lockouts_cnt; i++ ) slots[ (*slots_cnt)++ ] =
810 0 : deq_fd_vote_lockout_t_peek_index_const( v->lockouts, i )->slot;
811 0 : *hash = v->hash;
812 0 : *has_timestamp = v->has_timestamp;
813 0 : *timestamp = v->timestamp;
814 0 : break;
815 0 : }
816 0 : case fd_vote_instruction_enum_tower_sync_switch: {
817 0 : fd_tower_sync_t const * v = &ix->tower_sync_switch.tower_sync;
818 0 : for( ulong i=0UL; i<v->lockouts_cnt; i++ ) slots[ (*slots_cnt)++ ] =
819 0 : deq_fd_vote_lockout_t_peek_index_const( v->lockouts, i )->slot;
820 0 : *hash = v->hash;
821 0 : *has_timestamp = v->has_timestamp;
822 0 : *timestamp = v->timestamp;
823 0 : break;
824 0 : }
825 0 : default:
826 : /* Legacy vote instructions not supported */
827 0 : return 0;
828 0 : }
829 :
830 0 : *signature = FD_LOAD( fd_signature_t, fd_txn_get_signatures( txn, vote->transaction ) );
831 0 : return *slots_cnt>0UL;
832 0 : }
833 :
834 : static void
835 : fd_rpc_publish_vote_event( fd_rpc_tile_t * ctx,
836 0 : fd_gossip_vote_t const * vote ) {
837 0 : if( FD_UNLIKELY( !ctx->ws_subscribers_vote_cnt ) ) return;
838 :
839 0 : fd_pubkey_t vote_pubkey[1];
840 0 : fd_hash_t hash[1];
841 0 : long timestamp = 0L;
842 0 : uchar has_timestamp = 0;
843 0 : fd_signature_t signature[1];
844 0 : ulong slots[ FD_VOTE_INSTR_MAX_LOCKOUT_OFFSETS_LEN ];
845 0 : ulong slots_cnt = 0UL;
846 :
847 0 : if( FD_UNLIKELY( !fd_rpc_extract_vote_notification( vote, vote_pubkey, hash, ×tamp, &has_timestamp, signature, slots, &slots_cnt ) ) ) return;
848 :
849 0 : FD_BASE58_ENCODE_32_BYTES( vote_pubkey->uc, vote_pubkey_b58 );
850 0 : FD_BASE58_ENCODE_32_BYTES( hash->uc, hash_b58 );
851 0 : FD_BASE58_ENCODE_64_BYTES( signature->uc, signature_b58 );
852 :
853 0 : char txn_base64[ FD_BASE64_ENC_SZ( sizeof(vote->transaction) ) ];
854 0 : ulong txn_base64_len = fd_base64_encode( txn_base64, vote->transaction, vote->transaction_len );
855 :
856 0 : ulong sent_cnt = 0UL;
857 0 : for( ulong i=0UL; i<ctx->ws_subscribers_vote_cnt; ) {
858 0 : ulong ws_conn_id = ctx->ws_subscribers_vote[ i ];
859 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"method\":\"voteNotification\",\"params\":{\"subscription\":0,\"result\":{\"votePubkey\":\"%s\",\"slots\":[", vote_pubkey_b58 );
860 0 : for( ulong j=0UL; j<slots_cnt; j++ ) fd_http_server_printf( ctx->http, "%s%lu", j ? "," : "", slots[ j ] );
861 0 : fd_http_server_printf( ctx->http, "],\"hash\":\"%s\",", hash_b58 );
862 0 : if( FD_LIKELY( has_timestamp ) ) fd_http_server_printf( ctx->http, "\"timestamp\":%ld,", timestamp );
863 0 : else fd_http_server_printf( ctx->http, "\"timestamp\":null," );
864 0 : fd_http_server_printf( ctx->http, "\"signature\":\"%s\",\"transaction\":[\"%.*s\",\"base64\"]}}}\n", signature_b58, (int)txn_base64_len, txn_base64 );
865 :
866 0 : if( FD_UNLIKELY( fd_http_server_ws_send( ctx->http, ws_conn_id ) ) ) {
867 0 : fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
868 0 : fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
869 0 : continue;
870 0 : }
871 0 : if( FD_UNLIKELY( i>=ctx->ws_subscribers_vote_cnt || ctx->ws_subscribers_vote[ i ]!=ws_conn_id ) ) continue;
872 0 : sent_cnt++;
873 0 : i++;
874 0 : }
875 :
876 0 : FD_MCNT_INC( RPC, WEBSOCKET_NOTIFICATION_PUBLISHED_VOTE, !!sent_cnt );
877 0 : FD_MCNT_INC( RPC, WEBSOCKET_NOTIFICATION_TX_VOTE, sent_cnt );
878 0 : }
879 :
880 : static void
881 : fd_rpc_publish_slot_event( fd_rpc_tile_t * ctx,
882 0 : fd_replay_slot_completed_t const * slot_completed ) {
883 0 : if( FD_UNLIKELY( !ctx->ws_subscribers_slot_cnt ) ) return;
884 :
885 0 : ulong sent_cnt = 0UL;
886 0 : for( ulong i=0UL; i<ctx->ws_subscribers_slot_cnt; ) {
887 0 : ulong ws_conn_id = ctx->ws_subscribers_slot[ i ];
888 0 : fd_http_server_printf( ctx->http,
889 0 : "{\"jsonrpc\":\"2.0\",\"method\":\"slotNotification\",\"params\":{\"subscription\":0,\"result\":{\"parent\":%lu,\"root\":%lu,\"slot\":%lu}}}\n",
890 0 : slot_completed->parent_slot,
891 0 : slot_completed->root_slot,
892 0 : slot_completed->slot );
893 :
894 0 : if( FD_UNLIKELY( fd_http_server_ws_send( ctx->http, ws_conn_id ) ) ) {
895 0 : fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
896 0 : fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
897 0 : continue;
898 0 : }
899 0 : if( FD_UNLIKELY( i>=ctx->ws_subscribers_slot_cnt || ctx->ws_subscribers_slot[ i ]!=ws_conn_id ) ) continue;
900 0 : sent_cnt++;
901 0 : i++;
902 0 : }
903 :
904 0 : FD_MCNT_INC( RPC, WEBSOCKET_NOTIFICATION_PUBLISHED_SLOT, !!sent_cnt );
905 0 : FD_MCNT_INC( RPC, WEBSOCKET_NOTIFICATION_TX_SLOT, sent_cnt );
906 0 : }
907 :
908 : static inline void
909 : fd_rpc_cluster_finalized( fd_rpc_tile_t * ctx,
910 0 : ulong slot ) {
911 0 : ctx->cluster_confirmed_slot = ctx->cluster_confirmed_slot==ULONG_MAX ? slot : fd_ulong_max( ctx->cluster_confirmed_slot, slot );
912 0 : }
913 :
914 : static inline int
915 : returnable_frag( fd_rpc_tile_t * ctx,
916 : ulong in_idx,
917 : ulong seq FD_PARAM_UNUSED,
918 : ulong sig,
919 : ulong chunk,
920 : ulong sz FD_PARAM_UNUSED,
921 : ulong ctl FD_PARAM_UNUSED,
922 : ulong tsorig FD_PARAM_UNUSED,
923 : ulong tspub FD_PARAM_UNUSED,
924 0 : fd_stem_context_t * stem ) {
925 :
926 0 : ctx->idle_cnt = 0UL;
927 :
928 0 : if( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) {
929 0 : switch( sig ) {
930 0 : case REPLAY_SIG_SLOT_COMPLETED: {
931 0 : fd_replay_slot_completed_t const * slot_completed = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
932 :
933 0 : FD_TEST( slot_completed->bank_idx<ctx->max_live_slots );
934 0 : bank_info_t * bank = &ctx->banks[ slot_completed->bank_idx ];
935 0 : bank->slot = slot_completed->slot;
936 0 : bank->accdb_fork_id = slot_completed->accdb_fork_id;
937 0 : bank->epoch = slot_completed->epoch;
938 0 : bank->slot_in_epoch = slot_completed->slot_in_epoch;
939 0 : bank->slots_per_epoch = slot_completed->slots_per_epoch;
940 0 : bank->transaction_count = slot_completed->transaction_count;
941 0 : bank->block_height = slot_completed->block_height;
942 0 : fd_memcpy( bank->block_hash, slot_completed->block_hash.uc, 32 );
943 :
944 0 : bank->inflation.initial = slot_completed->inflation.initial;
945 0 : bank->inflation.terminal = slot_completed->inflation.terminal;
946 0 : bank->inflation.taper = slot_completed->inflation.taper;
947 0 : bank->inflation.foundation = slot_completed->inflation.foundation;
948 0 : bank->inflation.foundation_term = slot_completed->inflation.foundation_term;
949 :
950 0 : bank->rent.lamports_per_uint8_year = slot_completed->rent.lamports_per_uint8_year;
951 0 : bank->rent.exemption_threshold = slot_completed->rent.exemption_threshold;
952 0 : bank->rent.burn_percent = slot_completed->rent.burn_percent;
953 :
954 0 : fd_rpc_publish_slot_event( ctx, slot_completed );
955 :
956 : /* In Agave, "processed" confirmation is the bank we've just
957 : voted for (handle_votable_bank), which is also guaranteed to
958 : have been replayed.
959 :
960 : Right now tower is not really built out to replicate this
961 : exactly, so we use the latest replayed slot, which is
962 : slightly more eager than Agave but shouldn't really affect
963 : end-users, since any use-cases that assume "processed" means
964 : "voted-for" would fail in Agave in cases where a cast vote
965 : does not land.
966 :
967 : Due to bank eviction semantics, it is possible that the RPC
968 : can return data about a slot that is not the most recently
969 : replayed slot (since slots can be re-replayed).
970 :
971 : tldr: This isn't strictly conformant with Agave, but doesn't
972 : need to be since Agave doesn't provide any guarantees
973 : anyways. */
974 0 : if( FD_LIKELY( ctx->processed_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->processed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
975 0 : ctx->processed_idx = slot_completed->bank_idx;
976 0 : break;
977 0 : }
978 0 : case REPLAY_SIG_OC_ADVANCED: {
979 0 : fd_replay_oc_advanced_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
980 0 : if( FD_LIKELY( ctx->confirmed_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->confirmed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
981 0 : FD_TEST( msg->bank_idx<ctx->max_live_slots );
982 0 : ctx->confirmed_idx = msg->bank_idx;
983 0 : break;
984 0 : }
985 0 : case REPLAY_SIG_ROOT_ADVANCED: {
986 0 : fd_replay_root_advanced_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
987 0 : if( FD_LIKELY( ctx->finalized_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->finalized_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
988 0 : FD_TEST( msg->bank_idx<ctx->max_live_slots );
989 0 : ctx->finalized_idx = msg->bank_idx;
990 :
991 0 : if( FD_UNLIKELY( ctx->alpenglow ) ) {
992 0 : if( FD_LIKELY( ctx->confirmed_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->confirmed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
993 0 : ctx->confirmed_idx = msg->bank_idx;
994 :
995 0 : if( FD_LIKELY( ctx->cluster_confirmed_slot!=ULONG_MAX ) ) fd_rpc_cluster_finalized( ctx, msg->slot );
996 0 : }
997 0 : break;
998 0 : }
999 0 : case REPLAY_SIG_DROP_BANK_REF: {
1000 0 : fd_replay_drop_bank_ref_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
1001 0 : if( FD_UNLIKELY( ctx->processed_idx==msg->bank_idx ) ) {
1002 0 : fd_stem_publish( stem, ctx->replay_out->idx, ctx->processed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
1003 0 : ctx->processed_idx = ULONG_MAX;
1004 0 : }
1005 0 : if( FD_UNLIKELY( ctx->confirmed_idx==msg->bank_idx ) ) {
1006 0 : fd_stem_publish( stem, ctx->replay_out->idx, ctx->confirmed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
1007 0 : ctx->confirmed_idx = ULONG_MAX;
1008 0 : }
1009 0 : if( FD_UNLIKELY( ctx->finalized_idx==msg->bank_idx ) ) {
1010 0 : fd_stem_publish( stem, ctx->replay_out->idx, ctx->finalized_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
1011 0 : ctx->finalized_idx = ULONG_MAX;
1012 0 : }
1013 0 : break;
1014 0 : }
1015 0 : default: {
1016 0 : break;
1017 0 : }
1018 0 : }
1019 0 : } else if( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) {
1020 0 : fd_gossip_update_message_t const * update = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
1021 0 : switch( update->tag ) {
1022 0 : case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
1023 0 : if( FD_UNLIKELY( update->contact_info->idx>=FD_CONTACT_INFO_TABLE_SIZE ) ) FD_LOG_ERR(( "unexpected contact_info_idx %lu >= %lu", update->contact_info->idx, FD_CONTACT_INFO_TABLE_SIZE ));
1024 0 : fd_rpc_cluster_node_t * node = &ctx->cluster_nodes[ update->contact_info->idx ];
1025 0 : if( FD_LIKELY( node->valid ) ) fd_rpc_cluster_node_dlist_idx_remove( ctx->cluster_nodes_dlist, update->contact_info->idx, ctx->cluster_nodes );
1026 :
1027 0 : node->valid = 1;
1028 0 : node->identity = *(fd_pubkey_t *)update->origin;
1029 0 : fd_memcpy( node->ci, update->contact_info->value, sizeof(fd_gossip_contact_info_t) );
1030 :
1031 0 : fd_rpc_cluster_node_dlist_idx_push_tail( ctx->cluster_nodes_dlist, update->contact_info->idx, ctx->cluster_nodes );
1032 0 : break;
1033 0 : }
1034 0 : case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE: {
1035 0 : if( FD_UNLIKELY( update->contact_info_remove->idx>=FD_CONTACT_INFO_TABLE_SIZE ) ) FD_LOG_ERR(( "unexpected remove_contact_info_idx %lu >= %lu", update->contact_info_remove->idx, FD_CONTACT_INFO_TABLE_SIZE ));
1036 0 : fd_rpc_cluster_node_t * node = &ctx->cluster_nodes[ update->contact_info_remove->idx ];
1037 0 : FD_TEST( node->valid );
1038 0 : node->valid = 0;
1039 0 : fd_rpc_cluster_node_dlist_idx_remove( ctx->cluster_nodes_dlist, update->contact_info_remove->idx, ctx->cluster_nodes );
1040 0 : break;
1041 0 : }
1042 0 : case FD_GOSSIP_UPDATE_TAG_VOTE: {
1043 0 : fd_rpc_publish_vote_event( ctx, update->vote->value );
1044 0 : break;
1045 0 : }
1046 0 : default: break;
1047 0 : }
1048 0 : } else if( ctx->in_kind[ in_idx ]==IN_KIND_EPOCH ) {
1049 0 : fd_epoch_info_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
1050 0 : FD_TEST( msg->staked_vote_cnt<=MAX_STAKE_WEIGHTS );
1051 0 : ctx->epoch_schedule = msg->epoch_schedule;
1052 0 : ctx->has_epoch_schedule = 1;
1053 :
1054 0 : fd_rpc_mleaders_ingest( ctx->mleaders, msg );
1055 0 : } else if( ctx->in_kind[ in_idx ]==IN_KIND_VOTOR ) {
1056 0 : fd_votor_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
1057 0 : if( FD_LIKELY( msg->certed.kind==AG_CERT_KIND_FINAL || msg->certed.kind==AG_CERT_KIND_FAST_FINAL ) ) {
1058 0 : fd_rpc_cluster_finalized( ctx, msg->certed.slot );
1059 0 : }
1060 0 : } else if( ctx->in_kind[ in_idx ]==IN_KIND_GENESI ) {
1061 0 : ctx->has_genesis_hash = 1;
1062 0 : fd_genesis_meta_t const * genesis_meta = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
1063 0 : *ctx->genesis_hash = genesis_meta->genesis_hash;
1064 :
1065 0 : uchar const * blob = (uchar const *)( genesis_meta+1 );
1066 0 : ulong const blob_sz = genesis_meta->blob_sz;
1067 0 : FD_TEST( blob_sz<=ctx->genesis_max_message_size );
1068 :
1069 0 : ctx->genesis_tar_bz_sz = fd_rpc_file_as_tarball(
1070 0 : ctx,
1071 0 : "genesis.bin",
1072 0 : blob, blob_sz,
1073 0 : ctx->genesis_tar, ctx->genesis_tar_max_sz,
1074 0 : ctx->genesis_tar_bz, ctx->genesis_tar_bz_max_sz );
1075 0 : if( FD_UNLIKELY( ctx->genesis_tar_bz_sz==ULONG_MAX ) ) {
1076 0 : FD_LOG_ERR(( "failed to create genesis tarball (blob_sz=%lu)", blob_sz ));
1077 0 : }
1078 0 : }
1079 :
1080 0 : return 0;
1081 0 : }
1082 :
1083 0 : #define STAGE_JSON(__ctx) (__extension__({ \
1084 0 : fd_http_server_response_t __res = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 }; \
1085 0 : if( FD_UNLIKELY( fd_http_server_stage_body( __ctx->http, &__res ) ) ) { \
1086 0 : __res.status = 500; \
1087 0 : FD_LOG_WARNING(( "Failed to populate RPC response buffer" )); \
1088 0 : FD_LOG_HEXDUMP_WARNING(( "start of message:\n%.*s", __ctx->http->oring+(__ctx->http->stage_off%__ctx->http->oring_sz), fd_ulong_min( 500UL, __ctx->http->oring_sz-(__ctx->http->stage_off%__ctx->http->oring_sz)-1UL ) )); \
1089 0 : FD_LOG_HEXDUMP_WARNING(( "start of buffer:\n%.*s", __ctx->http->oring, fd_ulong_min( 500UL, __ctx->http->oring_sz ) )); \
1090 0 : } \
1091 0 : __res; }))
1092 :
1093 0 : #define PRINTF_JSON(__ctx, ...) (__extension__({ \
1094 0 : fd_http_server_printf( __ctx->http, __VA_ARGS__ ); \
1095 0 : fd_http_server_response_t __res = STAGE_JSON( __ctx ); \
1096 0 : __res; }))
1097 :
1098 :
1099 : static inline char const *
1100 0 : fd_rpc_val_kind_cstr( fd_rpc_val_t const * v ) {
1101 0 : switch( v->kind ) {
1102 0 : case FD_RPC_VAL_STR: return "string";
1103 0 : case FD_RPC_VAL_MAP: return "map";
1104 0 : case FD_RPC_VAL_SEQ: return "sequence";
1105 0 : case FD_RPC_VAL_BOOL: return "boolean";
1106 0 : case FD_RPC_VAL_FLOAT: return "floating point";
1107 0 : case FD_RPC_VAL_INT: return "integer";
1108 0 : case FD_RPC_VAL_NULL: return "null";
1109 0 : default: FD_LOG_ERR(( "unreachable" ));
1110 0 : }
1111 0 : }
1112 :
1113 : /* fd_rpc_str_ws_sz returns the size of the whitespace unit at the
1114 : start of the JSON string body [p,end), or 0 if it does not start
1115 : with one. A unit is a literal space or an escape that decodes to an
1116 : ASCII control character. */
1117 :
1118 : static ulong
1119 : fd_rpc_str_ws_sz( char const * p,
1120 0 : char const * end ) {
1121 0 : if( p[0]==' ' ) return 1UL;
1122 0 : if( p[0]!='\\' || p+1>=end ) return 0UL;
1123 0 : switch( p[1] ) {
1124 0 : case 'b': case 'f': case 'n': case 'r': case 't':
1125 0 : return 2UL;
1126 0 : case 'u':
1127 0 : if( p+6<=end && p[2]=='0' && p[3]=='0' && (p[4]=='0' || p[4]=='1') ) return 6UL;
1128 0 : return 0UL;
1129 0 : default:
1130 0 : return 0UL;
1131 0 : }
1132 0 : }
1133 :
1134 : /* fd_rpc_print_str appends the contents of a request string to the
1135 : response. The bytes between the quotes of a well formed JSON string
1136 : are themselves a well formed JSON string body, so they are copied
1137 : as-is, escapes included. Runs of whitespace (literal spaces and the
1138 : escapes for control characters) are collapsed into a single space to
1139 : keep the error message on one line. */
1140 :
1141 : static void
1142 : fd_rpc_print_str( fd_rpc_tile_t * ctx,
1143 0 : fd_jtok_str_t const * s ) {
1144 0 : char const * p = s->ptr;
1145 0 : char const * end = s->ptr + s->sz;
1146 0 : char const * span = p;
1147 0 : while( p<end ) {
1148 0 : ulong ws_sz = fd_rpc_str_ws_sz( p, end );
1149 0 : if( !ws_sz ) {
1150 0 : p += ( p[0]=='\\' && p+1<end ) ? 2 : 1; /* skip escapes whole */
1151 0 : continue;
1152 0 : }
1153 0 : if( span<p ) fd_http_server_memcpy( ctx->http, (uchar const *)span, (ulong)(p-span) );
1154 0 : fd_http_server_memcpy( ctx->http, (uchar const *)" ", 1UL );
1155 0 : do p += ws_sz; while( p<end && (ws_sz = fd_rpc_str_ws_sz( p, end )) );
1156 0 : span = p;
1157 0 : }
1158 0 : if( span<p ) fd_http_server_memcpy( ctx->http, (uchar const *)span, (ulong)(p-span) );
1159 0 : }
1160 :
1161 : /* fd_rpc_print_float formats a double to match serde_json / zmij. */
1162 :
1163 : static inline void
1164 : fd_rpc_print_float( fd_rpc_tile_t * ctx,
1165 0 : double d ) {
1166 0 : char buf[ 64 ];
1167 0 : double test;
1168 0 : int ok = 0;
1169 0 : if( FD_LIKELY( sprintf( buf, "%1.15g", d )>0 && sscanf( buf, "%lg", &test )==1 && test==d ) ) ok = 1;
1170 0 : else if( FD_LIKELY( sprintf( buf, "%1.16g", d )>0 && sscanf( buf, "%lg", &test )==1 && test==d ) ) ok = 1;
1171 0 : else if( FD_LIKELY( sprintf( buf, "%1.17g", d )>0 ) ) ok = 1;
1172 0 : if( FD_UNLIKELY( !ok ) ) {
1173 0 : fd_http_server_printf( ctx->http, " `0.0`" );
1174 0 : return;
1175 0 : }
1176 0 : if( !strchr( buf, '.' ) && !strchr( buf, 'e' ) ) {
1177 0 : fd_http_server_printf( ctx->http, " `%s.0`", buf );
1178 0 : } else {
1179 0 : fd_http_server_printf( ctx->http, " `%s`", buf );
1180 0 : }
1181 0 : }
1182 :
1183 : /* fd_rpc_err_invalid emits an "invalid type" / "invalid value" error
1184 : for v in the style of serde. what is "type" or "value", expected is
1185 : the name of the expected type. */
1186 :
1187 : static fd_http_server_response_t
1188 : fd_rpc_err_invalid( fd_rpc_tile_t * ctx,
1189 : char const * id_cstr,
1190 : char const * what,
1191 : fd_rpc_val_t const * v,
1192 0 : char const * expected ) {
1193 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid %s: %s", what, fd_rpc_val_kind_cstr( v ) );
1194 0 : switch( v->kind ) {
1195 0 : case FD_RPC_VAL_STR:
1196 0 : fd_http_server_printf( ctx->http, " \\\"" );
1197 0 : fd_rpc_print_str( ctx, &v->str );
1198 0 : fd_http_server_printf( ctx->http, "\\\"" );
1199 0 : break;
1200 0 : case FD_RPC_VAL_BOOL:
1201 0 : case FD_RPC_VAL_INT:
1202 0 : fd_http_server_printf( ctx->http, " `%.*s`", (int)v->raw_sz, v->raw );
1203 0 : break;
1204 0 : case FD_RPC_VAL_FLOAT: {
1205 0 : fd_jtok_t j[1];
1206 0 : double d;
1207 0 : fd_jtok_double( fd_jtok_init( j, v->raw, v->raw_sz ), &d );
1208 0 : if( FD_LIKELY( !fd_jtok_fini( j ) ) ) fd_rpc_print_float( ctx, d );
1209 0 : else fd_http_server_printf( ctx->http, " `%.*s`", (int)v->raw_sz, v->raw );
1210 0 : break;
1211 0 : }
1212 0 : default:
1213 0 : break;
1214 0 : }
1215 0 : fd_http_server_printf( ctx->http, ", expected %s.\"},\"id\":%s}\n", expected, id_cstr );
1216 0 : return STAGE_JSON( ctx );
1217 0 : }
1218 :
1219 : static fd_http_server_response_t
1220 : fd_rpc_err_unknown_encoding( fd_rpc_tile_t * ctx,
1221 : char const * id_cstr,
1222 0 : fd_jtok_str_t const * variant ) {
1223 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: unknown variant `" );
1224 0 : fd_rpc_print_str( ctx, variant );
1225 0 : fd_http_server_printf( ctx->http, "`, expected one of `binary`, `base58`, `base64`, `jsonParsed`, `base64+zstd`.\"},\"id\":%s}\n", id_cstr );
1226 0 : return STAGE_JSON( ctx );
1227 0 : }
1228 :
1229 : static inline int
1230 : fd_rpc_validate_params( fd_rpc_tile_t * ctx,
1231 : char const * id_cstr,
1232 : fd_rpc_params_t const * params,
1233 : ulong min_cnt,
1234 : ulong max_cnt,
1235 0 : fd_http_server_response_t * res ) {
1236 0 : FD_TEST( min_cnt <= max_cnt );
1237 : /* Agave also includes a "data" field in some responses with the
1238 : faulty params payload. Instead of printing raw JSON, they print the
1239 : representation which we won't replicate.
1240 :
1241 : e.g. "data" might contain something like
1242 : Array([String(\"\"), Object {}])
1243 :
1244 : instead, we just include the field with an empty string
1245 : */
1246 :
1247 0 : ulong param_cnt = 0UL;
1248 0 : switch( params->kind ) {
1249 0 : case FD_RPC_VAL_NONE:
1250 0 : case FD_RPC_VAL_NULL:
1251 0 : break;
1252 0 : case FD_RPC_VAL_SEQ:
1253 0 : param_cnt = params->cnt;
1254 0 : break;
1255 0 : case FD_RPC_VAL_MAP:
1256 0 : if( max_cnt==0UL ) *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: No parameters were expected\",\"data\":\"\"},\"id\":%s}\n", id_cstr );
1257 0 : else *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"`params` should be an array\"},\"id\":%s}\n", id_cstr );
1258 0 : return 0;
1259 0 : default:
1260 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
1261 0 : return 0;
1262 0 : }
1263 :
1264 0 : if( FD_UNLIKELY( param_cnt>0UL && max_cnt==0UL ) ) {
1265 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: No parameters were expected\",\"data\":\"\"},\"id\":%s}\n", id_cstr );
1266 0 : return 0;
1267 0 : }
1268 0 : if( FD_UNLIKELY( param_cnt<min_cnt ) ) {
1269 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"`params` should have at least %lu argument(s)\"},\"id\":%s}\n", min_cnt, id_cstr );
1270 0 : return 0;
1271 0 : }
1272 0 : if( param_cnt>max_cnt ) {
1273 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: Expected from %lu to %lu parameters.\",\"data\":\"\\\"Got: %lu\\\"\"},\"id\":%s}\n", min_cnt, max_cnt, param_cnt, id_cstr );
1274 0 : return 0;
1275 0 : }
1276 :
1277 0 : return 1;
1278 0 : }
1279 :
1280 : /* TODO: use optimized version of this from fd_base58_tmpl.c */
1281 : static const char base58_chars[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
1282 :
1283 : static inline int
1284 0 : fd_rpc_cstr_contains_non_base58(const char *str) {
1285 0 : for (; *str; str++) {
1286 0 : if (!strchr(base58_chars, *str)) return 1;
1287 0 : }
1288 0 : return 0;
1289 0 : }
1290 :
1291 : /* adapted from https://salsa.debian.org/debian/libbase58/-/blob/debian/master/base58.c */
1292 : static inline int
1293 0 : fd_rpc_base58_encode_128( char * b58, ulong * b58sz, const void *data, ulong binsz ) {
1294 0 : FD_TEST( binsz <= 128UL );
1295 :
1296 0 : const uchar * bin = data;
1297 0 : ulong carry;
1298 0 : ulong i, j, high, zcount = 0;
1299 0 : ulong size;
1300 :
1301 0 : while( zcount<binsz && !bin[ zcount ] ) zcount++;
1302 :
1303 0 : size = (binsz-zcount)*138/100+1; /* strict overestimate */
1304 0 : size = fd_ulong_min( size, FD_RPC_BASE58_ENCODED_128_LEN ); /* theoretical max */
1305 0 : uchar buf[ FD_RPC_BASE58_ENCODED_128_LEN ] = { 0 };
1306 :
1307 0 : for( i=zcount, high=size-1UL; i<binsz; i++, high=j ) {
1308 0 : for( carry=bin[ i ], j=size-1UL; (j>high) || carry; j-- ) {
1309 0 : carry += 256UL * buf[ j ];
1310 0 : buf[ j ] = (uchar)(carry%58UL);
1311 0 : carry /= 58UL;
1312 0 : if( FD_UNLIKELY( !j ) ) break;
1313 0 : }
1314 0 : }
1315 :
1316 0 : for( j=0; j<size && !buf[ j ]; j++);
1317 :
1318 0 : if( *b58sz<zcount+size-j ) {
1319 0 : *b58sz = zcount+size-j;
1320 0 : return 0;
1321 0 : }
1322 :
1323 0 : if (zcount) memset(b58, '1', zcount);
1324 0 : for( i=zcount; j<size; i++, j++) b58[ i ] = base58_chars[ buf[ j ] ];
1325 0 : *b58sz = i;
1326 :
1327 0 : return 1;
1328 0 : }
1329 :
1330 : /* getInflationGovernor and getMinimumBalanceForRentExemption take a
1331 : bare Option<CommitmentConfig> in Agave, so unlike the flattened
1332 : configs serde rejects a missing or malformed commitment member. */
1333 :
1334 : static int
1335 : fd_rpc_validate_commitment_strict( fd_rpc_tile_t * ctx,
1336 : char const * id_cstr,
1337 : fd_rpc_val_t const * config,
1338 0 : fd_http_server_response_t * res ) {
1339 0 : if( config->kind!=FD_RPC_VAL_MAP ) return 1; /* type errors are fd_rpc_validate_config's */
1340 0 : fd_rpc_config_t cfg[1];
1341 0 : fd_rpc_config_read( config, cfg );
1342 0 : fd_rpc_val_t const * commitment = &cfg->commitment;
1343 0 : if( FD_UNLIKELY( commitment->kind==FD_RPC_VAL_NONE ) ) {
1344 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `commitment`.\"},\"id\":%s}\n", id_cstr );
1345 0 : return 0;
1346 0 : }
1347 0 : if( FD_UNLIKELY( commitment->kind!=FD_RPC_VAL_STR ) ) {
1348 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", commitment, "enum CommitmentLevel" );
1349 0 : return 0;
1350 0 : }
1351 0 : if( FD_UNLIKELY( !fd_jtok_str_eq( &commitment->str, "processed" ) && !fd_jtok_str_eq( &commitment->str, "confirmed" ) && !fd_jtok_str_eq( &commitment->str, "finalized" ) ) ) {
1352 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: unknown variant `" );
1353 0 : fd_rpc_print_str( ctx, &commitment->str );
1354 0 : fd_http_server_printf( ctx->http, "`, expected one of `processed`, `confirmed`, `finalized`.\"},\"id\":%s}\n", id_cstr );
1355 0 : *res = STAGE_JSON( ctx );
1356 0 : return 0;
1357 0 : }
1358 0 : return 1;
1359 0 : }
1360 :
1361 : static inline int
1362 : fd_rpc_validate_config( fd_rpc_tile_t * ctx,
1363 : char const * id_cstr,
1364 : fd_rpc_val_t const * config,
1365 : char const * config_rust_type,
1366 : int has_commitment,
1367 : int has_encoding,
1368 : int has_data_slice,
1369 : int has_min_context_slot,
1370 : ulong * bank_idx,
1371 : char const ** opt_encoding_cstr,
1372 : ulong * opt_slice_length,
1373 : ulong * opt_slice_offset,
1374 : fd_rpc_val_t * opt_identity,
1375 0 : fd_http_server_response_t * res ) {
1376 :
1377 0 : switch( config->kind ) {
1378 0 : case FD_RPC_VAL_INT:
1379 0 : case FD_RPC_VAL_FLOAT:
1380 0 : case FD_RPC_VAL_BOOL:
1381 0 : case FD_RPC_VAL_STR:
1382 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", config, config_rust_type );
1383 0 : return 0;
1384 0 : case FD_RPC_VAL_SEQ:
1385 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: Positional config params not supported\"},\"id\":%s}\n", id_cstr );
1386 0 : return 0;
1387 0 : default:
1388 0 : break;
1389 0 : }
1390 :
1391 0 : fd_rpc_config_t cfg[1];
1392 0 : fd_rpc_config_read( config, cfg );
1393 0 : if( opt_identity ) *opt_identity = cfg->identity;
1394 :
1395 0 : ulong _bank_idx = ULONG_MAX;
1396 0 : if( FD_LIKELY( has_commitment ) ) {
1397 0 : fd_rpc_val_t const * commitment = &cfg->commitment;
1398 0 : char commitment_cstr[ 16 ];
1399 0 : if( FD_UNLIKELY( !fd_rpc_val_cstr( commitment, commitment_cstr, sizeof(commitment_cstr) ) ) ) _bank_idx = ctx->finalized_idx;
1400 0 : else if( FD_LIKELY( !strcmp( commitment_cstr, "processed" ) ) ) _bank_idx = ctx->processed_idx;
1401 0 : else if( FD_LIKELY( !strcmp( commitment_cstr, "confirmed" ) ) ) _bank_idx = ctx->confirmed_idx;
1402 0 : else if( FD_LIKELY( !strcmp( commitment_cstr, "finalized" ) ) ) _bank_idx = ctx->finalized_idx;
1403 0 : else _bank_idx = ctx->finalized_idx;
1404 0 : } else {
1405 0 : _bank_idx = ctx->finalized_idx;
1406 0 : }
1407 0 : if( FD_UNLIKELY( _bank_idx==ULONG_MAX ) ) {
1408 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
1409 0 : return 0;
1410 0 : }
1411 0 : *bank_idx = _bank_idx;
1412 :
1413 0 : if( FD_LIKELY( has_encoding ) ) {
1414 0 : fd_rpc_val_t const * encoding = &cfg->encoding;
1415 :
1416 0 : if( FD_UNLIKELY( encoding->kind==FD_RPC_VAL_INT || encoding->kind==FD_RPC_VAL_FLOAT || encoding->kind==FD_RPC_VAL_BOOL ) ) {
1417 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", encoding, "string or map" );
1418 0 : return 0;
1419 0 : }
1420 :
1421 : /* An encoding given as a map must be a single key naming the
1422 : variant with a null (unit) value, e.g. {"base64":null}. */
1423 0 : fd_jtok_str_t variant_key = {0};
1424 0 : fd_rpc_val_t variant_val[1] = {{ .kind = FD_RPC_VAL_NONE }};
1425 0 : if( FD_UNLIKELY( encoding->kind==FD_RPC_VAL_MAP ) ) {
1426 0 : fd_jtok_t j[1];
1427 0 : fd_jtok_str_t key;
1428 0 : ulong member_cnt = 0UL;
1429 0 : fd_jtok_obj_enter( fd_jtok_init( j, encoding->raw, encoding->raw_sz ) );
1430 0 : while( fd_jtok_obj_next( j, &key ) ) {
1431 0 : if( !member_cnt ) { variant_key = key; fd_rpc_val_read( j, variant_val ); }
1432 0 : member_cnt++;
1433 0 : }
1434 0 : if( FD_UNLIKELY( member_cnt!=1UL ) ) {
1435 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: map, expected map with a single key.\"},\"id\":%s}\n", id_cstr );
1436 0 : return 0;
1437 0 : }
1438 0 : }
1439 0 : if( FD_UNLIKELY( encoding->kind==FD_RPC_VAL_SEQ ) ) {
1440 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", encoding, "string or map" );
1441 0 : return 0;
1442 0 : }
1443 :
1444 0 : char const * encoding_cstr;
1445 0 : if( FD_UNLIKELY( encoding->kind==FD_RPC_VAL_MAP ) ) {
1446 0 : if( fd_jtok_str_eq( &variant_key, "binary" ) ) encoding_cstr = "binary";
1447 0 : else if( fd_jtok_str_eq( &variant_key, "base58" ) ) encoding_cstr = "base58";
1448 0 : else if( fd_jtok_str_eq( &variant_key, "base64" ) ) encoding_cstr = "base64";
1449 0 : else if( fd_jtok_str_eq( &variant_key, "base64+zstd" ) ) encoding_cstr = "base64+zstd";
1450 0 : else if( fd_jtok_str_eq( &variant_key, "jsonParsed" ) ) encoding_cstr = "jsonParsed";
1451 0 : else {
1452 0 : *res = fd_rpc_err_unknown_encoding( ctx, id_cstr, &variant_key );
1453 0 : return 0;
1454 0 : }
1455 0 : if( FD_UNLIKELY( variant_val->kind!=FD_RPC_VAL_NULL ) ) {
1456 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", variant_val, "unit" );
1457 0 : return 0;
1458 0 : }
1459 0 : } else {
1460 0 : char buf[ 16 ];
1461 0 : if( encoding->kind!=FD_RPC_VAL_STR ) encoding_cstr = "binary";
1462 0 : else if( !fd_rpc_val_cstr( encoding, buf, sizeof(buf) ) ) encoding_cstr = "";
1463 0 : else if( !strcmp( buf, "binary" ) ) encoding_cstr = "binary";
1464 0 : else if( !strcmp( buf, "base58" ) ) encoding_cstr = "base58";
1465 0 : else if( !strcmp( buf, "base64" ) ) encoding_cstr = "base64";
1466 0 : else if( !strcmp( buf, "base64+zstd" ) ) encoding_cstr = "base64+zstd";
1467 0 : else if( !strcmp( buf, "jsonParsed" ) ) encoding_cstr = "jsonParsed";
1468 0 : else encoding_cstr = "";
1469 0 : }
1470 :
1471 0 : if( 0==strcmp( encoding_cstr, "jsonParsed" ) ) {
1472 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: jsonParsed is unsupported\"},\"id\":%s}\n", id_cstr );
1473 0 : return 0;
1474 0 : } else if( FD_UNLIKELY( !encoding_cstr[0] ) ) {
1475 0 : *res = fd_rpc_err_unknown_encoding( ctx, id_cstr, &encoding->str );
1476 0 : return 0;
1477 0 : }
1478 :
1479 0 : if( FD_LIKELY( opt_encoding_cstr ) ) *opt_encoding_cstr = encoding_cstr;
1480 0 : }
1481 :
1482 0 : if( FD_LIKELY( has_data_slice ) ) {
1483 0 : fd_rpc_val_t const * dataSlice = &cfg->data_slice;
1484 :
1485 0 : if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_INT || dataSlice->kind==FD_RPC_VAL_FLOAT || dataSlice->kind==FD_RPC_VAL_BOOL || dataSlice->kind==FD_RPC_VAL_STR ) ) {
1486 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", dataSlice, "struct UiDataSliceConfig" );
1487 0 : return 0;
1488 0 : }
1489 :
1490 : /* A data slice is either {"offset":..,"length":..} or a two
1491 : element [offset,length] array. */
1492 0 : fd_rpc_val_t _offset[1] = {{ .kind = FD_RPC_VAL_NONE }};
1493 0 : fd_rpc_val_t _length[1] = {{ .kind = FD_RPC_VAL_NONE }};
1494 0 : ulong slice_cnt = 0UL;
1495 0 : if( dataSlice->kind==FD_RPC_VAL_MAP ) {
1496 0 : fd_jtok_t j[1];
1497 0 : fd_jtok_str_t key;
1498 0 : fd_jtok_obj_enter( fd_jtok_init( j, dataSlice->raw, dataSlice->raw_sz ) );
1499 0 : while( fd_jtok_obj_next( j, &key ) ) {
1500 0 : if( fd_jtok_str_eq( &key, "offset" ) ) { if( _offset->kind==FD_RPC_VAL_NONE ) fd_rpc_val_read( j, _offset ); }
1501 0 : else if( fd_jtok_str_eq( &key, "length" ) ) { if( _length->kind==FD_RPC_VAL_NONE ) fd_rpc_val_read( j, _length ); }
1502 0 : }
1503 0 : } else if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_SEQ ) ) {
1504 0 : fd_jtok_t j[1];
1505 0 : fd_jtok_arr_enter( fd_jtok_init( j, dataSlice->raw, dataSlice->raw_sz ) );
1506 0 : while( fd_jtok_arr_next( j ) ) {
1507 0 : if( slice_cnt==0UL ) fd_rpc_val_read( j, _offset );
1508 0 : else if( slice_cnt==1UL ) fd_rpc_val_read( j, _length );
1509 0 : slice_cnt++;
1510 0 : }
1511 0 : }
1512 :
1513 0 : if( FD_UNLIKELY( _offset->kind==FD_RPC_VAL_BOOL || _offset->kind==FD_RPC_VAL_FLOAT ) ) {
1514 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _offset, "usize" );
1515 0 : return 0;
1516 0 : }
1517 0 : if( FD_UNLIKELY( _length->kind==FD_RPC_VAL_BOOL || _length->kind==FD_RPC_VAL_FLOAT ) ) {
1518 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _length, "usize" );
1519 0 : return 0;
1520 0 : }
1521 :
1522 0 : if( FD_UNLIKELY( _offset->kind==FD_RPC_VAL_INT && _offset->neg ) ) {
1523 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "value", _offset, "usize" );
1524 0 : return 0;
1525 0 : }
1526 0 : if( FD_UNLIKELY( _length->kind==FD_RPC_VAL_INT && _length->neg ) ) {
1527 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "value", _length, "usize" );
1528 0 : return 0;
1529 0 : }
1530 :
1531 0 : if( FD_UNLIKELY( _offset->kind==FD_RPC_VAL_STR ) ) {
1532 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _offset, "usize" );
1533 0 : return 0;
1534 0 : }
1535 0 : if( FD_UNLIKELY( _length->kind==FD_RPC_VAL_STR ) ) {
1536 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _length, "usize" );
1537 0 : return 0;
1538 0 : }
1539 :
1540 0 : if( FD_UNLIKELY( _offset->kind!=FD_RPC_VAL_NONE && _offset->kind!=FD_RPC_VAL_INT ) ) {
1541 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _offset, "usize" );
1542 0 : return 0;
1543 0 : }
1544 0 : if( FD_UNLIKELY( _length->kind!=FD_RPC_VAL_NONE && _length->kind!=FD_RPC_VAL_INT ) ) {
1545 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _length, "usize" );
1546 0 : return 0;
1547 0 : }
1548 :
1549 0 : if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_MAP && _offset->kind==FD_RPC_VAL_NONE ) ) {
1550 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `offset`.\"},\"id\":%s}\n", id_cstr );
1551 0 : return 0;
1552 0 : }
1553 0 : if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_MAP && _length->kind==FD_RPC_VAL_NONE ) ) {
1554 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `length`.\"},\"id\":%s}\n", id_cstr );
1555 0 : return 0;
1556 0 : }
1557 :
1558 0 : if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_SEQ && slice_cnt!=2UL ) ) {
1559 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid length %lu, expected struct UiDataSliceConfig with 2 elements.\"},\"id\":%s}\n", slice_cnt, id_cstr );
1560 0 : return 0;
1561 0 : }
1562 :
1563 0 : if( dataSlice->kind==FD_RPC_VAL_MAP || dataSlice->kind==FD_RPC_VAL_SEQ ) {
1564 0 : if( FD_LIKELY( opt_slice_offset ) ) *opt_slice_offset = _offset->u;
1565 0 : if( FD_LIKELY( opt_slice_length ) ) *opt_slice_length = _length->u;
1566 0 : } else {
1567 0 : if( FD_LIKELY( opt_slice_offset ) ) *opt_slice_offset = 0UL;
1568 0 : if( FD_LIKELY( opt_slice_length ) ) *opt_slice_length = ULONG_MAX;
1569 0 : }
1570 0 : }
1571 :
1572 0 : if( FD_LIKELY( has_min_context_slot ) ) {
1573 0 : fd_rpc_val_t const * _minContextSlot = &cfg->min_context_slot;
1574 0 : if( FD_UNLIKELY( _minContextSlot->kind==FD_RPC_VAL_BOOL || _minContextSlot->kind==FD_RPC_VAL_FLOAT ) ) {
1575 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _minContextSlot, "u64" );
1576 0 : return 0;
1577 0 : }
1578 :
1579 0 : if( FD_UNLIKELY( _minContextSlot->kind==FD_RPC_VAL_INT && _minContextSlot->neg ) ) {
1580 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "value", _minContextSlot, "u64" );
1581 0 : return 0;
1582 0 : }
1583 :
1584 0 : if( FD_UNLIKELY( _minContextSlot->kind==FD_RPC_VAL_STR ) ) {
1585 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _minContextSlot, "u64" );
1586 0 : return 0;
1587 0 : }
1588 :
1589 0 : if( FD_UNLIKELY( _minContextSlot->kind==FD_RPC_VAL_MAP || _minContextSlot->kind==FD_RPC_VAL_SEQ ) ) {
1590 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _minContextSlot, "u64" );
1591 0 : return 0;
1592 0 : }
1593 :
1594 0 : ulong minContextSlot = _minContextSlot->kind==FD_RPC_VAL_INT ? _minContextSlot->u : 0UL;
1595 :
1596 0 : if( _bank_idx!=ULONG_MAX && ctx->banks[ _bank_idx ].slot<minContextSlot ) {
1597 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%s}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, ctx->banks[ _bank_idx ].slot, id_cstr );
1598 0 : return 0;
1599 0 : }
1600 0 : }
1601 :
1602 0 : return 1;
1603 0 : }
1604 :
1605 : static int
1606 : fd_rpc_validate_address( fd_rpc_tile_t * ctx,
1607 : char const * id_cstr,
1608 : fd_rpc_val_t const * address_in,
1609 : fd_pubkey_t * address_out,
1610 0 : fd_http_server_response_t * response ) {
1611 0 : if( FD_UNLIKELY( address_in->kind!=FD_RPC_VAL_STR ) ) {
1612 0 : *response = fd_rpc_err_invalid( ctx, id_cstr, "type", address_in, "a string" );
1613 0 : return 0;
1614 0 : }
1615 0 : char address_cstr[ FD_BASE58_ENCODED_32_SZ ];
1616 0 : fd_jtok_t s[1];
1617 0 : fd_jtok_cstr( fd_jtok_init( s, address_in->raw, address_in->raw_sz ), address_cstr, sizeof(address_cstr) );
1618 0 : int fits = !fd_jtok_err( s );
1619 0 : int invalid_char;
1620 0 : if( FD_LIKELY( fits ) ) invalid_char = fd_rpc_cstr_contains_non_base58( address_cstr );
1621 0 : else {
1622 : /* Too long to be a pubkey. A string of only base58 characters is
1623 : still WrongSize rather than Invalid. */
1624 0 : invalid_char = 0;
1625 0 : for( ulong i=0UL; i<address_in->str.sz; i++ ) {
1626 0 : if( !strchr( base58_chars, address_in->str.ptr[ i ] ) ) { invalid_char = 1; break; }
1627 0 : }
1628 0 : }
1629 0 : if( FD_UNLIKELY( invalid_char ) ) {
1630 0 : *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: Invalid\"},\"id\":%s}\n", id_cstr );
1631 0 : return 0;
1632 0 : }
1633 0 : int valid = fits && !!fd_base58_decode_32( address_cstr, address_out->uc );
1634 0 : if( FD_UNLIKELY( !valid ) ) {
1635 0 : *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: WrongSize\"},\"id\":%s}\n", id_cstr );
1636 0 : return 0;
1637 0 : }
1638 :
1639 0 : return 1;
1640 0 : }
1641 :
1642 : static inline int
1643 : fd_rpc_validate_uint_param( fd_rpc_tile_t * ctx,
1644 : char const * id_cstr,
1645 : fd_rpc_val_t const * val,
1646 : char const * type_cstr,
1647 : ulong * out,
1648 0 : fd_http_server_response_t * res ) {
1649 0 : if( FD_UNLIKELY( val->kind==FD_RPC_VAL_BOOL || val->kind==FD_RPC_VAL_FLOAT ) ) {
1650 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", val, type_cstr );
1651 0 : return 0;
1652 0 : }
1653 0 : if( FD_UNLIKELY( val->kind==FD_RPC_VAL_INT && val->neg ) ) {
1654 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "value", val, type_cstr );
1655 0 : return 0;
1656 0 : }
1657 0 : if( FD_UNLIKELY( val->kind!=FD_RPC_VAL_INT ) ) {
1658 0 : *res = fd_rpc_err_invalid( ctx, id_cstr, "type", val, type_cstr );
1659 0 : return 0;
1660 0 : }
1661 :
1662 0 : *out = val->u;
1663 0 : return 1;
1664 0 : }
1665 :
1666 : #define UNIMPLEMENTED(X) \
1667 : static fd_http_server_response_t \
1668 : X( fd_rpc_tile_t * ctx, \
1669 : char const * id_cstr, \
1670 0 : fd_rpc_params_t const * params ) { \
1671 0 : (void)ctx; (void)id_cstr; (void)params; \
1672 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_UNIMPLEMENTED, 1UL ); \
1673 0 : return (fd_http_server_response_t){ .status = 501 }; \
1674 0 : }
1675 :
1676 : UNIMPLEMENTED(getBlock)
1677 : UNIMPLEMENTED(getBlockCommitment)
1678 :
1679 : /* fd_rpc_encode_account_data encodes account data fields
1680 : (executable, lamports, owner, rentEpoch, space, data) into the http
1681 : staging buffer. Returns 1 on success. On failure, calls
1682 : fd_http_server_unstage and writes an error response into
1683 : err_response. */
1684 : static int
1685 : fd_rpc_encode_account_data( fd_rpc_tile_t * ctx,
1686 : uchar const * acct_data,
1687 : ulong acct_data_len,
1688 : uchar const * acct_owner,
1689 : ulong acct_lamports,
1690 : int acct_executable,
1691 : char const * encoding_cstr,
1692 : ulong slice_offset,
1693 : ulong slice_length,
1694 : char const * id_cstr,
1695 0 : fd_http_server_response_t * err_response ) {
1696 :
1697 0 : int is_binary = !strcmp( encoding_cstr, "binary" );
1698 0 : int is_base58 = !strcmp( encoding_cstr, "base58" );
1699 0 : int is_zstd = !strcmp( encoding_cstr, "base64+zstd" );
1700 :
1701 0 : ulong data_sz = acct_data_len;
1702 0 : uchar const * out = acct_data + fd_ulong_if( slice_offset<data_sz, slice_offset, 0UL );
1703 0 : ulong snip_sz = fd_ulong_min( fd_ulong_if( slice_offset<data_sz, data_sz-slice_offset, 0UL ), slice_length );
1704 0 : ulong out_sz = snip_sz;
1705 :
1706 0 : if( FD_UNLIKELY( (is_binary || is_base58) && snip_sz>128UL ) ) {
1707 0 : fd_http_server_unstage( ctx->http );
1708 0 : *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Encoded binary (base 58) data should be less than 128 bytes, please use Base64 encoding.\"},\"id\":%s}\n", id_cstr );
1709 0 : return 0;
1710 0 : }
1711 :
1712 0 : if( is_zstd ) {
1713 0 : ulong zstd_res = ZSTD_compressCCtx( ctx->zstd_cctx, ctx->compress_buf, sizeof(ctx->compress_buf), out, snip_sz, FD_RPC_ZSTD_LEVEL );
1714 0 : if( ZSTD_isError( zstd_res ) ) {
1715 0 : fd_http_server_unstage( ctx->http );
1716 0 : *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: zstandard compression failed (%s)\"},\"id\":%s}\n", ZSTD_getErrorName( zstd_res ), id_cstr );
1717 0 : return 0;
1718 0 : }
1719 0 : out = ctx->compress_buf;
1720 0 : out_sz = (ulong)zstd_res;
1721 0 : }
1722 :
1723 0 : FD_BASE58_ENCODE_32_BYTES( acct_owner, owner_b58 );
1724 0 : fd_http_server_printf( ctx->http,
1725 0 : "{\"executable\":%s,\"lamports\":%lu,\"owner\":\"%s\",\"rentEpoch\":18446744073709551615,\"space\":%lu,\"data\":",
1726 0 : acct_executable ? "true" : "false",
1727 0 : acct_lamports,
1728 0 : owner_b58,
1729 0 : data_sz );
1730 :
1731 0 : ulong encoded_sz = fd_ulong_if( is_base58 || is_binary, FD_RPC_BASE58_ENCODED_128_LEN, FD_BASE64_ENC_SZ( out_sz ) );
1732 0 : if( FD_UNLIKELY( is_binary ) ) {
1733 0 : fd_http_server_printf( ctx->http, "\"" );
1734 0 : } else {
1735 0 : fd_http_server_printf( ctx->http, "[\"" );
1736 0 : }
1737 :
1738 0 : uchar * encoded = fd_http_server_append_start( ctx->http, encoded_sz );
1739 0 : if( FD_UNLIKELY( !encoded ) ) {
1740 0 : fd_http_server_unstage( ctx->http );
1741 0 : *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: response encoding buffer overflow (account data too large for response buffer)\"},\"id\":%s}\n", id_cstr );
1742 0 : return 0;
1743 0 : }
1744 :
1745 0 : if( FD_UNLIKELY( is_base58 || is_binary ) ) {
1746 0 : if( FD_UNLIKELY( !fd_rpc_base58_encode_128( (char *)encoded, &encoded_sz, out, out_sz ) ) ) {
1747 0 : fd_http_server_unstage( ctx->http );
1748 0 : FD_LOG_WARNING(( "base58 encode failed out_sz=%lu", out_sz ));
1749 0 : *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: base58 encode failed\"},\"id\":%s}\n", id_cstr );
1750 0 : return 0;
1751 0 : }
1752 0 : } else {
1753 0 : encoded_sz = fd_base64_encode( (char *)encoded, out, out_sz );
1754 0 : }
1755 :
1756 0 : fd_http_server_append_end( ctx->http, encoded_sz );
1757 :
1758 0 : if( FD_UNLIKELY( is_binary ) ) fd_http_server_printf( ctx->http, "\"}" );
1759 0 : else fd_http_server_printf( ctx->http, "\",\"%s\"]}", encoding_cstr );
1760 :
1761 0 : return 1;
1762 0 : }
1763 :
1764 : static fd_http_server_response_t
1765 : getAccountInfo( fd_rpc_tile_t * ctx,
1766 : char const * id_cstr,
1767 0 : fd_rpc_params_t const * params ) {
1768 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_ACCOUNT_INFO, 1UL );
1769 :
1770 0 : fd_http_server_response_t response;
1771 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 2, &response ) ) ) return response;
1772 :
1773 0 : fd_pubkey_t address;
1774 0 : fd_rpc_val_t const * acct_pubkey = ¶ms->v[ 0 ];
1775 0 : if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id_cstr, acct_pubkey, &address, &response ) ) ) return response;
1776 :
1777 0 : ulong bank_idx = ULONG_MAX;
1778 0 : char const * encoding_cstr = NULL;
1779 0 : ulong slice_length = ULONG_MAX;
1780 0 : ulong slice_offset = 0;
1781 0 : fd_rpc_val_t const * config = ¶ms->v[ 1 ];
1782 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcAccountInfoConfig",
1783 0 : 1, /* has_commitment */
1784 0 : 1, /* has_encoding */
1785 0 : 1, /* has_data_slice */
1786 0 : 1, /* has_min_context_slot */
1787 0 : &bank_idx,
1788 0 : &encoding_cstr,
1789 0 : &slice_length,
1790 0 : &slice_offset,
1791 0 : NULL,
1792 0 : &response );
1793 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1794 :
1795 0 : bank_info_t * info = &ctx->banks[ bank_idx ];
1796 0 : ulong acct_lamports;
1797 0 : int acct_executable;
1798 0 : uchar acct_owner[ 32UL ];
1799 0 : ulong acct_data_len;
1800 0 : fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, address.uc,
1801 0 : &acct_lamports, &acct_executable, acct_owner,
1802 0 : ctx->scratch.accdb_data_buf, &acct_data_len );
1803 0 : if( FD_UNLIKELY( !acct_lamports ) ) {
1804 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":%lu},\"value\":null},\"id\":%s}\n", info->slot, id_cstr );
1805 0 : }
1806 :
1807 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":{\"context\":{\"apiVersion\":\"%s\",\"slot\":%lu},\"value\":", id_cstr, FD_RPC_AGAVE_API_VERSION, info->slot );
1808 :
1809 0 : fd_http_server_response_t err_response;
1810 0 : if( FD_UNLIKELY( !fd_rpc_encode_account_data( ctx, ctx->scratch.accdb_data_buf, acct_data_len, acct_owner, acct_lamports, acct_executable, encoding_cstr, slice_offset, slice_length, id_cstr, &err_response ) ) ) {
1811 0 : return err_response;
1812 0 : }
1813 :
1814 0 : fd_http_server_printf( ctx->http, "}}\n" );
1815 0 : return STAGE_JSON( ctx );
1816 0 : }
1817 :
1818 : static fd_http_server_response_t
1819 : getBalance( fd_rpc_tile_t * ctx,
1820 : char const * id_cstr,
1821 0 : fd_rpc_params_t const * params ) {
1822 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_BALANCE, 1UL );
1823 :
1824 0 : fd_http_server_response_t response;
1825 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 2, &response ) ) ) return response;
1826 :
1827 0 : fd_pubkey_t address;
1828 0 : fd_rpc_val_t const * acct_pubkey = ¶ms->v[ 0 ];
1829 0 : if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id_cstr, acct_pubkey, &address, &response ) ) ) return response;
1830 :
1831 0 : ulong bank_idx = ULONG_MAX;
1832 0 : fd_rpc_val_t const * config = ¶ms->v[ 1 ];
1833 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
1834 0 : 1, /* has_commitment */
1835 0 : 0, /* has_encoding */
1836 0 : 0, /* has_data_slice */
1837 0 : 1, /* has_min_context_slot */
1838 0 : &bank_idx,
1839 0 : NULL,
1840 0 : NULL,
1841 0 : NULL,
1842 0 : NULL,
1843 0 : &response );
1844 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1845 :
1846 0 : ulong balance = fd_accdb_lamports( ctx->accdb, ctx->banks[ bank_idx ].accdb_fork_id, address.uc );
1847 :
1848 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"%s\",\"slot\":%lu},\"value\":%lu},\"id\":%s}\n", FD_RPC_AGAVE_API_VERSION, ctx->banks[ bank_idx ].slot, balance, id_cstr );
1849 0 : }
1850 :
1851 : static fd_http_server_response_t
1852 : getBlockHeight( fd_rpc_tile_t * ctx,
1853 : char const * id_cstr,
1854 0 : fd_rpc_params_t const * params ) {
1855 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_BLOCK_HEIGHT, 1UL );
1856 :
1857 0 : fd_http_server_response_t response;
1858 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
1859 :
1860 0 : ulong bank_idx = ULONG_MAX;
1861 0 : fd_rpc_val_t const * config = ¶ms->v[ 0 ];
1862 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
1863 0 : 1, /* has_commitment */
1864 0 : 0, /* has_encoding */
1865 0 : 0, /* has_data_slice */
1866 0 : 1, /* has_min_context_slot */
1867 0 : &bank_idx,
1868 0 : NULL,
1869 0 : NULL,
1870 0 : NULL,
1871 0 : NULL,
1872 0 : &response );
1873 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1874 :
1875 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", ctx->banks[ bank_idx ].block_height, id_cstr );
1876 0 : }
1877 :
1878 : UNIMPLEMENTED(getBlockProduction) // TODO: Used by solana-exporter
1879 : UNIMPLEMENTED(getBlocks)
1880 : UNIMPLEMENTED(getBlocksWithLimit)
1881 : UNIMPLEMENTED(getBlockTime)
1882 :
1883 : static fd_http_server_response_t
1884 : getClusterNodes( fd_rpc_tile_t * ctx,
1885 : char const * id_cstr,
1886 0 : fd_rpc_params_t const * params ) {
1887 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_CLUSTER_NODES, 1UL );
1888 :
1889 0 : fd_http_server_response_t response;
1890 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
1891 :
1892 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":[" );
1893 :
1894 0 : for( fd_rpc_cluster_node_dlist_iter_t iter = fd_rpc_cluster_node_dlist_iter_rev_init( ctx->cluster_nodes_dlist, ctx->cluster_nodes );
1895 0 : !fd_rpc_cluster_node_dlist_iter_done( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes );
1896 0 : iter = fd_rpc_cluster_node_dlist_iter_rev_next( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes ) ) {
1897 0 : fd_rpc_cluster_node_t * ele = fd_rpc_cluster_node_dlist_iter_ele( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes );
1898 0 : FD_BASE58_ENCODE_32_BYTES( ele->identity.uc, identity_cstr );
1899 0 : int is_last = fd_rpc_cluster_node_dlist_iter_done( fd_rpc_cluster_node_dlist_iter_rev_next( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes ), ctx->cluster_nodes_dlist, ctx->cluster_nodes );
1900 :
1901 0 : fd_http_server_printf( ctx->http, "{\"featureSet\":%u,", ele->ci->version.feature_set );
1902 :
1903 0 : for( ulong i=0UL; i<FD_GOSSIP_CONTACT_INFO_SOCKET_CNT; i++ ) {
1904 0 : char const * name;
1905 0 : switch( i ) {
1906 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP: name = "gossip"; break;
1907 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR_QUIC: name = NULL; break;
1908 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_RPC: name = "rpc"; break;
1909 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_RPC_PUBSUB: name = "pubsub"; break;
1910 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR: name = "serveRepair"; break;
1911 : /* Even though Agave does not support "tpu" and "tpuForwards"
1912 : (hardcoded null), Firedancer still supports
1913 : UDP-TPU. */
1914 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU: name = "tpu"; break;
1915 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_FORWARDS: name = "tpuForwards"; break;
1916 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_FORWARDS_QUIC: name = "tpuForwardsQuic"; break;
1917 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_QUIC: name = "tpuQuic"; break;
1918 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE: name = "tpuVote"; break;
1919 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TVU: name = "tvu"; break;
1920 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TVU_QUIC: name = NULL; break;
1921 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE_QUIC: name = NULL; break;
1922 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_ALPENGLOW: name = NULL; break;
1923 0 : default: FD_LOG_ERR(( "unreachable "));
1924 0 : }
1925 0 : if( FD_UNLIKELY( !name ) ) continue;
1926 :
1927 0 : uint ip4 = ele->ci->sockets[ i ].is_ipv6 ? 0U : ele->ci->sockets[ i ].ip4;
1928 0 : if( FD_LIKELY( !!ip4 || !!ele->ci->sockets[ i ].port ) ) fd_http_server_printf( ctx->http, "\"%s\":\"" FD_IP4_ADDR_FMT ":%hu\",", name, FD_IP4_ADDR_FMT_ARGS( ip4 ), fd_ushort_bswap( ele->ci->sockets[ i ].port ) );
1929 0 : else fd_http_server_printf( ctx->http, "\"%s\":null,", name );
1930 0 : }
1931 0 : fd_http_server_printf( ctx->http, "\"pubkey\":\"%s\",", identity_cstr );
1932 0 : fd_http_server_printf( ctx->http, "\"shredVersion\":%u,", ele->ci->shred_version );
1933 :
1934 0 : char version[ 64UL ];
1935 0 : FD_TEST( fd_gossip_version_cstr( ele->ci->version.major, ele->ci->version.minor, ele->ci->version.patch, version, sizeof( version ) ) );
1936 0 : fd_http_server_printf( ctx->http, "\"version\":\"%s\",", version );
1937 :
1938 0 : char const * client_id;
1939 0 : switch( ele->ci->version.client ) {
1940 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_SOLANA_LABS: client_id = "SolanaLabs"; break;
1941 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_JITO_LABS: client_id = "JitoLabs"; break;
1942 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_FRANKENDANCER: client_id = "Frankendancer"; break;
1943 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE: client_id = "Agave"; break;
1944 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE_PALADIN: client_id = "AgavePaladin"; break;
1945 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_FIREDANCER: client_id = "Firedancer"; break;
1946 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE_BAM: client_id = "AgaveBam"; break;
1947 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_SIG: client_id = "Sig"; break;
1948 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_RAKURAI: client_id = "Rakurai"; break;
1949 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_HARMONIC_FIREDANCER: client_id = "HarmonicFiredancer"; break;
1950 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_HARMONIC_AGAVE: client_id = "HarmonicAgave"; break;
1951 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_HARMONIC_FRANKENDANCER: client_id = "HarmonicFrankendancer"; break;
1952 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_FIRE_BAM: client_id = "FireBAM"; break;
1953 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_RAIKU: client_id = "Raiku"; break;
1954 0 : default: client_id = NULL; break;
1955 0 : }
1956 0 : if( FD_LIKELY( client_id ) ) fd_http_server_printf( ctx->http, "\"clientId\":\"%s\"", client_id );
1957 0 : else fd_http_server_printf( ctx->http, "\"clientId\":\"Unknown(%hu)\"", ele->ci->version.client );
1958 :
1959 0 : if( FD_UNLIKELY( is_last ) ) fd_http_server_printf( ctx->http, "}" );
1960 0 : else fd_http_server_printf( ctx->http, "}," );
1961 0 : }
1962 :
1963 0 : fd_http_server_printf( ctx->http, "],\"id\":%s}\n", id_cstr );
1964 0 : return STAGE_JSON( ctx );
1965 0 : }
1966 :
1967 : static fd_http_server_response_t
1968 : getEpochInfo( fd_rpc_tile_t * ctx,
1969 : char const * id_cstr,
1970 0 : fd_rpc_params_t const * params ) {
1971 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_EPOCH_INFO, 1UL );
1972 :
1973 0 : fd_http_server_response_t response;
1974 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
1975 :
1976 0 : ulong bank_idx = ULONG_MAX;
1977 0 : fd_rpc_val_t const * config = ¶ms->v[ 0 ];
1978 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
1979 0 : 1, /* has_commitment */
1980 0 : 0, /* has_encoding */
1981 0 : 0, /* has_data_slice */
1982 0 : 1, /* has_min_context_slot */
1983 0 : &bank_idx,
1984 0 : NULL,
1985 0 : NULL,
1986 0 : NULL,
1987 0 : NULL,
1988 0 : &response );
1989 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1990 :
1991 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"absoluteSlot\":%lu,\"blockHeight\":%lu,\"epoch\":%lu,\"slotIndex\":%lu,\"slotsInEpoch\":%lu,\"transactionCount\":%lu},\"id\":%s}\n", ctx->banks[ bank_idx ].slot, ctx->banks[ bank_idx ].block_height, ctx->banks[ bank_idx ].epoch, ctx->banks[ bank_idx ].slot_in_epoch, ctx->banks[ bank_idx ].slots_per_epoch, ctx->banks[ bank_idx ].transaction_count, id_cstr );
1992 0 : }
1993 :
1994 : UNIMPLEMENTED(getEpochSchedule)
1995 : UNIMPLEMENTED(getFeeForMessage)
1996 : UNIMPLEMENTED(getFirstAvailableBlock) // TODO: Used by solana-exporter
1997 :
1998 : /* Get the genesis hash of the cluster. Firedancer deviates slightly
1999 : from Agave here, as the genesis hash is not always known when RPC
2000 : is first booted, it may need to be determined asynchronously in the
2001 : background, fetched from a peer node. If the genesis hash is not yet
2002 : known, we return an error indicating no snapshot is available. */
2003 :
2004 : static fd_http_server_response_t
2005 : getGenesisHash( fd_rpc_tile_t * ctx,
2006 : char const * id_cstr,
2007 0 : fd_rpc_params_t const * params ) {
2008 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_GENESIS_HASH, 1UL );
2009 :
2010 0 : fd_http_server_response_t response;
2011 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
2012 :
2013 0 : if( FD_UNLIKELY( !ctx->has_genesis_hash ) ) {
2014 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Firedancer Error: No genesis hash\"},\"id\":%s}\n", FD_RPC_ERROR_NO_SNAPSHOT, id_cstr );
2015 0 : }
2016 :
2017 0 : FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, genesis_hash_b58 );
2018 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%s}\n", genesis_hash_b58, id_cstr );
2019 0 : }
2020 :
2021 : /* getAgGenesisCert returns the alpenglow genesis certificate, or null
2022 : if Tower consensus is active still.
2023 :
2024 : The account holds a wincode encoded WireBlockCertMessage,
2025 :
2026 : u64 block.slot
2027 : [u8;32] block.block_id
2028 : [u8;192] signature.signature (uncompressed BLS signature)
2029 : u64 signature.bitmap_len
2030 : [u8] signature.bitmap
2031 :
2032 : and the result is that struct as JSON, with the fixed size byte
2033 : arrays and the bitmap encoded as arrays of numbers, or null if the
2034 : account is empty or does not exist. */
2035 :
2036 0 : #define FD_RPC_AG_GENESIS_CERT_HDR_SZ (8UL+32UL+FD_BLS_SIG_SZ+8UL)
2037 :
2038 : static fd_http_server_response_t
2039 : getAgGenesisCert( fd_rpc_tile_t * ctx,
2040 : char const * id_cstr,
2041 0 : fd_rpc_params_t const * params ) {
2042 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_AG_GENESIS_CERT, 1UL );
2043 :
2044 0 : fd_http_server_response_t response;
2045 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
2046 :
2047 0 : if( FD_UNLIKELY( ctx->finalized_idx==ULONG_MAX ) ) {
2048 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
2049 0 : }
2050 :
2051 0 : bank_info_t * info = &ctx->banks[ ctx->finalized_idx ];
2052 0 : ulong acct_lamports;
2053 0 : int acct_executable;
2054 0 : uchar acct_owner[ 32UL ];
2055 0 : ulong acct_data_len;
2056 0 : fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, ctx->ag_genesis_cert_addr->uc,
2057 0 : &acct_lamports, &acct_executable, acct_owner,
2058 0 : ctx->scratch.accdb_data_buf, &acct_data_len );
2059 0 : if( FD_UNLIKELY( !acct_lamports || !acct_data_len ) ) {
2060 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":%s}\n", id_cstr );
2061 0 : }
2062 :
2063 0 : uchar const * data = ctx->scratch.accdb_data_buf;
2064 0 : ulong bitmap_sz = 0UL;
2065 0 : int malformed = acct_data_len<FD_RPC_AG_GENESIS_CERT_HDR_SZ;
2066 0 : if( FD_LIKELY( !malformed ) ) {
2067 0 : bitmap_sz = FD_LOAD( ulong, data+FD_RPC_AG_GENESIS_CERT_HDR_SZ-8UL );
2068 0 : malformed = bitmap_sz>acct_data_len-FD_RPC_AG_GENESIS_CERT_HDR_SZ;
2069 0 : }
2070 0 : if( FD_UNLIKELY( malformed ) ) {
2071 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32603,\"message\":\"Firedancer Error: malformed genesis certificate\"},\"id\":%s}\n", id_cstr );
2072 0 : }
2073 :
2074 0 : uchar const * block_id = data+8UL;
2075 0 : uchar const * signature = block_id+32UL;
2076 0 : uchar const * bitmap = data+FD_RPC_AG_GENESIS_CERT_HDR_SZ;
2077 :
2078 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":{\"block\":{\"slot\":%lu,\"blockId\":[", FD_LOAD( ulong, data ) );
2079 0 : for( ulong i=0UL; i<32UL; i++ ) fd_http_server_printf( ctx->http, i ? ",%u" : "%u", (uint)block_id [ i ] );
2080 0 : fd_http_server_printf( ctx->http, "]},\"signature\":{\"signature\":[" );
2081 0 : for( ulong i=0UL; i<FD_BLS_SIG_SZ; i++ ) fd_http_server_printf( ctx->http, i ? ",%u" : "%u", (uint)signature[ i ] );
2082 0 : fd_http_server_printf( ctx->http, "],\"bitmap\":[" );
2083 0 : for( ulong i=0UL; i<bitmap_sz; i++ ) fd_http_server_printf( ctx->http, i ? ",%u" : "%u", (uint)bitmap [ i ] );
2084 0 : fd_http_server_printf( ctx->http, "]}},\"id\":%s}\n", id_cstr );
2085 0 : return STAGE_JSON( ctx );
2086 0 : }
2087 :
2088 : /* Determines if the node is healthy. Agave defines this as follows,
2089 :
2090 : - On boot, nodes must go through the entire snapshot slot database
2091 : and hash everything, and once it's done verify the hash matches.
2092 : While this is ongoing, the node is unhealthy with a "slotsBehind"
2093 : value of null.
2094 :
2095 : - On boot, if the cluster is restarting and we are currently waiting
2096 : for a supermajority of stake to join gossip to proceed with
2097 : booting, the node is forcibly marked as healthy, to, per Agave,
2098 :
2099 : > prevent load balancers from removing the node from their list
2100 : > of candidates during a manual restart
2101 :
2102 : - In addition, once booted, there is a period where we do not yet
2103 : know the cluster confirmed slot, because we have not yet observed
2104 : any (or enough) votes arrive from peers in the cluster. During
2105 : this period the node is unhealthy with a "slotsBehind" value of
2106 : null.
2107 :
2108 : - Finally, once the cluster confirmed slot is known, which is the
2109 : highest optimistically confirmed slot observed from both gossip,
2110 : and votes processed in blocks, it is compared to our own
2111 : optimistically confirmed slot, which is just the highest slot down
2112 : the cluster confirmed fork that we have finished replaying
2113 : locally. The difference between these two slots is compared, and
2114 : if it is less than or equal to 128, the node is healthy, otherwise
2115 : it is unhealthy with a "slotsBehind" value equal to the
2116 : difference.
2117 :
2118 : Under alpenglow there are no optimistic confirmations. The cluster
2119 : slot is instead the highest slot for which votor has observed a
2120 : finalization certificate (slow or fast), and the node stays Unknown
2121 : until it has seen one.
2122 :
2123 : Firedancer currently only implements the final two checks, and does
2124 : not forcibly mark the node as healthy while waiting for a
2125 : supermajority, nor does it mark a node as unhealthy while hashing the
2126 : snapshot database on boot. Firedancer hashes snapshots so quickly
2127 : that the node will die on boot if the hash is not valid. */
2128 :
2129 : static inline int
2130 0 : _getHealth( fd_rpc_tile_t * ctx ) {
2131 : /* Requests are not served until RPC has initialized banks */
2132 0 : if( FD_UNLIKELY( ctx->confirmed_idx==ULONG_MAX ) ) return FD_RPC_HEALTH_STATUS_UNKNOWN;
2133 0 : if( FD_UNLIKELY( ctx->cluster_confirmed_slot==ULONG_MAX ) ) return FD_RPC_HEALTH_STATUS_UNKNOWN;
2134 :
2135 0 : ulong slots_behind = fd_ulong_sat_sub( ctx->cluster_confirmed_slot, ctx->banks[ ctx->confirmed_idx ].slot );
2136 0 : if( FD_LIKELY( slots_behind<=FD_RPC_HEALTH_CHECK_SLOT_DISTANCE ) ) return FD_RPC_HEALTH_STATUS_OK;
2137 0 : else return FD_RPC_HEALTH_STATUS_BEHIND;
2138 0 : }
2139 :
2140 : static fd_http_server_response_t
2141 : getHealth( fd_rpc_tile_t * ctx,
2142 : char const * id_cstr,
2143 0 : fd_rpc_params_t const * params ) {
2144 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_HEALTH, 1UL );
2145 :
2146 0 : fd_http_server_response_t response;
2147 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
2148 :
2149 : // TODO: We should probably implement the same waiting_for_supermajority
2150 : // logic to conform with Agave here.
2151 :
2152 0 : int health_status = _getHealth( ctx );
2153 :
2154 0 : switch( health_status ) {
2155 0 : case FD_RPC_HEALTH_STATUS_UNKNOWN: return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Node is unhealthy\",\"data\":{\"slotsBehind\":null}},\"id\":%s}\n", FD_RPC_ERROR_NODE_UNHEALTHY, id_cstr );
2156 0 : case FD_RPC_HEALTH_STATUS_BEHIND: return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Node is unhealthy\",\"data\":{\"slotsBehind\":%lu}},\"id\":%s}\n", FD_RPC_ERROR_NODE_UNHEALTHY, fd_ulong_sat_sub( ctx->cluster_confirmed_slot, ctx->banks[ ctx->confirmed_idx ].slot ), id_cstr );
2157 0 : case FD_RPC_HEALTH_STATUS_OK: return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"ok\",\"id\":%s}\n", id_cstr );
2158 0 : default: FD_LOG_ERR(( "unknown health status" ));
2159 0 : }
2160 0 : }
2161 :
2162 : UNIMPLEMENTED(getHighestSnapshotSlot)
2163 :
2164 : static fd_http_server_response_t
2165 : getIdentity( fd_rpc_tile_t * ctx,
2166 : char const * id_cstr,
2167 0 : fd_rpc_params_t const * params ) {
2168 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_IDENTITY, 1UL );
2169 :
2170 0 : fd_http_server_response_t response;
2171 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
2172 :
2173 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_pubkey, identity_pubkey_b58 );
2174 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"identity\":\"%s\"},\"id\":%s}\n", identity_pubkey_b58, id_cstr );
2175 0 : }
2176 :
2177 : static fd_http_server_response_t
2178 : getInflationGovernor( fd_rpc_tile_t * ctx,
2179 : char const * id_cstr,
2180 0 : fd_rpc_params_t const * params ) {
2181 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_INFLATION_GOVERNOR, 1UL );
2182 :
2183 0 : fd_http_server_response_t response;
2184 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
2185 :
2186 0 : ulong bank_idx = ULONG_MAX;
2187 0 : fd_rpc_val_t const * config = ¶ms->v[ 0 ];
2188 0 : if( FD_UNLIKELY( !fd_rpc_validate_commitment_strict( ctx, id_cstr, config, &response ) ) ) return response;
2189 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct CommitmentConfig",
2190 0 : 1, /* has_commitment */
2191 0 : 0, /* has_encoding */
2192 0 : 0, /* has_data_slice */
2193 0 : 0, /* has_min_context_slot */
2194 0 : &bank_idx,
2195 0 : NULL,
2196 0 : NULL,
2197 0 : NULL,
2198 0 : NULL,
2199 0 : &response );
2200 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
2201 :
2202 0 : bank_info_t const * bank = &ctx->banks[ bank_idx ];
2203 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"foundation\":%g%s,\"foundationTerm\":%g%s,\"initial\":%g%s,\"taper\":%g%s,\"terminal\":%g%s},\"id\":%s}\n",
2204 0 : bank->inflation.foundation, bank->inflation.foundation==0 ? ".0" : "",
2205 0 : bank->inflation.foundation_term, bank->inflation.foundation_term==0 ? ".0" : "",
2206 0 : bank->inflation.initial, bank->inflation.initial==0 ? ".0" : "",
2207 0 : bank->inflation.taper, bank->inflation.taper==0 ? ".0" : "",
2208 0 : bank->inflation.terminal, bank->inflation.terminal==0 ? ".0" : "",
2209 0 : id_cstr );
2210 0 : }
2211 :
2212 : UNIMPLEMENTED(getInflationRate)
2213 : UNIMPLEMENTED(getInflationReward) // TODO: Used by solana-exporter
2214 : UNIMPLEMENTED(getLargestAccounts)
2215 :
2216 : static fd_http_server_response_t
2217 : getLatestBlockhash( fd_rpc_tile_t * ctx,
2218 : char const * id_cstr,
2219 0 : fd_rpc_params_t const * params ) {
2220 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_LATEST_BLOCKHASH, 1UL );
2221 :
2222 0 : if( FD_UNLIKELY( ctx->processed_idx==ULONG_MAX ) ) {
2223 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
2224 0 : }
2225 :
2226 0 : fd_http_server_response_t response;
2227 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
2228 :
2229 0 : ulong bank_idx = ULONG_MAX;
2230 0 : fd_rpc_val_t const * config = ¶ms->v[ 0 ];
2231 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
2232 0 : 1, /* has_commitment */
2233 0 : 0, /* has_encoding */
2234 0 : 0, /* has_data_slice */
2235 0 : 1, /* has_min_context_slot */
2236 0 : &bank_idx,
2237 0 : NULL,
2238 0 : NULL,
2239 0 : NULL,
2240 0 : NULL,
2241 0 : &response );
2242 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
2243 :
2244 0 : bank_info_t * bank = &ctx->banks[ bank_idx ];
2245 0 : FD_BASE58_ENCODE_32_BYTES( bank->block_hash, block_hash_b58 );
2246 :
2247 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":%lu,\"apiVersion\":\"%s\"},\"value\":{\"blockhash\":\"%s\",\"lastValidBlockHeight\":%lu}},\"id\":%s}\n", bank->slot, FD_RPC_AGAVE_API_VERSION, block_hash_b58, bank->block_height + 150UL, id_cstr );
2248 0 : }
2249 :
2250 : /* Order getLeaderSchedule pairs by identity, breaking ties by rotation
2251 : so each identity's rotations end up ascending (matching Agave, which
2252 : folds the per-slot leaders into the result in slot order). */
2253 : #define SORT_NAME sort_gls_pairs_by_id
2254 0 : #define SORT_KEY_T fd_rpc_gls_pair_t
2255 0 : #define SORT_BEFORE(a,b) ( memcmp( (a).id_key.uc, (b).id_key.uc, sizeof(fd_pubkey_t) )<0 ? 1 : \
2256 0 : memcmp( (a).id_key.uc, (b).id_key.uc, sizeof(fd_pubkey_t) )>0 ? 0 : \
2257 0 : (a).rot<(b).rot )
2258 : #include "../../util/tmpl/fd_sort.c"
2259 :
2260 : static fd_http_server_response_t
2261 : getLeaderSchedule( fd_rpc_tile_t * ctx,
2262 : char const * id_cstr,
2263 0 : fd_rpc_params_t const * params ) {
2264 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_LEADER_SCHEDULE, 1UL );
2265 :
2266 0 : fd_http_server_response_t response;
2267 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 2, &response ) ) ) return response;
2268 :
2269 0 : fd_rpc_val_t const * p0 = ¶ms->v[ 0 ];
2270 0 : fd_rpc_val_t const * config = ¶ms->v[ 1 ];
2271 0 : ulong req_slot = 0UL;
2272 0 : int have_slot = 0;
2273 0 : int wrapper_config = 0;
2274 0 : if( p0->kind==FD_RPC_VAL_NONE || p0->kind==FD_RPC_VAL_NULL ) {
2275 : /* config stays the second param */
2276 0 : } else if( p0->kind==FD_RPC_VAL_INT && !p0->neg ) {
2277 0 : req_slot = p0->u;
2278 0 : have_slot = 1;
2279 0 : } else if( p0->kind==FD_RPC_VAL_MAP ) {
2280 0 : config = p0;
2281 0 : wrapper_config = 1;
2282 :
2283 : /* Clients can pass a second config object, which also needs
2284 : validation. */
2285 0 : fd_rpc_val_t const * fallback_config = ¶ms->v[ 1 ];
2286 0 : if( fallback_config->kind!=FD_RPC_VAL_NONE ) {
2287 0 : ulong fallback_bank_idx = ULONG_MAX;
2288 0 : fd_rpc_val_t fallback_identity[1];
2289 0 : int fallback_config_valid = fd_rpc_validate_config( ctx, id_cstr, fallback_config, "struct RpcLeaderScheduleConfig",
2290 0 : 1, /* has_commitment */
2291 0 : 0, /* has_encoding */
2292 0 : 0, /* has_data_slice */
2293 0 : 0, /* has_min_context_slot */
2294 0 : &fallback_bank_idx,
2295 0 : NULL,
2296 0 : NULL,
2297 0 : NULL,
2298 0 : fallback_identity,
2299 0 : &response );
2300 0 : if( FD_UNLIKELY( !fallback_config_valid ) ) return response;
2301 :
2302 0 : if( FD_UNLIKELY( fallback_identity->kind!=FD_RPC_VAL_NONE && fallback_identity->kind!=FD_RPC_VAL_NULL && fallback_identity->kind!=FD_RPC_VAL_STR ) ) {
2303 0 : return fd_rpc_err_invalid( ctx, id_cstr, "type", fallback_identity, "a string" );
2304 0 : }
2305 0 : }
2306 0 : } else {
2307 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: data did not match any variant of untagged enum RpcLeaderScheduleConfigWrapper.\"},\"id\":%s}\n", id_cstr );
2308 0 : }
2309 :
2310 0 : ulong bank_idx = ULONG_MAX;
2311 0 : fd_rpc_val_t identity[1];
2312 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcLeaderScheduleConfig",
2313 0 : 1, /* has_commitment */
2314 0 : 0, /* has_encoding */
2315 0 : 0, /* has_data_slice */
2316 0 : 0, /* has_min_context_slot */
2317 0 : &bank_idx,
2318 0 : NULL,
2319 0 : NULL,
2320 0 : NULL,
2321 0 : identity,
2322 0 : &response );
2323 0 : if( FD_UNLIKELY( !config_valid ) ) {
2324 0 : if( !wrapper_config ) return response;
2325 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: data did not match any variant of untagged enum RpcLeaderScheduleConfigWrapper.\"},\"id\":%s}\n", id_cstr );
2326 0 : }
2327 :
2328 : /* Optional identity filter: restrict the result to a single validator. */
2329 0 : int have_identity = 0;
2330 0 : fd_pubkey_t identity_filter[ 1 ];
2331 0 : if( identity->kind!=FD_RPC_VAL_NONE && identity->kind!=FD_RPC_VAL_NULL ) {
2332 0 : if( FD_UNLIKELY( wrapper_config && identity->kind!=FD_RPC_VAL_STR ) ) {
2333 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: data did not match any variant of untagged enum RpcLeaderScheduleConfigWrapper.\"},\"id\":%s}\n", id_cstr );
2334 0 : }
2335 0 : if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id_cstr, identity, identity_filter, &response ) ) ) return response;
2336 0 : have_identity = 1;
2337 0 : }
2338 :
2339 0 : ulong slot = have_slot ? req_slot : ctx->banks[ bank_idx ].slot;
2340 0 : fd_epoch_leaders_t const * lsched = fd_rpc_mleaders_get_lsched_for_slot( ctx->mleaders, slot );
2341 0 : if( FD_UNLIKELY( !lsched ) ) {
2342 : /* Unlike getSlotLeader(s), Agave returns a null result (not an
2343 : error) when the requested epoch's schedule is unavailable. */
2344 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":%s}\n", id_cstr );
2345 0 : }
2346 :
2347 0 : ulong pub_cnt = lsched->pub_cnt;
2348 0 : ulong sched_cnt = lsched->sched_cnt;
2349 0 : ulong slot_cnt = lsched->slot_cnt;
2350 0 : FD_TEST( pub_cnt<=MAX_STAKE_WEIGHTS );
2351 0 : FD_TEST( sched_cnt<=(sizeof(ctx->scratch.gls_pairs)/sizeof(ctx->scratch.gls_pairs[0])) );
2352 :
2353 0 : fd_rpc_gls_pair_t * pairs = ctx->scratch.gls_pairs;
2354 0 : ulong pair_cnt = 0UL;
2355 0 : for( ulong r=0UL; r<sched_cnt; r++ ) {
2356 0 : ulong idx = lsched->sched[ r ];
2357 0 : if( FD_UNLIKELY( idx>=pub_cnt ) ) continue; /* indeterminate leader; not reported */
2358 0 : if( have_identity && 0!=memcmp( lsched->pub[ idx ].uc, identity_filter->uc, sizeof(fd_pubkey_t) ) ) continue;
2359 0 : pairs[ pair_cnt ].id_key = lsched->pub[ idx ];
2360 0 : pairs[ pair_cnt ].rot = (uint)r;
2361 0 : pair_cnt++;
2362 0 : }
2363 0 : sort_gls_pairs_by_id_inplace( pairs, pair_cnt );
2364 :
2365 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":{" );
2366 0 : int first = 1;
2367 0 : for( ulong i=0UL; i<pair_cnt; ) {
2368 : /* [i,j) is the run of pairs sharing pairs[i].id_key (one identity). */
2369 0 : ulong j = i+1UL;
2370 0 : while( j<pair_cnt && 0==memcmp( pairs[ j ].id_key.uc, pairs[ i ].id_key.uc, sizeof(fd_pubkey_t) ) ) j++;
2371 :
2372 0 : FD_BASE58_ENCODE_32_BYTES( pairs[ i ].id_key.uc, pub_b58 );
2373 0 : fd_http_server_printf( ctx->http, "%s\"%s\":[", first ? "" : ",", pub_b58 );
2374 0 : first = 0;
2375 :
2376 0 : int first_slot = 1;
2377 0 : for( ulong b=i; b<j; b++ ) {
2378 0 : ulong r = pairs[ b ].rot;
2379 0 : for( ulong k=0UL; k<FD_EPOCH_SLOTS_PER_ROTATION; k++ ) {
2380 0 : ulong rel = r*FD_EPOCH_SLOTS_PER_ROTATION + k;
2381 0 : if( FD_UNLIKELY( rel>=slot_cnt ) ) break;
2382 0 : fd_http_server_printf( ctx->http, "%s%lu", first_slot ? "" : ",", rel );
2383 0 : first_slot = 0;
2384 0 : }
2385 0 : }
2386 0 : fd_http_server_printf( ctx->http, "]" );
2387 0 : i = j;
2388 0 : }
2389 0 : fd_http_server_printf( ctx->http, "},\"id\":%s}\n", id_cstr );
2390 0 : return STAGE_JSON( ctx );
2391 0 : }
2392 :
2393 : static fd_http_server_response_t
2394 : getMaxRetransmitSlot( fd_rpc_tile_t * ctx,
2395 : char const * id_cstr,
2396 0 : fd_rpc_params_t const * params ) {
2397 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_MAX_RETRANSMIT_SLOT, 1UL );
2398 :
2399 0 : fd_http_server_response_t response;
2400 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
2401 :
2402 0 : ulong max_retransmit_slot = 0UL;
2403 0 : for( ulong i=0UL; i<ctx->shred_rtx_cnt; i++ ) {
2404 0 : ulong slot = fd_fseq_query( ctx->shred_rtx[ i ] );
2405 0 : if( FD_LIKELY( slot!=ULONG_MAX ) ) max_retransmit_slot = fd_ulong_max( max_retransmit_slot, slot );
2406 0 : }
2407 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", max_retransmit_slot, id_cstr );
2408 0 : }
2409 :
2410 : UNIMPLEMENTED(getMaxShredInsertSlot)
2411 :
2412 : static fd_http_server_response_t
2413 : getMinimumBalanceForRentExemption( fd_rpc_tile_t * ctx,
2414 : char const * id_cstr,
2415 0 : fd_rpc_params_t const * params ) {
2416 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION, 1UL );
2417 :
2418 0 : fd_http_server_response_t response;
2419 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 2, &response ) ) ) return response;
2420 :
2421 0 : ulong bank_idx = ULONG_MAX;
2422 0 : fd_rpc_val_t const * config = ¶ms->v[ 1 ];
2423 0 : if( FD_UNLIKELY( !fd_rpc_validate_commitment_strict( ctx, id_cstr, config, &response ) ) ) return response;
2424 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct CommitmentConfig",
2425 0 : 1, /* has_commitment */
2426 0 : 0, /* has_encoding */
2427 0 : 0, /* has_data_slice */
2428 0 : 0, /* has_min_context_slot */
2429 0 : &bank_idx,
2430 0 : NULL,
2431 0 : NULL,
2432 0 : NULL,
2433 0 : NULL,
2434 0 : &response );
2435 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
2436 :
2437 0 : fd_rpc_val_t const * acct_sz = ¶ms->v[ 0 ];
2438 0 : ulong acct_sz_val;
2439 0 : if( FD_UNLIKELY( !fd_rpc_validate_uint_param( ctx, id_cstr, acct_sz, "usize", &acct_sz_val, &response ) ) ) return response;
2440 0 : if( FD_UNLIKELY( acct_sz_val>FD_RUNTIME_ACC_SZ_MAX ) ) {
2441 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2442 0 : }
2443 :
2444 0 : bank_info_t const * bank = &ctx->banks[ bank_idx ];
2445 :
2446 0 : fd_rent_t rent = {
2447 0 : .lamports_per_uint8_year = bank->rent.lamports_per_uint8_year,
2448 0 : .exemption_threshold = bank->rent.exemption_threshold,
2449 0 : .burn_percent = bank->rent.burn_percent,
2450 0 : };
2451 0 : ulong minimum = fd_rent_exempt_minimum_balance( &rent, acct_sz_val );
2452 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", minimum, id_cstr );
2453 0 : }
2454 :
2455 : static fd_http_server_response_t
2456 : getMultipleAccounts( fd_rpc_tile_t * ctx,
2457 : char const * id_cstr,
2458 0 : fd_rpc_params_t const * params ) {
2459 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_MULTIPLE_ACCOUNTS, 1UL );
2460 :
2461 0 : fd_http_server_response_t response;
2462 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 2, &response ) ) ) return response;
2463 :
2464 0 : fd_rpc_val_t const * keys_arr = ¶ms->v[ 0 ];
2465 0 : if( FD_UNLIKELY( keys_arr->kind!=FD_RPC_VAL_SEQ ) ) {
2466 0 : return fd_rpc_err_invalid( ctx, id_cstr, "type", keys_arr, "a sequence" );
2467 0 : }
2468 :
2469 0 : fd_rpc_val_t keys[ 100UL ];
2470 0 : ulong cnt = 0UL;
2471 0 : fd_jtok_t j[1];
2472 0 : fd_jtok_arr_enter( fd_jtok_init( j, keys_arr->raw, keys_arr->raw_sz ) );
2473 0 : while( fd_jtok_arr_next( j ) ) {
2474 0 : if( cnt<100UL ) fd_rpc_val_read( j, &keys[ cnt ] );
2475 0 : cnt++;
2476 0 : }
2477 0 : if( FD_UNLIKELY( cnt>100UL ) ) {
2478 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Too many inputs provided; max 100\"},\"id\":%s}\n", id_cstr );
2479 0 : }
2480 :
2481 0 : ulong bank_idx = ULONG_MAX;
2482 0 : char const * encoding_cstr = NULL;
2483 0 : ulong slice_length = ULONG_MAX;
2484 0 : ulong slice_offset = 0;
2485 0 : fd_rpc_val_t const * config = ¶ms->v[ 1 ];
2486 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcAccountInfoConfig",
2487 0 : 1, 1, 1, 1,
2488 0 : &bank_idx, &encoding_cstr,
2489 0 : &slice_length, &slice_offset,
2490 0 : NULL,
2491 0 : &response );
2492 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
2493 :
2494 : /* Agave defaults getMultipleAccounts to base64 (getAccountInfo to binary) */
2495 0 : fd_rpc_config_t cfg[1];
2496 0 : fd_rpc_config_read( config, cfg );
2497 0 : if( cfg->encoding.kind==FD_RPC_VAL_NONE || cfg->encoding.kind==FD_RPC_VAL_NULL ) encoding_cstr = "base64";
2498 :
2499 0 : bank_info_t * info = &ctx->banks[ bank_idx ];
2500 :
2501 0 : fd_pubkey_t addresses[ 100UL ];
2502 0 : for( ulong i=0UL; i<cnt; i++ ) {
2503 0 : if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id_cstr, &keys[ i ], &addresses[ i ], &response ) ) ) return response;
2504 0 : }
2505 :
2506 0 : fd_http_server_printf( ctx->http,
2507 0 : "{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":{\"context\":{\"apiVersion\":\"%s\",\"slot\":%lu},\"value\":[",
2508 0 : id_cstr, FD_RPC_AGAVE_API_VERSION, info->slot );
2509 :
2510 0 : for( ulong i=0UL; i<cnt; i++ ) {
2511 0 : if( i>0UL ) fd_http_server_printf( ctx->http, "," );
2512 :
2513 0 : ulong acct_lamports;
2514 0 : int acct_executable;
2515 0 : uchar acct_owner[ 32UL ];
2516 0 : ulong acct_data_len;
2517 0 : fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, addresses[i].uc,
2518 0 : &acct_lamports, &acct_executable, acct_owner,
2519 0 : ctx->scratch.accdb_data_buf, &acct_data_len );
2520 0 : if( FD_UNLIKELY( !acct_lamports ) ) {
2521 0 : fd_http_server_printf( ctx->http, "null" );
2522 0 : continue;
2523 0 : }
2524 :
2525 0 : fd_http_server_response_t err_response;
2526 0 : if( FD_UNLIKELY( !fd_rpc_encode_account_data( ctx, ctx->scratch.accdb_data_buf, acct_data_len, acct_owner, acct_lamports, acct_executable, encoding_cstr, slice_offset, slice_length, id_cstr, &err_response ) ) ) {
2527 0 : return err_response;
2528 0 : }
2529 0 : }
2530 :
2531 0 : fd_http_server_printf( ctx->http, "]}}\n" );
2532 0 : return STAGE_JSON( ctx );
2533 0 : }
2534 :
2535 : UNIMPLEMENTED(getProgramAccounts)
2536 : UNIMPLEMENTED(getRecentPerformanceSamples)
2537 : UNIMPLEMENTED(getRecentPrioritizationFees)
2538 : UNIMPLEMENTED(getSignaturesForAddress)
2539 : UNIMPLEMENTED(getSignatureStatuses)
2540 :
2541 : static fd_http_server_response_t
2542 : getSlot( fd_rpc_tile_t * ctx,
2543 : char const * id_cstr,
2544 0 : fd_rpc_params_t const * params ) {
2545 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_SLOT, 1UL );
2546 :
2547 0 : fd_http_server_response_t response;
2548 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
2549 :
2550 0 : ulong bank_idx = ULONG_MAX;
2551 0 : fd_rpc_val_t const * config = ¶ms->v[ 0 ];
2552 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
2553 0 : 1, /* has_commitment */
2554 0 : 0, /* has_encoding */
2555 0 : 0, /* has_data_slice */
2556 0 : 1, /* has_min_context_slot */
2557 0 : &bank_idx,
2558 0 : NULL,
2559 0 : NULL,
2560 0 : NULL,
2561 0 : NULL,
2562 0 : &response );
2563 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
2564 :
2565 0 : bank_info_t * bank = &ctx->banks[ bank_idx ];
2566 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", bank->slot, id_cstr );
2567 0 : }
2568 :
2569 : static fd_http_server_response_t
2570 : getSlotLeader( fd_rpc_tile_t * ctx,
2571 : char const * id_cstr,
2572 0 : fd_rpc_params_t const * params ) {
2573 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_SLOT_LEADER, 1UL );
2574 :
2575 0 : fd_http_server_response_t response;
2576 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
2577 :
2578 0 : ulong bank_idx = ULONG_MAX;
2579 0 : fd_rpc_val_t const * config = ¶ms->v[ 0 ];
2580 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
2581 0 : 1, /* has_commitment */
2582 0 : 0, /* has_encoding */
2583 0 : 0, /* has_data_slice */
2584 0 : 1, /* has_min_context_slot */
2585 0 : &bank_idx,
2586 0 : NULL,
2587 0 : NULL,
2588 0 : NULL,
2589 0 : NULL,
2590 0 : &response );
2591 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
2592 :
2593 0 : ulong slot = ctx->banks[ bank_idx ].slot;
2594 0 : fd_pubkey_t const * leader = fd_rpc_mleaders_get_leader_for_slot( ctx->mleaders, slot );
2595 0 : if( FD_UNLIKELY( !leader ) ) {
2596 0 : ulong epoch = ctx->has_epoch_schedule ? fd_slot_to_epoch( &ctx->epoch_schedule, slot, NULL ) : 0UL;
2597 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid slot range: leader schedule for epoch %lu is unavailable\"},\"id\":%s}\n", epoch, id_cstr );
2598 0 : }
2599 :
2600 0 : FD_BASE58_ENCODE_32_BYTES( leader->uc, leader_b58 );
2601 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%s}\n", leader_b58, id_cstr );
2602 0 : }
2603 :
2604 : static fd_http_server_response_t
2605 : getSlotLeaders( fd_rpc_tile_t * ctx,
2606 : char const * id_cstr,
2607 0 : fd_rpc_params_t const * params ) {
2608 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_SLOT_LEADERS, 1UL );
2609 :
2610 0 : fd_http_server_response_t response;
2611 0 : if( FD_UNLIKELY( params->kind==FD_RPC_VAL_NULL ) ) {
2612 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: null, expected a tuple of size 2.\"},\"id\":%s}\n", id_cstr );
2613 0 : }
2614 0 : if( FD_UNLIKELY( params->kind==FD_RPC_VAL_MAP ) ) {
2615 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: map, expected a tuple of size 2.\"},\"id\":%s}\n", id_cstr );
2616 0 : }
2617 0 : fd_rpc_val_t const * p0 = ¶ms->v[ 0 ];
2618 0 : fd_rpc_val_t const * p1 = ¶ms->v[ 1 ];
2619 0 : if( FD_UNLIKELY( params->kind==FD_RPC_VAL_SEQ && params->cnt!=2UL ) ) {
2620 0 : ulong param_cnt = params->cnt;
2621 0 : ulong unused;
2622 0 : if( FD_UNLIKELY( param_cnt>=1UL && !fd_rpc_validate_uint_param( ctx, id_cstr, p0, "u64", &unused, &response ) ) ) return response;
2623 0 : if( FD_UNLIKELY( param_cnt>=2UL && !fd_rpc_validate_uint_param( ctx, id_cstr, p1, "u64", &unused, &response ) ) ) return response;
2624 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid length %lu, expected a tuple of size 2.\"},\"id\":%s}\n", param_cnt, id_cstr );
2625 0 : }
2626 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 2, 2, &response ) ) ) return response;
2627 :
2628 0 : ulong start_slot;
2629 0 : ulong limit;
2630 0 : if( FD_UNLIKELY( !fd_rpc_validate_uint_param( ctx, id_cstr, p0, "u64", &start_slot, &response ) ) ) return response;
2631 0 : if( FD_UNLIKELY( !fd_rpc_validate_uint_param( ctx, id_cstr, p1, "u64", &limit, &response ) ) ) return response;
2632 :
2633 0 : if( FD_UNLIKELY( limit>5000UL ) ) {
2634 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid limit; max 5000\"},\"id\":%s}\n", id_cstr );
2635 0 : }
2636 :
2637 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":[" );
2638 0 : for( ulong i=0UL; i<limit; i++ ) {
2639 0 : ulong slot = start_slot + i;
2640 0 : fd_pubkey_t const * leader = FD_LIKELY( slot>=start_slot ) ? fd_rpc_mleaders_get_leader_for_slot( ctx->mleaders, slot ) : NULL;
2641 0 : if( FD_UNLIKELY( !leader ) ) {
2642 0 : fd_http_server_unstage( ctx->http );
2643 0 : ulong epoch = ctx->has_epoch_schedule ? fd_slot_to_epoch( &ctx->epoch_schedule, slot, NULL ) : 0UL;
2644 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid slot range: leader schedule for epoch %lu is unavailable\"},\"id\":%s}\n", epoch, id_cstr );
2645 0 : }
2646 0 : FD_BASE58_ENCODE_32_BYTES( leader->uc, leader_b58 );
2647 0 : fd_http_server_printf( ctx->http, "%s\"%s\"", i ? "," : "", leader_b58 );
2648 0 : }
2649 0 : fd_http_server_printf( ctx->http, "],\"id\":%s}\n", id_cstr );
2650 0 : return STAGE_JSON( ctx );
2651 0 : }
2652 : UNIMPLEMENTED(getStakeMinimumDelegation)
2653 : UNIMPLEMENTED(getSupply)
2654 : UNIMPLEMENTED(getTokenAccountBalance)
2655 : UNIMPLEMENTED(getTokenAccountsByDelegate)
2656 : UNIMPLEMENTED(getTokenAccountsByOwner)
2657 : UNIMPLEMENTED(getTokenLargestAccounts)
2658 : UNIMPLEMENTED(getTokenSupply)
2659 : UNIMPLEMENTED(getTransaction)
2660 :
2661 : static fd_http_server_response_t
2662 : getTransactionCount( fd_rpc_tile_t * ctx,
2663 : char const * id_cstr,
2664 0 : fd_rpc_params_t const * params ) {
2665 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_TRANSACTION_COUNT, 1UL );
2666 :
2667 0 : fd_http_server_response_t response;
2668 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
2669 :
2670 0 : ulong bank_idx = ULONG_MAX;
2671 0 : fd_rpc_val_t const * config = ¶ms->v[ 0 ];
2672 0 : int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
2673 0 : 1, /* has_commitment */
2674 0 : 0, /* has_encoding */
2675 0 : 0, /* has_data_slice */
2676 0 : 1, /* has_min_context_slot */
2677 0 : &bank_idx,
2678 0 : NULL,
2679 0 : NULL,
2680 0 : NULL,
2681 0 : NULL,
2682 0 : &response );
2683 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
2684 :
2685 0 : bank_info_t * bank = &ctx->banks[ bank_idx ];
2686 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", bank->transaction_count, id_cstr );
2687 0 : }
2688 :
2689 : static fd_http_server_response_t
2690 : getVersion( fd_rpc_tile_t * ctx,
2691 : char const * id_cstr,
2692 0 : fd_rpc_params_t const * params ) {
2693 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_VERSION, 1UL );
2694 :
2695 0 : fd_http_server_response_t response;
2696 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
2697 :
2698 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"solana-core\":\"%s\",\"feature-set\":%u},\"id\":%s}\n", fd_version_cstr, FD_FEATURE_SET_ID, id_cstr );
2699 0 : }
2700 :
2701 : static fd_http_server_response_t
2702 : voteSubscribe( fd_rpc_tile_t * ctx,
2703 : char const * id_cstr,
2704 : fd_rpc_params_t const * params,
2705 0 : ulong ws_conn_id ) {
2706 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_VOTE_SUBSCRIBE, 1UL );
2707 :
2708 0 : fd_http_server_response_t response;
2709 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
2710 :
2711 0 : if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
2712 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
2713 0 : }
2714 0 : FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
2715 :
2716 0 : fd_rpc_ws_subscriber_vote_add( ctx, ws_conn_id );
2717 :
2718 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":%s}\n", id_cstr );
2719 0 : }
2720 :
2721 : static fd_http_server_response_t
2722 : slotSubscribe( fd_rpc_tile_t * ctx,
2723 : char const * id_cstr,
2724 : fd_rpc_params_t const * params,
2725 0 : ulong ws_conn_id ) {
2726 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_SLOT_SUBSCRIBE, 1UL );
2727 :
2728 0 : fd_http_server_response_t response;
2729 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
2730 :
2731 0 : if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
2732 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
2733 0 : }
2734 0 : FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
2735 :
2736 0 : fd_rpc_ws_subscriber_slot_add( ctx, ws_conn_id );
2737 :
2738 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":%s}\n", id_cstr );
2739 0 : }
2740 :
2741 : static fd_http_server_response_t
2742 : voteUnsubscribe( fd_rpc_tile_t * ctx,
2743 : char const * id_cstr,
2744 : fd_rpc_params_t const * params,
2745 0 : ulong ws_conn_id ) {
2746 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_VOTE_UNSUBSCRIBE, 1UL );
2747 :
2748 0 : fd_http_server_response_t response;
2749 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 1, &response ) ) ) return response;
2750 :
2751 0 : if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
2752 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
2753 0 : }
2754 0 : FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
2755 :
2756 0 : fd_rpc_val_t const * subscription = ¶ms->v[ 0 ];
2757 0 : if( FD_UNLIKELY( subscription->kind!=FD_RPC_VAL_INT || subscription->neg ) ) {
2758 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_val_kind_cstr( subscription ), id_cstr );
2759 0 : }
2760 :
2761 0 : int unsubscribed = 0;
2762 0 : if( FD_LIKELY( subscription->u==0UL ) )
2763 0 : unsubscribed = fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
2764 :
2765 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%s,\"id\":%s}\n", unsubscribed ? "true" : "false", id_cstr );
2766 0 : }
2767 :
2768 : static fd_http_server_response_t
2769 : slotUnsubscribe( fd_rpc_tile_t * ctx,
2770 : char const * id_cstr,
2771 : fd_rpc_params_t const * params,
2772 0 : ulong ws_conn_id ) {
2773 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_SLOT_UNSUBSCRIBE, 1UL );
2774 :
2775 0 : fd_http_server_response_t response;
2776 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 1, &response ) ) ) return response;
2777 :
2778 0 : if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
2779 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
2780 0 : }
2781 0 : FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
2782 :
2783 0 : fd_rpc_val_t const * subscription = ¶ms->v[ 0 ];
2784 0 : if( FD_UNLIKELY( subscription->kind!=FD_RPC_VAL_INT || subscription->neg ) ) {
2785 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_val_kind_cstr( subscription ), id_cstr );
2786 0 : }
2787 :
2788 0 : int unsubscribed = 0;
2789 0 : if( FD_LIKELY( subscription->u==0UL ) )
2790 0 : unsubscribed = fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
2791 :
2792 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%s,\"id\":%s}\n", unsubscribed ? "true" : "false", id_cstr );
2793 0 : }
2794 :
2795 : UNIMPLEMENTED(getVoteAccounts) // TODO: Used by solana-exporter
2796 : UNIMPLEMENTED(isBlockhashValid)
2797 : UNIMPLEMENTED(minimumLedgerSlot) // TODO: Used by solana-exporter
2798 : UNIMPLEMENTED(requestAirdrop)
2799 : UNIMPLEMENTED(sendTransaction)
2800 : UNIMPLEMENTED(simulateTransaction)
2801 :
2802 : static fd_http_server_response_t
2803 : rpc_json_request( fd_rpc_tile_t * ctx,
2804 : uchar const * body,
2805 : ulong body_len,
2806 : ulong ws_conn_id );
2807 :
2808 : static fd_http_server_response_t
2809 : rpc_http_request1( fd_rpc_tile_t * ctx,
2810 0 : fd_http_server_request_t const * request ) {
2811 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET &&
2812 0 : request->headers.upgrade_websocket &&
2813 0 : (!strcmp( request->path, "/" ) || !strcmp( request->path, "/websocket" )) ) ) {
2814 0 : if( FD_UNLIKELY( !ctx->http->max_ws_conns ) ) return (fd_http_server_response_t){ .status = 404 };
2815 0 : return (fd_http_server_response_t){
2816 0 : .status = 200,
2817 0 : .upgrade_websocket = 1,
2818 0 : };
2819 0 : }
2820 :
2821 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET && !strcmp( request->path, "/health" ) ) ) {
2822 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_HEALTH, 1UL );
2823 0 : int health_status = _getHealth( ctx );
2824 :
2825 0 : switch( health_status ) {
2826 0 : case FD_RPC_HEALTH_STATUS_UNKNOWN: return PRINTF_JSON( ctx, "unknown" );
2827 0 : case FD_RPC_HEALTH_STATUS_BEHIND: return PRINTF_JSON( ctx, "behind" );
2828 0 : case FD_RPC_HEALTH_STATUS_OK: return PRINTF_JSON( ctx, "ok" );
2829 0 : default: FD_LOG_ERR(( "unknown health status" ));
2830 0 : }
2831 0 : }
2832 :
2833 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET && !strcmp( request->path, "/genesis.tar.bz2" ) ) ) {
2834 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_GENESIS, 1UL );
2835 0 : if( FD_UNLIKELY( ctx->genesis_tar_bz_sz==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 404 };
2836 :
2837 0 : return (fd_http_server_response_t) {
2838 0 : .status = 200,
2839 0 : .static_body = ctx->genesis_tar_bz,
2840 0 : .static_body_len = ctx->genesis_tar_bz_sz,
2841 0 : };
2842 0 : }
2843 :
2844 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET &&
2845 0 : ( !strncmp( request->path, "/snapshot", 9UL ) ||
2846 0 : !strncmp( request->path, "/incremental-snapshot", 21UL ) ) ) ) {
2847 0 : if( FD_UNLIKELY( !ctx->snapshot_server_enabled ) ) {
2848 0 : return (fd_http_server_response_t){ .status = 403 }; /* forbidden */
2849 0 : }
2850 0 : return (fd_http_server_response_t){
2851 0 : .status = 302,
2852 0 : .location = { ctx->snapshot_server_url, request->path_raw },
2853 0 : .location_len = { strlen( ctx->snapshot_server_url ), request->path_len },
2854 0 : };
2855 0 : }
2856 :
2857 :
2858 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET ) ) {
2859 0 : return (fd_http_server_response_t){ .status = 404 };
2860 0 : }
2861 :
2862 0 : if( FD_UNLIKELY( request->method!=FD_HTTP_SERVER_METHOD_POST ) ) {
2863 0 : return (fd_http_server_response_t){ .status = 405 };
2864 0 : }
2865 :
2866 0 : return rpc_json_request( ctx, request->post.body, request->post.body_len, ULONG_MAX );
2867 0 : }
2868 :
2869 : static fd_http_server_response_t
2870 : rpc_json_request( fd_rpc_tile_t * ctx,
2871 : uchar const * body,
2872 : ulong body_len,
2873 0 : ulong ws_conn_id ) { /* ULONG_MAX implies HTTP */
2874 0 : fd_jtok_t j[1];
2875 0 : fd_jtok_init( j, body, body_len );
2876 0 : int kind = fd_jtok_peek( j );
2877 :
2878 0 : if( FD_UNLIKELY( kind==FD_JTOK_ARR ) ) {
2879 : /* A bug in Agave ¯\_(ツ)_/¯ */
2880 0 : fd_jtok_arr_enter( j );
2881 0 : if( FD_LIKELY( !fd_jtok_arr_next( j ) && !fd_jtok_fini( j ) ) ) return (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
2882 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2883 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
2884 0 : }
2885 :
2886 0 : if( FD_UNLIKELY( kind!=FD_JTOK_OBJ ) ) {
2887 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2888 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
2889 0 : }
2890 :
2891 : /* For duplicate members the first occurrence wins, later ones are
2892 : skipped. */
2893 0 : fd_rpc_val_t id [1] = {{ .kind = FD_RPC_VAL_NONE }};
2894 0 : fd_rpc_val_t jsonrpc[1] = {{ .kind = FD_RPC_VAL_NONE }};
2895 0 : fd_rpc_val_t _method[1] = {{ .kind = FD_RPC_VAL_NONE }};
2896 0 : fd_rpc_val_t _params[1] = {{ .kind = FD_RPC_VAL_NONE }};
2897 0 : int unknown_member = 0;
2898 :
2899 0 : fd_jtok_str_t key;
2900 0 : fd_jtok_obj_enter( j );
2901 0 : while( fd_jtok_obj_next( j, &key ) ) {
2902 0 : fd_rpc_val_t * member;
2903 0 : if( fd_jtok_str_eq( &key, "jsonrpc" ) ) member = jsonrpc;
2904 0 : else if( fd_jtok_str_eq( &key, "id" ) ) member = id;
2905 0 : else if( fd_jtok_str_eq( &key, "method" ) ) member = _method;
2906 0 : else if( fd_jtok_str_eq( &key, "params" ) ) member = _params;
2907 0 : else { unknown_member = 1; continue; }
2908 0 : if( member->kind==FD_RPC_VAL_NONE ) fd_rpc_val_read( j, member );
2909 0 : }
2910 0 : if( FD_UNLIKELY( fd_jtok_fini( j ) ) ) {
2911 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2912 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
2913 0 : }
2914 :
2915 : /* Echo the id back verbatim. */
2916 0 : char id_buf[ FD_RPC_ID_MAX_SZ+1UL ];
2917 0 : char const * id_cstr = "null";
2918 0 : if( id->kind!=FD_RPC_VAL_NONE ) {
2919 0 : if( FD_UNLIKELY( id->raw_sz>FD_RPC_ID_MAX_SZ ) ) {
2920 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2921 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
2922 0 : }
2923 0 : fd_memcpy( id_buf, id->raw, id->raw_sz );
2924 0 : id_buf[ id->raw_sz ] = '\0';
2925 0 : id_cstr = id_buf;
2926 0 : }
2927 :
2928 0 : if( FD_UNLIKELY( unknown_member ) ) {
2929 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2930 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2931 0 : }
2932 :
2933 0 : if( FD_UNLIKELY( _method->kind!=FD_RPC_VAL_NONE && id->kind==FD_RPC_VAL_NONE ) ) {
2934 : /* A bug in Agave ¯\_(ツ)_/¯ */
2935 0 : return (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
2936 0 : }
2937 :
2938 0 : if( FD_UNLIKELY( jsonrpc->kind==FD_RPC_VAL_NONE && _method->kind!=FD_RPC_VAL_NONE ) ) {
2939 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2940 0 : return PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":%s}\n", id_cstr );
2941 0 : }
2942 :
2943 0 : if( FD_UNLIKELY( jsonrpc->kind==FD_RPC_VAL_NONE || _method->kind==FD_RPC_VAL_NONE ) ) {
2944 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2945 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2946 0 : }
2947 :
2948 0 : if( FD_UNLIKELY( !(id->kind==FD_RPC_VAL_INT && !id->neg) && id->kind!=FD_RPC_VAL_STR && id->kind!=FD_RPC_VAL_NULL ) ) {
2949 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2950 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
2951 0 : }
2952 :
2953 0 : if( FD_UNLIKELY( jsonrpc->kind==FD_RPC_VAL_NULL ) ) {
2954 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2955 0 : return PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":%s}\n", id_cstr );
2956 0 : }
2957 :
2958 0 : if( FD_UNLIKELY( jsonrpc->kind!=FD_RPC_VAL_STR || !fd_jtok_str_eq( &jsonrpc->str, "2.0" ) ) ) {
2959 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2960 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2961 0 : }
2962 :
2963 0 : fd_http_server_response_t response;
2964 :
2965 0 : if( FD_UNLIKELY( _method->kind!=FD_RPC_VAL_STR ) ) {
2966 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
2967 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2968 0 : }
2969 0 : char method[ 64 ];
2970 0 : fd_rpc_val_cstr( _method, method, sizeof(method) ); /* too long or undecodable is an unknown method */
2971 :
2972 0 : fd_rpc_params_t params[1];
2973 0 : fd_rpc_params_read( _params, params );
2974 :
2975 0 : if( FD_LIKELY( !strcmp( method, "getAccountInfo" ) ) ) response = getAccountInfo( ctx, id_cstr, params );
2976 0 : else if( FD_LIKELY( !strcmp( method, "getBalance" ) ) ) response = getBalance( ctx, id_cstr, params );
2977 0 : else if( FD_LIKELY( !strcmp( method, "getBlock" ) ) ) response = getBlock( ctx, id_cstr, params );
2978 0 : else if( FD_LIKELY( !strcmp( method, "getBlockCommitment" ) ) ) response = getBlockCommitment( ctx, id_cstr, params );
2979 0 : else if( FD_LIKELY( !strcmp( method, "getBlockHeight" ) ) ) response = getBlockHeight( ctx, id_cstr, params );
2980 0 : else if( FD_LIKELY( !strcmp( method, "getBlockProduction" ) ) ) response = getBlockProduction( ctx, id_cstr, params );
2981 0 : else if( FD_LIKELY( !strcmp( method, "getBlocks" ) ) ) response = getBlocks( ctx, id_cstr, params );
2982 0 : else if( FD_LIKELY( !strcmp( method, "getBlocksWithLimit" ) ) ) response = getBlocksWithLimit( ctx, id_cstr, params );
2983 0 : else if( FD_LIKELY( !strcmp( method, "getBlockTime" ) ) ) response = getBlockTime( ctx, id_cstr, params );
2984 0 : else if( FD_LIKELY( !strcmp( method, "getClusterNodes" ) ) ) response = getClusterNodes( ctx, id_cstr, params );
2985 0 : else if( FD_LIKELY( !strcmp( method, "getEpochInfo" ) ) ) response = getEpochInfo( ctx, id_cstr, params );
2986 0 : else if( FD_LIKELY( !strcmp( method, "getEpochSchedule" ) ) ) response = getEpochSchedule( ctx, id_cstr, params );
2987 0 : else if( FD_LIKELY( !strcmp( method, "getFeeForMessage" ) ) ) response = getFeeForMessage( ctx, id_cstr, params );
2988 0 : else if( FD_LIKELY( !strcmp( method, "getFirstAvailableBlock" ) ) ) response = getFirstAvailableBlock( ctx, id_cstr, params );
2989 0 : else if( FD_LIKELY( !strcmp( method, "getGenesisHash" ) ) ) response = getGenesisHash( ctx, id_cstr, params );
2990 0 : else if( FD_LIKELY( !strcmp( method, "getHealth" ) ) ) response = getHealth( ctx, id_cstr, params );
2991 0 : else if( FD_LIKELY( !strcmp( method, "getHighestSnapshotSlot" ) ) ) response = getHighestSnapshotSlot( ctx, id_cstr, params );
2992 0 : else if( FD_LIKELY( !strcmp( method, "getIdentity" ) ) ) response = getIdentity( ctx, id_cstr, params );
2993 0 : else if( FD_LIKELY( !strcmp( method, "getInflationGovernor" ) ) ) response = getInflationGovernor( ctx, id_cstr, params );
2994 0 : else if( FD_LIKELY( !strcmp( method, "getInflationRate" ) ) ) response = getInflationRate( ctx, id_cstr, params );
2995 0 : else if( FD_LIKELY( !strcmp( method, "getInflationReward" ) ) ) response = getInflationReward( ctx, id_cstr, params );
2996 0 : else if( FD_LIKELY( !strcmp( method, "getLargestAccounts" ) ) ) response = getLargestAccounts( ctx, id_cstr, params );
2997 0 : else if( FD_LIKELY( !strcmp( method, "getLatestBlockhash" ) ) ) response = getLatestBlockhash( ctx, id_cstr, params );
2998 0 : else if( FD_LIKELY( !strcmp( method, "getLeaderSchedule" ) ) ) response = getLeaderSchedule( ctx, id_cstr, params );
2999 0 : else if( FD_LIKELY( !strcmp( method, "getMaxRetransmitSlot" ) ) ) response = getMaxRetransmitSlot( ctx, id_cstr, params );
3000 0 : else if( FD_LIKELY( !strcmp( method, "getMaxShredInsertSlot" ) ) ) response = getMaxShredInsertSlot( ctx, id_cstr, params );
3001 0 : else if( FD_LIKELY( !strcmp( method, "getMinimumBalanceForRentExemption" ) ) ) response = getMinimumBalanceForRentExemption( ctx, id_cstr, params );
3002 0 : else if( FD_LIKELY( !strcmp( method, "getMultipleAccounts" ) ) ) response = getMultipleAccounts( ctx, id_cstr, params );
3003 0 : else if( FD_LIKELY( !strcmp( method, "getProgramAccounts" ) ) ) response = getProgramAccounts( ctx, id_cstr, params );
3004 0 : else if( FD_LIKELY( !strcmp( method, "getRecentPerformanceSamples" ) ) ) response = getRecentPerformanceSamples( ctx, id_cstr, params );
3005 0 : else if( FD_LIKELY( !strcmp( method, "getRecentPrioritizationFees" ) ) ) response = getRecentPrioritizationFees( ctx, id_cstr, params );
3006 0 : else if( FD_LIKELY( !strcmp( method, "getSignaturesForAddress" ) ) ) response = getSignaturesForAddress( ctx, id_cstr, params );
3007 0 : else if( FD_LIKELY( !strcmp( method, "getSignatureStatuses" ) ) ) response = getSignatureStatuses( ctx, id_cstr, params );
3008 0 : else if( FD_LIKELY( !strcmp( method, "getSlot" ) ) ) response = getSlot( ctx, id_cstr, params );
3009 0 : else if( FD_LIKELY( !strcmp( method, "getSlotLeader" ) ) ) response = getSlotLeader( ctx, id_cstr, params );
3010 0 : else if( FD_LIKELY( !strcmp( method, "getSlotLeaders" ) ) ) response = getSlotLeaders( ctx, id_cstr, params );
3011 0 : else if( FD_LIKELY( !strcmp( method, "getStakeMinimumDelegation" ) ) ) response = getStakeMinimumDelegation( ctx, id_cstr, params );
3012 0 : else if( FD_LIKELY( !strcmp( method, "getSupply" ) ) ) response = getSupply( ctx, id_cstr, params );
3013 0 : else if( FD_LIKELY( !strcmp( method, "getTokenAccountBalance" ) ) ) response = getTokenAccountBalance( ctx, id_cstr, params );
3014 0 : else if( FD_LIKELY( !strcmp( method, "getTokenAccountsByDelegate" ) ) ) response = getTokenAccountsByDelegate( ctx, id_cstr, params );
3015 0 : else if( FD_LIKELY( !strcmp( method, "getTokenAccountsByOwner" ) ) ) response = getTokenAccountsByOwner( ctx, id_cstr, params );
3016 0 : else if( FD_LIKELY( !strcmp( method, "getTokenLargestAccounts" ) ) ) response = getTokenLargestAccounts( ctx, id_cstr, params );
3017 0 : else if( FD_LIKELY( !strcmp( method, "getTokenSupply" ) ) ) response = getTokenSupply( ctx, id_cstr, params );
3018 0 : else if( FD_LIKELY( !strcmp( method, "getTransaction" ) ) ) response = getTransaction( ctx, id_cstr, params );
3019 0 : else if( FD_LIKELY( !strcmp( method, "getTransactionCount" ) ) ) response = getTransactionCount( ctx, id_cstr, params );
3020 0 : else if( FD_LIKELY( !strcmp( method, "getVersion" ) ) ) response = getVersion( ctx, id_cstr, params );
3021 0 : else if( FD_LIKELY( !strcmp( method, "getVoteAccounts" ) ) ) response = getVoteAccounts( ctx, id_cstr, params );
3022 0 : else if( FD_LIKELY( !strcmp( method, "slotSubscribe" ) ) ) response = slotSubscribe( ctx, id_cstr, params, ws_conn_id );
3023 0 : else if( FD_LIKELY( !strcmp( method, "slotUnsubscribe" ) ) ) response = slotUnsubscribe( ctx, id_cstr, params, ws_conn_id );
3024 0 : else if( FD_LIKELY( !strcmp( method, "voteSubscribe" ) ) ) response = voteSubscribe( ctx, id_cstr, params, ws_conn_id );
3025 0 : else if( FD_LIKELY( !strcmp( method, "voteUnsubscribe" ) ) ) response = voteUnsubscribe( ctx, id_cstr, params, ws_conn_id );
3026 0 : else if( FD_LIKELY( !strcmp( method, "isBlockhashValid" ) ) ) response = isBlockhashValid( ctx, id_cstr, params );
3027 0 : else if( FD_LIKELY( !strcmp( method, "minimumLedgerSlot" ) ) ) response = minimumLedgerSlot( ctx, id_cstr, params );
3028 0 : else if( FD_LIKELY( !strcmp( method, "requestAirdrop" ) ) ) response = requestAirdrop( ctx, id_cstr, params );
3029 0 : else if( FD_LIKELY( !strcmp( method, "sendTransaction" ) ) ) response = sendTransaction( ctx, id_cstr, params );
3030 0 : else if( FD_LIKELY( !strcmp( method, "simulateTransaction" ) ) ) response = simulateTransaction( ctx, id_cstr, params );
3031 0 : else if( FD_LIKELY( !strcmp( method, "getAgGenesisCert" ) ) ) response = getAgGenesisCert( ctx, id_cstr, params );
3032 0 : else {
3033 0 : FD_MCNT_INC( RPC, REQUEST_HANDLED_UNKNOWN, 1UL );
3034 0 : response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
3035 0 : }
3036 :
3037 : /* Agave pubsub answers non-subscription methods with -32601 and
3038 : keeps the socket; a bare 501 would close it and drop subscriptions */
3039 0 : if( FD_UNLIKELY( ws_conn_id!=ULONG_MAX && response.status==501UL ) ) {
3040 0 : response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
3041 0 : }
3042 :
3043 0 : return response;
3044 0 : }
3045 :
3046 : static fd_http_server_response_t
3047 0 : rpc_http_request( fd_http_server_request_t const * request ) {
3048 0 : fd_rpc_tile_t * ctx = request->ctx;
3049 0 : long dt = -fd_tickcount();
3050 0 : fd_http_server_response_t response = rpc_http_request1( ctx, request );
3051 0 : dt += fd_tickcount();
3052 0 : fd_histf_sample( ctx->request_duration, (ulong)dt );
3053 0 : return response;
3054 0 : }
3055 :
3056 : static void
3057 : rpc_ws_open( ulong ws_conn_id,
3058 0 : void * ctx ) {
3059 0 : (void)ws_conn_id; (void)ctx;
3060 0 : }
3061 :
3062 : static void
3063 : rpc_ws_close( ulong ws_conn_id,
3064 : int reason FD_PARAM_UNUSED,
3065 0 : void * _ctx ) {
3066 0 : fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)_ctx;
3067 0 : if( FD_UNLIKELY( ws_conn_id>=ctx->http->max_ws_conns ) ) return;
3068 0 : fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
3069 0 : fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
3070 0 : }
3071 :
3072 : static void
3073 : rpc_ws_message( ulong ws_conn_id,
3074 : uchar const * data,
3075 : ulong data_len,
3076 0 : void * _ctx ) {
3077 0 : fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)_ctx;
3078 :
3079 0 : fd_http_server_unstage( ctx->http );
3080 :
3081 0 : long dt = -fd_tickcount();
3082 0 : fd_http_server_response_t response = rpc_json_request( ctx, data, data_len, ws_conn_id );
3083 0 : dt += fd_tickcount();
3084 0 : fd_histf_sample( ctx->request_duration, (ulong)dt );
3085 :
3086 : /* fd_http_server_reserve may evict this connection while staging the response. */
3087 0 : if( FD_UNLIKELY( ctx->http->pollfds[ ctx->http->max_conns+ws_conn_id ].fd==-1 ) ) return;
3088 :
3089 0 : if( FD_UNLIKELY( response.status!=200UL ) ) {
3090 0 : fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
3091 0 : return;
3092 0 : }
3093 :
3094 0 : if( FD_LIKELY( response._body_len ) ) {
3095 0 : ulong response_body_end = response._body_off + response._body_len;
3096 :
3097 0 : ctx->http->stage_off = response._body_off;
3098 0 : ctx->http->stage_len = response._body_len;
3099 0 : ctx->http->stage_comp_len = 0UL;
3100 :
3101 0 : int err = fd_http_server_ws_send( ctx->http, ws_conn_id );
3102 0 : if( FD_UNLIKELY( ctx->http->stage_off<response_body_end ) ) ctx->http->stage_off = response_body_end;
3103 0 : if( FD_UNLIKELY( err ) )
3104 0 : fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
3105 0 : }
3106 0 : }
3107 :
3108 : static void
3109 : privileged_init( fd_topo_t const * topo,
3110 0 : fd_topo_tile_t const * tile ) {
3111 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
3112 :
3113 0 : fd_http_server_params_t http_params = derive_http_params( tile );
3114 :
3115 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
3116 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
3117 0 : fd_http_server_t * _http = FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(), fd_http_server_footprint( http_params ) );
3118 :
3119 0 : fd_memset( ctx, 0, sizeof(fd_rpc_tile_t) );
3120 :
3121 0 : if( FD_UNLIKELY( !strcmp( tile->rpc.identity_key_path, "" ) ) )
3122 0 : FD_LOG_ERR(( "identity_key_path not set" ));
3123 :
3124 0 : const uchar * identity_key = fd_keyload_load( tile->rpc.identity_key_path, /* pubkey only: */ 1 );
3125 0 : fd_memcpy( ctx->identity_pubkey, identity_key, 32UL );
3126 :
3127 0 : fd_http_server_callbacks_t callbacks = {
3128 0 : .request = rpc_http_request,
3129 0 : .ws_open = rpc_ws_open,
3130 0 : .ws_close = rpc_ws_close,
3131 0 : .ws_message = rpc_ws_message,
3132 0 : };
3133 0 : ctx->http = fd_http_server_join( fd_http_server_new( _http, http_params, callbacks, ctx ) );
3134 0 : ctx->snapshot_server_enabled = tile->rpc.snapshot_server_enabled;
3135 0 : if( FD_LIKELY( ctx->snapshot_server_enabled ) ) {
3136 0 : FD_TEST( fd_cstr_printf_check(
3137 0 : ctx->snapshot_server_url, sizeof(ctx->snapshot_server_url), NULL,
3138 0 : "http://%s:%u", tile->rpc.snapshot_server_host,
3139 0 : tile->rpc.snapshot_server_port ) );
3140 0 : }
3141 0 : ctx->waker_client_idx = tile->waker_client_idx;
3142 0 : FD_TEST( ctx->waker_client_idx!=ULONG_MAX );
3143 0 : fd_http_server_listen6( ctx->http, FD_WAKER_INNER_FD( ctx->waker_client_idx ), &tile->rpc.listen_addr, tile->rpc.listen_port );
3144 0 : char listen_addr_cstr[ FD_IP6_ADDR_CSTR_MAX ]; fd_ip6_addr_cstr( listen_addr_cstr, &tile->rpc.listen_addr );
3145 0 : FD_LOG_NOTICE(( "rpc server listening at %shttp://%s:%u%s", fd_log_style_bold(), listen_addr_cstr, tile->rpc.listen_port, fd_log_style_normal() ));
3146 0 : }
3147 :
3148 : static inline fd_rpc_out_t
3149 : out1( fd_topo_t const * topo,
3150 : fd_topo_tile_t const * tile,
3151 0 : char const * name ) {
3152 0 : ulong idx = ULONG_MAX;
3153 :
3154 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
3155 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
3156 0 : if( !strcmp( link->name, name ) ) {
3157 0 : if( FD_UNLIKELY( idx!=ULONG_MAX ) ) FD_LOG_ERR(( "tile %s:%lu had multiple output links named %s but expected one", tile->name, tile->kind_id, name ));
3158 0 : idx = i;
3159 0 : }
3160 0 : }
3161 :
3162 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_rpc_out_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
3163 :
3164 :
3165 0 : ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
3166 0 : if( FD_UNLIKELY( mtu==0UL ) ) return (fd_rpc_out_t){ .idx = idx, .mem = NULL, .chunk0 = ULONG_MAX, .wmark = ULONG_MAX, .chunk = ULONG_MAX };
3167 :
3168 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
3169 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
3170 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
3171 :
3172 0 : return (fd_rpc_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
3173 0 : }
3174 :
3175 : static void
3176 : unprivileged_init( fd_topo_t const * topo,
3177 0 : fd_topo_tile_t const * tile ) {
3178 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
3179 :
3180 0 : fd_http_server_params_t http_params = derive_http_params( tile );
3181 :
3182 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
3183 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rpc_tile_t), sizeof(fd_rpc_tile_t) );
3184 0 : FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(), fd_http_server_footprint( http_params ) );
3185 0 : void * _bz2_alloc = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
3186 0 : void * _banks = FD_SCRATCH_ALLOC_APPEND( l, alignof(bank_info_t), tile->rpc.max_live_slots*sizeof(bank_info_t) );
3187 0 : void * _nodes_dlist = FD_SCRATCH_ALLOC_APPEND( l, fd_rpc_cluster_node_dlist_align(), fd_rpc_cluster_node_dlist_footprint() );
3188 0 : void * _accdb_join = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->rpc.max_live_slots, 0 ) );
3189 0 : void * _ws_sub_vote = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), http_params.max_ws_connection_cnt*sizeof(ulong) );
3190 0 : void * _ws_sub_slot = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), http_params.max_ws_connection_cnt*sizeof(ulong) );
3191 0 : void * _genesis_tar = FD_SCRATCH_ALLOC_APPEND( l, alignof(uchar), fd_rpc_genesis_tar_max_sz( tile->rpc.genesis_max_message_size ) );
3192 0 : void * _genesis_tar_bz = FD_SCRATCH_ALLOC_APPEND( l, alignof(uchar), fd_rpc_genesis_tar_bz_max_sz( tile->rpc.genesis_max_message_size ) );
3193 0 : ulong zstd_wksp_sz = ZSTD_estimateCCtxSize( FD_RPC_ZSTD_LEVEL );
3194 0 : void * _zstd_wksp = FD_SCRATCH_ALLOC_APPEND( l, 16UL, zstd_wksp_sz );
3195 :
3196 0 : ctx->delay_startup = tile->rpc.delay_startup;
3197 0 : ctx->alpenglow = tile->rpc.alpenglow;
3198 0 : fd_alpenglow_pda( "carlgration", ctx->ag_genesis_cert_addr );
3199 0 : ctx->waker_rearm_pending = 0;
3200 0 : ctx->ws_subscribers_vote = _ws_sub_vote;
3201 0 : ctx->ws_subscribers_vote_cnt = 0UL;
3202 0 : ctx->ws_subscribers_slot = _ws_sub_slot;
3203 0 : ctx->ws_subscribers_slot_cnt = 0UL;
3204 :
3205 0 : ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
3206 0 : FD_TEST( ctx->keyswitch );
3207 :
3208 0 : ctx->bz2_alloc = fd_alloc_join( fd_alloc_new( _bz2_alloc, 1UL ), 1UL );
3209 0 : FD_TEST( ctx->bz2_alloc );
3210 :
3211 0 : ctx->zstd_cctx = ZSTD_initStaticCCtx( _zstd_wksp, zstd_wksp_sz );
3212 0 : FD_CHECK_ERR( ctx->zstd_cctx, "ZSTD_initStaticCCtx failed" );
3213 :
3214 0 : fd_clock_tile_init( ctx->clock );
3215 0 : ctx->in_cnt = tile->in_cnt;
3216 0 : ctx->idle_cnt = 0UL;
3217 :
3218 0 : FD_TEST( ctx->waker_client_idx!=ULONG_MAX );
3219 0 : ctx->waker_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->waker_fseq_obj_id ) );
3220 0 : FD_TEST( ctx->waker_fseq );
3221 :
3222 0 : ctx->cluster_confirmed_slot = ULONG_MAX;
3223 0 : ctx->genesis_max_message_size = tile->rpc.genesis_max_message_size;
3224 0 : ctx->genesis_tar_max_sz = fd_rpc_genesis_tar_max_sz( tile->rpc.genesis_max_message_size );
3225 0 : ctx->genesis_tar_bz_max_sz = fd_rpc_genesis_tar_bz_max_sz( tile->rpc.genesis_max_message_size );
3226 0 : ctx->genesis_tar = _genesis_tar;
3227 0 : ctx->genesis_tar_bz = _genesis_tar_bz;
3228 0 : ctx->genesis_tar_bz_sz = ULONG_MAX;
3229 :
3230 0 : ctx->processed_idx = ULONG_MAX;
3231 0 : ctx->confirmed_idx = ULONG_MAX;
3232 0 : ctx->finalized_idx = ULONG_MAX;
3233 :
3234 0 : ctx->cluster_nodes_dlist = fd_rpc_cluster_node_dlist_join( fd_rpc_cluster_node_dlist_new( _nodes_dlist ) );
3235 0 : ctx->banks = _banks;
3236 0 : ctx->max_live_slots = tile->rpc.max_live_slots;
3237 0 : for( ulong i=0UL; i<ctx->max_live_slots; i++ ) ctx->banks[ i ].slot = ULONG_MAX;
3238 :
3239 0 : fd_rpc_mleaders_init( ctx->mleaders );
3240 0 : ctx->has_epoch_schedule = 0;
3241 :
3242 0 : FD_TEST( tile->in_cnt<=sizeof( ctx->in )/sizeof( ctx->in[ 0 ] ) );
3243 0 : for( ulong i=0; i<tile->in_cnt; i++ ) {
3244 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
3245 0 : fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
3246 :
3247 0 : ctx->in[ i ].mem = link_wksp->wksp;
3248 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
3249 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
3250 0 : ctx->in[ i ].mtu = link->mtu;
3251 :
3252 0 : if ( FD_LIKELY( !strcmp( link->name, "replay_slot" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
3253 0 : else if( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESI;
3254 0 : else if( FD_LIKELY( !strcmp( link->name, "gossip_ciaddr" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
3255 0 : else if( FD_LIKELY( !strcmp( link->name, "gossip_vote" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
3256 0 : else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH;
3257 0 : else if( FD_UNLIKELY( !strcmp( link->name, "votor_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_VOTOR;
3258 0 : else FD_LOG_ERR(( "unexpected link name %s", link->name ));
3259 0 : }
3260 :
3261 0 : for( ctx->shred_rtx_cnt=0UL; ctx->shred_rtx_cnt<FD_TOPO_MAX_TILES; ctx->shred_rtx_cnt++ ) {
3262 0 : ulong rtx_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "shred_rtx.%lu", ctx->shred_rtx_cnt );
3263 0 : if( FD_UNLIKELY( rtx_obj_id==ULONG_MAX ) ) break;
3264 0 : ctx->shred_rtx[ ctx->shred_rtx_cnt ] = fd_fseq_join( fd_topo_obj_laddr( topo, rtx_obj_id ) );
3265 0 : FD_TEST( ctx->shred_rtx[ ctx->shred_rtx_cnt ] );
3266 0 : }
3267 :
3268 0 : *ctx->replay_out = out1( topo, tile, "rpc_replay" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
3269 :
3270 : /* Read-only join to accdb. The accdb workspace is mapped
3271 : PROT_READ in this tile (see topology); the only writable
3272 : external mapping is our private epoch fseq. fd FD_ACCDB_FD_RO is
3273 : the O_RDONLY dup of the accdb data file. */
3274 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->rpc.accdb_obj_id );
3275 0 : fd_accdb_shmem_t * accdb_shmem_ro = fd_accdb_shmem_join( _accdb_shmem );
3276 0 : FD_TEST( accdb_shmem_ro );
3277 0 : ulong * epoch_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->rpc.accdb_epoch_fseq_obj_id ) );
3278 0 : FD_TEST( epoch_fseq );
3279 0 : ctx->accdb = fd_accdb_join_readonly( _accdb_join, accdb_shmem_ro, epoch_fseq, FD_ACCDB_FD_RO );
3280 0 : FD_TEST( ctx->accdb );
3281 :
3282 0 : fd_histf_join( fd_metrics_histf_new_ns( ctx->request_duration, FD_MHIST_EDGES_NS( RPC, REQUEST_DURATION_NANOS ) ) );
3283 :
3284 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
3285 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
3286 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
3287 0 : }
3288 :
3289 : static ulong
3290 : populate_allowed_seccomp( fd_topo_t const * topo,
3291 : fd_topo_tile_t const * tile,
3292 : ulong out_cnt,
3293 0 : struct sock_filter * out ) {
3294 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
3295 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
3296 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
3297 :
3298 0 : uint epoll_inner_fd = (uint)FD_WAKER_INNER_FD( tile->waker_client_idx );
3299 0 : uint epoll_outer_fd = (uint)FD_WAKER_OUTER_FD;
3300 :
3301 0 : populate_sock_filter_policy_fd_rpc_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)fd_http_server_fd( ctx->http ), (uint)FD_ACCDB_FD_RO, epoll_inner_fd, epoll_outer_fd );
3302 0 : return sock_filter_policy_fd_rpc_tile_instr_cnt;
3303 0 : }
3304 :
3305 : static ulong
3306 : populate_allowed_fds( fd_topo_t const * topo,
3307 : fd_topo_tile_t const * tile,
3308 : ulong out_fds_cnt,
3309 0 : int * out_fds ) {
3310 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
3311 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
3312 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
3313 :
3314 0 : if( FD_UNLIKELY( out_fds_cnt<6UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
3315 :
3316 0 : ulong out_cnt = 0UL;
3317 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
3318 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
3319 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
3320 0 : out_fds[ out_cnt++ ] = fd_http_server_fd( ctx->http ); /* rpc listen socket */
3321 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RO; /* accounts db readonly fd */
3322 0 : out_fds[ out_cnt++ ] = FD_WAKER_OUTER_FD;
3323 0 : out_fds[ out_cnt++ ] = FD_WAKER_INNER_FD( tile->waker_client_idx );
3324 :
3325 0 : return out_cnt;
3326 0 : }
3327 :
3328 : static ulong
3329 : rlimit_file_cnt( fd_topo_t const * topo FD_PARAM_UNUSED,
3330 0 : fd_topo_tile_t const * tile ) {
3331 : /* pipefd, socket, stderr, logfile, and one spare for new accept() connections */
3332 0 : ulong base = 5UL;
3333 0 : return base + tile->rpc.max_http_connections + tile->rpc.max_websocket_connections;
3334 0 : }
3335 :
3336 0 : #define STEM_BURST (3UL)
3337 :
3338 : /* The default STEM_LAZY is based on cr_max, which is the minimum depth
3339 : across all output links that have at least one reliable consumer.
3340 : RPC has one tiny output link used to release banks, with a
3341 : significantly slower line rate than assumed in the formula for the
3342 : default STEM_LAZY value.
3343 :
3344 : Instead, lazy just needs to be frequent enough to relinquish credits
3345 : to upstream producers faster than they are exhausted. 384us is a
3346 : reasonable default used in many other non-critical tiles. */
3347 0 : #define STEM_LAZY (128L*3000L)
3348 :
3349 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_rpc_tile_t
3350 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_rpc_tile_t)
3351 :
3352 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
3353 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
3354 0 : #define STEM_CALLBACK_BEFORE_CREDIT before_credit
3355 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
3356 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
3357 :
3358 : #include "../../disco/stem/fd_stem.c"
3359 :
3360 : #ifndef FD_TILE_TEST
3361 : fd_topo_run_tile_t fd_tile_rpc = {
3362 : .name = "rpc",
3363 : .rlimit_file_cnt_fn = rlimit_file_cnt,
3364 : .populate_allowed_seccomp = populate_allowed_seccomp,
3365 : .populate_allowed_fds = populate_allowed_fds,
3366 : .scratch_align = scratch_align,
3367 : .scratch_footprint = scratch_footprint,
3368 : .loose_footprint = loose_footprint,
3369 : .privileged_init = privileged_init,
3370 : .unprivileged_init = unprivileged_init,
3371 : .run = stem_run,
3372 : };
3373 : #endif
|