Line data Source code
1 : #include "../replay/fd_replay_tile.h"
2 : #include "../genesis/fd_genesi_tile.h"
3 :
4 : #include "../../ballet/json/cJSON_alloc.h"
5 : #include "../../ballet/base64/fd_base64.h"
6 : #include "../../ballet/json/cJSON.h"
7 : #include "../../disco/topo/fd_topo.h"
8 : #include "../../disco/metrics/fd_metrics.h"
9 : #include "../../disco/keyguard/fd_keyload.h"
10 : #include "../../disco/keyguard/fd_keyswitch.h"
11 : #include "../../disco/metrics/fd_metrics.h"
12 : #include "../../flamenco/features/fd_features.h"
13 : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
14 : #include "../../flamenco/runtime/fd_runtime_const.h"
15 : #include "../../flamenco/accdb/fd_accdb.h"
16 : #include "../../flamenco/accdb/fd_accdb_shmem.h"
17 : #include "../../tango/fseq/fd_fseq.h"
18 : #include "../../flamenco/gossip/fd_gossip_message.h"
19 : #include "../../flamenco/genesis/fd_genesis_parse.h"
20 : #include "../../flamenco/runtime/program/vote/fd_vote_codec.h"
21 : #include "../../util/net/fd_ip4.h"
22 : #include "../../waltz/http/fd_http_server.h"
23 : #include "../../waltz/http/fd_http_server_private.h"
24 :
25 : #include <stddef.h>
26 : #include <sys/socket.h>
27 : #include <math.h> /* floor, isfinite */
28 : #include <string.h>
29 :
30 : #if FD_HAS_ZSTD
31 : #include <zstd.h>
32 : #endif
33 :
34 : #include "../../util/archive/fd_tar.h"
35 : #include "../../ballet/bzip2/bzlib.h"
36 :
37 : #include "generated/fd_rpc_tile_seccomp.h"
38 :
39 0 : #define FD_RPC_AGAVE_API_VERSION "4.0.0-beta.6"
40 :
41 0 : #define FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN 8192UL
42 0 : #define FD_HTTP_SERVER_RPC_MAX_WS_SEND_FRAME_CNT 128UL
43 :
44 0 : #define IN_KIND_REPLAY (0)
45 0 : #define IN_KIND_GENESI (1)
46 0 : #define IN_KIND_GOSSIP_OUT (2)
47 :
48 : /* From bzip2 docs:
49 : To guarantee that the compressed data will fit in its buffer,
50 : allocate an output buffer of size 1% larger than the uncompressed
51 : data, plus six hundred extra bytes.
52 : */
53 : #define FD_RPC_TAR_SZ (FD_GENESIS_MAX_MESSAGE_SIZE + 4UL*512UL)
54 : #define FD_RPC_TAR_BZ_SZ (FD_RPC_TAR_SZ + ((FD_RPC_TAR_SZ + 100UL - 1UL) / 100UL) + 600UL)
55 :
56 0 : #define FD_RPC_BASE58_ENCODED_128_LEN (175UL) /* ceil(128*log58(256)) */
57 :
58 : #define FD_RPC_COMMITMENT_PROCESSED (0)
59 : #define FD_RPC_COMMITMENT_CONFIRMED (1)
60 : #define FD_RPC_COMMITMENT_FINALIZED (2)
61 :
62 : #define FD_RPC_ENCODING_BASE58 (0)
63 : #define FD_RPC_ENCODING_BASE64 (1)
64 : #define FD_RPC_ENCODING_BASE64_ZSTD (2)
65 : #define FD_RPC_ENCODING_BINARY (3)
66 : #define FD_RPC_ENCODING_JSON_PARSED (4)
67 :
68 0 : #define FD_RPC_HEALTH_STATUS_OK (0)
69 0 : #define FD_RPC_HEALTH_STATUS_BEHIND (1)
70 0 : #define FD_RPC_HEALTH_STATUS_UNKNOWN (2)
71 :
72 : #define FD_RPC_METHOD_GET_ACCOUNT_INFO ( 0)
73 : #define FD_RPC_METHOD_GET_BALANCE ( 1)
74 : #define FD_RPC_METHOD_GET_BLOCK ( 2)
75 : #define FD_RPC_METHOD_GET_BLOCK_COMMITMENT ( 3)
76 : #define FD_RPC_METHOD_GET_BLOCK_HEIGHT ( 4)
77 : #define FD_RPC_METHOD_GET_BLOCK_PRODUCTION ( 5)
78 : #define FD_RPC_METHOD_GET_BLOCKS ( 6)
79 : #define FD_RPC_METHOD_GET_BLOCKS_WITH_LIMIT ( 7)
80 : #define FD_RPC_METHOD_GET_BLOCK_TIME ( 8)
81 : #define FD_RPC_METHOD_GET_CLUSTER_NODES ( 9)
82 : #define FD_RPC_METHOD_GET_EPOCH_INFO (10)
83 : #define FD_RPC_METHOD_GET_EPOCH_SCHEDULE (11)
84 : #define FD_RPC_METHOD_GET_FEE_FOR_MESSAGE (12)
85 : #define FD_RPC_METHOD_GET_FIRST_AVAILABLE_BLOCK (13)
86 : #define FD_RPC_METHOD_GET_GENESIS_HASH (14)
87 : #define FD_RPC_METHOD_GET_HEALTH (15)
88 : #define FD_RPC_METHOD_GET_HIGHEST_SNAPSHOT_SLOT (16)
89 : #define FD_RPC_METHOD_GET_IDENTITY (17)
90 : #define FD_RPC_METHOD_GET_INFLATION_GOVERNOR (18)
91 : #define FD_RPC_METHOD_GET_INFLATION_RATE (19)
92 : #define FD_RPC_METHOD_GET_INFLATION_REWARD (20)
93 : #define FD_RPC_METHOD_GET_LARGEST_ACCOUNTS (21)
94 : #define FD_RPC_METHOD_GET_LATEST_BLOCKHASH (22)
95 : #define FD_RPC_METHOD_GET_LEADER_SCHEDULE (23)
96 : #define FD_RPC_METHOD_GET_MAX_RETRANSMIT_SLOT (24)
97 : #define FD_RPC_METHOD_GET_MAX_SHRED_INSERT_SLOT (25)
98 : #define FD_RPC_METHOD_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION (26)
99 : #define FD_RPC_METHOD_GET_MULTIPLE_ACCOUNTS (27)
100 : #define FD_RPC_METHOD_GET_PROGRAM_ACCOUNTS (28)
101 : #define FD_RPC_METHOD_GET_RECENT_PERFORMANCE_SAMPLES (29)
102 : #define FD_RPC_METHOD_GET_RECENT_PRIORITIZATION_FEES (30)
103 : #define FD_RPC_METHOD_GET_SIGNATURES_FOR_ADDRESS (31)
104 : #define FD_RPC_METHOD_GET_SIGNATURE_STATUSES (32)
105 : #define FD_RPC_METHOD_GET_SLOT (33)
106 : #define FD_RPC_METHOD_GET_SLOT_LEADER (34)
107 : #define FD_RPC_METHOD_GET_SLOT_LEADERS (35)
108 : #define FD_RPC_METHOD_GET_STAKE_MINIMUM_DELEGATION (36)
109 : #define FD_RPC_METHOD_GET_SUPPLY (37)
110 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNT_BALANCE (38)
111 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_DELEGATE (39)
112 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_OWNER (40)
113 : #define FD_RPC_METHOD_GET_TOKEN_LARGEST_ACCOUNTS (41)
114 : #define FD_RPC_METHOD_GET_TOKEN_SUPPLY (42)
115 : #define FD_RPC_METHOD_GET_TRANSACTION (43)
116 : #define FD_RPC_METHOD_GET_TRANSACTION_COUNT (44)
117 : #define FD_RPC_METHOD_GET_VERSION (45)
118 : #define FD_RPC_METHOD_GET_VOTE_ACCOUNTS (46)
119 : #define FD_RPC_METHOD_IS_BLOCKHASH_VALID (47)
120 : #define FD_RPC_METHOD_MINIMUM_LEDGER_SLOT (48)
121 : #define FD_RPC_METHOD_REQUEST_AIRDROP (49)
122 : #define FD_RPC_METHOD_SEND_TRANSACTION (50)
123 : #define FD_RPC_METHOD_SIMULATE_TRANSACTION (51)
124 :
125 : // Keep in sync with https://github.com/solana-labs/solana-web3.js/blob/master/src/errors.ts
126 : // and https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/custom_error.rs
127 : #define FD_RPC_ERROR_BLOCK_CLEANED_UP (-32001)
128 : #define FD_RPC_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE (-32002)
129 : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE (-32003)
130 : #define FD_RPC_ERROR_BLOCK_NOT_AVAILABLE (-32004)
131 : #define FD_RPC_ERROR_NODE_UNHEALTHY (-32005)
132 : #define FD_RPC_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE (-32006)
133 : #define FD_RPC_ERROR_SLOT_SKIPPED (-32007)
134 : #define FD_RPC_ERROR_NO_SNAPSHOT (-32008)
135 : #define FD_RPC_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED (-32009)
136 : #define FD_RPC_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX (-32010)
137 : #define FD_RPC_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE (-32011)
138 : #define FD_RPC_ROR (-32012)
139 : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH (-32013)
140 : #define FD_RPC_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET (-32014)
141 : #define FD_RPC_ERROR_UNSUPPORTED_TRANSACTION_VERSION (-32015)
142 : #define FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED (-32016)
143 : #define FD_RPC_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE (-32017)
144 : #define FD_RPC_ERROR_SLOT_NOT_EPOCH_BOUNDARY (-32018)
145 : #define FD_RPC_ERROR_LONG_TERM_STORAGE_UNREACHABLE (-32019)
146 :
147 0 : static void fd_rpc_cstr_cJSON_free( char ** p ) { cJSON_free( *p ); }
148 0 : #define CSTR_JSON(__json, __out) __attribute__((cleanup(fd_rpc_cstr_cJSON_free))) char * __out = cJSON_PrintUnformatted( __json );
149 :
150 : /* Like CSTR_JSON, but strips the surrounding quotes from a string
151 : node's JSON representation. */
152 : #define CSTR_JSON_UNQUOTED(__json, __out) \
153 0 : CSTR_JSON( (__json), __out##_quoted_ ); \
154 0 : ulong __out##_len_ = __out##_quoted_ ? strlen( __out##_quoted_ ) : 0; \
155 0 : if( FD_LIKELY( __out##_len_>=2 ) ) \
156 0 : __out##_quoted_[ __out##_len_ - 1 ] = '\0'; \
157 0 : char const * (__out) = __out##_len_>=2 ? __out##_quoted_ + 1 : ""
158 :
159 : static fd_http_server_params_t
160 0 : derive_http_params( fd_topo_tile_t const * tile ) {
161 0 : return (fd_http_server_params_t) {
162 0 : .max_connection_cnt = tile->rpc.max_http_connections,
163 0 : .max_ws_connection_cnt = tile->rpc.max_websocket_connections,
164 0 : .max_request_len = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
165 0 : .max_ws_recv_frame_len = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
166 0 : .max_ws_send_frame_cnt = FD_HTTP_SERVER_RPC_MAX_WS_SEND_FRAME_CNT,
167 0 : .outgoing_buffer_sz = tile->rpc.send_buffer_size_mb * (1UL<<20UL),
168 0 : .compress_websocket = 0,
169 0 : };
170 0 : }
171 :
172 : struct fd_rpc_in {
173 : fd_wksp_t * mem;
174 : ulong chunk0;
175 : ulong wmark;
176 : ulong mtu;
177 : };
178 :
179 : typedef struct fd_rpc_in fd_rpc_in_t;
180 :
181 : struct fd_rpc_out {
182 : ulong idx;
183 : fd_wksp_t * mem;
184 : ulong chunk0;
185 : ulong wmark;
186 : ulong chunk;
187 : };
188 :
189 : typedef struct fd_rpc_out fd_rpc_out_t;
190 :
191 : struct bank_info {
192 : ulong slot; /* default ULONG_MAX */
193 : ulong bank_idx;
194 : fd_accdb_fork_id_t accdb_fork_id;
195 : ulong epoch;
196 : ulong slot_in_epoch;
197 : ulong slots_per_epoch;
198 :
199 : ulong transaction_count;
200 : uchar block_hash[ 32 ];
201 : ulong block_height;
202 :
203 : struct {
204 : double initial;
205 : double terminal;
206 : double taper;
207 : double foundation;
208 : double foundation_term;
209 : } inflation;
210 :
211 : struct {
212 : ulong lamports_per_uint8_year;
213 : double exemption_threshold;
214 : uchar burn_percent;
215 : } rent;
216 : };
217 :
218 : typedef struct bank_info bank_info_t;
219 :
220 : struct fd_rpc_cluster_node {
221 : int valid;
222 : fd_pubkey_t identity;
223 : fd_gossip_contact_info_t ci[ 1 ];
224 :
225 : struct { ulong prev, next; } dlist;
226 : };
227 :
228 : typedef struct fd_rpc_cluster_node fd_rpc_cluster_node_t;
229 :
230 : #define DLIST_NAME fd_rpc_cluster_node_dlist
231 : #define DLIST_ELE_T fd_rpc_cluster_node_t
232 0 : #define DLIST_PREV dlist.prev
233 0 : #define DLIST_NEXT dlist.next
234 : #include "../../util/tmpl/fd_dlist.c"
235 :
236 : struct fd_rpc_tile {
237 : int delay_startup;
238 : fd_http_server_t * http;
239 :
240 : ulong * ws_subscribers_vote;
241 : ulong ws_subscribers_vote_cnt;
242 : ulong * ws_subscribers_slot;
243 : ulong ws_subscribers_slot_cnt;
244 :
245 : fd_rpc_cluster_node_dlist_t * cluster_nodes_dlist;
246 : fd_rpc_cluster_node_t cluster_nodes[ FD_CONTACT_INFO_TABLE_SIZE ];
247 :
248 : bank_info_t * banks;
249 : ulong max_live_slots;
250 :
251 : fd_accdb_t * accdb;
252 :
253 : ulong cluster_confirmed_slot;
254 :
255 : ulong processed_idx;
256 : ulong confirmed_idx;
257 : ulong finalized_idx;
258 :
259 : int has_genesis_hash;
260 : fd_hash_t genesis_hash[ 1 ];
261 :
262 : uchar genesis_tar[ FD_RPC_TAR_SZ ];
263 : uchar genesis_tar_bz[ FD_RPC_TAR_BZ_SZ ];
264 : ulong genesis_tar_bz_sz;
265 :
266 : fd_alloc_t * bz2_alloc;
267 :
268 : long next_poll_deadline;
269 :
270 : fd_keyswitch_t * keyswitch;
271 : uchar identity_pubkey[ 32UL ];
272 :
273 : int in_kind[ 64UL ];
274 : fd_rpc_in_t in[ 64UL ];
275 :
276 : fd_rpc_out_t replay_out[1];
277 :
278 : fd_histf_t request_duration[ 1 ];
279 :
280 : # if FD_HAS_ZSTD
281 : uchar compress_buf[ ZSTD_COMPRESSBOUND( FD_RUNTIME_ACC_SZ_MAX ) ];
282 : # endif
283 :
284 : /* Scratch buffer for fd_accdb_read_one_nocache: holds the account
285 : data bytes returned by the readonly accdb path. Sized to the
286 : runtime account data maximum. Must not be in accdb shmem. */
287 : uchar accdb_data_buf[ FD_RUNTIME_ACC_SZ_MAX ];
288 : };
289 :
290 : typedef struct fd_rpc_tile fd_rpc_tile_t;
291 :
292 : static void
293 : fd_rpc_ws_subscriber_vote_add( fd_rpc_tile_t * ctx,
294 0 : ulong ws_conn_id ) {
295 0 : for( ulong i=0UL; i<ctx->ws_subscribers_vote_cnt; i++ )
296 0 : if( FD_UNLIKELY( ctx->ws_subscribers_vote[ i ]==ws_conn_id ) ) return;
297 :
298 0 : FD_TEST( ctx->ws_subscribers_vote_cnt<ctx->http->max_ws_conns );
299 0 : ctx->ws_subscribers_vote[ ctx->ws_subscribers_vote_cnt++ ] = ws_conn_id;
300 0 : }
301 :
302 : static int
303 : fd_rpc_ws_subscriber_vote_remove( fd_rpc_tile_t * ctx,
304 0 : ulong ws_conn_id ) {
305 0 : for( ulong idx=0UL; idx<ctx->ws_subscribers_vote_cnt; idx++ ) {
306 0 : if( FD_LIKELY( ctx->ws_subscribers_vote[ idx ]!=ws_conn_id ) ) continue;
307 :
308 0 : ctx->ws_subscribers_vote_cnt--;
309 0 : ctx->ws_subscribers_vote[ idx ] = ctx->ws_subscribers_vote[ ctx->ws_subscribers_vote_cnt ];
310 0 : return 1;
311 0 : }
312 :
313 0 : return 0;
314 0 : }
315 :
316 : static void
317 : fd_rpc_ws_subscriber_slot_add( fd_rpc_tile_t * ctx,
318 0 : ulong ws_conn_id ) {
319 0 : for( ulong i=0UL; i<ctx->ws_subscribers_slot_cnt; i++ )
320 0 : if( FD_UNLIKELY( ctx->ws_subscribers_slot[ i ]==ws_conn_id ) ) return;
321 :
322 0 : FD_TEST( ctx->ws_subscribers_slot_cnt<ctx->http->max_ws_conns );
323 0 : ctx->ws_subscribers_slot[ ctx->ws_subscribers_slot_cnt++ ] = ws_conn_id;
324 0 : }
325 :
326 : static int
327 : fd_rpc_ws_subscriber_slot_remove( fd_rpc_tile_t * ctx,
328 0 : ulong ws_conn_id ) {
329 0 : for( ulong idx=0UL; idx<ctx->ws_subscribers_slot_cnt; idx++ ) {
330 0 : if( FD_LIKELY( ctx->ws_subscribers_slot[ idx ]!=ws_conn_id ) ) continue;
331 :
332 0 : ctx->ws_subscribers_slot_cnt--;
333 0 : ctx->ws_subscribers_slot[ idx ] = ctx->ws_subscribers_slot[ ctx->ws_subscribers_slot_cnt ];
334 0 : return 1;
335 0 : }
336 :
337 0 : return 0;
338 0 : }
339 :
340 : static void *
341 : bz2_malloc( void * opaque,
342 : int items,
343 0 : int size ) {
344 0 : fd_alloc_t * alloc = (fd_alloc_t *)opaque;
345 :
346 0 : if( FD_UNLIKELY( items<=0 || size<=0 ) ) {
347 0 : FD_LOG_WARNING(( "bz2_malloc: invalid items=%d or size=%d", items, size ));
348 0 : return NULL;
349 0 : }
350 0 : void * result = fd_alloc_malloc( alloc, alignof(max_align_t), (ulong)items*(ulong)size );
351 0 : if( FD_UNLIKELY( !result ) ) return NULL;
352 0 : return result;
353 0 : }
354 :
355 : static void
356 : bz2_free( void * opaque,
357 0 : void * addr ) {
358 0 : fd_alloc_t * alloc = (fd_alloc_t *)opaque;
359 :
360 0 : if( FD_UNLIKELY( !addr ) ) return;
361 0 : fd_alloc_free( alloc, addr );
362 0 : }
363 :
364 : static inline ulong
365 : fd_rpc_file_as_tarball( fd_rpc_tile_t * ctx,
366 : char const * filename_cstr,
367 : uchar const * data,
368 : ulong data_sz,
369 : uchar * scratch,
370 : ulong scratch_sz,
371 : uchar * out,
372 0 : ulong out_sz ) {
373 0 : ulong padding_sz = 2*512UL;
374 0 : if( FD_LIKELY( data_sz % 512UL ) ) padding_sz += 512UL - (data_sz % 512UL);
375 :
376 0 : if( FD_UNLIKELY( data_sz>FD_GENESIS_MAX_MESSAGE_SIZE ) ) {
377 0 : FD_LOG_ERR(( "Genesis data exceeds maximum size (data_sz=%lu max=%lu)", data_sz, (ulong)FD_GENESIS_MAX_MESSAGE_SIZE ));
378 0 : }
379 0 : ulong tar_sz = sizeof(fd_tar_meta_t) + data_sz + padding_sz;
380 0 : if( FD_UNLIKELY( tar_sz>scratch_sz ) ) {
381 0 : FD_LOG_WARNING(( "tar_sz exceeds scratch_sz (tar_sz=%lu scratch_sz=%lu)", tar_sz, scratch_sz ));
382 0 : return ULONG_MAX;
383 0 : }
384 :
385 0 : fd_tar_meta_init_file_default( (fd_tar_meta_t *)scratch, filename_cstr, data_sz, fd_log_wallclock() );
386 0 : fd_memcpy( scratch+sizeof(fd_tar_meta_t), data, data_sz );
387 0 : memset( scratch+sizeof(fd_tar_meta_t)+data_sz, 0, padding_sz );
388 :
389 : /* NOTE: Agave's genesis.tar also contains a `rocksdb` folder */
390 :
391 0 : bz_stream bzstrm = {0};
392 0 : bzstrm.bzalloc = bz2_malloc;
393 0 : bzstrm.bzfree = bz2_free;
394 0 : bzstrm.opaque = ctx->bz2_alloc;
395 0 : int bzerr = BZ2_bzCompressInit( &bzstrm, 1, 0, 0 );
396 0 : if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzCompressInit() failed (%d)", bzerr ));
397 :
398 0 : ulong tar_bz_sz = out_sz;
399 :
400 0 : bzstrm.next_in = (char *)scratch;
401 0 : bzstrm.avail_in = (uint)tar_sz;
402 0 : bzstrm.next_out = (char *)out;
403 0 : bzstrm.avail_out = (uint)tar_bz_sz;
404 :
405 0 : for(;;) {
406 0 : bzerr = BZ2_bzCompress( &bzstrm, BZ_FINISH );
407 0 : if( FD_LIKELY( bzerr==BZ_STREAM_END ) ) break;
408 0 : if( FD_UNLIKELY( bzerr>=0 ) ) continue;
409 0 : FD_LOG_ERR(( "BZ2_bzCompress(_, BZ_FINISH) failed (%d)", bzerr ));
410 0 : }
411 :
412 0 : tar_bz_sz -= (ulong)bzstrm.avail_out;
413 :
414 0 : bzerr = BZ2_bzCompressEnd( &bzstrm );
415 0 : if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzCompressEnd() failed (%d)", bzerr ));
416 :
417 0 : return tar_bz_sz;
418 0 : }
419 :
420 : FD_FN_CONST static inline ulong
421 0 : scratch_align( void ) {
422 0 : ulong a = alignof( fd_rpc_tile_t );
423 0 : a = fd_ulong_max( a, fd_http_server_align() );
424 0 : a = fd_ulong_max( a, fd_alloc_align() );
425 0 : a = fd_ulong_max( a, alignof(bank_info_t) );
426 0 : a = fd_ulong_max( a, fd_rpc_cluster_node_dlist_align() );
427 0 : a = fd_ulong_max( a, fd_accdb_align() );
428 0 : return a;
429 0 : }
430 :
431 : static inline ulong
432 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
433 0 : fd_http_server_params_t http_params = derive_http_params( tile );
434 0 : ulong http_fp = fd_http_server_footprint( http_params );
435 0 : if( FD_UNLIKELY( !http_fp ) ) FD_LOG_ERR(( "Invalid [tiles.rpc] config parameters" ));
436 :
437 0 : ulong l = FD_LAYOUT_INIT;
438 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
439 0 : l = FD_LAYOUT_APPEND( l, fd_http_server_align(), http_fp );
440 0 : l = FD_LAYOUT_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
441 0 : l = FD_LAYOUT_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
442 0 : l = FD_LAYOUT_APPEND( l, alignof(bank_info_t), tile->rpc.max_live_slots*sizeof(bank_info_t) );
443 0 : l = FD_LAYOUT_APPEND( l, fd_rpc_cluster_node_dlist_align(), fd_rpc_cluster_node_dlist_footprint() );
444 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->rpc.max_live_slots ) );
445 0 : l = FD_LAYOUT_APPEND( l, alignof(ulong), http_params.max_ws_connection_cnt*sizeof(ulong) );
446 0 : l = FD_LAYOUT_APPEND( l, alignof(ulong), http_params.max_ws_connection_cnt*sizeof(ulong) );
447 0 : return FD_LAYOUT_FINI( l, scratch_align() );
448 0 : }
449 :
450 : FD_FN_PURE static inline ulong
451 0 : loose_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
452 0 : return 256UL * (1UL<<20UL); /* 256MiB of heap space for the cJSON allocator */
453 0 : }
454 :
455 : static inline void
456 0 : during_housekeeping( fd_rpc_tile_t * ctx ) {
457 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
458 0 : fd_memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
459 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
460 0 : }
461 0 : }
462 :
463 : static inline void
464 0 : metrics_write( fd_rpc_tile_t * ctx ) {
465 0 : FD_MHIST_COPY( RPC, REQUEST_DURATION_SECONDS, ctx->request_duration );
466 0 : FD_MGAUGE_SET( RPC, CONN_ACTIVE, ctx->http->metrics.connection_cnt );
467 0 : FD_MGAUGE_SET( RPC, WEBSOCKET_CONN_ACTIVE, ctx->http->metrics.ws_connection_cnt );
468 0 : FD_MGAUGE_SET( RPC, WEBSOCKET_SUBSCRIPTION_ACTIVE_VOTE, ctx->ws_subscribers_vote_cnt );
469 0 : FD_MGAUGE_SET( RPC, WEBSOCKET_SUBSCRIPTION_ACTIVE_SLOT, ctx->ws_subscribers_slot_cnt );
470 0 : FD_ACCDB_METRICS_WRITE_RO( RPC, fd_accdb_metrics( ctx->accdb ) );
471 0 : }
472 :
473 : static void
474 : before_credit( fd_rpc_tile_t * ctx,
475 : fd_stem_context_t * stem,
476 0 : int * charge_busy ) {
477 0 : (void)stem;
478 :
479 0 : long now = fd_tickcount();
480 0 : int replay_ready = ctx->confirmed_idx!=ULONG_MAX && ctx->processed_idx!=ULONG_MAX && ctx->finalized_idx!=ULONG_MAX;
481 0 : if( FD_UNLIKELY( (!ctx->delay_startup || replay_ready) && now>=ctx->next_poll_deadline ) ) {
482 0 : *charge_busy = fd_http_server_poll( ctx->http, 0 );
483 0 : ctx->next_poll_deadline = fd_tickcount() + (long)(fd_tempo_tick_per_ns( NULL )*128L*1000L);
484 0 : }
485 0 : }
486 :
487 : static int
488 : before_frag( fd_rpc_tile_t * ctx,
489 : ulong in_idx,
490 : ulong seq FD_PARAM_UNUSED,
491 0 : ulong sig ) {
492 0 : if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) ) {
493 0 : return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO &&
494 0 : sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE &&
495 0 : sig!=FD_GOSSIP_UPDATE_TAG_VOTE;
496 0 : }
497 :
498 0 : return 0;
499 0 : }
500 :
501 : static int
502 : fd_rpc_extract_vote_notification( fd_gossip_vote_t const * vote,
503 : fd_pubkey_t * vote_pubkey,
504 : fd_hash_t * hash,
505 : long * timestamp,
506 : uchar * has_timestamp,
507 : fd_signature_t * signature,
508 : ulong * slots,
509 0 : ulong * slots_cnt ) {
510 0 : uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t))));
511 0 : ulong txn_sz = fd_txn_parse( vote->transaction, vote->transaction_len, txn_mem, NULL );
512 0 : if( FD_UNLIKELY( !txn_sz ) ) return 0;
513 :
514 0 : fd_txn_t const * txn = (fd_txn_t const *)txn_mem;
515 0 : if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, vote->transaction ) ) ) return 0;
516 0 : if( FD_UNLIKELY( txn->instr_cnt!=1UL || txn->signature_cnt<1UL ) ) return 0;
517 :
518 0 : fd_txn_instr_t const * instr = &txn->instr[ 0 ];
519 0 : if( FD_UNLIKELY( !instr->acct_cnt ) ) return 0;
520 0 : uchar const * instr_accts = fd_txn_get_instr_accts( instr, vote->transaction );
521 0 : uchar vote_pubkey_idx = instr_accts[ 0 ];
522 0 : if( FD_UNLIKELY( vote_pubkey_idx>=txn->acct_addr_cnt ) ) return 0;
523 :
524 0 : fd_pubkey_t const * acct_addrs = (fd_pubkey_t const *)fd_type_pun_const( vote->transaction + txn->acct_addr_off );
525 0 : *vote_pubkey = acct_addrs[ vote_pubkey_idx ];
526 :
527 0 : fd_vote_instruction_t ix[1];
528 0 : if( FD_UNLIKELY( !fd_vote_instruction_deserialize( ix, fd_txn_get_instr_data( instr, vote->transaction ), instr->data_sz ) ) ) return 0;
529 :
530 0 : *slots_cnt = 0UL;
531 0 : *has_timestamp = 0;
532 0 : *timestamp = 0L;
533 :
534 0 : switch( ix->discriminant ) {
535 0 : case fd_vote_instruction_enum_tower_sync: {
536 0 : fd_tower_sync_t const * v = &ix->tower_sync;
537 0 : for( ulong i=0UL; i<v->lockouts_cnt; i++ ) slots[ (*slots_cnt)++ ] =
538 0 : deq_fd_vote_lockout_t_peek_index_const( v->lockouts, i )->slot;
539 0 : *hash = v->hash;
540 0 : *has_timestamp = v->has_timestamp;
541 0 : *timestamp = v->timestamp;
542 0 : break;
543 0 : }
544 0 : case fd_vote_instruction_enum_tower_sync_switch: {
545 0 : fd_tower_sync_t const * v = &ix->tower_sync_switch.tower_sync;
546 0 : for( ulong i=0UL; i<v->lockouts_cnt; i++ ) slots[ (*slots_cnt)++ ] =
547 0 : deq_fd_vote_lockout_t_peek_index_const( v->lockouts, i )->slot;
548 0 : *hash = v->hash;
549 0 : *has_timestamp = v->has_timestamp;
550 0 : *timestamp = v->timestamp;
551 0 : break;
552 0 : }
553 0 : default:
554 : /* Legacy vote instructions not supported */
555 0 : return 0;
556 0 : }
557 :
558 0 : *signature = FD_LOAD( fd_signature_t, fd_txn_get_signatures( txn, vote->transaction ) );
559 0 : return *slots_cnt>0UL;
560 0 : }
561 :
562 : static void
563 : fd_rpc_publish_vote_event( fd_rpc_tile_t * ctx,
564 0 : fd_gossip_vote_t const * vote ) {
565 0 : if( FD_UNLIKELY( !ctx->ws_subscribers_vote_cnt ) ) return;
566 :
567 0 : fd_pubkey_t vote_pubkey[1];
568 0 : fd_hash_t hash[1];
569 0 : long timestamp = 0L;
570 0 : uchar has_timestamp = 0;
571 0 : fd_signature_t signature[1];
572 0 : ulong slots[ FD_VOTE_INSTR_MAX_LOCKOUT_OFFSETS_LEN ];
573 0 : ulong slots_cnt = 0UL;
574 :
575 0 : if( FD_UNLIKELY( !fd_rpc_extract_vote_notification( vote, vote_pubkey, hash, ×tamp, &has_timestamp, signature, slots, &slots_cnt ) ) ) return;
576 :
577 0 : FD_BASE58_ENCODE_32_BYTES( vote_pubkey->uc, vote_pubkey_b58 );
578 0 : FD_BASE58_ENCODE_32_BYTES( hash->uc, hash_b58 );
579 0 : FD_BASE58_ENCODE_64_BYTES( signature->uc, signature_b58 );
580 :
581 0 : char txn_base64[ FD_BASE64_ENC_SZ( sizeof(vote->transaction) ) ];
582 0 : ulong txn_base64_len = fd_base64_encode( txn_base64, vote->transaction, vote->transaction_len );
583 :
584 0 : ulong sent_cnt = 0UL;
585 0 : for( ulong i=0UL; i<ctx->ws_subscribers_vote_cnt; ) {
586 0 : ulong ws_conn_id = ctx->ws_subscribers_vote[ i ];
587 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"method\":\"voteNotification\",\"params\":{\"subscription\":0,\"result\":{\"votePubkey\":\"%s\",\"slots\":[", vote_pubkey_b58 );
588 0 : for( ulong j=0UL; j<slots_cnt; j++ ) fd_http_server_printf( ctx->http, "%s%lu", j ? "," : "", slots[ j ] );
589 0 : fd_http_server_printf( ctx->http, "],\"hash\":\"%s\",", hash_b58 );
590 0 : if( FD_LIKELY( has_timestamp ) ) fd_http_server_printf( ctx->http, "\"timestamp\":%ld,", timestamp );
591 0 : else fd_http_server_printf( ctx->http, "\"timestamp\":null," );
592 0 : fd_http_server_printf( ctx->http, "\"signature\":\"%s\",\"transaction\":[\"%.*s\",\"base64\"]}}}\n", signature_b58, (int)txn_base64_len, txn_base64 );
593 :
594 0 : if( FD_UNLIKELY( fd_http_server_ws_send( ctx->http, ws_conn_id ) ) ) {
595 0 : fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
596 0 : fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
597 0 : continue;
598 0 : }
599 0 : if( FD_UNLIKELY( i>=ctx->ws_subscribers_vote_cnt || ctx->ws_subscribers_vote[ i ]!=ws_conn_id ) ) continue;
600 0 : sent_cnt++;
601 0 : i++;
602 0 : }
603 :
604 0 : FD_MCNT_INC( RPC, WEBSOCKET_EVENT_UNIQUE_SENT_VOTE, !!sent_cnt );
605 0 : FD_MCNT_INC( RPC, WEBSOCKET_EVENT_SENT_VOTE, sent_cnt );
606 0 : }
607 :
608 : static void
609 : fd_rpc_publish_slot_event( fd_rpc_tile_t * ctx,
610 0 : fd_replay_slot_completed_t const * slot_completed ) {
611 0 : if( FD_UNLIKELY( !ctx->ws_subscribers_slot_cnt ) ) return;
612 :
613 0 : ulong sent_cnt = 0UL;
614 0 : for( ulong i=0UL; i<ctx->ws_subscribers_slot_cnt; ) {
615 0 : ulong ws_conn_id = ctx->ws_subscribers_slot[ i ];
616 0 : fd_http_server_printf( ctx->http,
617 0 : "{\"jsonrpc\":\"2.0\",\"method\":\"slotNotification\",\"params\":{\"subscription\":0,\"result\":{\"parent\":%lu,\"root\":%lu,\"slot\":%lu}}}\n",
618 0 : slot_completed->parent_slot,
619 0 : slot_completed->root_slot,
620 0 : slot_completed->slot );
621 :
622 0 : if( FD_UNLIKELY( fd_http_server_ws_send( ctx->http, ws_conn_id ) ) ) {
623 0 : fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
624 0 : fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
625 0 : continue;
626 0 : }
627 0 : if( FD_UNLIKELY( i>=ctx->ws_subscribers_slot_cnt || ctx->ws_subscribers_slot[ i ]!=ws_conn_id ) ) continue;
628 0 : sent_cnt++;
629 0 : i++;
630 0 : }
631 :
632 0 : FD_MCNT_INC( RPC, WEBSOCKET_EVENT_UNIQUE_SENT_SLOT, !!sent_cnt );
633 0 : FD_MCNT_INC( RPC, WEBSOCKET_EVENT_SENT_SLOT, sent_cnt );
634 0 : }
635 :
636 : static inline int
637 : returnable_frag( fd_rpc_tile_t * ctx,
638 : ulong in_idx,
639 : ulong seq FD_PARAM_UNUSED,
640 : ulong sig,
641 : ulong chunk,
642 : ulong sz FD_PARAM_UNUSED,
643 : ulong ctl FD_PARAM_UNUSED,
644 : ulong tsorig FD_PARAM_UNUSED,
645 : ulong tspub FD_PARAM_UNUSED,
646 0 : fd_stem_context_t * stem ) {
647 :
648 0 : if( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) {
649 0 : switch( sig ) {
650 0 : case REPLAY_SIG_SLOT_COMPLETED: {
651 0 : fd_replay_slot_completed_t const * slot_completed = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
652 :
653 0 : FD_TEST( slot_completed->bank_idx<ctx->max_live_slots );
654 0 : bank_info_t * bank = &ctx->banks[ slot_completed->bank_idx ];
655 0 : bank->slot = slot_completed->slot;
656 0 : bank->accdb_fork_id = slot_completed->accdb_fork_id;
657 0 : bank->epoch = slot_completed->epoch;
658 0 : bank->slot_in_epoch = slot_completed->slot_in_epoch;
659 0 : bank->slots_per_epoch = slot_completed->slots_per_epoch;
660 0 : bank->transaction_count = slot_completed->transaction_count;
661 0 : bank->block_height = slot_completed->block_height;
662 0 : fd_memcpy( bank->block_hash, slot_completed->block_hash.uc, 32 );
663 :
664 0 : bank->inflation.initial = slot_completed->inflation.initial;
665 0 : bank->inflation.terminal = slot_completed->inflation.terminal;
666 0 : bank->inflation.taper = slot_completed->inflation.taper;
667 0 : bank->inflation.foundation = slot_completed->inflation.foundation;
668 0 : bank->inflation.foundation_term = slot_completed->inflation.foundation_term;
669 :
670 0 : bank->rent.lamports_per_uint8_year = slot_completed->rent.lamports_per_uint8_year;
671 0 : bank->rent.exemption_threshold = slot_completed->rent.exemption_threshold;
672 0 : bank->rent.burn_percent = slot_completed->rent.burn_percent;
673 :
674 0 : fd_rpc_publish_slot_event( ctx, slot_completed );
675 :
676 : /* In Agave, "processed" confirmation is the bank we've just
677 : voted for (handle_votable_bank), which is also guaranteed to
678 : have been replayed.
679 :
680 : Right now tower is not really built out to replicate this
681 : exactly, so we use the latest replayed slot, which is
682 : slightly more eager than Agave but shouldn't really affect
683 : end-users, since any use-cases that assume "processed" means
684 : "voted-for" would fail in Agave in cases where a cast vote
685 : does not land.
686 :
687 : tldr: This isn't strictly conformant with Agave, but doesn't
688 : need to be since Agave doesn't provide any guarantees anyways. */
689 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 );
690 0 : ctx->processed_idx = slot_completed->bank_idx;
691 0 : break;
692 0 : }
693 0 : case REPLAY_SIG_OC_ADVANCED: {
694 0 : fd_replay_oc_advanced_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
695 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 );
696 0 : FD_TEST( msg->bank_idx<ctx->max_live_slots );
697 0 : ctx->confirmed_idx = msg->bank_idx;
698 0 : ctx->cluster_confirmed_slot = msg->slot;
699 0 : break;
700 0 : }
701 0 : case REPLAY_SIG_ROOT_ADVANCED: {
702 0 : fd_replay_root_advanced_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
703 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 );
704 0 : FD_TEST( msg->bank_idx<ctx->max_live_slots );
705 0 : ctx->finalized_idx = msg->bank_idx;
706 0 : break;
707 0 : }
708 0 : default: {
709 0 : break;
710 0 : }
711 0 : }
712 0 : } else if( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) {
713 0 : fd_gossip_update_message_t const * update = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
714 0 : switch( update->tag ) {
715 0 : case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
716 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 ));
717 0 : fd_rpc_cluster_node_t * node = &ctx->cluster_nodes[ update->contact_info->idx ];
718 0 : if( FD_LIKELY( node->valid ) ) fd_rpc_cluster_node_dlist_idx_remove( ctx->cluster_nodes_dlist, update->contact_info->idx, ctx->cluster_nodes );
719 :
720 0 : node->valid = 1;
721 0 : node->identity = *(fd_pubkey_t *)update->origin;
722 0 : fd_memcpy( node->ci, update->contact_info->value, sizeof(fd_gossip_contact_info_t) );
723 :
724 0 : fd_rpc_cluster_node_dlist_idx_push_tail( ctx->cluster_nodes_dlist, update->contact_info->idx, ctx->cluster_nodes );
725 0 : break;
726 0 : }
727 0 : case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE: {
728 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 ));
729 0 : fd_rpc_cluster_node_t * node = &ctx->cluster_nodes[ update->contact_info_remove->idx ];
730 0 : FD_TEST( node->valid );
731 0 : node->valid = 0;
732 0 : fd_rpc_cluster_node_dlist_idx_remove( ctx->cluster_nodes_dlist, update->contact_info_remove->idx, ctx->cluster_nodes );
733 0 : break;
734 0 : }
735 0 : case FD_GOSSIP_UPDATE_TAG_VOTE: {
736 0 : fd_rpc_publish_vote_event( ctx, update->vote->value );
737 0 : break;
738 0 : }
739 0 : default: break;
740 0 : }
741 0 : } else if( ctx->in_kind[ in_idx ]==IN_KIND_GENESI ) {
742 0 : ctx->has_genesis_hash = 1;
743 0 : fd_genesis_meta_t const * genesis_meta = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
744 0 : *ctx->genesis_hash = genesis_meta->genesis_hash;
745 :
746 0 : uchar const * blob = (uchar const *)( genesis_meta+1 );
747 0 : ulong const blob_sz = genesis_meta->blob_sz;
748 0 : FD_TEST( blob_sz<=FD_GENESIS_MAX_MESSAGE_SIZE );
749 :
750 0 : ctx->genesis_tar_bz_sz = fd_rpc_file_as_tarball(
751 0 : ctx,
752 0 : "genesis.bin",
753 0 : blob, blob_sz,
754 0 : ctx->genesis_tar, sizeof(ctx->genesis_tar),
755 0 : ctx->genesis_tar_bz, sizeof(ctx->genesis_tar_bz) );
756 0 : if( FD_UNLIKELY( ctx->genesis_tar_bz_sz==ULONG_MAX ) ) {
757 0 : FD_LOG_ERR(( "failed to create genesis tarball (blob_sz=%lu)", blob_sz ));
758 0 : }
759 0 : }
760 :
761 0 : return 0;
762 0 : }
763 :
764 : /* Silence warnings due gcc not recognizing nan-infinity-disabled
765 : pragma, which is required by clang */
766 : #pragma GCC diagnostic push
767 : #pragma GCC diagnostic ignored "-Wpragmas"
768 : #pragma GCC diagnostic ignored "-Wunknown-warning-option"
769 : #pragma GCC diagnostic ignored "-Wnan-infinity-disabled"
770 :
771 : static inline int
772 0 : fd_rpc_cjson_is_integer( const cJSON * item ) {
773 0 : return cJSON_IsNumber(item)
774 0 : && isfinite(item->valuedouble)
775 0 : && floor(item->valuedouble) == item->valuedouble;
776 0 : }
777 :
778 : #pragma GCC diagnostic pop
779 :
780 : static inline char const *
781 0 : fd_rpc_cjson_type_to_cstr( cJSON const * elt ) {
782 0 : FD_TEST( elt );
783 0 : if( cJSON_IsString( elt ) ) return "string";
784 0 : if( cJSON_IsObject( elt ) ) return "map";
785 0 : if( cJSON_IsArray ( elt ) ) return "sequence";
786 0 : if( cJSON_IsBool ( elt ) ) return "boolean";
787 0 : if( cJSON_IsNumber( elt ) && !fd_rpc_cjson_is_integer( elt ) ) return "floating point";
788 0 : if( cJSON_IsNumber( elt ) ) return "integer";
789 0 : if( cJSON_IsNull ( elt ) ) return "null";
790 0 : CSTR_JSON( elt, elt_cstr );
791 0 : FD_LOG_ERR(( "unreachable %s", elt_cstr ));
792 0 : }
793 :
794 0 : #define STAGE_JSON(__ctx) (__extension__({ \
795 0 : fd_http_server_response_t __res = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 }; \
796 0 : if( FD_UNLIKELY( fd_http_server_stage_body( __ctx->http, &__res ) ) ) { \
797 0 : __res.status = 500; \
798 0 : FD_LOG_WARNING(( "Failed to populate RPC response buffer" )); \
799 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 ) )); \
800 0 : FD_LOG_HEXDUMP_WARNING(( "start of buffer:\n%.*s", __ctx->http->oring, fd_ulong_min( 500UL, __ctx->http->oring_sz ) )); \
801 0 : } \
802 0 : __res; }))
803 :
804 0 : #define PRINTF_JSON(__ctx, ...) (__extension__({ \
805 0 : fd_http_server_printf( __ctx->http, __VA_ARGS__ ); \
806 0 : fd_http_server_response_t __res = STAGE_JSON( __ctx ); \
807 0 : __res; }))
808 :
809 :
810 : static inline int
811 : fd_rpc_validate_params( fd_rpc_tile_t * ctx,
812 : cJSON const * id,
813 : cJSON const * params,
814 : ulong min_cnt,
815 : ulong max_cnt,
816 0 : fd_http_server_response_t * res ) {
817 0 : FD_TEST( min_cnt <= max_cnt );
818 : /* Agave also includes a "data" field in some responses with the
819 : faulty params payload. Instead of printing raw JSON, they print the
820 : representation which we won't replicate.
821 :
822 : e.g. "data" might contain something like
823 : Array([String(\"\"), Object {}])
824 :
825 : instead, we just include the field with an empty string
826 : */
827 :
828 0 : ulong param_cnt;
829 0 : if( FD_UNLIKELY( !params ) ) param_cnt = 0UL;
830 0 : else if( FD_UNLIKELY( cJSON_IsNumber( params ) || cJSON_IsString( params ) || cJSON_IsBool( params ) ) ) {
831 0 : CSTR_JSON( id, id_cstr );
832 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
833 0 : return 0;
834 0 : }
835 0 : else if( FD_UNLIKELY( cJSON_IsObject( params ) && max_cnt==0UL ) ) {
836 0 : CSTR_JSON( id, id_cstr );
837 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: No parameters were expected\",\"data\":\"\"},\"id\":%s}\n", id_cstr );
838 0 : return 0;
839 0 : }
840 0 : else if( FD_UNLIKELY( cJSON_IsObject( params ) && max_cnt>0UL ) ) {
841 0 : CSTR_JSON( id, id_cstr );
842 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"`params` should be an array\"},\"id\":%s}\n", id_cstr );
843 0 : return 0;
844 0 : }
845 0 : else if( FD_UNLIKELY( cJSON_IsNull( params ) ) ) param_cnt = 0UL;
846 0 : else if( FD_UNLIKELY( cJSON_IsArray( params ) ) ) param_cnt = (ulong)cJSON_GetArraySize( params );
847 0 : else FD_LOG_ERR(("unreachable"));
848 :
849 0 : if( FD_UNLIKELY( param_cnt>0UL && max_cnt==0UL ) ) {
850 0 : CSTR_JSON( id, id_cstr );
851 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: No parameters were expected\",\"data\":\"\"},\"id\":%s}\n", id_cstr );
852 0 : return 0;
853 0 : }
854 0 : if( FD_UNLIKELY( param_cnt<min_cnt ) ) {
855 0 : CSTR_JSON( id, id_cstr );
856 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 );
857 0 : return 0;
858 0 : }
859 0 : if( param_cnt>max_cnt ) {
860 0 : CSTR_JSON( id, id_cstr );
861 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 );
862 0 : return 0;
863 0 : }
864 :
865 0 : return 1;
866 0 : }
867 :
868 : /* TODO: use optimized version of this from fd_base58_tmpl.c */
869 : static const char base58_chars[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
870 :
871 : static inline int
872 0 : fd_rpc_cstr_contains_non_base58(const char *str) {
873 0 : for (; *str; str++) {
874 0 : if (!strchr(base58_chars, *str)) return 1;
875 0 : }
876 0 : return 0;
877 0 : }
878 :
879 : /* adapted from https://salsa.debian.org/debian/libbase58/-/blob/debian/master/base58.c */
880 : static inline int
881 0 : fd_rpc_base58_encode_128( char * b58, ulong * b58sz, const void *data, ulong binsz ) {
882 0 : FD_TEST( binsz <= 128UL );
883 :
884 0 : const uchar * bin = data;
885 0 : ulong carry;
886 0 : ulong i, j, high, zcount = 0;
887 0 : ulong size;
888 :
889 0 : while( zcount<binsz && !bin[ zcount ] ) zcount++;
890 :
891 0 : size = (binsz-zcount)*138/100+1; /* strict overestimate */
892 0 : size = fd_ulong_min( size, FD_RPC_BASE58_ENCODED_128_LEN ); /* theoretical max */
893 0 : uchar buf[ FD_RPC_BASE58_ENCODED_128_LEN ] = { 0 };
894 :
895 0 : for( i=zcount, high=size-1UL; i<binsz; i++, high=j ) {
896 0 : for( carry=bin[ i ], j=size-1UL; (j>high) || carry; j-- ) {
897 0 : carry += 256UL * buf[ j ];
898 0 : buf[ j ] = (uchar)(carry%58UL);
899 0 : carry /= 58UL;
900 0 : if( FD_UNLIKELY( !j ) ) break;
901 0 : }
902 0 : }
903 :
904 0 : for( j=0; j<size && !buf[ j ]; j++);
905 :
906 0 : if( *b58sz<zcount+size-j ) {
907 0 : *b58sz = zcount+size-j;
908 0 : return 0;
909 0 : }
910 :
911 0 : if (zcount) memset(b58, '1', zcount);
912 0 : for( i=zcount; j<size; i++, j++) b58[ i ] = base58_chars[ buf[ j ] ];
913 0 : *b58sz = i;
914 :
915 0 : return 1;
916 0 : }
917 :
918 : static inline int
919 : fd_rpc_validate_config( fd_rpc_tile_t * ctx,
920 : cJSON const * id,
921 : cJSON const * config,
922 : char const * config_rust_type,
923 : int has_commitment,
924 : int has_encoding,
925 : int has_data_slice,
926 : int has_min_context_slot,
927 : ulong * bank_idx,
928 : char const ** opt_encoding_cstr,
929 : ulong * opt_slice_length,
930 : ulong * opt_slice_offset,
931 0 : fd_http_server_response_t * res ) {
932 :
933 0 : if( FD_UNLIKELY( config && (cJSON_IsNumber( config ) || cJSON_IsBool( config )) ) ) {
934 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( config, config_cstr );
935 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected %s.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( config ), config_cstr, config_rust_type, id_cstr );
936 0 : return 0;
937 0 : }
938 0 : if( FD_UNLIKELY( config && cJSON_IsString( config ) ) ) {
939 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( config, config_esc );
940 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected %s.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( config ), config_esc, config_rust_type, id_cstr );
941 0 : return 0;
942 0 : }
943 0 : if( FD_UNLIKELY( cJSON_IsArray( config ) ) ) {
944 0 : CSTR_JSON( id, id_cstr );
945 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: Positional config params not supported\"},\"id\":%s}\n", id_cstr );
946 0 : return 0;
947 0 : }
948 0 : if( FD_UNLIKELY( config && !(cJSON_IsNull( config ) || cJSON_IsObject( config )) ) ) {
949 0 : CSTR_JSON( id, id_cstr );
950 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected %s.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( config ), config_rust_type, id_cstr );
951 0 : return 0;
952 0 : }
953 :
954 0 : ulong _bank_idx = ULONG_MAX;
955 0 : cJSON const * commitment = NULL;
956 0 : if( FD_LIKELY( has_commitment ) ) {
957 0 : commitment = cJSON_GetObjectItemCaseSensitive( config, "commitment" );
958 0 : if( FD_UNLIKELY( !commitment || !cJSON_IsString( commitment ) ) ) _bank_idx = ctx->finalized_idx;
959 0 : else if( FD_LIKELY( !strcmp( commitment->valuestring, "processed" ) ) ) _bank_idx = ctx->processed_idx;
960 0 : else if( FD_LIKELY( !strcmp( commitment->valuestring, "confirmed" ) ) ) _bank_idx = ctx->confirmed_idx;
961 0 : else if( FD_LIKELY( !strcmp( commitment->valuestring, "finalized" ) ) ) _bank_idx = ctx->finalized_idx;
962 0 : else _bank_idx = ctx->finalized_idx;
963 0 : } else {
964 0 : _bank_idx = ctx->finalized_idx;
965 0 : }
966 0 : if( FD_UNLIKELY( _bank_idx==ULONG_MAX ) ) {
967 0 : CSTR_JSON( id, id_cstr );
968 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
969 0 : return 0;
970 0 : }
971 0 : *bank_idx = _bank_idx;
972 :
973 0 : if( FD_LIKELY( has_encoding ) ) {
974 0 : cJSON const * encoding = cJSON_GetObjectItemCaseSensitive( config, "encoding" );
975 :
976 0 : if( FD_UNLIKELY( cJSON_IsNumber( encoding ) || cJSON_IsBool( encoding ) ) ) {
977 0 : CSTR_JSON( id, id_cstr );
978 0 : CSTR_JSON( encoding, encoding_cstr );
979 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected string or map.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding ), encoding_cstr, id_cstr );
980 0 : return 0;
981 0 : }
982 0 : if( FD_UNLIKELY( cJSON_IsObject( encoding ) && !(encoding->child && encoding->child->next==NULL) ) ) {
983 0 : CSTR_JSON( id, id_cstr );
984 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 );
985 0 : return 0;
986 0 : }
987 0 : if( FD_UNLIKELY( encoding && !cJSON_IsString( encoding ) && !cJSON_IsNull( encoding ) && !cJSON_IsObject( encoding ) ) ) {
988 0 : CSTR_JSON( id, id_cstr );
989 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected string or map.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding ), id_cstr );
990 0 : return 0;
991 0 : }
992 :
993 0 : char const * encoding_cstr;
994 0 : if( FD_UNLIKELY( cJSON_IsObject( encoding ) ) ) {
995 0 : if( cJSON_HasObjectItem( encoding, "binary" ) ) encoding_cstr = "binary";
996 0 : else if( cJSON_HasObjectItem( encoding, "base58" ) ) encoding_cstr = "base58";
997 0 : else if( cJSON_HasObjectItem( encoding, "base64" ) ) encoding_cstr = "base64";
998 0 : else if( cJSON_HasObjectItem( encoding, "base64+zstd" ) ) encoding_cstr = "base64+zstd";
999 0 : else if( cJSON_HasObjectItem( encoding, "jsonParsed" ) ) encoding_cstr = "jsonParsed";
1000 0 : else {
1001 0 : cJSON * _key_node = cJSON_CreateString( encoding->child->string );
1002 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( _key_node, key_esc );
1003 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: unknown variant `%s`, expected one of `binary`, `base58`, `base64`, `jsonParsed`, `base64+zstd`.\"},\"id\":%s}\n", key_esc, id_cstr );
1004 0 : cJSON_Delete( _key_node );
1005 0 : return 0;
1006 0 : }
1007 0 : } else {
1008 0 : encoding_cstr = encoding && cJSON_IsString( encoding ) ? encoding->valuestring : "binary";
1009 0 : }
1010 :
1011 0 : if( FD_UNLIKELY( cJSON_IsObject( encoding ) && (cJSON_IsNumber( encoding->child ) || cJSON_IsBool( encoding->child )) ) ) {
1012 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( encoding->child, child_cstr );
1013 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected unit.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding->child ), child_cstr, id_cstr );
1014 0 : return 0;
1015 0 : }
1016 0 : if( FD_UNLIKELY( cJSON_IsObject( encoding ) && cJSON_IsString( encoding->child ) ) ) {
1017 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( encoding->child, encoding_child_esc );
1018 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected unit.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding->child ), encoding_child_esc, id_cstr );
1019 0 : return 0;
1020 0 : }
1021 0 : if( FD_UNLIKELY( cJSON_IsObject( encoding ) && !cJSON_IsNull( encoding->child ) ) ) {
1022 0 : CSTR_JSON( id, id_cstr );
1023 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected unit.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding->child ), id_cstr );
1024 0 : return 0;
1025 0 : }
1026 :
1027 0 : if( 0==strcmp( encoding_cstr, "jsonParsed" ) ) {
1028 0 : CSTR_JSON( id, id_cstr );
1029 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: jsonParsed is unsupported\"},\"id\":%s}\n", id_cstr );
1030 0 : return 0;
1031 0 : } else if( 0!=strcmp( encoding_cstr, "binary" ) && 0!=strcmp( encoding_cstr, "base58" ) && 0!=strcmp( encoding_cstr, "base64" ) && 0!=strcmp( encoding_cstr, "base64+zstd" ) ) {
1032 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( encoding, encoding_esc );
1033 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: unknown variant `%s`, expected one of `binary`, `base58`, `base64`, `jsonParsed`, `base64+zstd`.\"},\"id\":%s}\n", encoding_esc, id_cstr );
1034 0 : return 0;
1035 0 : }
1036 :
1037 0 : if( FD_LIKELY( opt_encoding_cstr ) ) *opt_encoding_cstr = encoding_cstr;
1038 0 : }
1039 :
1040 0 : if( FD_LIKELY( has_data_slice ) ) {
1041 0 : const cJSON * dataSlice = cJSON_GetObjectItemCaseSensitive( config, "dataSlice" );
1042 :
1043 0 : if( FD_UNLIKELY( cJSON_IsNumber( dataSlice ) || cJSON_IsBool( dataSlice ) ) ) {
1044 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( dataSlice, data_slice_cstr );
1045 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected struct UiDataSliceConfig.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( dataSlice ), data_slice_cstr, id_cstr );
1046 0 : return 0;
1047 0 : }
1048 0 : if( FD_UNLIKELY( cJSON_IsString( dataSlice ) ) ) {
1049 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( dataSlice, data_slice_esc );
1050 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected struct UiDataSliceConfig.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( dataSlice ), data_slice_esc, id_cstr );
1051 0 : return 0;
1052 0 : }
1053 0 : if( FD_UNLIKELY( dataSlice && !cJSON_IsObject( dataSlice ) && !cJSON_IsNull( dataSlice ) && !cJSON_IsArray( dataSlice ) ) ) {
1054 0 : CSTR_JSON( id, id_cstr );
1055 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected struct UiDataSliceConfig.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( dataSlice ), id_cstr );
1056 0 : return 0;
1057 0 : }
1058 :
1059 0 : int has_offset = cJSON_IsObject( dataSlice ) && cJSON_HasObjectItem( dataSlice, "offset" );
1060 0 : int has_length = cJSON_IsObject( dataSlice ) && cJSON_HasObjectItem( dataSlice, "length" );
1061 :
1062 0 : cJSON const * _length = NULL;
1063 0 : cJSON const * _offset = NULL;
1064 0 : if( cJSON_IsObject( dataSlice ) ) {
1065 0 : _length = cJSON_GetObjectItemCaseSensitive( dataSlice, "length" );
1066 0 : _offset = cJSON_GetObjectItemCaseSensitive( dataSlice, "offset" );
1067 0 : } else if( FD_UNLIKELY( cJSON_IsArray( dataSlice ) ) ) {
1068 0 : _offset = cJSON_GetArrayItem( dataSlice, 0 );
1069 0 : _length = cJSON_GetArrayItem( dataSlice, 1 );
1070 0 : }
1071 :
1072 0 : if( FD_UNLIKELY( cJSON_IsBool( _offset ) || (cJSON_IsNumber( _offset ) && !fd_rpc_cjson_is_integer( _offset )) ) ) {
1073 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( _offset, offset_cstr );
1074 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _offset ), offset_cstr, id_cstr );
1075 0 : return 0;
1076 0 : }
1077 0 : if( FD_UNLIKELY( cJSON_IsBool( _length ) || (cJSON_IsNumber( _length ) && !fd_rpc_cjson_is_integer( _length )) ) ) {
1078 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( _length, length_cstr );
1079 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _length ), length_cstr, id_cstr );
1080 0 : return 0;
1081 0 : }
1082 :
1083 0 : if( FD_UNLIKELY( cJSON_IsNumber( _offset ) && _offset->valueint<0 ) ) {
1084 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( _offset, offset_cstr );
1085 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _offset ), offset_cstr, id_cstr );
1086 0 : return 0;
1087 0 : }
1088 0 : if( FD_UNLIKELY( cJSON_IsNumber( _length ) && _length->valueint<0 ) ) {
1089 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( _length, length_cstr );
1090 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _length ), length_cstr, id_cstr );
1091 0 : return 0;
1092 0 : }
1093 :
1094 0 : if( FD_UNLIKELY( cJSON_IsString( _offset ) ) ) {
1095 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( _offset, offset_esc );
1096 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _offset ), offset_esc, id_cstr );
1097 0 : return 0;
1098 0 : }
1099 0 : if( FD_UNLIKELY( cJSON_IsString( _length ) ) ) {
1100 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( _length, length_esc );
1101 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _length ), length_esc, id_cstr );
1102 0 : return 0;
1103 0 : }
1104 :
1105 0 : if( FD_UNLIKELY( _offset && !fd_rpc_cjson_is_integer( _offset ) ) ) {
1106 0 : CSTR_JSON( id, id_cstr );
1107 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _offset ), id_cstr );
1108 0 : return 0;
1109 0 : }
1110 0 : if( FD_UNLIKELY( _length && !fd_rpc_cjson_is_integer( _length ) ) ) {
1111 0 : CSTR_JSON( id, id_cstr );
1112 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _length ), id_cstr );
1113 0 : return 0;
1114 0 : }
1115 :
1116 0 : if( FD_UNLIKELY( cJSON_IsObject( dataSlice ) && !has_offset ) ) {
1117 0 : CSTR_JSON( id, id_cstr );
1118 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `offset`.\"},\"id\":%s}\n", id_cstr );
1119 0 : return 0;
1120 0 : }
1121 0 : if( FD_UNLIKELY( cJSON_IsObject( dataSlice ) && !has_length ) ) {
1122 0 : CSTR_JSON( id, id_cstr );
1123 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `length`.\"},\"id\":%s}\n", id_cstr );
1124 0 : return 0;
1125 0 : }
1126 :
1127 0 : if( FD_UNLIKELY( cJSON_IsArray( dataSlice ) && cJSON_GetArraySize( dataSlice )!=2 ) ) {
1128 0 : CSTR_JSON( id, id_cstr );
1129 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", (ulong)cJSON_GetArraySize( dataSlice ), id_cstr );
1130 0 : return 0;
1131 0 : }
1132 :
1133 0 : if( dataSlice && !cJSON_IsNull( dataSlice ) ) {
1134 0 : if( FD_LIKELY( opt_slice_offset ) ) *opt_slice_offset = _offset ? _offset->valueulong : 0UL;
1135 0 : if( FD_LIKELY( opt_slice_length ) ) *opt_slice_length = _length ? _length->valueulong : ULONG_MAX;
1136 0 : } else {
1137 0 : if( FD_LIKELY( opt_slice_offset ) ) *opt_slice_offset = 0UL;
1138 0 : if( FD_LIKELY( opt_slice_length ) ) *opt_slice_length = ULONG_MAX;
1139 0 : }
1140 0 : }
1141 :
1142 0 : if( FD_LIKELY( has_min_context_slot ) ) {
1143 0 : ulong minContextSlot = 0UL;
1144 0 : cJSON const * _minContextSlot = cJSON_GetObjectItemCaseSensitive( config, "minContextSlot" );
1145 0 : if( FD_UNLIKELY( cJSON_IsBool( _minContextSlot ) || (cJSON_IsNumber( _minContextSlot ) && !fd_rpc_cjson_is_integer( _minContextSlot )) ) ) {
1146 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( _minContextSlot, min_context_slot_cstr );
1147 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected u64.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _minContextSlot ), min_context_slot_cstr, id_cstr );
1148 0 : return 0;
1149 0 : }
1150 :
1151 0 : if( FD_UNLIKELY( cJSON_IsNumber( _minContextSlot ) && _minContextSlot->valueint<0 ) ) {
1152 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( _minContextSlot, min_context_slot_cstr );
1153 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: %s `%s`, expected u64.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _minContextSlot ), min_context_slot_cstr, id_cstr );
1154 0 : return 0;
1155 0 : }
1156 :
1157 0 : if( FD_UNLIKELY( cJSON_IsString( _minContextSlot ) ) ) {
1158 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( _minContextSlot, min_ctx_slot_esc );
1159 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected u64.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _minContextSlot ), min_ctx_slot_esc, id_cstr );
1160 0 : return 0;
1161 0 : }
1162 :
1163 0 : if( FD_UNLIKELY( _minContextSlot && !cJSON_IsNull( _minContextSlot ) && !fd_rpc_cjson_is_integer( _minContextSlot ) ) ) {
1164 0 : CSTR_JSON( id, id_cstr );
1165 0 : *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected u64.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _minContextSlot ), id_cstr );
1166 0 : return 0;
1167 0 : }
1168 :
1169 0 : minContextSlot = _minContextSlot && fd_rpc_cjson_is_integer( _minContextSlot ) ? _minContextSlot->valueulong : 0UL;
1170 :
1171 0 : if( _bank_idx!=ULONG_MAX && ctx->banks[ _bank_idx ].slot<minContextSlot ) {
1172 0 : CSTR_JSON( id, id_cstr );
1173 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 );
1174 0 : return 0;
1175 0 : }
1176 0 : }
1177 :
1178 0 : return 1;
1179 0 : }
1180 :
1181 : static int
1182 : fd_rpc_validate_address( fd_rpc_tile_t * ctx,
1183 : cJSON const * id,
1184 : cJSON const * address_in,
1185 : fd_pubkey_t * address_out,
1186 0 : fd_http_server_response_t * response ) {
1187 0 : FD_TEST( address_in );
1188 0 : if( FD_UNLIKELY( cJSON_IsNumber( address_in ) || cJSON_IsBool( address_in ) ) ) {
1189 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( address_in, address_in_cstr );
1190 0 : *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected a string.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( address_in ), address_in_cstr, id_cstr );
1191 0 : return 0;
1192 0 : }
1193 0 : if( FD_UNLIKELY( !cJSON_IsString( address_in ) ) ) {
1194 0 : CSTR_JSON( id, id_cstr );
1195 0 : *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected a string.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( address_in ), id_cstr );
1196 0 : return 0;
1197 0 : }
1198 0 : int invalid_char = fd_rpc_cstr_contains_non_base58( address_in->valuestring );
1199 0 : if( FD_UNLIKELY( invalid_char ) ) {
1200 0 : CSTR_JSON( id, id_cstr );
1201 0 : *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: Invalid\"},\"id\":%s}\n", id_cstr );
1202 0 : return 0;
1203 0 : }
1204 0 : int valid = !!fd_base58_decode_32( address_in->valuestring, address_out->uc );
1205 0 : if( FD_UNLIKELY( !valid ) ) {
1206 0 : CSTR_JSON( id, id_cstr );
1207 0 : *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: WrongSize\"},\"id\":%s}\n", id_cstr );
1208 0 : return 0;
1209 0 : }
1210 :
1211 0 : return 1;
1212 0 : }
1213 :
1214 : #define UNIMPLEMENTED(X) \
1215 : static fd_http_server_response_t \
1216 : X( fd_rpc_tile_t * ctx, \
1217 : cJSON const * id, \
1218 0 : cJSON const * params ) { \
1219 0 : (void)ctx; (void)id; (void)params; \
1220 0 : return (fd_http_server_response_t){ .status = 501 }; \
1221 0 : }
1222 :
1223 : UNIMPLEMENTED(getBlock)
1224 : UNIMPLEMENTED(getBlockCommitment)
1225 :
1226 : /* fd_rpc_encode_account_data encodes account data fields
1227 : (executable, lamports, owner, rentEpoch, space, data) into the http
1228 : staging buffer. Returns 1 on success. On failure, calls
1229 : fd_http_server_unstage and writes an error response into
1230 : err_response. */
1231 : static int
1232 : fd_rpc_encode_account_data( fd_rpc_tile_t * ctx,
1233 : uchar const * acct_data,
1234 : ulong acct_data_len,
1235 : uchar const * acct_owner,
1236 : ulong acct_lamports,
1237 : int acct_executable,
1238 : char const * encoding_cstr,
1239 : ulong slice_offset,
1240 : ulong slice_length,
1241 : char const * id_cstr,
1242 0 : fd_http_server_response_t * err_response ) {
1243 :
1244 0 : int is_binary = !strcmp( encoding_cstr, "binary" );
1245 0 : int is_base58 = !strcmp( encoding_cstr, "base58" );
1246 0 : int is_zstd = !strcmp( encoding_cstr, "base64+zstd" );
1247 :
1248 0 : ulong data_sz = acct_data_len;
1249 0 : uchar const * out = acct_data + fd_ulong_if( slice_offset<data_sz, slice_offset, 0UL );
1250 0 : ulong snip_sz = fd_ulong_min( fd_ulong_if( slice_offset<data_sz, data_sz-slice_offset, 0UL ), slice_length );
1251 0 : ulong out_sz = snip_sz;
1252 :
1253 0 : if( FD_UNLIKELY( (is_binary || is_base58) && snip_sz>128UL ) ) {
1254 0 : fd_http_server_unstage( ctx->http );
1255 0 : *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Encoded binary (base 58) data should be less than {MAX_BASE58_BYTES} bytes, please use Base64 encoding.\"},\"id\":%s}\n", id_cstr );
1256 0 : return 0;
1257 0 : }
1258 :
1259 0 : # if FD_HAS_ZSTD
1260 0 : if( is_zstd ) {
1261 0 : ulong zstd_res = ZSTD_compress( ctx->compress_buf, sizeof(ctx->compress_buf), out, snip_sz, 0 );
1262 0 : if( ZSTD_isError( zstd_res ) ) {
1263 0 : fd_http_server_unstage( ctx->http );
1264 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 );
1265 0 : return 0;
1266 0 : }
1267 0 : out = ctx->compress_buf;
1268 0 : out_sz = (ulong)zstd_res;
1269 0 : }
1270 : # else
1271 : if( is_zstd ) {
1272 : fd_http_server_unstage( ctx->http );
1273 : *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: zstandard is disabled\"},\"id\":%s}\n", id_cstr );
1274 : return 0;
1275 : }
1276 : # endif
1277 :
1278 0 : FD_BASE58_ENCODE_32_BYTES( acct_owner, owner_b58 );
1279 0 : fd_http_server_printf( ctx->http,
1280 0 : "{\"executable\":%s,\"lamports\":%lu,\"owner\":\"%s\",\"rentEpoch\":18446744073709551615,\"space\":%lu,\"data\":",
1281 0 : acct_executable ? "true" : "false",
1282 0 : acct_lamports,
1283 0 : owner_b58,
1284 0 : data_sz );
1285 :
1286 0 : ulong encoded_sz = fd_ulong_if( is_base58 || is_binary, FD_RPC_BASE58_ENCODED_128_LEN, FD_BASE64_ENC_SZ( out_sz ) );
1287 0 : if( FD_UNLIKELY( is_binary ) ) {
1288 0 : fd_http_server_printf( ctx->http, "\"" );
1289 0 : } else {
1290 0 : fd_http_server_printf( ctx->http, "[\"" );
1291 0 : }
1292 :
1293 0 : uchar * encoded = fd_http_server_append_start( ctx->http, encoded_sz );
1294 0 : if( FD_UNLIKELY( !encoded ) ) {
1295 0 : fd_http_server_unstage( ctx->http );
1296 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 );
1297 0 : return 0;
1298 0 : }
1299 :
1300 0 : if( FD_UNLIKELY( is_base58 || is_binary ) ) {
1301 0 : if( FD_UNLIKELY( !fd_rpc_base58_encode_128( (char *)encoded, &encoded_sz, out, out_sz ) ) ) {
1302 0 : fd_http_server_unstage( ctx->http );
1303 0 : FD_LOG_WARNING(( "base58 encode failed out_sz=%lu", out_sz ));
1304 0 : *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: base58 encode failed\"},\"id\":%s}\n", id_cstr );
1305 0 : return 0;
1306 0 : }
1307 0 : } else {
1308 0 : encoded_sz = fd_base64_encode( (char *)encoded, out, out_sz );
1309 0 : }
1310 :
1311 0 : fd_http_server_append_end( ctx->http, encoded_sz );
1312 :
1313 0 : if( FD_UNLIKELY( is_binary ) ) fd_http_server_printf( ctx->http, "\"}" );
1314 0 : else fd_http_server_printf( ctx->http, "\",\"%s\"]}", encoding_cstr );
1315 :
1316 0 : return 1;
1317 0 : }
1318 :
1319 : static fd_http_server_response_t
1320 : getAccountInfo( fd_rpc_tile_t * ctx,
1321 : cJSON const * id,
1322 0 : cJSON const * params ) {
1323 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_ACCOUNT_INFO, 1UL );
1324 :
1325 0 : fd_http_server_response_t response;
1326 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 2, &response ) ) ) return response;
1327 :
1328 0 : fd_pubkey_t address;
1329 0 : cJSON const * acct_pubkey = cJSON_GetArrayItem( params, 0 );
1330 0 : if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id, acct_pubkey, &address, &response ) ) ) return response;
1331 :
1332 0 : ulong bank_idx = ULONG_MAX;
1333 0 : char const * encoding_cstr = NULL;
1334 0 : ulong slice_length = ULONG_MAX;
1335 0 : ulong slice_offset = 0;
1336 0 : cJSON const * config = cJSON_GetArrayItem( params, 1 );
1337 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcAccountInfoConfig",
1338 0 : 1, /* has_commitment */
1339 0 : 1, /* has_encoding */
1340 0 : 1, /* has_data_slice */
1341 0 : 1, /* has_min_context_slot */
1342 0 : &bank_idx,
1343 0 : &encoding_cstr,
1344 0 : &slice_length,
1345 0 : &slice_offset,
1346 0 : &response );
1347 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1348 :
1349 0 : bank_info_t * info = &ctx->banks[ bank_idx ];
1350 0 : ulong acct_lamports;
1351 0 : int acct_executable;
1352 0 : uchar acct_owner[ 32UL ];
1353 0 : ulong acct_data_len;
1354 0 : fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, address.uc,
1355 0 : &acct_lamports, &acct_executable, acct_owner,
1356 0 : ctx->accdb_data_buf, &acct_data_len );
1357 0 : if( FD_UNLIKELY( !acct_lamports ) ) {
1358 0 : CSTR_JSON( id, id_cstr );
1359 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":%lu},\"value\":null},\"id\":%s}\n", info->slot, id_cstr );
1360 0 : }
1361 :
1362 0 : CSTR_JSON( id, id_cstr );
1363 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 );
1364 :
1365 0 : fd_http_server_response_t err_response;
1366 0 : if( FD_UNLIKELY( !fd_rpc_encode_account_data( ctx, ctx->accdb_data_buf, acct_data_len, acct_owner, acct_lamports, acct_executable, encoding_cstr, slice_offset, slice_length, id_cstr, &err_response ) ) ) {
1367 0 : return err_response;
1368 0 : }
1369 :
1370 0 : fd_http_server_printf( ctx->http, "}}\n" );
1371 0 : return STAGE_JSON( ctx );
1372 0 : }
1373 :
1374 : static fd_http_server_response_t
1375 : getBalance( fd_rpc_tile_t * ctx,
1376 : cJSON const * id,
1377 0 : cJSON const * params ) {
1378 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_BALANCE, 1UL );
1379 :
1380 0 : fd_http_server_response_t response;
1381 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 2, &response ) ) ) return response;
1382 :
1383 0 : fd_pubkey_t address;
1384 0 : cJSON const * acct_pubkey = cJSON_GetArrayItem( params, 0 );
1385 0 : if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id, acct_pubkey, &address, &response ) ) ) return response;
1386 :
1387 0 : ulong bank_idx = ULONG_MAX;
1388 0 : cJSON const * config = cJSON_GetArrayItem( params, 1 );
1389 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcContextConfig",
1390 0 : 1, /* has_commitment */
1391 0 : 0, /* has_encoding */
1392 0 : 0, /* has_data_slice */
1393 0 : 1, /* has_min_context_slot */
1394 0 : &bank_idx,
1395 0 : NULL,
1396 0 : NULL,
1397 0 : NULL,
1398 0 : &response );
1399 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1400 :
1401 0 : ulong balance = fd_accdb_lamports( ctx->accdb, ctx->banks[ bank_idx ].accdb_fork_id, address.uc );
1402 :
1403 0 : CSTR_JSON( id, id_cstr );
1404 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 );
1405 0 : }
1406 :
1407 : static fd_http_server_response_t
1408 : getBlockHeight( fd_rpc_tile_t * ctx,
1409 : cJSON const * id,
1410 0 : cJSON const * params ) {
1411 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_BLOCK_HEIGHT, 1UL );
1412 :
1413 0 : fd_http_server_response_t response;
1414 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
1415 :
1416 0 : ulong bank_idx = ULONG_MAX;
1417 0 : cJSON const * config = cJSON_GetArrayItem( params, 0 );
1418 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcContextConfig",
1419 0 : 1, /* has_commitment */
1420 0 : 0, /* has_encoding */
1421 0 : 0, /* has_data_slice */
1422 0 : 1, /* has_min_context_slot */
1423 0 : &bank_idx,
1424 0 : NULL,
1425 0 : NULL,
1426 0 : NULL,
1427 0 : &response );
1428 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1429 :
1430 0 : CSTR_JSON( id, id_cstr );
1431 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", ctx->banks[ bank_idx ].block_height, id_cstr );
1432 0 : }
1433 :
1434 : UNIMPLEMENTED(getBlockProduction) // TODO: Used by solana-exporter
1435 : UNIMPLEMENTED(getBlocks)
1436 : UNIMPLEMENTED(getBlocksWithLimit)
1437 : UNIMPLEMENTED(getBlockTime)
1438 :
1439 : static fd_http_server_response_t
1440 : getClusterNodes( fd_rpc_tile_t * ctx,
1441 : cJSON const * id,
1442 0 : cJSON const * params ) {
1443 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_CLUSTER_NODES, 1UL );
1444 :
1445 0 : fd_http_server_response_t response;
1446 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
1447 :
1448 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":[" );
1449 :
1450 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 );
1451 0 : !fd_rpc_cluster_node_dlist_iter_done( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes );
1452 0 : iter = fd_rpc_cluster_node_dlist_iter_rev_next( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes ) ) {
1453 0 : fd_rpc_cluster_node_t * ele = fd_rpc_cluster_node_dlist_iter_ele( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes );
1454 0 : FD_BASE58_ENCODE_32_BYTES( ele->identity.uc, identity_cstr );
1455 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 );
1456 :
1457 0 : fd_http_server_printf( ctx->http, "{\"featureSet\":%u,", ele->ci->version.feature_set );
1458 :
1459 0 : for( ulong i=0UL; i<FD_GOSSIP_CONTACT_INFO_SOCKET_CNT; i++ ) {
1460 0 : char const * name;
1461 0 : switch( i ) {
1462 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP: name = "gossip"; break;
1463 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR_QUIC: name = NULL; break;
1464 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_RPC: name = "rpc"; break;
1465 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_RPC_PUBSUB: name = "pubsub"; break;
1466 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR: name = "serveRepair"; break;
1467 : /* Even though Agave does not support "tpu" and "tpuForwards"
1468 : (hardcoded null), frankendacer/firedancer still support
1469 : UDP-TPU. */
1470 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU: name = "tpu"; break;
1471 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_FORWARDS: name = "tpuForwards"; break;
1472 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_FORWARDS_QUIC: name = "tpuForwardsQuic"; break;
1473 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_QUIC: name = "tpuQuic"; break;
1474 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE: name = "tpuVote"; break;
1475 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TVU: name = "tvu"; break;
1476 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TVU_QUIC: name = NULL; break;
1477 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE_QUIC: name = NULL; break;
1478 0 : case FD_GOSSIP_CONTACT_INFO_SOCKET_ALPENGLOW: name = NULL; break;
1479 0 : default: FD_LOG_ERR(( "unreachable "));
1480 0 : }
1481 0 : if( FD_UNLIKELY( !name ) ) continue;
1482 :
1483 0 : uint ip4 = ele->ci->sockets[ i ].is_ipv6 ? 0U : ele->ci->sockets[ i ].ip4;
1484 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 ) );
1485 0 : else fd_http_server_printf( ctx->http, "\"%s\":null,", name );
1486 0 : }
1487 0 : fd_http_server_printf( ctx->http, "\"pubkey\":\"%s\",", identity_cstr );
1488 0 : fd_http_server_printf( ctx->http, "\"shredVersion\":%u,", ele->ci->shred_version );
1489 :
1490 0 : char version[ 64UL ];
1491 0 : FD_TEST( fd_gossip_version_cstr( ele->ci->version.major, ele->ci->version.minor, ele->ci->version.patch, version, sizeof( version ) ) );
1492 0 : fd_http_server_printf( ctx->http, "\"version\":\"%s\",", version );
1493 :
1494 0 : char const * client_id;
1495 0 : switch( ele->ci->version.client ) {
1496 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_SOLANA_LABS: client_id = "SolanaLabs"; break;
1497 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_JITO_LABS: client_id = "JitoLabs"; break;
1498 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_FRANKENDANCER: client_id = "Frankendancer"; break;
1499 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE: client_id = "Agave"; break;
1500 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE_PALADIN: client_id = "AgavePaladin"; break;
1501 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_FIREDANCER: client_id = "Firedancer"; break;
1502 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE_BAM: client_id = "AgaveBam"; break;
1503 0 : case FD_GOSSIP_CONTACT_INFO_CLIENT_SIG: client_id = "Sig"; break;
1504 0 : default: client_id = NULL; break;
1505 0 : }
1506 0 : if( FD_LIKELY( client_id ) ) fd_http_server_printf( ctx->http, "\"clientId\":\"%s\"", client_id );
1507 0 : else fd_http_server_printf( ctx->http, "\"clientId\":\"Unknown(%hu)\"", ele->ci->version.client );
1508 :
1509 0 : if( FD_UNLIKELY( is_last ) ) fd_http_server_printf( ctx->http, "}" );
1510 0 : else fd_http_server_printf( ctx->http, "}," );
1511 0 : }
1512 :
1513 0 : CSTR_JSON( id, id_cstr );
1514 0 : fd_http_server_printf( ctx->http, "],\"id\":%s}\n", id_cstr );
1515 0 : return STAGE_JSON( ctx );
1516 0 : }
1517 :
1518 : static fd_http_server_response_t
1519 : getEpochInfo( fd_rpc_tile_t * ctx,
1520 : cJSON const * id,
1521 0 : cJSON const * params ) {
1522 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_EPOCH_INFO, 1UL );
1523 :
1524 0 : fd_http_server_response_t response;
1525 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
1526 :
1527 0 : ulong bank_idx = ULONG_MAX;
1528 0 : cJSON const * config = cJSON_GetArrayItem( params, 0 );
1529 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcContextConfig",
1530 0 : 1, /* has_commitment */
1531 0 : 0, /* has_encoding */
1532 0 : 0, /* has_data_slice */
1533 0 : 1, /* has_min_context_slot */
1534 0 : &bank_idx,
1535 0 : NULL,
1536 0 : NULL,
1537 0 : NULL,
1538 0 : &response );
1539 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1540 :
1541 0 : CSTR_JSON( id, id_cstr );
1542 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 );
1543 0 : }
1544 :
1545 : UNIMPLEMENTED(getEpochSchedule)
1546 : UNIMPLEMENTED(getFeeForMessage)
1547 : UNIMPLEMENTED(getFirstAvailableBlock) // TODO: Used by solana-exporter
1548 :
1549 : /* Get the genesis hash of the cluster. Firedancer deviates slightly
1550 : from Agave here, as the genesis hash is not always known when RPC
1551 : is first booted, it may need to be determined asynchronously in the
1552 : background, fetched from a peer node. If the genesis hash is not yet
1553 : known, we return an error indicating no snapshot is available. */
1554 :
1555 : static fd_http_server_response_t
1556 : getGenesisHash( fd_rpc_tile_t * ctx,
1557 : cJSON const * id,
1558 0 : cJSON const * params ) {
1559 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_GENESIS_HASH, 1UL );
1560 :
1561 0 : fd_http_server_response_t response;
1562 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
1563 :
1564 0 : if( FD_UNLIKELY( !ctx->has_genesis_hash ) ) {
1565 0 : CSTR_JSON( id, id_cstr );
1566 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 );
1567 0 : }
1568 :
1569 0 : FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, genesis_hash_b58 );
1570 0 : CSTR_JSON( id, id_cstr );
1571 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%s}\n", genesis_hash_b58, id_cstr );
1572 0 : }
1573 :
1574 : /* Determines if the node is healthy. Agave defines this as follows,
1575 :
1576 : - On boot, nodes must go through the entire snapshot slot database
1577 : and hash everything, and once it's done verify the hash matches.
1578 : While this is ongoing, the node is unhealthy with a "slotsBehind"
1579 : value of null.
1580 :
1581 : - On boot, if the cluster is restarting and we are currently waiting
1582 : for a supermajority of stake to join gossip to proceed with
1583 : booting, the node is forcibly marked as healthy, to, per Agave,
1584 :
1585 : > prevent load balancers from removing the node from their list
1586 : > of candidates during a manual restart
1587 :
1588 : - In addition, once booted, there is a period where we do not yet
1589 : know the cluster confirmed slot, because we have not yet observed
1590 : any (or enough) votes arrive from peers in the cluster. During
1591 : this period the node is unhealthy with a "slotsBehind" value of
1592 : null.
1593 :
1594 : - Finally, once the cluster confirmed slot is known, which is the
1595 : highest optimistically confirmed slot observed from both gossip,
1596 : and votes procesed in blocks, it is compared to our own
1597 : optimistically confirmed slot, which is just the highest slot down
1598 : the cluster confirmed fork that we have finished replaying
1599 : locally. The difference between these two slots is compared, and
1600 : if it is less than or equal to 128, the node is healthy, otherwise
1601 : it is unhealthy with a "slotsBehind" value equal to the
1602 : difference.
1603 :
1604 : Firedancer currently only implements the final two checks, and does
1605 : not forcibly mark the node as healthy while waiting for a
1606 : supermajority, nor does it mark a node as unhealthy while hashing the
1607 : snapshot database on boot. Firedancer hashes snapshots so quickly
1608 : that the node will die on boot if the hash is not valid. */
1609 :
1610 : static inline int
1611 0 : _getHealth( fd_rpc_tile_t * ctx ) {
1612 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_HEALTH, 1UL );
1613 :
1614 : /* fd_http_server_listen is not called until after RPC has initialized banks */
1615 0 : if( FD_UNLIKELY( ctx->confirmed_idx==ULONG_MAX ) ) return FD_RPC_HEALTH_STATUS_UNKNOWN;
1616 0 : if( FD_UNLIKELY( ctx->cluster_confirmed_slot==ULONG_MAX ) ) return FD_RPC_HEALTH_STATUS_UNKNOWN;
1617 :
1618 0 : ulong slots_behind = fd_ulong_sat_sub( ctx->cluster_confirmed_slot, ctx->banks[ ctx->confirmed_idx ].slot );
1619 0 : if( FD_LIKELY( slots_behind<=128UL ) ) return FD_RPC_HEALTH_STATUS_OK;
1620 0 : else return FD_RPC_HEALTH_STATUS_BEHIND;
1621 0 : }
1622 :
1623 : static fd_http_server_response_t
1624 : getHealth( fd_rpc_tile_t * ctx,
1625 : cJSON const * id,
1626 0 : cJSON const * params ) {
1627 0 : fd_http_server_response_t response;
1628 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
1629 :
1630 : // TODO: We should probably implement the same waiting_for_supermajority
1631 : // logic to conform with Agave here.
1632 :
1633 0 : int health_status = _getHealth( ctx );
1634 :
1635 0 : CSTR_JSON( id, id_cstr );
1636 0 : switch( health_status ) {
1637 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 );
1638 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 );
1639 0 : case FD_RPC_HEALTH_STATUS_OK: return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"ok\",\"id\":%s}\n", id_cstr );
1640 0 : default: FD_LOG_ERR(( "unknown health status" ));
1641 0 : }
1642 0 : }
1643 :
1644 : UNIMPLEMENTED(getHighestSnapshotSlot)
1645 :
1646 : static fd_http_server_response_t
1647 : getIdentity( fd_rpc_tile_t * ctx,
1648 : cJSON const * id,
1649 0 : cJSON const * params ) {
1650 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_IDENTITY, 1UL );
1651 :
1652 0 : fd_http_server_response_t response;
1653 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
1654 :
1655 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_pubkey, identity_pubkey_b58 );
1656 0 : CSTR_JSON( id, id_cstr );
1657 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"identity\":\"%s\"},\"id\":%s}\n", identity_pubkey_b58, id_cstr );
1658 0 : }
1659 :
1660 : static fd_http_server_response_t
1661 : getInflationGovernor( fd_rpc_tile_t * ctx,
1662 : cJSON const * id,
1663 0 : cJSON const * params ) {
1664 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_INFLATION_GOVERNOR, 1UL );
1665 :
1666 0 : fd_http_server_response_t response;
1667 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
1668 :
1669 0 : ulong bank_idx = ULONG_MAX;
1670 0 : cJSON const * config = cJSON_GetArrayItem( params, 0 );
1671 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
1672 0 : 1, /* has_commitment */
1673 0 : 0, /* has_encoding */
1674 0 : 0, /* has_data_slice */
1675 0 : 0, /* has_min_context_slot */
1676 0 : &bank_idx,
1677 0 : NULL,
1678 0 : NULL,
1679 0 : NULL,
1680 0 : &response );
1681 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1682 :
1683 0 : bank_info_t const * bank = &ctx->banks[ bank_idx ];
1684 0 : CSTR_JSON( id, id_cstr );
1685 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",
1686 0 : bank->inflation.foundation, bank->inflation.foundation==0 ? ".0" : "",
1687 0 : bank->inflation.foundation_term, bank->inflation.foundation_term==0 ? ".0" : "",
1688 0 : bank->inflation.initial, bank->inflation.initial==0 ? ".0" : "",
1689 0 : bank->inflation.taper, bank->inflation.taper==0 ? ".0" : "",
1690 0 : bank->inflation.terminal, bank->inflation.terminal==0 ? ".0" : "",
1691 0 : id_cstr );
1692 0 : }
1693 :
1694 : UNIMPLEMENTED(getInflationRate)
1695 : UNIMPLEMENTED(getInflationReward) // TODO: Used by solana-exporter
1696 : UNIMPLEMENTED(getLargestAccounts)
1697 :
1698 : static fd_http_server_response_t
1699 : getLatestBlockhash( fd_rpc_tile_t * ctx,
1700 : cJSON const * id,
1701 0 : cJSON const * params ) {
1702 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_LATEST_BLOCKHASH, 1UL );
1703 :
1704 0 : if( FD_UNLIKELY( ctx->processed_idx==ULONG_MAX ) ) {
1705 0 : CSTR_JSON( id, id_cstr );
1706 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
1707 0 : }
1708 :
1709 0 : fd_http_server_response_t response;
1710 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
1711 :
1712 0 : ulong bank_idx = ULONG_MAX;
1713 0 : cJSON const * config = cJSON_GetArrayItem( params, 0 );
1714 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
1715 0 : 1, /* has_commitment */
1716 0 : 0, /* has_encoding */
1717 0 : 0, /* has_data_slice */
1718 0 : 1, /* has_min_context_slot */
1719 0 : &bank_idx,
1720 0 : NULL,
1721 0 : NULL,
1722 0 : NULL,
1723 0 : &response );
1724 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1725 :
1726 0 : bank_info_t * bank = &ctx->banks[ bank_idx ];
1727 0 : FD_BASE58_ENCODE_32_BYTES( bank->block_hash, block_hash_b58 );
1728 :
1729 0 : CSTR_JSON( id, id_cstr );
1730 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 );
1731 0 : }
1732 :
1733 : UNIMPLEMENTED(getLeaderSchedule) // TODO: Used by solana-exporter
1734 : UNIMPLEMENTED(getMaxRetransmitSlot)
1735 : UNIMPLEMENTED(getMaxShredInsertSlot)
1736 :
1737 : static fd_http_server_response_t
1738 : getMinimumBalanceForRentExemption( fd_rpc_tile_t * ctx,
1739 : cJSON const * id,
1740 0 : cJSON const * params ) {
1741 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION, 1UL );
1742 :
1743 0 : fd_http_server_response_t response;
1744 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 2, &response ) ) ) return response;
1745 :
1746 0 : ulong bank_idx = ULONG_MAX;
1747 0 : cJSON const * config = cJSON_GetArrayItem( params, 1 );
1748 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
1749 0 : 1, /* has_commitment */
1750 0 : 0, /* has_encoding */
1751 0 : 0, /* has_data_slice */
1752 0 : 0, /* has_min_context_slot */
1753 0 : &bank_idx,
1754 0 : NULL,
1755 0 : NULL,
1756 0 : NULL,
1757 0 : &response );
1758 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1759 :
1760 0 : cJSON const * acct_sz = cJSON_GetArrayItem( params, 0 );
1761 :
1762 0 : if( FD_UNLIKELY( cJSON_IsBool( acct_sz ) || (cJSON_IsNumber( acct_sz ) && !fd_rpc_cjson_is_integer( acct_sz )) ) ) {
1763 0 : CSTR_JSON( id, id_cstr );
1764 0 : CSTR_JSON( acct_sz, acct_sz_cstr );
1765 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( acct_sz ), acct_sz_cstr, id_cstr );
1766 0 : }
1767 0 : if( FD_UNLIKELY( cJSON_IsNumber( acct_sz ) && acct_sz->valueint<0 ) ) {
1768 0 : CSTR_JSON( id, id_cstr );
1769 0 : CSTR_JSON( acct_sz, acct_sz_cstr );
1770 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( acct_sz ), acct_sz_cstr, id_cstr );
1771 0 : }
1772 0 : if( FD_UNLIKELY( cJSON_IsString( acct_sz ) ) ) {
1773 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( acct_sz, acct_sz_esc );
1774 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( acct_sz ), acct_sz_esc, id_cstr );
1775 0 : }
1776 0 : if( FD_UNLIKELY( acct_sz && !fd_rpc_cjson_is_integer( acct_sz ) ) ) {
1777 0 : CSTR_JSON( id, id_cstr );
1778 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( acct_sz ), id_cstr );
1779 0 : }
1780 :
1781 0 : bank_info_t const * bank = &ctx->banks[ bank_idx ];
1782 :
1783 0 : fd_rent_t rent = {
1784 0 : .lamports_per_uint8_year = bank->rent.lamports_per_uint8_year,
1785 0 : .exemption_threshold = bank->rent.exemption_threshold,
1786 0 : .burn_percent = bank->rent.burn_percent,
1787 0 : };
1788 0 : ulong minimum = fd_rent_exempt_minimum_balance( &rent, acct_sz->valueulong );
1789 0 : CSTR_JSON( id, id_cstr );
1790 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", minimum, id_cstr );
1791 0 : }
1792 :
1793 : static fd_http_server_response_t
1794 : getMultipleAccounts( fd_rpc_tile_t * ctx,
1795 : cJSON const * id,
1796 0 : cJSON const * params ) {
1797 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_MULTIPLE_ACCOUNTS, 1UL );
1798 :
1799 0 : fd_http_server_response_t response;
1800 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 2, &response ) ) ) return response;
1801 :
1802 0 : cJSON const * keys_arr = cJSON_GetArrayItem( params, 0 );
1803 0 : if( FD_UNLIKELY( cJSON_IsNumber( keys_arr ) || cJSON_IsBool( keys_arr ) ) ) {
1804 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON( keys_arr, keys_arr_cstr );
1805 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected a sequence.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( keys_arr ), keys_arr_cstr, id_cstr );
1806 0 : }
1807 0 : if( FD_UNLIKELY( cJSON_IsString( keys_arr ) ) ) {
1808 0 : CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( keys_arr, keys_arr_esc );
1809 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected a sequence.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( keys_arr ), keys_arr_esc, id_cstr );
1810 0 : }
1811 0 : if( FD_UNLIKELY( !cJSON_IsArray( keys_arr ) ) ) {
1812 0 : CSTR_JSON( id, id_cstr );
1813 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected a sequence.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( keys_arr ), id_cstr );
1814 0 : }
1815 :
1816 0 : int cnt = cJSON_GetArraySize( keys_arr );
1817 0 : if( FD_UNLIKELY( cnt<0 || cnt>100 ) ) {
1818 0 : CSTR_JSON( id, id_cstr );
1819 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Too many accounts provided; max 100\"},\"id\":%s}\n", id_cstr );
1820 0 : }
1821 :
1822 0 : ulong bank_idx = ULONG_MAX;
1823 0 : char const * encoding_cstr = NULL;
1824 0 : ulong slice_length = ULONG_MAX;
1825 0 : ulong slice_offset = 0;
1826 0 : cJSON const * config = cJSON_GetArrayItem( params, 1 );
1827 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcAccountInfoConfig",
1828 0 : 1, 1, 1, 1,
1829 0 : &bank_idx, &encoding_cstr,
1830 0 : &slice_length, &slice_offset,
1831 0 : &response );
1832 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1833 :
1834 0 : bank_info_t * info = &ctx->banks[ bank_idx ];
1835 :
1836 0 : fd_pubkey_t addresses[ 100UL ];
1837 0 : for( ulong i=0UL; i<(ulong)cnt; i++ ) {
1838 0 : cJSON const * key_json = cJSON_GetArrayItem( keys_arr, (int)i );
1839 0 : if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id, key_json, &addresses[ i ], &response ) ) ) return response;
1840 0 : }
1841 :
1842 0 : CSTR_JSON( id, id_cstr );
1843 0 : fd_http_server_printf( ctx->http,
1844 0 : "{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":{\"context\":{\"apiVersion\":\"%s\",\"slot\":%lu},\"value\":[",
1845 0 : id_cstr, FD_RPC_AGAVE_API_VERSION, info->slot );
1846 :
1847 0 : for( ulong i=0; i<(ulong)cnt; i++ ) {
1848 0 : if( i>0 ) fd_http_server_printf( ctx->http, "," );
1849 :
1850 0 : ulong acct_lamports;
1851 0 : int acct_executable;
1852 0 : uchar acct_owner[ 32UL ];
1853 0 : ulong acct_data_len;
1854 0 : fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, addresses[i].uc,
1855 0 : &acct_lamports, &acct_executable, acct_owner,
1856 0 : ctx->accdb_data_buf, &acct_data_len );
1857 0 : if( FD_UNLIKELY( !acct_lamports ) ) {
1858 0 : fd_http_server_printf( ctx->http, "null" );
1859 0 : continue;
1860 0 : }
1861 :
1862 0 : fd_http_server_response_t err_response;
1863 0 : if( FD_UNLIKELY( !fd_rpc_encode_account_data( ctx, ctx->accdb_data_buf, acct_data_len, acct_owner, acct_lamports, acct_executable, encoding_cstr, slice_offset, slice_length, id_cstr, &err_response ) ) ) {
1864 0 : return err_response;
1865 0 : }
1866 0 : }
1867 :
1868 0 : fd_http_server_printf( ctx->http, "]}}\n" );
1869 0 : return STAGE_JSON( ctx );
1870 0 : }
1871 :
1872 : UNIMPLEMENTED(getProgramAccounts)
1873 : UNIMPLEMENTED(getRecentPerformanceSamples)
1874 : UNIMPLEMENTED(getRecentPrioritizationFees)
1875 : UNIMPLEMENTED(getSignaturesForAddress)
1876 : UNIMPLEMENTED(getSignatureStatuses)
1877 :
1878 : static fd_http_server_response_t
1879 : getSlot( fd_rpc_tile_t * ctx,
1880 : cJSON const * id,
1881 0 : cJSON const * params ) {
1882 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_SLOT, 1UL );
1883 :
1884 0 : fd_http_server_response_t response;
1885 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
1886 :
1887 0 : ulong bank_idx = ULONG_MAX;
1888 0 : cJSON const * config = cJSON_GetArrayItem( params, 0 );
1889 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
1890 0 : 1, /* has_commitment */
1891 0 : 0, /* has_encoding */
1892 0 : 0, /* has_data_slice */
1893 0 : 1, /* has_min_context_slot */
1894 0 : &bank_idx,
1895 0 : NULL,
1896 0 : NULL,
1897 0 : NULL,
1898 0 : &response );
1899 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1900 :
1901 0 : bank_info_t * bank = &ctx->banks[ bank_idx ];
1902 0 : CSTR_JSON( id, id_cstr );
1903 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", bank->slot, id_cstr );
1904 0 : }
1905 :
1906 : UNIMPLEMENTED(getSlotLeader)
1907 : UNIMPLEMENTED(getSlotLeaders)
1908 : UNIMPLEMENTED(getStakeMinimumDelegation)
1909 : UNIMPLEMENTED(getSupply)
1910 : UNIMPLEMENTED(getTokenAccountBalance)
1911 : UNIMPLEMENTED(getTokenAccountsByDelegate)
1912 : UNIMPLEMENTED(getTokenAccountsByOwner)
1913 : UNIMPLEMENTED(getTokenLargestAccounts)
1914 : UNIMPLEMENTED(getTokenSupply)
1915 : UNIMPLEMENTED(getTransaction)
1916 :
1917 : static fd_http_server_response_t
1918 : getTransactionCount( fd_rpc_tile_t * ctx,
1919 : cJSON const * id,
1920 0 : cJSON const * params ) {
1921 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_TRANSACTION_COUNT, 1UL );
1922 :
1923 0 : fd_http_server_response_t response;
1924 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
1925 :
1926 0 : ulong bank_idx = ULONG_MAX;
1927 0 : cJSON const * config = cJSON_GetArrayItem( params, 0 );
1928 0 : int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
1929 0 : 1, /* has_commitment */
1930 0 : 0, /* has_encoding */
1931 0 : 0, /* has_data_slice */
1932 0 : 1, /* has_min_context_slot */
1933 0 : &bank_idx,
1934 0 : NULL,
1935 0 : NULL,
1936 0 : NULL,
1937 0 : &response );
1938 0 : if( FD_UNLIKELY( !config_valid ) ) return response;
1939 :
1940 0 : bank_info_t * bank = &ctx->banks[ bank_idx ];
1941 0 : CSTR_JSON( id, id_cstr );
1942 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", bank->transaction_count, id_cstr );
1943 0 : }
1944 :
1945 : static fd_http_server_response_t
1946 : getVersion( fd_rpc_tile_t * ctx,
1947 : cJSON const * id,
1948 0 : cJSON const * params ) {
1949 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GET_VERSION, 1UL );
1950 :
1951 0 : fd_http_server_response_t response;
1952 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
1953 :
1954 0 : CSTR_JSON( id, id_cstr );
1955 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 );
1956 0 : }
1957 :
1958 : static fd_http_server_response_t
1959 : voteSubscribe( fd_rpc_tile_t * ctx,
1960 : cJSON const * id,
1961 : cJSON const * params,
1962 0 : ulong ws_conn_id ) {
1963 0 : fd_http_server_response_t response;
1964 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
1965 :
1966 0 : CSTR_JSON( id, id_cstr );
1967 0 : if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
1968 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
1969 0 : }
1970 0 : FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
1971 :
1972 0 : fd_rpc_ws_subscriber_vote_add( ctx, ws_conn_id );
1973 :
1974 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":%s}\n", id_cstr );
1975 0 : }
1976 :
1977 : static fd_http_server_response_t
1978 : slotSubscribe( fd_rpc_tile_t * ctx,
1979 : cJSON const * id,
1980 : cJSON const * params,
1981 0 : ulong ws_conn_id ) {
1982 0 : fd_http_server_response_t response;
1983 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
1984 :
1985 0 : CSTR_JSON( id, id_cstr );
1986 0 : if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
1987 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
1988 0 : }
1989 0 : FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
1990 :
1991 0 : fd_rpc_ws_subscriber_slot_add( ctx, ws_conn_id );
1992 :
1993 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":%s}\n", id_cstr );
1994 0 : }
1995 :
1996 : static fd_http_server_response_t
1997 : voteUnsubscribe( fd_rpc_tile_t * ctx,
1998 : cJSON const * id,
1999 : cJSON const * params,
2000 0 : ulong ws_conn_id ) {
2001 0 : fd_http_server_response_t response;
2002 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 1, &response ) ) ) return response;
2003 :
2004 0 : CSTR_JSON( id, id_cstr );
2005 0 : if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
2006 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
2007 0 : }
2008 0 : FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
2009 :
2010 0 : cJSON const * subscription = cJSON_GetArrayItem( params, 0 );
2011 0 : if( FD_UNLIKELY( !fd_rpc_cjson_is_integer( subscription ) || subscription->valueint<0 ) ) {
2012 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( subscription ), id_cstr );
2013 0 : }
2014 :
2015 0 : int unsubscribed = 0;
2016 0 : if( FD_LIKELY( subscription->valueint==0 ) )
2017 0 : unsubscribed = fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
2018 :
2019 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%s,\"id\":%s}\n", unsubscribed ? "true" : "false", id_cstr );
2020 0 : }
2021 :
2022 : static fd_http_server_response_t
2023 : slotUnsubscribe( fd_rpc_tile_t * ctx,
2024 : cJSON const * id,
2025 : cJSON const * params,
2026 0 : ulong ws_conn_id ) {
2027 0 : fd_http_server_response_t response;
2028 0 : if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 1, &response ) ) ) return response;
2029 :
2030 0 : CSTR_JSON( id, id_cstr );
2031 0 : if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
2032 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
2033 0 : }
2034 0 : FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
2035 :
2036 0 : cJSON const * subscription = cJSON_GetArrayItem( params, 0 );
2037 0 : if( FD_UNLIKELY( !fd_rpc_cjson_is_integer( subscription ) || subscription->valueint<0 ) ) {
2038 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( subscription ), id_cstr );
2039 0 : }
2040 :
2041 0 : int unsubscribed = 0;
2042 0 : if( FD_LIKELY( subscription->valueint==0 ) )
2043 0 : unsubscribed = fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
2044 :
2045 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%s,\"id\":%s}\n", unsubscribed ? "true" : "false", id_cstr );
2046 0 : }
2047 :
2048 : UNIMPLEMENTED(getVoteAccounts) // TODO: Used by solana-exporter
2049 : UNIMPLEMENTED(isBlockhashValid)
2050 : UNIMPLEMENTED(minimumLedgerSlot) // TODO: Used by solana-exporter
2051 : UNIMPLEMENTED(requestAirdrop)
2052 : UNIMPLEMENTED(sendTransaction)
2053 : UNIMPLEMENTED(simulateTransaction)
2054 :
2055 : static fd_http_server_response_t
2056 : rpc_json_request( fd_rpc_tile_t * ctx,
2057 : uchar const * body,
2058 : ulong body_len,
2059 : ulong ws_conn_id );
2060 :
2061 : static fd_http_server_response_t
2062 : rpc_http_request1( fd_rpc_tile_t * ctx,
2063 0 : fd_http_server_request_t const * request ) {
2064 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET &&
2065 0 : request->headers.upgrade_websocket &&
2066 0 : (!strcmp( request->path, "/" ) || !strcmp( request->path, "/websocket" )) ) ) {
2067 0 : if( FD_UNLIKELY( !ctx->http->max_ws_conns ) ) return (fd_http_server_response_t){ .status = 404 };
2068 0 : return (fd_http_server_response_t){
2069 0 : .status = 200,
2070 0 : .upgrade_websocket = 1,
2071 0 : };
2072 0 : }
2073 :
2074 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET && !strcmp( request->path, "/health" ) ) ) {
2075 0 : int health_status = _getHealth( ctx );
2076 :
2077 0 : switch( health_status ) {
2078 0 : case FD_RPC_HEALTH_STATUS_UNKNOWN: return PRINTF_JSON( ctx, "unknown" );
2079 0 : case FD_RPC_HEALTH_STATUS_BEHIND: return PRINTF_JSON( ctx, "behind" );
2080 0 : case FD_RPC_HEALTH_STATUS_OK: return PRINTF_JSON( ctx, "ok" );
2081 0 : default: FD_LOG_ERR(( "unknown health status" ));
2082 0 : }
2083 0 : }
2084 :
2085 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET && !strcmp( request->path, "/genesis.tar.bz2" ) ) ) {
2086 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_GENESIS, 1UL );
2087 0 : if( FD_UNLIKELY( ctx->genesis_tar_bz_sz==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 404 };
2088 :
2089 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .status = 200 };
2090 0 : fd_http_server_memcpy( ctx->http, ctx->genesis_tar_bz, ctx->genesis_tar_bz_sz );
2091 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
2092 0 : return response;
2093 0 : }
2094 :
2095 0 : if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET ) ) {
2096 0 : return (fd_http_server_response_t){ .status = 404 };
2097 0 : }
2098 :
2099 0 : if( FD_UNLIKELY( request->method!=FD_HTTP_SERVER_METHOD_POST ) ) {
2100 0 : return (fd_http_server_response_t){ .status = 405 };
2101 0 : }
2102 :
2103 0 : return rpc_json_request( ctx, request->post.body, request->post.body_len, ULONG_MAX );
2104 0 : }
2105 :
2106 : static fd_http_server_response_t
2107 : rpc_json_request( fd_rpc_tile_t * ctx,
2108 : uchar const * body,
2109 : ulong body_len,
2110 0 : ulong ws_conn_id ) { /* ULONG_MAX implies HTTP */
2111 0 : const char * parse_end;
2112 0 : cJSON * json = cJSON_ParseWithLengthOpts( (char const *)body, body_len, &parse_end, 0 );
2113 :
2114 0 : if( FD_UNLIKELY( cJSON_IsArray( json ) && cJSON_GetArraySize( json )==0UL ) ) {
2115 : /* A bug in Agave ¯\_(ツ)_/¯ */
2116 0 : cJSON_Delete( json );
2117 0 : return (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
2118 0 : }
2119 :
2120 0 : if( FD_UNLIKELY( !json || !cJSON_IsObject( json ) ) ) {
2121 0 : cJSON_Delete( json );
2122 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
2123 0 : }
2124 0 : const cJSON * id = cJSON_GetObjectItemCaseSensitive( json, "id" );
2125 :
2126 0 : cJSON * item = json->child;
2127 0 : while( item ) {
2128 0 : if( FD_UNLIKELY( strcmp( item->string, "jsonrpc" ) && strcmp( item->string, "id" ) && strcmp( item->string, "method" ) && strcmp( item->string, "params" ) ) ) {
2129 0 : fd_http_server_response_t res;
2130 0 : if( FD_LIKELY( id ) ) {
2131 0 : CSTR_JSON( id, id_cstr );
2132 0 : res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2133 0 : } else {
2134 0 : res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":null}\n" );
2135 0 : }
2136 0 : cJSON_Delete( json );
2137 0 : return res;
2138 0 : }
2139 0 : item = item->next;
2140 0 : }
2141 :
2142 0 : if( FD_UNLIKELY( cJSON_HasObjectItem( json, "method") && !cJSON_HasObjectItem( json, "id") ) ) {
2143 : /* A bug in Agave ¯\_(ツ)_/¯ */
2144 0 : cJSON_Delete( json );
2145 0 : return (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
2146 0 : }
2147 :
2148 0 : const cJSON * jsonrpc = cJSON_GetObjectItemCaseSensitive( json, "jsonrpc" );
2149 0 : if( FD_UNLIKELY( !cJSON_HasObjectItem( json, "jsonrpc" ) && cJSON_HasObjectItem( json, "method" ) ) ) {
2150 0 : fd_http_server_response_t res;
2151 0 : if( FD_LIKELY( id ) ) {
2152 0 : CSTR_JSON( id, id_cstr );
2153 0 : res = PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":%s}\n", id_cstr );
2154 0 : } else {
2155 0 : res = PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":null}\n" );;
2156 0 : }
2157 0 : cJSON_Delete( json );
2158 0 : return res;
2159 0 : }
2160 :
2161 0 : if( FD_UNLIKELY( cJSON_IsObject( json ) && (!cJSON_HasObjectItem( json, "jsonrpc" ) || !cJSON_HasObjectItem( json, "method" )) ) ) {
2162 0 : fd_http_server_response_t res;
2163 0 : if( FD_LIKELY( id ) ) {
2164 0 : CSTR_JSON( id, id_cstr );
2165 0 : res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2166 0 : } else {
2167 0 : res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":null}\n" );
2168 0 : }
2169 0 : cJSON_Delete( json );
2170 0 : return res;
2171 0 : }
2172 :
2173 0 : if( FD_UNLIKELY( !(id && fd_rpc_cjson_is_integer( id ) && id->valueint >= 0) && !cJSON_IsString( id ) && !cJSON_IsNull( id ) ) ) {
2174 0 : cJSON_Delete( json );
2175 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
2176 0 : }
2177 :
2178 0 : if( FD_UNLIKELY( !cJSON_HasObjectItem( json, "jsonrpc" ) || cJSON_IsNull( jsonrpc ) ) ) {
2179 0 : CSTR_JSON( id, id_cstr );
2180 0 : cJSON_Delete( json );
2181 0 : return PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":%s}\n", id_cstr );
2182 0 : }
2183 :
2184 0 : if( FD_UNLIKELY( !cJSON_IsString( jsonrpc ) || strcmp( jsonrpc->valuestring, "2.0" ) ) ) {
2185 0 : CSTR_JSON( id, id_cstr );
2186 0 : cJSON_Delete( json );
2187 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2188 0 : }
2189 :
2190 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
2191 0 : fd_http_server_response_t response;
2192 :
2193 0 : const cJSON * _method = cJSON_GetObjectItemCaseSensitive( json, "method" );
2194 0 : if( FD_UNLIKELY( !cJSON_IsString( _method ) || _method->valuestring==NULL ) ) {
2195 0 : CSTR_JSON( id, id_cstr );
2196 0 : cJSON_Delete( json );
2197 0 : return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
2198 0 : }
2199 :
2200 0 : if( FD_LIKELY( !strcmp( _method->valuestring, "getAccountInfo" ) ) ) response = getAccountInfo( ctx, id, params );
2201 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBalance" ) ) ) response = getBalance( ctx, id, params );
2202 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlock" ) ) ) response = getBlock( ctx, id, params );
2203 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockCommitment" ) ) ) response = getBlockCommitment( ctx, id, params );
2204 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockHeight" ) ) ) response = getBlockHeight( ctx, id, params );
2205 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockProduction" ) ) ) response = getBlockProduction( ctx, id, params );
2206 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlocks" ) ) ) response = getBlocks( ctx, id, params );
2207 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlocksWithLimit" ) ) ) response = getBlocksWithLimit( ctx, id, params );
2208 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockTime" ) ) ) response = getBlockTime( ctx, id, params );
2209 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getClusterNodes" ) ) ) response = getClusterNodes( ctx, id, params );
2210 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getEpochInfo" ) ) ) response = getEpochInfo( ctx, id, params );
2211 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getEpochSchedule" ) ) ) response = getEpochSchedule( ctx, id, params );
2212 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getFeeForMessage" ) ) ) response = getFeeForMessage( ctx, id, params );
2213 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getFirstAvailableBlock" ) ) ) response = getFirstAvailableBlock( ctx, id, params );
2214 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getGenesisHash" ) ) ) response = getGenesisHash( ctx, id, params );
2215 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getHealth" ) ) ) response = getHealth( ctx, id, params );
2216 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getHighestSnapshotSlot" ) ) ) response = getHighestSnapshotSlot( ctx, id, params );
2217 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getIdentity" ) ) ) response = getIdentity( ctx, id, params );
2218 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationGovernor" ) ) ) response = getInflationGovernor( ctx, id, params );
2219 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationRate" ) ) ) response = getInflationRate( ctx, id, params );
2220 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationReward" ) ) ) response = getInflationReward( ctx, id, params );
2221 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLargestAccounts" ) ) ) response = getLargestAccounts( ctx, id, params );
2222 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLatestBlockhash" ) ) ) response = getLatestBlockhash( ctx, id, params );
2223 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLeaderSchedule" ) ) ) response = getLeaderSchedule( ctx, id, params );
2224 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMaxRetransmitSlot" ) ) ) response = getMaxRetransmitSlot( ctx, id, params );
2225 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMaxShredInsertSlot" ) ) ) response = getMaxShredInsertSlot( ctx, id, params );
2226 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMinimumBalanceForRentExemption" ) ) ) response = getMinimumBalanceForRentExemption( ctx, id, params );
2227 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMultipleAccounts" ) ) ) response = getMultipleAccounts( ctx, id, params );
2228 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getProgramAccounts" ) ) ) response = getProgramAccounts( ctx, id, params );
2229 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getRecentPerformanceSamples" ) ) ) response = getRecentPerformanceSamples( ctx, id, params );
2230 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getRecentPrioritizationFees" ) ) ) response = getRecentPrioritizationFees( ctx, id, params );
2231 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSignaturesForAddress" ) ) ) response = getSignaturesForAddress( ctx, id, params );
2232 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSignatureStatuses" ) ) ) response = getSignatureStatuses( ctx, id, params );
2233 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlot" ) ) ) response = getSlot( ctx, id, params );
2234 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlotLeader" ) ) ) response = getSlotLeader( ctx, id, params );
2235 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlotLeaders" ) ) ) response = getSlotLeaders( ctx, id, params );
2236 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getStakeMinimumDelegation" ) ) ) response = getStakeMinimumDelegation( ctx, id, params );
2237 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSupply" ) ) ) response = getSupply( ctx, id, params );
2238 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountBalance" ) ) ) response = getTokenAccountBalance( ctx, id, params );
2239 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountsByDelegate" ) ) ) response = getTokenAccountsByDelegate( ctx, id, params );
2240 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountsByOwner" ) ) ) response = getTokenAccountsByOwner( ctx, id, params );
2241 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenLargestAccounts" ) ) ) response = getTokenLargestAccounts( ctx, id, params );
2242 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenSupply" ) ) ) response = getTokenSupply( ctx, id, params );
2243 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTransaction" ) ) ) response = getTransaction( ctx, id, params );
2244 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTransactionCount" ) ) ) response = getTransactionCount( ctx, id, params );
2245 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getVersion" ) ) ) response = getVersion( ctx, id, params );
2246 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getVoteAccounts" ) ) ) response = getVoteAccounts( ctx, id, params );
2247 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "slotSubscribe" ) ) ) response = slotSubscribe( ctx, id, params, ws_conn_id );
2248 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "slotUnsubscribe" ) ) ) response = slotUnsubscribe( ctx, id, params, ws_conn_id );
2249 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "voteSubscribe" ) ) ) response = voteSubscribe( ctx, id, params, ws_conn_id );
2250 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "voteUnsubscribe" ) ) ) response = voteUnsubscribe( ctx, id, params, ws_conn_id );
2251 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "isBlockhashValid" ) ) ) response = isBlockhashValid( ctx, id, params );
2252 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "minimumLedgerSlot" ) ) ) response = minimumLedgerSlot( ctx, id, params );
2253 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "requestAirdrop" ) ) ) response = requestAirdrop( ctx, id, params );
2254 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "sendTransaction" ) ) ) response = sendTransaction( ctx, id, params );
2255 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "simulateTransaction" ) ) ) response = simulateTransaction( ctx, id, params );
2256 0 : else {
2257 0 : FD_MCNT_INC( RPC, REQUEST_SERVED_UNKNOWN, 1UL );
2258 0 : CSTR_JSON( id, id_cstr );
2259 0 : response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
2260 0 : }
2261 :
2262 0 : cJSON_Delete( json );
2263 0 : return response;
2264 0 : }
2265 :
2266 : static fd_http_server_response_t
2267 0 : rpc_http_request( fd_http_server_request_t const * request ) {
2268 0 : fd_rpc_tile_t * ctx = request->ctx;
2269 0 : long dt = -fd_tickcount();
2270 0 : fd_http_server_response_t response = rpc_http_request1( ctx, request );
2271 0 : dt += fd_tickcount();
2272 0 : fd_histf_sample( ctx->request_duration, (ulong)dt );
2273 0 : return response;
2274 0 : }
2275 :
2276 : static void
2277 : rpc_ws_open( ulong ws_conn_id,
2278 0 : void * ctx ) {
2279 0 : (void)ws_conn_id; (void)ctx;
2280 0 : }
2281 :
2282 : static void
2283 : rpc_ws_close( ulong ws_conn_id,
2284 : int reason FD_PARAM_UNUSED,
2285 0 : void * _ctx ) {
2286 0 : fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)_ctx;
2287 0 : if( FD_UNLIKELY( ws_conn_id>=ctx->http->max_ws_conns ) ) return;
2288 0 : fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
2289 0 : fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
2290 0 : }
2291 :
2292 : static void
2293 : rpc_ws_message( ulong ws_conn_id,
2294 : uchar const * data,
2295 : ulong data_len,
2296 0 : void * _ctx ) {
2297 0 : fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)_ctx;
2298 :
2299 0 : fd_http_server_unstage( ctx->http );
2300 :
2301 0 : long dt = -fd_tickcount();
2302 0 : fd_http_server_response_t response = rpc_json_request( ctx, data, data_len, ws_conn_id );
2303 0 : dt += fd_tickcount();
2304 0 : fd_histf_sample( ctx->request_duration, (ulong)dt );
2305 :
2306 0 : if( FD_UNLIKELY( response.status!=200UL ) ) {
2307 0 : fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
2308 0 : return;
2309 0 : }
2310 :
2311 0 : if( FD_LIKELY( response._body_len ) ) {
2312 0 : ulong response_body_end = response._body_off + response._body_len;
2313 :
2314 0 : ctx->http->stage_off = response._body_off;
2315 0 : ctx->http->stage_len = response._body_len;
2316 0 : ctx->http->stage_comp_len = 0UL;
2317 :
2318 0 : int err = fd_http_server_ws_send( ctx->http, ws_conn_id );
2319 0 : if( FD_UNLIKELY( ctx->http->stage_off<response_body_end ) ) ctx->http->stage_off = response_body_end;
2320 0 : if( FD_UNLIKELY( err ) )
2321 0 : fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
2322 0 : }
2323 0 : }
2324 :
2325 : static void
2326 : privileged_init( fd_topo_t const * topo,
2327 0 : fd_topo_tile_t const * tile ) {
2328 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
2329 :
2330 0 : fd_http_server_params_t http_params = derive_http_params( tile );
2331 :
2332 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
2333 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
2334 0 : fd_http_server_t * _http = FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(), fd_http_server_footprint( http_params ) );
2335 :
2336 0 : fd_memset( ctx, 0, sizeof(fd_rpc_tile_t) );
2337 :
2338 0 : if( FD_UNLIKELY( !strcmp( tile->rpc.identity_key_path, "" ) ) )
2339 0 : FD_LOG_ERR(( "identity_key_path not set" ));
2340 :
2341 0 : const uchar * identity_key = fd_keyload_load( tile->rpc.identity_key_path, /* pubkey only: */ 1 );
2342 0 : fd_memcpy( ctx->identity_pubkey, identity_key, 32UL );
2343 :
2344 0 : fd_http_server_callbacks_t callbacks = {
2345 0 : .request = rpc_http_request,
2346 0 : .ws_open = rpc_ws_open,
2347 0 : .ws_close = rpc_ws_close,
2348 0 : .ws_message = rpc_ws_message,
2349 0 : };
2350 0 : ctx->http = fd_http_server_join( fd_http_server_new( _http, http_params, callbacks, ctx ) );
2351 0 : fd_http_server_listen( ctx->http, tile->rpc.listen_addr, tile->rpc.listen_port );
2352 0 : FD_LOG_NOTICE(( "rpc server listening at http://" FD_IP4_ADDR_FMT ":%u", FD_IP4_ADDR_FMT_ARGS( tile->rpc.listen_addr ), tile->rpc.listen_port ));
2353 0 : }
2354 :
2355 : static inline fd_rpc_out_t
2356 : out1( fd_topo_t const * topo,
2357 : fd_topo_tile_t const * tile,
2358 0 : char const * name ) {
2359 0 : ulong idx = ULONG_MAX;
2360 :
2361 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
2362 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
2363 0 : if( !strcmp( link->name, name ) ) {
2364 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 ));
2365 0 : idx = i;
2366 0 : }
2367 0 : }
2368 :
2369 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_rpc_out_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
2370 :
2371 :
2372 0 : ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
2373 0 : if( FD_UNLIKELY( mtu==0UL ) ) return (fd_rpc_out_t){ .idx = idx, .mem = NULL, .chunk0 = ULONG_MAX, .wmark = ULONG_MAX, .chunk = ULONG_MAX };
2374 :
2375 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
2376 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
2377 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
2378 :
2379 0 : return (fd_rpc_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
2380 0 : }
2381 :
2382 : static void
2383 : unprivileged_init( fd_topo_t const * topo,
2384 0 : fd_topo_tile_t const * tile ) {
2385 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
2386 :
2387 0 : fd_http_server_params_t http_params = derive_http_params( tile );
2388 :
2389 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
2390 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
2391 0 : FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(), fd_http_server_footprint( http_params ) );
2392 0 : void * _alloc = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
2393 0 : void * _bz2_alloc = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
2394 0 : void * _banks = FD_SCRATCH_ALLOC_APPEND( l, alignof(bank_info_t), tile->rpc.max_live_slots*sizeof(bank_info_t) );
2395 0 : void * _nodes_dlist = FD_SCRATCH_ALLOC_APPEND( l, fd_rpc_cluster_node_dlist_align(), fd_rpc_cluster_node_dlist_footprint() );
2396 0 : void * _accdb_join = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->rpc.max_live_slots ) );
2397 0 : void * _ws_sub_vote = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), http_params.max_ws_connection_cnt*sizeof(ulong) );
2398 0 : void * _ws_sub_slot = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), http_params.max_ws_connection_cnt*sizeof(ulong) );
2399 :
2400 0 : fd_alloc_t * alloc = fd_alloc_join( fd_alloc_new( _alloc, 1UL ), 1UL );
2401 0 : FD_TEST( alloc );
2402 0 : cJSON_alloc_install( alloc );
2403 :
2404 0 : ctx->delay_startup = tile->rpc.delay_startup;
2405 0 : ctx->ws_subscribers_vote = _ws_sub_vote;
2406 0 : ctx->ws_subscribers_vote_cnt = 0UL;
2407 0 : ctx->ws_subscribers_slot = _ws_sub_slot;
2408 0 : ctx->ws_subscribers_slot_cnt = 0UL;
2409 :
2410 0 : ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
2411 0 : FD_TEST( ctx->keyswitch );
2412 :
2413 0 : ctx->bz2_alloc = fd_alloc_join( fd_alloc_new( _bz2_alloc, 1UL ), 1UL );
2414 0 : FD_TEST( ctx->bz2_alloc );
2415 :
2416 0 : ctx->next_poll_deadline = fd_tickcount();
2417 :
2418 0 : ctx->cluster_confirmed_slot = ULONG_MAX;
2419 0 : ctx->genesis_tar_bz_sz = ULONG_MAX;
2420 :
2421 0 : ctx->processed_idx = ULONG_MAX;
2422 0 : ctx->confirmed_idx = ULONG_MAX;
2423 0 : ctx->finalized_idx = ULONG_MAX;
2424 :
2425 0 : ctx->cluster_nodes_dlist = fd_rpc_cluster_node_dlist_join( fd_rpc_cluster_node_dlist_new( _nodes_dlist ) );
2426 0 : ctx->banks = _banks;
2427 0 : ctx->max_live_slots = tile->rpc.max_live_slots;
2428 0 : for( ulong i=0UL; i<ctx->max_live_slots; i++ ) ctx->banks[ i ].slot = ULONG_MAX;
2429 :
2430 0 : FD_TEST( tile->in_cnt<=sizeof( ctx->in )/sizeof( ctx->in[ 0 ] ) );
2431 0 : for( ulong i=0; i<tile->in_cnt; i++ ) {
2432 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
2433 0 : fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
2434 :
2435 0 : ctx->in[ i ].mem = link_wksp->wksp;
2436 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
2437 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
2438 0 : ctx->in[ i ].mtu = link->mtu;
2439 :
2440 0 : if ( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
2441 0 : else if( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESI;
2442 0 : else if( FD_LIKELY( !strcmp( link->name, "gossip_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
2443 0 : else FD_LOG_ERR(( "unexpected link name %s", link->name ));
2444 0 : }
2445 :
2446 0 : *ctx->replay_out = out1( topo, tile, "rpc_replay" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
2447 :
2448 : /* Read-only join to accdb. The accdb workspace is mapped
2449 : PROT_READ in this tile (see topology); the only writable
2450 : external mapping is our private epoch fseq. fd FD_ACCDB_FD_RO is
2451 : the O_RDONLY dup of the accdb data file. */
2452 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->rpc.accdb_obj_id );
2453 0 : fd_accdb_shmem_t * accdb_shmem_ro = fd_accdb_shmem_join( _accdb_shmem );
2454 0 : FD_TEST( accdb_shmem_ro );
2455 0 : ulong * epoch_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->rpc.accdb_epoch_fseq_obj_id ) );
2456 0 : FD_TEST( epoch_fseq );
2457 0 : ctx->accdb = fd_accdb_join_readonly( _accdb_join, accdb_shmem_ro, epoch_fseq, FD_ACCDB_FD_RO );
2458 0 : FD_TEST( ctx->accdb );
2459 :
2460 0 : fd_histf_join( fd_histf_new( ctx->request_duration, FD_MHIST_SECONDS_MIN( RPC, REQUEST_DURATION_SECONDS ),
2461 0 : FD_MHIST_SECONDS_MAX( RPC, REQUEST_DURATION_SECONDS ) ) );
2462 :
2463 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
2464 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
2465 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
2466 0 : }
2467 :
2468 : static ulong
2469 : populate_allowed_seccomp( fd_topo_t const * topo,
2470 : fd_topo_tile_t const * tile,
2471 : ulong out_cnt,
2472 0 : struct sock_filter * out ) {
2473 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
2474 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
2475 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
2476 :
2477 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 );
2478 0 : return sock_filter_policy_fd_rpc_tile_instr_cnt;
2479 0 : }
2480 :
2481 : static ulong
2482 : populate_allowed_fds( fd_topo_t const * topo,
2483 : fd_topo_tile_t const * tile,
2484 : ulong out_fds_cnt,
2485 0 : int * out_fds ) {
2486 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
2487 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
2488 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
2489 :
2490 0 : if( FD_UNLIKELY( out_fds_cnt<4UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
2491 :
2492 0 : ulong out_cnt = 0UL;
2493 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
2494 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
2495 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
2496 0 : out_fds[ out_cnt++ ] = fd_http_server_fd( ctx->http ); /* rpc listen socket */
2497 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RO; /* accounts db readonly fd */
2498 :
2499 0 : return out_cnt;
2500 0 : }
2501 :
2502 : static ulong
2503 : rlimit_file_cnt( fd_topo_t const * topo FD_PARAM_UNUSED,
2504 0 : fd_topo_tile_t const * tile ) {
2505 : /* pipefd, socket, stderr, logfile, and one spare for new accept() connections */
2506 0 : ulong base = 5UL;
2507 0 : return base + tile->rpc.max_http_connections + tile->rpc.max_websocket_connections;
2508 0 : }
2509 :
2510 0 : #define STEM_BURST (1UL)
2511 :
2512 : /* The default STEM_LAZY is based on cr_max, which is the minimum depth
2513 : across all output links that have at least one reliable consumer.
2514 : RPC has one tiny output link used to release banks, with a
2515 : significantly slower line rate than assumed in the formula for the
2516 : default STEM_LAZY value.
2517 :
2518 : Instead, lazy just needs to be frequent enough to relinquish credits
2519 : to upstream producers faster than they are exhausted. 384us is a
2520 : reasonable default used in many other non-critical tiles. */
2521 0 : #define STEM_LAZY (128L*3000L)
2522 :
2523 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_rpc_tile_t
2524 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_rpc_tile_t)
2525 :
2526 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
2527 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
2528 0 : #define STEM_CALLBACK_BEFORE_CREDIT before_credit
2529 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
2530 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
2531 :
2532 : #include "../../disco/stem/fd_stem.c"
2533 :
2534 : #ifndef FD_TILE_TEST
2535 : fd_topo_run_tile_t fd_tile_rpc = {
2536 : .name = "rpc",
2537 : .rlimit_file_cnt_fn = rlimit_file_cnt,
2538 : .populate_allowed_seccomp = populate_allowed_seccomp,
2539 : .populate_allowed_fds = populate_allowed_fds,
2540 : .scratch_align = scratch_align,
2541 : .scratch_footprint = scratch_footprint,
2542 : .loose_footprint = loose_footprint,
2543 : .privileged_init = privileged_init,
2544 : .unprivileged_init = unprivileged_init,
2545 : .run = stem_run,
2546 : };
2547 : #endif
|