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