Line data Source code
1 : #include "../replay/fd_replay_tile.h"
2 : #include "../genesis/fd_genesi_tile.h"
3 : #include "../../disco/topo/fd_topo.h"
4 : #include "../../disco/keyguard/fd_keyload.h"
5 : #include "../../disco/keyguard/fd_keyswitch.h"
6 : #include "../../discof/fd_accdb_topo.h"
7 : #include "../../flamenco/accdb/fd_accdb_sync.h"
8 : #include "../../flamenco/features/fd_features.h"
9 : #include "../../flamenco/runtime/fd_runtime_const.h"
10 : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
11 : #include "../../waltz/http/fd_http_server.h"
12 : #include "../../waltz/http/fd_http_server_private.h"
13 : #include "../../ballet/base64/fd_base64.h"
14 : #include "../../ballet/json/cJSON.h"
15 : #include "../../ballet/json/cJSON_alloc.h"
16 : #include "../../ballet/lthash/fd_lthash.h"
17 :
18 : #include <stddef.h>
19 : #include <sys/socket.h>
20 :
21 : #if FD_HAS_ZSTD
22 : #include <zstd.h>
23 : #endif
24 :
25 : #include "generated/fd_rpc_tile_seccomp.h"
26 :
27 0 : #define FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN 8192UL
28 :
29 0 : #define IN_KIND_REPLAY (0)
30 0 : #define IN_KIND_GENESI (0)
31 :
32 0 : #define FD_RPC_COMMITMENT_PROCESSED (0)
33 0 : #define FD_RPC_COMMITMENT_CONFIRMED (1)
34 0 : #define FD_RPC_COMMITMENT_FINALIZED (2)
35 :
36 : #define FD_RPC_ENCODING_BASE58 (0)
37 : #define FD_RPC_ENCODING_BASE64 (1)
38 : #define FD_RPC_ENCODING_BASE64_ZSTD (2)
39 : #define FD_RPC_ENCODING_BINARY (3)
40 : #define FD_RPC_ENCODING_JSON_PARSED (4)
41 :
42 : #define FD_RPC_METHOD_GET_ACCOUNT_INFO ( 0)
43 : #define FD_RPC_METHOD_GET_BALANCE ( 1)
44 : #define FD_RPC_METHOD_GET_BLOCK ( 2)
45 : #define FD_RPC_METHOD_GET_BLOCK_COMMITMENT ( 3)
46 : #define FD_RPC_METHOD_GET_BLOCK_HEIGHT ( 4)
47 : #define FD_RPC_METHOD_GET_BLOCK_PRODUCTION ( 5)
48 : #define FD_RPC_METHOD_GET_BLOCKS ( 6)
49 : #define FD_RPC_METHOD_GET_BLOCKS_WITH_LIMIT ( 7)
50 : #define FD_RPC_METHOD_GET_BLOCK_TIME ( 8)
51 : #define FD_RPC_METHOD_GET_CLUSTER_NODES ( 9)
52 : #define FD_RPC_METHOD_GET_EPOCH_INFO (10)
53 : #define FD_RPC_METHOD_GET_EPOCH_SCHEDULE (11)
54 : #define FD_RPC_METHOD_GET_FEE_FOR_MESSAGE (12)
55 : #define FD_RPC_METHOD_GET_FIRST_AVAILABLE_BLOCK (13)
56 : #define FD_RPC_METHOD_GET_GENESIS_HASH (14)
57 : #define FD_RPC_METHOD_GET_HEALTH (15)
58 : #define FD_RPC_METHOD_GET_HIGHEST_SNAPSHOT_SLOT (16)
59 : #define FD_RPC_METHOD_GET_IDENTITY (17)
60 : #define FD_RPC_METHOD_GET_INFLATION_GOVERNOR (18)
61 : #define FD_RPC_METHOD_GET_INFLATION_RATE (19)
62 : #define FD_RPC_METHOD_GET_INFLATION_REWARD (20)
63 : #define FD_RPC_METHOD_GET_LARGEST_ACCOUNTS (21)
64 : #define FD_RPC_METHOD_GET_LATEST_BLOCKHASH (22)
65 : #define FD_RPC_METHOD_GET_LEADER_SCHEDULE (23)
66 : #define FD_RPC_METHOD_GET_MAX_RETRANSMIT_SLOT (24)
67 : #define FD_RPC_METHOD_GET_MAX_SHRED_INSERT_SLOT (25)
68 : #define FD_RPC_METHOD_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION (26)
69 : #define FD_RPC_METHOD_GET_MULTIPLE_ACCOUNTS (27)
70 : #define FD_RPC_METHOD_GET_PROGRAM_ACCOUNTS (28)
71 : #define FD_RPC_METHOD_GET_RECENT_PERFORMANCE_SAMPLES (29)
72 : #define FD_RPC_METHOD_GET_RECENT_PRIORITIZATION_FEES (30)
73 : #define FD_RPC_METHOD_GET_SIGNATURES_FOR_ADDRESS (31)
74 : #define FD_RPC_METHOD_GET_SIGNATURE_STATUSES (32)
75 : #define FD_RPC_METHOD_GET_SLOT (33)
76 : #define FD_RPC_METHOD_GET_SLOT_LEADER (34)
77 : #define FD_RPC_METHOD_GET_SLOT_LEADERS (35)
78 : #define FD_RPC_METHOD_GET_STAKE_MINIMUM_DELEGATION (36)
79 : #define FD_RPC_METHOD_GET_SUPPLY (37)
80 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNT_BALANCE (38)
81 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_DELEGATE (39)
82 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_OWNER (40)
83 : #define FD_RPC_METHOD_GET_TOKEN_LARGEST_ACCOUNTS (41)
84 : #define FD_RPC_METHOD_GET_TOKEN_SUPPLY (42)
85 : #define FD_RPC_METHOD_GET_TRANSACTION (43)
86 : #define FD_RPC_METHOD_GET_TRANSACTION_COUNT (44)
87 : #define FD_RPC_METHOD_GET_VERSION (45)
88 : #define FD_RPC_METHOD_GET_VOTE_ACCOUNTS (46)
89 : #define FD_RPC_METHOD_IS_BLOCKHASH_VALID (47)
90 : #define FD_RPC_METHOD_MINIMUM_LEDGER_SLOT (48)
91 : #define FD_RPC_METHOD_REQUEST_AIRDROP (49)
92 : #define FD_RPC_METHOD_SEND_TRANSACTION (50)
93 : #define FD_RPC_METHOD_SIMULATE_TRANSACTION (51)
94 :
95 : // Keep in sync with https://github.com/solana-labs/solana-web3.js/blob/master/src/errors.ts
96 : // and https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/custom_error.rs
97 : #define FD_RPC_ERROR_BLOCK_CLEANED_UP (-32001)
98 : #define FD_RPC_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE (-32002)
99 : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE (-32003)
100 : #define FD_RPC_ERROR_BLOCK_NOT_AVAILABLE (-32004)
101 0 : #define FD_RPC_ERROR_NODE_UNHEALTHY (-32005)
102 : #define FD_RPC_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE (-32006)
103 : #define FD_RPC_ERROR_SLOT_SKIPPED (-32007)
104 0 : #define FD_RPC_ERROR_NO_SNAPSHOT (-32008)
105 : #define FD_RPC_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED (-32009)
106 : #define FD_RPC_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX (-32010)
107 : #define FD_RPC_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE (-32011)
108 : #define FD_RPC_ROR (-32012)
109 : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH (-32013)
110 : #define FD_RPC_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET (-32014)
111 : #define FD_RPC_ERROR_UNSUPPORTED_TRANSACTION_VERSION (-32015)
112 0 : #define FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED (-32016)
113 : #define FD_RPC_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE (-32017)
114 : #define FD_RPC_ERROR_SLOT_NOT_EPOCH_BOUNDARY (-32018)
115 : #define FD_RPC_ERROR_LONG_TERM_STORAGE_UNREACHABLE (-32019)
116 :
117 : static fd_http_server_params_t
118 0 : derive_http_params( fd_topo_tile_t const * tile ) {
119 0 : return (fd_http_server_params_t) {
120 0 : .max_connection_cnt = tile->rpc.max_http_connections,
121 0 : .max_ws_connection_cnt = 0UL,
122 0 : .max_request_len = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
123 0 : .max_ws_recv_frame_len = 0UL,
124 0 : .max_ws_send_frame_cnt = 0UL,
125 0 : .outgoing_buffer_sz = tile->rpc.send_buffer_size_mb * (1UL<<20UL),
126 0 : .compress_websocket = 0,
127 0 : };
128 0 : }
129 :
130 : struct fd_rpc_in {
131 : fd_wksp_t * mem;
132 : ulong chunk0;
133 : ulong wmark;
134 : ulong mtu;
135 : };
136 :
137 : typedef struct fd_rpc_in fd_rpc_in_t;
138 :
139 : struct fd_rpc_out {
140 : ulong idx;
141 : fd_wksp_t * mem;
142 : ulong chunk0;
143 : ulong wmark;
144 : ulong chunk;
145 : };
146 :
147 : typedef struct fd_rpc_out fd_rpc_out_t;
148 :
149 : struct bank_info {
150 : ulong slot;
151 : ulong transaction_count;
152 : uchar block_hash[ 32 ];
153 : ulong block_height;
154 :
155 : struct {
156 : double initial;
157 : double terminal;
158 : double taper;
159 : double foundation;
160 : double foundation_term;
161 : } inflation;
162 :
163 : struct {
164 : ulong lamports_per_uint8_year;
165 : double exemption_threshold;
166 : uchar burn_percent;
167 : } rent;
168 : };
169 :
170 : typedef struct bank_info bank_info_t;
171 :
172 : struct fd_rpc_tile {
173 : fd_http_server_t * http;
174 :
175 : bank_info_t * banks;
176 :
177 : ulong cluster_confirmed_slot;
178 :
179 : ulong processed_idx;
180 : ulong confirmed_idx;
181 : ulong finalized_idx;
182 :
183 : int has_genesis_hash;
184 : uchar genesis_hash[ 32 ];
185 :
186 : long next_poll_deadline;
187 :
188 : char version_string[ 16UL ];
189 :
190 : fd_keyswitch_t * keyswitch;
191 : uchar identity_pubkey[ 32UL ];
192 :
193 : int in_kind[ 64UL ];
194 : fd_rpc_in_t in[ 64UL ];
195 :
196 : fd_rpc_out_t replay_out[1];
197 :
198 : fd_accdb_user_t accdb[1];
199 :
200 : # if FD_HAS_ZSTD
201 : uchar compress_buf[ ZSTD_COMPRESSBOUND( FD_RUNTIME_ACC_SZ_MAX ) ];
202 : # endif
203 : };
204 :
205 : typedef struct fd_rpc_tile fd_rpc_tile_t;
206 :
207 : FD_FN_CONST static inline ulong
208 0 : scratch_align( void ) {
209 0 : return alignof( fd_rpc_tile_t );
210 0 : }
211 :
212 : FD_FN_PURE static inline ulong
213 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
214 0 : ulong http_fp = fd_http_server_footprint( derive_http_params( tile ) );
215 0 : if( FD_UNLIKELY( !http_fp ) ) FD_LOG_ERR(( "Invalid [tiles.rpc] config parameters" ));
216 :
217 0 : ulong l = FD_LAYOUT_INIT;
218 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
219 0 : l = FD_LAYOUT_APPEND( l, fd_http_server_align(), http_fp );
220 0 : l = FD_LAYOUT_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
221 0 : l = FD_LAYOUT_APPEND( l, alignof(bank_info_t), tile->rpc.max_live_slots*sizeof(bank_info_t) );
222 0 : return FD_LAYOUT_FINI( l, scratch_align() );
223 0 : }
224 :
225 : FD_FN_PURE static inline ulong
226 0 : loose_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
227 0 : return 256UL * (1UL<<20UL); /* 256MiB of heap space for the cJSON allocator */
228 0 : }
229 :
230 : static inline void
231 0 : during_housekeeping( fd_rpc_tile_t * ctx ) {
232 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
233 0 : fd_memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
234 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
235 0 : }
236 0 : }
237 :
238 : static void
239 : before_credit( fd_rpc_tile_t * ctx,
240 : fd_stem_context_t * stem,
241 0 : int * charge_busy ) {
242 0 : (void)stem;
243 :
244 0 : long now = fd_tickcount();
245 0 : if( FD_UNLIKELY( now>=ctx->next_poll_deadline ) ) {
246 0 : *charge_busy = fd_http_server_poll( ctx->http, 0 );
247 0 : ctx->next_poll_deadline = fd_tickcount() + (long)(fd_tempo_tick_per_ns( NULL )*128L*1000L);
248 0 : }
249 0 : }
250 :
251 : static inline int
252 : returnable_frag( fd_rpc_tile_t * ctx,
253 : ulong in_idx,
254 : ulong seq,
255 : ulong sig,
256 : ulong chunk,
257 : ulong sz,
258 : ulong ctl,
259 : ulong tsorig,
260 : ulong tspub,
261 0 : fd_stem_context_t * stem ) {
262 0 : (void)ctx;
263 0 : (void)in_idx;
264 0 : (void)seq;
265 0 : (void)sig;
266 0 : (void)chunk;
267 0 : (void)sz;
268 0 : (void)ctl;
269 0 : (void)tsorig;
270 0 : (void)tspub;
271 0 : (void)stem;
272 :
273 0 : if( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) {
274 0 : switch( sig ) {
275 0 : case REPLAY_SIG_SLOT_COMPLETED: {
276 0 : fd_replay_slot_completed_t const * slot_completed = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
277 :
278 0 : bank_info_t * bank = &ctx->banks[ slot_completed->bank_idx ];
279 0 : bank->slot = slot_completed->slot;
280 0 : bank->transaction_count = slot_completed->transaction_count;
281 0 : bank->block_height = slot_completed->block_height;
282 0 : fd_memcpy( bank->block_hash, slot_completed->block_hash.uc, 32 );
283 :
284 0 : bank->inflation.initial = slot_completed->inflation.initial;
285 0 : bank->inflation.terminal = slot_completed->inflation.terminal;
286 0 : bank->inflation.taper = slot_completed->inflation.taper;
287 0 : bank->inflation.foundation = slot_completed->inflation.foundation;
288 0 : bank->inflation.foundation_term = slot_completed->inflation.foundation_term;
289 :
290 0 : bank->rent.lamports_per_uint8_year = slot_completed->rent.lamports_per_uint8_year;
291 0 : bank->rent.exemption_threshold = slot_completed->rent.exemption_threshold;
292 0 : bank->rent.burn_percent = slot_completed->rent.burn_percent;
293 :
294 0 : break;
295 0 : }
296 0 : case REPLAY_SIG_RESET: {
297 0 : fd_poh_reset_t const * reset = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
298 :
299 0 : ulong prior_processed_idx = ctx->processed_idx;
300 0 : ctx->processed_idx = reset->bank_idx;
301 :
302 0 : if( FD_LIKELY( prior_processed_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, prior_processed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
303 0 : break;
304 0 : }
305 0 : default: break;
306 0 : }
307 0 : } else if( ctx->in_kind[ in_idx ]==IN_KIND_GENESI ) {
308 0 : ctx->has_genesis_hash = 1;
309 0 : uchar const * src = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
310 0 : if( FD_LIKELY( sig==GENESI_SIG_BOOTSTRAP_COMPLETED ) ) {
311 0 : fd_memcpy( ctx->genesis_hash, src+sizeof(fd_lthash_value_t), 32UL );
312 0 : } else {
313 0 : fd_memcpy( ctx->genesis_hash, src, 32UL );
314 0 : }
315 0 : }
316 :
317 0 : return 0;
318 0 : }
319 :
320 : static void
321 0 : jsonp_strip_trailing_comma( fd_http_server_t * http ) {
322 0 : if( FD_LIKELY( !http->stage_err &&
323 0 : http->stage_len>=1UL &&
324 0 : http->oring[ (http->stage_off%http->oring_sz)+http->stage_len-1UL ]==(uchar)',' ) ) {
325 0 : http->stage_len--;
326 0 : }
327 0 : }
328 :
329 : static void
330 : jsonp_open_object( fd_http_server_t * http,
331 0 : char const * key ) {
332 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":{", key );
333 0 : else fd_http_server_printf( http, "{" );
334 0 : }
335 :
336 : static void
337 0 : jsonp_close_object( fd_http_server_t * http ) {
338 0 : jsonp_strip_trailing_comma( http );
339 0 : fd_http_server_printf( http, "}," );
340 0 : }
341 :
342 :
343 : static void FD_FN_UNUSED
344 : jsonp_open_array( fd_http_server_t * http,
345 0 : char const * key ) {
346 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":[", key );
347 0 : else fd_http_server_printf( http, "[" );
348 0 : }
349 :
350 : static void FD_FN_UNUSED
351 0 : jsonp_close_array( fd_http_server_t * http ) {
352 0 : jsonp_strip_trailing_comma( http );
353 0 : fd_http_server_printf( http, "]," );
354 0 : }
355 :
356 : static void
357 : jsonp_ulong( fd_http_server_t * http,
358 : char const * key,
359 0 : ulong value ) {
360 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":%lu,", key, value );
361 0 : else fd_http_server_printf( http, "%lu,", value );
362 0 : }
363 :
364 : static void FD_FN_UNUSED
365 : jsonp_long( fd_http_server_t * http,
366 : char const * key,
367 0 : long value ) {
368 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":%ld,", key, value );
369 0 : else fd_http_server_printf( http, "%ld,", value );
370 0 : }
371 :
372 : static void FD_FN_UNUSED
373 : jsonp_double( fd_http_server_t * http,
374 : char const * key,
375 0 : double value ) {
376 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":%.2f,", key, value );
377 0 : else fd_http_server_printf( http, "%.2f,", value );
378 0 : }
379 :
380 : static void
381 : jsonp_string( fd_http_server_t * http,
382 : char const * key,
383 0 : char const * value ) {
384 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":", key );
385 0 : if( FD_LIKELY( value ) ) {
386 0 : ulong value_len = strlen( value );
387 0 : FD_TEST( fd_utf8_verify( value, value_len ) );
388 0 : for( ulong i=0UL; i<value_len; i++ ) FD_TEST( value[ i ]>=0x20 && value[ i ]!='"' && value[ i ]!='\\' );
389 :
390 0 : fd_http_server_printf( http, "\"%s\",", value );
391 0 : } else {
392 0 : fd_http_server_printf( http, "null," );
393 0 : }
394 0 : }
395 :
396 : static void FD_FN_UNUSED
397 : jsonp_bool( fd_http_server_t * http,
398 : char const * key,
399 0 : int value ) {
400 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":%s,", key, value ? "true" : "false" );
401 0 : else fd_http_server_printf( http, "%s,", value ? "true" : "false" );
402 0 : }
403 :
404 : static void FD_FN_UNUSED
405 : jsonp_null( fd_http_server_t * http,
406 0 : char const * key ) {
407 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\": null,", key );
408 0 : else fd_http_server_printf( http, "null," );
409 0 : }
410 :
411 : static void
412 0 : jsonp_open_envelope( fd_http_server_t * http ) {
413 0 : jsonp_open_object( http, NULL );
414 0 : jsonp_string( http, "jsonrpc", "2.0" );
415 0 : jsonp_open_object( http, "result" );
416 0 : }
417 :
418 : static void
419 : jsonp_close_envelope( fd_http_server_t * http,
420 0 : ulong id ) {
421 0 : jsonp_close_object( http );
422 0 : jsonp_ulong( http, "id", id );
423 0 : jsonp_close_object( http );
424 0 : jsonp_strip_trailing_comma( http );
425 0 : }
426 :
427 : #define UNIMPLEMENTED(X) \
428 : static fd_http_server_response_t \
429 : X( fd_rpc_tile_t * ctx, \
430 : ulong request_id, \
431 0 : cJSON const * params ) { \
432 0 : (void)ctx; (void)request_id; (void)params; \
433 0 : return (fd_http_server_response_t){ .status = 501 }; \
434 0 : }
435 :
436 : UNIMPLEMENTED(getBlock) // TODO: Used by solana-exporter
437 : UNIMPLEMENTED(getBlockCommitment)
438 :
439 : static fd_http_server_response_t
440 : getAccountInfo( fd_rpc_tile_t * ctx,
441 : ulong request_id,
442 0 : cJSON const * params ) {
443 0 : int param_cnt = cJSON_GetArraySize( params );
444 0 : if( param_cnt!=2 ) {
445 : /* In theory, the second argument (options) is not required. But if
446 : it is omitted, it implies Base58-encoded account data, which we
447 : deliberately don't support. */
448 0 : return (fd_http_server_response_t){ .status = 400 };
449 0 : }
450 :
451 0 : cJSON const * address_node = cJSON_GetArrayItem( params, 0 );
452 0 : cJSON const * config = cJSON_GetArrayItem( params, 1 );
453 0 : if( FD_UNLIKELY( !cJSON_IsString( address_node ) ) ) {
454 0 : return (fd_http_server_response_t){ .status = 400 };
455 0 : }
456 0 : if( FD_UNLIKELY( !cJSON_IsObject( config ) ) ) {
457 0 : return (fd_http_server_response_t){ .status = 400 };
458 0 : }
459 :
460 0 : char const * encoding = cJSON_GetStringValue( cJSON_GetObjectItemCaseSensitive( config, "encoding" ) );
461 0 : if( !encoding ) encoding = "binary"; /* "binary" is base58 */
462 0 : _Bool use_zstd = 0; (void)use_zstd;
463 0 : if( 0==strcmp( encoding, "base64+zstd" ) && FD_HAS_ZSTD ) {
464 0 : use_zstd = 1;
465 0 : } else if( 0==strcmp( encoding, "base64" ) ||
466 0 : 0==strcmp( encoding, "jsonParsed" ) ) {
467 0 : encoding = "base64";
468 0 : } else {
469 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: this server only supports 'base64' account data encoding\"},\"id\":%lu}\n", request_id );
470 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
471 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
472 0 : return response;
473 0 : }
474 :
475 0 : ulong bank_idx = ULONG_MAX;
476 0 : char const * commitment = cJSON_GetStringValue( cJSON_GetObjectItemCaseSensitive( config, "commitment" ) );
477 0 : if( !commitment ) commitment = "confirmed";
478 0 : if( 0==strcmp( commitment, "confirmed" ) ) {
479 0 : bank_idx = ctx->confirmed_idx;
480 0 : } else if( 0==strcmp( commitment, "processed" ) ) {
481 0 : bank_idx = ctx->processed_idx;
482 0 : } else if( 0==strcmp( commitment, "finalized" ) ) {
483 0 : bank_idx = ctx->finalized_idx;
484 0 : } else {
485 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: unsupported commitment level\"},\"id\":%lu}\n", request_id );
486 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
487 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
488 0 : return response;
489 0 : }
490 0 : if( FD_UNLIKELY( bank_idx==ULONG_MAX ) ) {
491 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: cannot resolve slot for '%s' commitment level\"},\"id\":%lu}\n", commitment, request_id );
492 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
493 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
494 0 : return response;
495 0 : }
496 :
497 0 : ulong minContextSlot = 0UL;
498 0 : cJSON const * _minContextSlot = cJSON_GetObjectItemCaseSensitive( config, "minContextSlot" );
499 0 : if( FD_UNLIKELY( _minContextSlot && !cJSON_IsNull( _minContextSlot ) ) ) {
500 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
501 0 : minContextSlot = _minContextSlot->valueulong;
502 0 : }
503 :
504 0 : bank_info_t const * info = &ctx->banks[ bank_idx ];
505 0 : if( info->slot < minContextSlot ) {
506 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%lu}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, info->slot, request_id );
507 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
508 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
509 0 : return response;
510 0 : }
511 :
512 0 : ulong data_off = 0U;
513 0 : ulong data_max = UINT_MAX;
514 0 : const cJSON * dataSlice = cJSON_GetObjectItemCaseSensitive( config, "dataSlice" );
515 0 : if( dataSlice && !cJSON_IsNull( dataSlice ) ) {
516 0 : cJSON const * _length = cJSON_GetObjectItemCaseSensitive( dataSlice, "length" );
517 0 : cJSON const * _offset = cJSON_GetObjectItemCaseSensitive( dataSlice, "offset" );
518 0 : if( FD_UNLIKELY( !_length || !cJSON_IsNumber( _length ) ||
519 0 : !_offset || !cJSON_IsNumber( _offset ) ) ) {
520 0 : return (fd_http_server_response_t){ .status = 400 };
521 0 : }
522 0 : data_off = _offset->valueulong;
523 0 : data_max = _length->valueulong;
524 0 : }
525 :
526 0 : uchar address[ 32 ];
527 0 : if( FD_UNLIKELY( !fd_base58_decode_32( cJSON_GetStringValue( address_node ), address ) ) ) {
528 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: address is not a valid Base58 encoding\"},\"id\":%lu}\n", request_id );
529 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
530 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
531 0 : return response;
532 0 : }
533 :
534 0 : fd_funk_txn_xid_t xid = { .ul={ info->slot, bank_idx } };
535 0 : fd_accdb_ro_t ro[1];
536 0 : if( FD_UNLIKELY( !fd_accdb_open_ro( ctx->accdb, ro, &xid, address ) ) ) {
537 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":%lu},\"value\":null},\"id\":%lu}\n", info->slot, request_id );
538 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
539 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
540 0 : return response;
541 0 : }
542 :
543 0 : ulong const data_sz = fd_accdb_ref_data_sz( ro );
544 0 : if( data_off>data_sz ) data_off = data_sz;
545 0 : ulong snip_sz = data_sz - data_off;
546 0 : if( snip_sz>data_max ) snip_sz = data_max;
547 :
548 0 : uchar const * compressed = (uchar const *)fd_accdb_ref_data_const( ro )+data_off;
549 0 : ulong compressed_sz = snip_sz;
550 0 : # if FD_HAS_ZSTD
551 0 : if( use_zstd ) {
552 0 : size_t zstd_res = ZSTD_compress( ctx->compress_buf, sizeof(ctx->compress_buf), compressed, snip_sz, 3 );
553 0 : if( ZSTD_isError( zstd_res ) ) {
554 0 : fd_accdb_close_ro( ctx->accdb, ro );
555 0 : return (fd_http_server_response_t){ .status = 500 };
556 0 : }
557 0 : compressed = ctx->compress_buf;
558 0 : compressed_sz = (ulong)zstd_res;
559 0 : }
560 0 : # endif
561 :
562 0 : FD_BASE58_ENCODE_32_BYTES( fd_accdb_ref_owner( ro ), owner_b58 );
563 0 : fd_http_server_printf( ctx->http,
564 0 : "{\"jsonrpc\":\"2.0\",\"id\":%lu,\"result\":{\"context\":{\"slot\":%lu},\"value\":{"
565 0 : "\"executable\":%s,"
566 0 : "\"lamports\":%lu,"
567 0 : "\"owner\":\"%s\","
568 0 : "\"rentEpoch\":18446744073709551615,"
569 0 : "\"space\":%lu,"
570 0 : "\"data\":[\"",
571 0 : request_id,
572 0 : info->slot,
573 0 : fd_accdb_ref_exec_bit( ro ) ? "true" : "false",
574 0 : fd_accdb_ref_lamports( ro ),
575 0 : owner_b58,
576 0 : data_sz );
577 :
578 0 : ulong encoded_sz = FD_BASE64_ENC_SZ( snip_sz );
579 0 : uchar * encoded = fd_http_server_append_start( ctx->http, encoded_sz );
580 0 : if( FD_UNLIKELY( !encoded ) ) {
581 0 : fd_accdb_close_ro( ctx->accdb, ro );
582 0 : return (fd_http_server_response_t){ .status = 500 };
583 0 : }
584 0 : encoded_sz = fd_base64_encode( (char *)encoded, compressed, compressed_sz );
585 0 : fd_http_server_append_end( ctx->http, encoded_sz );
586 :
587 0 : fd_http_server_printf( ctx->http, "\",\"%s\"]}}}\n", encoding );
588 0 : fd_accdb_close_ro( ctx->accdb, ro );
589 :
590 0 : fd_http_server_response_t response = { .content_type = "application/json", .status = 200 };
591 0 : if( fd_http_server_stage_body( ctx->http, &response ) ) response.status = 500;
592 0 : return response;
593 0 : }
594 :
595 : static fd_http_server_response_t
596 : getBalance( fd_rpc_tile_t * ctx,
597 : ulong request_id,
598 0 : cJSON const * params ) {
599 0 : int param_cnt = cJSON_GetArraySize( params );
600 0 : if( param_cnt<1 || param_cnt>2 ) {
601 0 : return (fd_http_server_response_t){ .status = 400 };
602 0 : }
603 :
604 0 : cJSON const * address_node = cJSON_GetArrayItem( params, 0 );
605 0 : cJSON const * config = cJSON_GetArrayItem( params, 1 );
606 0 : if( FD_UNLIKELY( !cJSON_IsString( address_node ) ) ) {
607 0 : return (fd_http_server_response_t){ .status = 400 };
608 0 : }
609 0 : if( FD_UNLIKELY( config && !cJSON_IsNull( config ) && !cJSON_IsString( config ) ) ) {
610 0 : return (fd_http_server_response_t){ .status = 400 };
611 0 : }
612 :
613 0 : ulong bank_idx = ULONG_MAX;
614 0 : char const * commitment = cJSON_GetStringValue( cJSON_GetObjectItemCaseSensitive( config, "commitment" ) );
615 0 : if( !commitment ) commitment = "confirmed";
616 0 : if( 0==strcmp( commitment, "confirmed" ) ) {
617 0 : bank_idx = ctx->confirmed_idx;
618 0 : } else if( 0==strcmp( commitment, "processed" ) ) {
619 0 : bank_idx = ctx->processed_idx;
620 0 : } else if( 0==strcmp( commitment, "finalized" ) ) {
621 0 : bank_idx = ctx->finalized_idx;
622 0 : } else {
623 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: unsupported commitment level\"},\"id\":%lu}\n", request_id );
624 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
625 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
626 0 : return response;
627 0 : }
628 0 : if( FD_UNLIKELY( bank_idx==ULONG_MAX ) ) {
629 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: cannot resolve slot for '%s' commitment level\"},\"id\":%lu}\n", commitment, request_id );
630 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
631 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
632 0 : return response;
633 0 : }
634 :
635 0 : ulong minContextSlot = 0UL;
636 0 : cJSON const * _minContextSlot = cJSON_GetObjectItemCaseSensitive( config, "minContextSlot" );
637 0 : if( FD_UNLIKELY( _minContextSlot && !cJSON_IsNull( _minContextSlot ) ) ) {
638 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
639 0 : minContextSlot = _minContextSlot->valueulong;
640 0 : }
641 :
642 0 : bank_info_t const * info = &ctx->banks[ bank_idx ];
643 0 : if( info->slot < minContextSlot ) {
644 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%lu}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, info->slot, request_id );
645 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
646 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
647 0 : return response;
648 0 : }
649 :
650 0 : uchar address[ 32 ];
651 0 : if( FD_UNLIKELY( !fd_base58_decode_32( cJSON_GetStringValue( address_node ), address ) ) ) {
652 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: address is not a valid Base58 encoding\"},\"id\":%lu}\n", request_id );
653 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
654 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
655 0 : return response;
656 0 : }
657 :
658 0 : ulong balance = 0UL;
659 0 : fd_funk_txn_xid_t xid = { .ul={ info->slot, bank_idx } };
660 0 : fd_accdb_ro_t ro[1];
661 0 : if( FD_UNLIKELY( fd_accdb_open_ro( ctx->accdb, ro, &xid, address ) ) ) {
662 0 : balance = fd_accdb_ref_lamports( ro );
663 0 : fd_accdb_close_ro( ctx->accdb, ro );
664 0 : }
665 :
666 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":%lu},\"value\":%lu},\"id\":%lu}\n", info->slot, balance, request_id );
667 0 : fd_http_server_response_t response = { .content_type = "application/json", .status = 200 };
668 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
669 0 : return response;
670 0 : }
671 :
672 : static fd_http_server_response_t
673 : getBlockHeight( fd_rpc_tile_t * ctx,
674 : ulong request_id,
675 0 : cJSON const * params ) {
676 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
677 0 : ulong minContextSlot = ULONG_MAX;
678 :
679 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
680 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
681 :
682 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
683 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
684 :
685 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
686 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
687 :
688 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
689 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
690 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
691 0 : else return (fd_http_server_response_t){ .status = 400 };
692 :
693 0 : const cJSON * _minContextSlot = cJSON_GetObjectItemCaseSensitive( param, "minContextSlot" );
694 0 : if( FD_UNLIKELY( _minContextSlot && !cJSON_IsNull( _minContextSlot ) ) ) {
695 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
696 0 : minContextSlot = _minContextSlot->valueulong;
697 0 : }
698 0 : }
699 :
700 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
701 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
702 :
703 0 : if( FD_UNLIKELY( minContextSlot!=ULONG_MAX && minContextSlot>bank->slot ) ) {
704 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%lu}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, bank->slot, request_id );
705 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
706 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
707 0 : return response;
708 0 : }
709 :
710 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}\n", bank->block_height, request_id );
711 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
712 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
713 0 : return response;
714 0 : }
715 :
716 : UNIMPLEMENTED(getBlockProduction) // TODO: Used by solana-exporter
717 : UNIMPLEMENTED(getBlocks)
718 : UNIMPLEMENTED(getBlocksWithLimit)
719 : UNIMPLEMENTED(getBlockTime)
720 : UNIMPLEMENTED(getClusterNodes)
721 : UNIMPLEMENTED(getEpochInfo) // TODO: Used by solana-exporter
722 : UNIMPLEMENTED(getEpochSchedule)
723 : UNIMPLEMENTED(getFeeForMessage)
724 : UNIMPLEMENTED(getFirstAvailableBlock) // TODO: Used by solana-exporter
725 :
726 : /* Get the genesis hash of the cluster. Firedancer deviates slightly
727 : from Agave here, as the genesis hash is not always known when RPC
728 : is first booted, it may need to be determined asynchronously in the
729 : background, fetched from a peer node. If the genesis hash is not yet
730 : known, we return an error indicating no snapshot is available. */
731 :
732 : static fd_http_server_response_t
733 : getGenesisHash( fd_rpc_tile_t * ctx,
734 : ulong request_id,
735 0 : cJSON const * params ) {
736 0 : (void)params;
737 :
738 0 : if( FD_UNLIKELY( !ctx->has_genesis_hash ) ) {
739 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"No genesis hash\"},\"id\":%lu}\n", FD_RPC_ERROR_NO_SNAPSHOT, request_id );
740 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
741 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
742 0 : return response;
743 0 : }
744 :
745 0 : FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash, genesis_hash_b58 );
746 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%lu}\n", genesis_hash_b58, request_id );
747 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
748 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
749 0 : return response;
750 0 : }
751 :
752 : /* Determines if the node is healthy. Agave defines this as follows,
753 :
754 : - On boot, nodes must go through the entire snapshot slot database
755 : and hash everything, and once it's done verify the hash matches.
756 : While this is ongoing, the node is unhealthy with a "slotsBehind"
757 : value of null.
758 :
759 : - On boot, if the cluster is restarting and we are currently waiting
760 : for a supermajority of stake to join gossip to proceed with
761 : booting, the node is forcibly marked as healthy, to, per Agave,
762 :
763 : > prevent load balancers from removing the node from their list
764 : > of candidates during a manual restart
765 :
766 : - In addition, once booted, there is a period where we do not yet
767 : know the cluster confirmed slot, because we have not yet observed
768 : any (or enough) votes arrive from peers in the cluster. During
769 : this period the node is unhealthy with a "slotsBehind" value of
770 : null.
771 :
772 : - Finally, once the cluster confirmed slot is known, which is the
773 : highest optimistically confirmed slot observed from both gossip,
774 : and votes procesed in blocks, it is compared to our own
775 : optimistically confirmed slot, which is just the highest slot down
776 : the cluster confirmed fork that we have finished replaying
777 : locally. The difference between these two slots is compared, and
778 : if it is less than or equal to 128, the node is healthy, otherwise
779 : it is unhealthy with a "slotsBehind" value equal to the
780 : difference.
781 :
782 : Firedancer currently only implements the final two checks, and does
783 : not forcibly mark the node as healthy while waiting for a
784 : supermajority, nor does it mark a node as unhealthy while hashing the
785 : snapshot database on boot. Firedancer hashes snapshots so quickly
786 : that the node will die on boot if the hash is not valid. */
787 :
788 : static fd_http_server_response_t
789 : getHealth( fd_rpc_tile_t * ctx,
790 : ulong request_id,
791 0 : cJSON const * params ) {
792 0 : (void)params;
793 :
794 : // TODO: We should probably implement the same waiting_for_supermajority
795 : // logic to conform with Agave here.
796 :
797 0 : int unknown = ctx->cluster_confirmed_slot==ULONG_MAX || ctx->confirmed_idx==ULONG_MAX;
798 0 : if( FD_UNLIKELY( unknown ) ) fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Node is unhealthy\",\"data\":{\"slotsBehind\":null}},\"id\":%lu}\n", FD_RPC_ERROR_NODE_UNHEALTHY, request_id );
799 0 : else {
800 0 : ulong slots_behind = fd_ulong_sat_sub( ctx->cluster_confirmed_slot, ctx->banks[ ctx->confirmed_idx ].slot );
801 0 : if( FD_LIKELY( slots_behind<=128UL ) ) fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":\"ok\",\"id\":%lu}\n", request_id );
802 0 : else fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Node is unhealthy\",\"data\":{\"slotsBehind\":%lu}},\"id\":%lu}\n", FD_RPC_ERROR_NODE_UNHEALTHY, slots_behind, request_id );
803 0 : }
804 :
805 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
806 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
807 0 : return response;
808 0 : }
809 :
810 : static fd_http_server_response_t
811 : getHighestSnapshotSlot( fd_rpc_tile_t * ctx,
812 : ulong request_id,
813 0 : cJSON const * params ) {
814 0 : (void)params;
815 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"No snapshot\"},\"id\":%lu}\n", FD_RPC_ERROR_NO_SNAPSHOT, request_id );
816 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
817 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
818 0 : return response;
819 0 : }
820 :
821 : static fd_http_server_response_t
822 : getIdentity( fd_rpc_tile_t * ctx,
823 : ulong request_id,
824 0 : cJSON const * params ) {
825 0 : (void)params;
826 :
827 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_pubkey, identity_pubkey_b58 );
828 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":{\"identity\":\"%s\"},\"id\":%lu}\n", identity_pubkey_b58, request_id );
829 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
830 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
831 0 : return response;
832 0 : }
833 :
834 : static fd_http_server_response_t
835 : getInflationGovernor( fd_rpc_tile_t * ctx,
836 : ulong request_id,
837 0 : cJSON const * params ) {
838 0 : (void)params;
839 :
840 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
841 :
842 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
843 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
844 :
845 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
846 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
847 :
848 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
849 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
850 :
851 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
852 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
853 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
854 0 : else return (fd_http_server_response_t){ .status = 400 };
855 0 : }
856 :
857 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
858 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
859 :
860 0 : jsonp_open_envelope( ctx->http );
861 0 : jsonp_double( ctx->http, "foundation", bank->inflation.foundation );
862 0 : jsonp_double( ctx->http, "foundationTerm", bank->inflation.foundation_term );
863 0 : jsonp_double( ctx->http, "initial", bank->inflation.initial );
864 0 : jsonp_double( ctx->http, "taper", bank->inflation.taper );
865 0 : jsonp_double( ctx->http, "terminal", bank->inflation.terminal );
866 0 : jsonp_close_envelope( ctx->http, request_id );
867 :
868 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
869 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
870 0 : return response;
871 0 : }
872 :
873 : UNIMPLEMENTED(getInflationRate)
874 : UNIMPLEMENTED(getInflationReward) // TODO: Used by solana-exporter
875 : UNIMPLEMENTED(getLargestAccounts)
876 :
877 : static fd_http_server_response_t
878 : getLatestBlockhash( fd_rpc_tile_t * ctx,
879 : ulong request_id,
880 0 : cJSON const * params ) {
881 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
882 0 : ulong minContextSlot = ULONG_MAX;
883 :
884 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
885 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
886 :
887 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
888 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
889 :
890 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
891 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
892 :
893 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
894 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
895 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
896 0 : else return (fd_http_server_response_t){ .status = 400 };
897 :
898 0 : const cJSON * _minContextSlot = cJSON_GetObjectItemCaseSensitive( param, "minContextSlot" );
899 0 : if( FD_UNLIKELY( _minContextSlot ) ) {
900 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
901 0 : minContextSlot = _minContextSlot->valueulong;
902 0 : }
903 0 : }
904 :
905 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
906 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
907 :
908 0 : if( FD_UNLIKELY( minContextSlot!=ULONG_MAX && minContextSlot>bank->slot ) ) {
909 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%lu}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, bank->slot, request_id );
910 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
911 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
912 0 : return response;
913 0 : }
914 :
915 0 : FD_BASE58_ENCODE_32_BYTES( bank->block_hash, block_hash_b58 );
916 0 : jsonp_open_envelope( ctx->http );
917 0 : jsonp_open_object( ctx->http, "context" );
918 0 : jsonp_ulong( ctx->http, "slot", bank->slot );
919 0 : jsonp_close_object( ctx->http );
920 :
921 0 : jsonp_open_object( ctx->http, "value" );
922 0 : jsonp_string( ctx->http, "blockhash", block_hash_b58 );
923 0 : jsonp_ulong( ctx->http, "lastValidBlockHeight", 0UL /* TODO: Implement */ );
924 0 : jsonp_close_object( ctx->http );
925 0 : jsonp_close_envelope( ctx->http, request_id );
926 :
927 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
928 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
929 0 : return response;
930 0 : }
931 :
932 : UNIMPLEMENTED(getLeaderSchedule) // TODO: Used by solana-exporter
933 : UNIMPLEMENTED(getMaxRetransmitSlot)
934 : UNIMPLEMENTED(getMaxShredInsertSlot)
935 :
936 : static fd_http_server_response_t
937 : getMinimumBalanceForRentExemption( fd_rpc_tile_t * ctx,
938 : ulong request_id,
939 0 : cJSON const * params ) {
940 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
941 :
942 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>2 || !cJSON_GetArraySize( params ) ) ) return (fd_http_server_response_t){ .status = 400 };
943 :
944 0 : const cJSON * data_len = cJSON_GetArrayItem( params, 0 );
945 0 : if( FD_UNLIKELY( !cJSON_IsNumber( data_len ) ) ) return (fd_http_server_response_t){ .status = 400 };
946 :
947 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )==2 ) ) {
948 0 : const cJSON * config = cJSON_GetArrayItem( params, 1 );
949 0 : if( FD_UNLIKELY( !cJSON_IsObject( config ) ) ) return (fd_http_server_response_t){ .status = 400 };
950 :
951 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( config, "commitment" );
952 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
953 :
954 0 : if( FD_UNLIKELY( _commitment ) ) {
955 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
956 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
957 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
958 0 : else return (fd_http_server_response_t){ .status = 400 };
959 0 : }
960 0 : }
961 :
962 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
963 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
964 :
965 0 : fd_rent_t rent = {
966 0 : .lamports_per_uint8_year = bank->rent.lamports_per_uint8_year,
967 0 : .exemption_threshold = bank->rent.exemption_threshold,
968 0 : .burn_percent = bank->rent.burn_percent,
969 0 : };
970 0 : ulong minimum = fd_rent_exempt_minimum_balance( &rent, data_len->valueulong );
971 :
972 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}\n", minimum, request_id );
973 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
974 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
975 0 : return response;
976 0 : }
977 :
978 : static fd_http_server_response_t
979 : getMultipleAccounts( fd_rpc_tile_t * ctx,
980 : ulong request_id,
981 0 : cJSON const * params ) {
982 0 : int param_cnt = cJSON_GetArraySize( params );
983 0 : if( param_cnt<1 || param_cnt>3 ) {
984 0 : return (fd_http_server_response_t){ .status = 400 };
985 0 : }
986 :
987 0 : cJSON const * address_list = cJSON_GetArrayItem( params, 0 );
988 0 : cJSON const * config = cJSON_GetArrayItem( params, 1 );
989 0 : if( FD_UNLIKELY( !cJSON_IsArray( address_list ) ) ) {
990 0 : return (fd_http_server_response_t){ .status = 400 };
991 0 : }
992 :
993 0 : char const * encoding = cJSON_GetStringValue( cJSON_GetObjectItemCaseSensitive( config, "encoding" ) );
994 0 : if( !encoding ) encoding = "base64";
995 0 : _Bool use_zstd = 0; (void)use_zstd;
996 0 : if( 0==strcmp( encoding, "base64+zstd" ) && FD_HAS_ZSTD ) {
997 0 : use_zstd = 1;
998 0 : } else if( 0==strcmp( encoding, "base64" ) ||
999 0 : 0==strcmp( encoding, "jsonParsed" ) ) {
1000 0 : encoding = "base64";
1001 0 : } else {
1002 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: this server only supports 'base64' account data encoding\"},\"id\":%lu}\n", request_id );
1003 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
1004 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1005 0 : return response;
1006 0 : }
1007 :
1008 0 : ulong bank_idx = ULONG_MAX;
1009 0 : char const * commitment = cJSON_GetStringValue( cJSON_GetObjectItemCaseSensitive( config, "commitment" ) );
1010 0 : if( !commitment ) commitment = "confirmed";
1011 0 : if( 0==strcmp( commitment, "confirmed" ) ) {
1012 0 : bank_idx = ctx->confirmed_idx;
1013 0 : } else if( 0==strcmp( commitment, "processed" ) ) {
1014 0 : bank_idx = ctx->processed_idx;
1015 0 : } else if( 0==strcmp( commitment, "finalized" ) ) {
1016 0 : bank_idx = ctx->finalized_idx;
1017 0 : } else {
1018 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: unsupported commitment level\"},\"id\":%lu}\n", request_id );
1019 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
1020 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1021 0 : return response;
1022 0 : }
1023 0 : if( FD_UNLIKELY( bank_idx==ULONG_MAX ) ) {
1024 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: cannot resolve slot for '%s' commitment level\"},\"id\":%lu}\n", commitment, request_id );
1025 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
1026 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1027 0 : return response;
1028 0 : }
1029 :
1030 0 : ulong minContextSlot = 0UL;
1031 0 : cJSON const * _minContextSlot = cJSON_GetObjectItemCaseSensitive( config, "minContextSlot" );
1032 0 : if( FD_UNLIKELY( _minContextSlot && !cJSON_IsNull( _minContextSlot ) ) ) {
1033 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
1034 0 : minContextSlot = _minContextSlot->valueulong;
1035 0 : }
1036 :
1037 0 : bank_info_t const * info = &ctx->banks[ bank_idx ];
1038 0 : if( info->slot < minContextSlot ) {
1039 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%lu}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, info->slot, request_id );
1040 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
1041 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1042 0 : return response;
1043 0 : }
1044 0 : fd_funk_txn_xid_t const xid = { .ul={ info->slot, bank_idx } };
1045 :
1046 0 : ulong data_off = 0U;
1047 0 : ulong data_max = UINT_MAX;
1048 0 : const cJSON * dataSlice = cJSON_GetObjectItemCaseSensitive( config, "dataSlice" );
1049 0 : if( dataSlice && !cJSON_IsNull( dataSlice ) ) {
1050 0 : cJSON const * _length = cJSON_GetObjectItemCaseSensitive( dataSlice, "length" );
1051 0 : cJSON const * _offset = cJSON_GetObjectItemCaseSensitive( dataSlice, "offset" );
1052 0 : if( FD_UNLIKELY( !_length || !cJSON_IsNumber( _length ) ||
1053 0 : !_offset || !cJSON_IsNumber( _offset ) ) ) {
1054 0 : return (fd_http_server_response_t){ .status = 400 };
1055 0 : }
1056 0 : data_off = _offset->valueulong;
1057 0 : data_max = _length->valueulong;
1058 0 : }
1059 :
1060 0 : fd_http_server_printf( ctx->http,
1061 0 : "{\"jsonrpc\":\"2.0\",\"id\":%lu,\"result\":{\"context\":{\"slot\":%lu},\"value\":[",
1062 0 : request_id, info->slot );
1063 :
1064 0 : for( cJSON const * _address = address_list->child; _address; _address = _address->next ) {
1065 0 : if( _address != address_list->child ) {
1066 0 : fd_http_server_printf( ctx->http, "," );
1067 0 : }
1068 :
1069 0 : char const * addr_b58 = cJSON_GetStringValue( _address );
1070 0 : if( FD_UNLIKELY( !addr_b58 ) ) {
1071 0 : return (fd_http_server_response_t){ .status = 400 };
1072 0 : }
1073 0 : uchar addr[ 32 ];
1074 0 : if( FD_UNLIKELY( !fd_base58_decode_32( addr_b58, addr ) ) ) {
1075 0 : return (fd_http_server_response_t){ .status = 400 };
1076 0 : }
1077 :
1078 0 : fd_accdb_ro_t ro[1];
1079 0 : if( FD_UNLIKELY( !fd_accdb_open_ro( ctx->accdb, ro, &xid, addr ) ) ) {
1080 0 : fd_http_server_printf( ctx->http, "null" );
1081 0 : continue;
1082 0 : }
1083 :
1084 0 : ulong const data_sz = fd_accdb_ref_data_sz( ro );
1085 0 : if( data_off>data_sz ) data_off = data_sz;
1086 0 : ulong snip_sz = data_sz - data_off;
1087 0 : if( snip_sz>data_max ) snip_sz = data_max;
1088 :
1089 0 : uchar const * compressed = (uchar const *)fd_accdb_ref_data_const( ro )+data_off;
1090 0 : ulong compressed_sz = snip_sz;
1091 0 : # if FD_HAS_ZSTD
1092 0 : if( use_zstd ) {
1093 0 : size_t zstd_res = ZSTD_compress( ctx->compress_buf, sizeof(ctx->compress_buf), compressed, snip_sz, 3 );
1094 0 : if( ZSTD_isError( zstd_res ) ) {
1095 0 : fd_accdb_close_ro( ctx->accdb, ro );
1096 0 : return (fd_http_server_response_t){ .status = 500 };
1097 0 : }
1098 0 : compressed = ctx->compress_buf;
1099 0 : compressed_sz = (ulong)zstd_res;
1100 0 : }
1101 0 : # endif
1102 :
1103 0 : FD_BASE58_ENCODE_32_BYTES( fd_accdb_ref_owner( ro ), owner_b58 );
1104 0 : fd_http_server_printf( ctx->http,
1105 0 : "{\"executable\":%s,"
1106 0 : "\"lamports\":%lu,"
1107 0 : "\"owner\":\"%s\","
1108 0 : "\"rentEpoch\":18446744073709551615,"
1109 0 : "\"space\":%lu,"
1110 0 : "\"data\":[\"",
1111 0 : fd_accdb_ref_exec_bit( ro ) ? "true" : "false",
1112 0 : fd_accdb_ref_lamports( ro ),
1113 0 : owner_b58,
1114 0 : data_sz );
1115 :
1116 0 : ulong encoded_sz = FD_BASE64_ENC_SZ( snip_sz );
1117 0 : uchar * encoded = fd_http_server_append_start( ctx->http, encoded_sz );
1118 0 : if( FD_UNLIKELY( !encoded ) ) {
1119 0 : fd_accdb_close_ro( ctx->accdb, ro );
1120 0 : return (fd_http_server_response_t){ .status = 500 };
1121 0 : }
1122 0 : encoded_sz = fd_base64_encode( (char *)encoded, compressed, compressed_sz );
1123 0 : fd_http_server_append_end( ctx->http, encoded_sz );
1124 :
1125 0 : fd_http_server_printf( ctx->http, "\",\"%s\"]}\n", encoding );
1126 0 : fd_accdb_close_ro( ctx->accdb, ro );
1127 0 : }
1128 :
1129 0 : fd_http_server_printf( ctx->http, "]}}\n" );
1130 :
1131 0 : fd_http_server_response_t response = { .content_type = "application/json", .status = 200 };
1132 0 : if( fd_http_server_stage_body( ctx->http, &response ) ) response.status = 500;
1133 0 : return response;
1134 0 : }
1135 :
1136 : UNIMPLEMENTED(getProgramAccounts)
1137 : UNIMPLEMENTED(getRecentPerformanceSamples)
1138 : UNIMPLEMENTED(getRecentPrioritizationFees)
1139 : UNIMPLEMENTED(getSignaturesForAddress)
1140 : UNIMPLEMENTED(getSignatureStatuses)
1141 :
1142 : static fd_http_server_response_t
1143 : getSlot( fd_rpc_tile_t * ctx,
1144 : ulong request_id,
1145 0 : cJSON const * params ) {
1146 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
1147 0 : ulong minContextSlot = ULONG_MAX;
1148 :
1149 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
1150 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
1151 :
1152 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
1153 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
1154 :
1155 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
1156 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
1157 :
1158 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
1159 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
1160 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
1161 0 : else return (fd_http_server_response_t){ .status = 400 };
1162 :
1163 0 : const cJSON * _minContextSlot = cJSON_GetObjectItemCaseSensitive( param, "minContextSlot" );
1164 0 : if( FD_UNLIKELY( _minContextSlot ) ) {
1165 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
1166 0 : minContextSlot = _minContextSlot->valueulong;
1167 0 : }
1168 0 : }
1169 :
1170 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
1171 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
1172 :
1173 0 : if( FD_UNLIKELY( minContextSlot!=ULONG_MAX && minContextSlot>bank->slot ) ) {
1174 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%lu}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, bank->slot, request_id );
1175 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
1176 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1177 0 : return response;
1178 0 : }
1179 :
1180 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}\n", bank->slot, request_id );
1181 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
1182 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1183 0 : return response;
1184 0 : }
1185 :
1186 : UNIMPLEMENTED(getSlotLeader)
1187 : UNIMPLEMENTED(getSlotLeaders)
1188 : UNIMPLEMENTED(getStakeMinimumDelegation)
1189 : UNIMPLEMENTED(getSupply)
1190 : UNIMPLEMENTED(getTokenAccountBalance)
1191 : UNIMPLEMENTED(getTokenAccountsByDelegate)
1192 : UNIMPLEMENTED(getTokenAccountsByOwner)
1193 : UNIMPLEMENTED(getTokenLargestAccounts)
1194 : UNIMPLEMENTED(getTokenSupply)
1195 : UNIMPLEMENTED(getTransaction)
1196 :
1197 : static fd_http_server_response_t
1198 : getTransactionCount( fd_rpc_tile_t * ctx,
1199 : ulong request_id,
1200 0 : cJSON const * params ) {
1201 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
1202 0 : ulong minContextSlot = ULONG_MAX;
1203 :
1204 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
1205 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
1206 :
1207 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
1208 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
1209 :
1210 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
1211 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
1212 :
1213 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
1214 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
1215 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
1216 0 : else return (fd_http_server_response_t){ .status = 400 };
1217 :
1218 0 : const cJSON * _minContextSlot = cJSON_GetObjectItemCaseSensitive( param, "minContextSlot" );
1219 0 : if( FD_UNLIKELY( _minContextSlot ) ) {
1220 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
1221 0 : minContextSlot = _minContextSlot->valueulong;
1222 0 : }
1223 0 : }
1224 :
1225 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
1226 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
1227 :
1228 0 : if( FD_UNLIKELY( minContextSlot!=ULONG_MAX && minContextSlot>bank->slot ) ) {
1229 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%lu}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, bank->slot, request_id );
1230 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
1231 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1232 0 : return response;
1233 0 : }
1234 :
1235 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}\n", bank->transaction_count, request_id );
1236 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
1237 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1238 0 : return response;
1239 0 : }
1240 :
1241 : static fd_http_server_response_t
1242 : getVersion( fd_rpc_tile_t * ctx,
1243 : ulong request_id,
1244 0 : cJSON const * params ) {
1245 0 : (void)params;
1246 :
1247 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":{\"solana-core\":\"%s\",\"feature-set\":%u},\"id\":%lu}\n", ctx->version_string, FD_FEATURE_SET_ID, request_id );
1248 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
1249 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
1250 0 : return response;
1251 0 : }
1252 :
1253 : UNIMPLEMENTED(getVoteAccounts) // TODO: Used by solana-exporter
1254 : UNIMPLEMENTED(isBlockhashValid)
1255 : UNIMPLEMENTED(minimumLedgerSlot) // TODO: Used by solana-exporter
1256 : UNIMPLEMENTED(requestAirdrop)
1257 : UNIMPLEMENTED(sendTransaction)
1258 : UNIMPLEMENTED(simulateTransaction)
1259 :
1260 : static fd_http_server_response_t
1261 0 : rpc_http_request( fd_http_server_request_t const * request ) {
1262 0 : fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)request->ctx;
1263 :
1264 0 : if( FD_UNLIKELY( request->method!=FD_HTTP_SERVER_METHOD_POST ) ) {
1265 0 : return (fd_http_server_response_t){
1266 0 : .status = 400,
1267 0 : };
1268 0 : }
1269 :
1270 0 : const char * parse_end;
1271 0 : cJSON * json = cJSON_ParseWithLengthOpts( (char *)request->post.body, request->post.body_len, &parse_end, 0 );
1272 0 : if( FD_UNLIKELY( !json ) ) {
1273 0 : return (fd_http_server_response_t){ .status = 400 };
1274 0 : }
1275 :
1276 0 : const cJSON * jsonrpc = cJSON_GetObjectItemCaseSensitive( json, "jsonrpc" );
1277 0 : if( FD_UNLIKELY( !cJSON_IsString( jsonrpc ) || strcmp( jsonrpc->valuestring, "2.0" ) ) ) goto bad_request;
1278 :
1279 0 : const cJSON * id = cJSON_GetObjectItemCaseSensitive( json, "id" );
1280 0 : ulong request_id = 0UL;
1281 0 : if( FD_UNLIKELY( !cJSON_IsNumber( id ) ) ) goto bad_request;
1282 0 : request_id = id->valueulong;
1283 :
1284 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
1285 0 : if( FD_UNLIKELY( params && !cJSON_IsArray( params ) ) ) goto bad_request;
1286 :
1287 0 : fd_http_server_response_t response;
1288 :
1289 0 : const cJSON * _method = cJSON_GetObjectItemCaseSensitive( json, "method" );
1290 0 : if( FD_LIKELY( !cJSON_IsString( _method ) || _method->valuestring==NULL ) ) goto bad_request;
1291 0 : if( FD_LIKELY( !strcmp( _method->valuestring, "getAccountInfo" ) ) ) response = getAccountInfo( ctx, request_id, params );
1292 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBalance" ) ) ) response = getBalance( ctx, request_id, params );
1293 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlock" ) ) ) response = getBlock( ctx, request_id, params );
1294 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockCommitment" ) ) ) response = getBlockCommitment( ctx, request_id, params );
1295 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockHeight" ) ) ) response = getBlockHeight( ctx, request_id, params );
1296 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockProduction" ) ) ) response = getBlockProduction( ctx, request_id, params );
1297 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlocks" ) ) ) response = getBlocks( ctx, request_id, params );
1298 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlocksWithLimit" ) ) ) response = getBlocksWithLimit( ctx, request_id, params );
1299 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockTime" ) ) ) response = getBlockTime( ctx, request_id, params );
1300 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getClusterNodes" ) ) ) response = getClusterNodes( ctx, request_id, params );
1301 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getEpochInfo" ) ) ) response = getEpochInfo( ctx, request_id, params );
1302 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getEpochSchedule" ) ) ) response = getEpochSchedule( ctx, request_id, params );
1303 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getFeeForMessage" ) ) ) response = getFeeForMessage( ctx, request_id, params );
1304 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getFirstAvailableBlock" ) ) ) response = getFirstAvailableBlock( ctx, request_id, params );
1305 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getGenesisHash" ) ) ) response = getGenesisHash( ctx, request_id, params );
1306 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getHealth" ) ) ) response = getHealth( ctx, request_id, params );
1307 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getHighestSnapshotSlot" ) ) ) response = getHighestSnapshotSlot( ctx, request_id, params );
1308 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getIdentity" ) ) ) response = getIdentity( ctx, request_id, params );
1309 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationGovernor" ) ) ) response = getInflationGovernor( ctx, request_id, params );
1310 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationRate" ) ) ) response = getInflationRate( ctx, request_id, params );
1311 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationReward" ) ) ) response = getInflationReward( ctx, request_id, params );
1312 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLargestAccounts" ) ) ) response = getLargestAccounts( ctx, request_id, params );
1313 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLatestBlockhash" ) ) ) response = getLatestBlockhash( ctx, request_id, params );
1314 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLeaderSchedule" ) ) ) response = getLeaderSchedule( ctx, request_id, params );
1315 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMaxRetransmitSlot" ) ) ) response = getMaxRetransmitSlot( ctx, request_id, params );
1316 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMaxShredInsertSlot" ) ) ) response = getMaxShredInsertSlot( ctx, request_id, params );
1317 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMinimumBalanceForRentExemption" ) ) ) response = getMinimumBalanceForRentExemption( ctx, request_id, params );
1318 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMultipleAccounts" ) ) ) response = getMultipleAccounts( ctx, request_id, params );
1319 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getProgramAccounts" ) ) ) response = getProgramAccounts( ctx, request_id, params );
1320 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getRecentPerformanceSamples" ) ) ) response = getRecentPerformanceSamples( ctx, request_id, params );
1321 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getRecentPrioritizationFees" ) ) ) response = getRecentPrioritizationFees( ctx, request_id, params );
1322 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSignaturesForAddress" ) ) ) response = getSignaturesForAddress( ctx, request_id, params );
1323 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSignatureStatuses" ) ) ) response = getSignatureStatuses( ctx, request_id, params );
1324 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlot" ) ) ) response = getSlot( ctx, request_id, params );
1325 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlotLeader" ) ) ) response = getSlotLeader( ctx, request_id, params );
1326 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlotLeaders" ) ) ) response = getSlotLeaders( ctx, request_id, params );
1327 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getStakeMinimumDelegation" ) ) ) response = getStakeMinimumDelegation( ctx, request_id, params );
1328 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSupply" ) ) ) response = getSupply( ctx, request_id, params );
1329 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountBalance" ) ) ) response = getTokenAccountBalance( ctx, request_id, params );
1330 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountsByDelegate" ) ) ) response = getTokenAccountsByDelegate( ctx, request_id, params );
1331 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountsByOwner" ) ) ) response = getTokenAccountsByOwner( ctx, request_id, params );
1332 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenLargestAccounts" ) ) ) response = getTokenLargestAccounts( ctx, request_id, params );
1333 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenSupply" ) ) ) response = getTokenSupply( ctx, request_id, params );
1334 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTransaction" ) ) ) response = getTransaction( ctx, request_id, params );
1335 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTransactionCount" ) ) ) response = getTransactionCount( ctx, request_id, params );
1336 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getVersion" ) ) ) response = getVersion( ctx, request_id, params );
1337 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getVoteAccounts" ) ) ) response = getVoteAccounts( ctx, request_id, params );
1338 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "isBlockhashValid" ) ) ) response = isBlockhashValid( ctx, request_id, params );
1339 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "minimumLedgerSlot" ) ) ) response = minimumLedgerSlot( ctx, request_id, params );
1340 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "requestAirdrop" ) ) ) response = requestAirdrop( ctx, request_id, params );
1341 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "sendTransaction" ) ) ) response = sendTransaction( ctx, request_id, params );
1342 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "simulateTransaction" ) ) ) response = simulateTransaction( ctx, request_id, params );
1343 0 : else goto bad_request;
1344 :
1345 0 : cJSON_Delete( json );
1346 0 : return response;
1347 :
1348 0 : bad_request:
1349 0 : cJSON_Delete( json );
1350 0 : return (fd_http_server_response_t){ .status = 400 };
1351 0 : }
1352 :
1353 : static void
1354 : privileged_init( fd_topo_t * topo,
1355 0 : fd_topo_tile_t * tile ) {
1356 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1357 :
1358 0 : fd_http_server_params_t http_params = derive_http_params( tile );
1359 :
1360 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1361 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
1362 0 : fd_http_server_t * _http = FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(), fd_http_server_footprint( http_params ) );
1363 :
1364 0 : if( FD_UNLIKELY( !strcmp( tile->rpc.identity_key_path, "" ) ) )
1365 0 : FD_LOG_ERR(( "identity_key_path not set" ));
1366 :
1367 0 : const uchar * identity_key = fd_keyload_load( tile->rpc.identity_key_path, /* pubkey only: */ 1 );
1368 0 : fd_memcpy( ctx->identity_pubkey, identity_key, 32UL );
1369 :
1370 0 : fd_http_server_callbacks_t callbacks = {
1371 0 : .request = rpc_http_request,
1372 0 : };
1373 0 : ctx->http = fd_http_server_join( fd_http_server_new( _http, http_params, callbacks, ctx ) );
1374 0 : fd_http_server_listen( ctx->http, tile->rpc.listen_addr, tile->rpc.listen_port );
1375 :
1376 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 ));
1377 0 : }
1378 :
1379 : extern char const fdctl_version_string[];
1380 :
1381 : static inline fd_rpc_out_t
1382 : out1( fd_topo_t const * topo,
1383 : fd_topo_tile_t const * tile,
1384 0 : char const * name ) {
1385 0 : ulong idx = ULONG_MAX;
1386 :
1387 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
1388 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
1389 0 : if( !strcmp( link->name, name ) ) {
1390 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 ));
1391 0 : idx = i;
1392 0 : }
1393 0 : }
1394 :
1395 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_rpc_out_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
1396 :
1397 :
1398 0 : ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
1399 0 : if( FD_UNLIKELY( mtu==0UL ) ) return (fd_rpc_out_t){ .idx = idx, .mem = NULL, .chunk0 = ULONG_MAX, .wmark = ULONG_MAX, .chunk = ULONG_MAX };
1400 :
1401 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
1402 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
1403 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
1404 :
1405 0 : return (fd_rpc_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
1406 0 : }
1407 :
1408 : static void
1409 : unprivileged_init( fd_topo_t * topo,
1410 0 : fd_topo_tile_t * tile ) {
1411 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1412 :
1413 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1414 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
1415 0 : FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(), fd_http_server_footprint( derive_http_params( tile ) ) );
1416 0 : void * _alloc = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
1417 0 : void * _banks = FD_SCRATCH_ALLOC_APPEND( l, alignof(bank_info_t), tile->rpc.max_live_slots*sizeof(bank_info_t) );
1418 :
1419 0 : fd_alloc_t * alloc = fd_alloc_join( fd_alloc_new( _alloc, 1UL ), 1UL );
1420 0 : FD_TEST( alloc );
1421 0 : cJSON_alloc_install( alloc );
1422 :
1423 0 : ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->keyswitch_obj_id ) );
1424 0 : FD_TEST( ctx->keyswitch );
1425 :
1426 0 : ctx->next_poll_deadline = fd_tickcount();
1427 :
1428 0 : ctx->cluster_confirmed_slot = ULONG_MAX;
1429 :
1430 0 : ctx->processed_idx = ULONG_MAX;
1431 0 : ctx->confirmed_idx = ULONG_MAX;
1432 0 : ctx->finalized_idx = ULONG_MAX;
1433 :
1434 0 : ctx->banks = _banks;
1435 :
1436 0 : FD_TEST( fd_cstr_printf_check( ctx->version_string, sizeof( ctx->version_string ), NULL, "%s", fdctl_version_string ) );
1437 :
1438 0 : FD_TEST( tile->in_cnt<=sizeof( ctx->in )/sizeof( ctx->in[ 0 ] ) );
1439 0 : for( ulong i=0; i<tile->in_cnt; i++ ) {
1440 0 : fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ];
1441 0 : fd_topo_wksp_t * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
1442 :
1443 0 : ctx->in[ i ].mem = link_wksp->wksp;
1444 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
1445 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
1446 0 : ctx->in[ i ].mtu = link->mtu;
1447 :
1448 0 : if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
1449 0 : else if( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESI;
1450 0 : else FD_LOG_ERR(( "unexpected link name %s", link->name ));
1451 0 : }
1452 :
1453 0 : *ctx->replay_out = out1( topo, tile, "rpc_replay" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
1454 :
1455 0 : fd_accdb_init_from_topo( ctx->accdb, topo, tile );
1456 :
1457 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, 1UL );
1458 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
1459 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
1460 0 : }
1461 :
1462 : static ulong
1463 : populate_allowed_seccomp( fd_topo_t const * topo,
1464 : fd_topo_tile_t const * tile,
1465 : ulong out_cnt,
1466 0 : struct sock_filter * out ) {
1467 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1468 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1469 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
1470 :
1471 0 : populate_sock_filter_policy_fd_rpc_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)fd_http_server_fd( ctx->http ) );
1472 0 : return sock_filter_policy_fd_rpc_tile_instr_cnt;
1473 0 : }
1474 :
1475 : static ulong
1476 : populate_allowed_fds( fd_topo_t const * topo,
1477 : fd_topo_tile_t const * tile,
1478 : ulong out_fds_cnt,
1479 0 : int * out_fds ) {
1480 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1481 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1482 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
1483 :
1484 0 : if( FD_UNLIKELY( out_fds_cnt<3UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
1485 :
1486 0 : ulong out_cnt = 0UL;
1487 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
1488 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
1489 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
1490 0 : out_fds[ out_cnt++ ] = fd_http_server_fd( ctx->http ); /* rpc listen socket */
1491 0 : return out_cnt;
1492 0 : }
1493 :
1494 : static ulong
1495 : rlimit_file_cnt( fd_topo_t const * topo FD_PARAM_UNUSED,
1496 0 : fd_topo_tile_t const * tile ) {
1497 : /* pipefd, socket, stderr, logfile, and one spare for new accept() connections */
1498 0 : ulong base = 5UL;
1499 0 : return base+tile->rpc.max_http_connections;
1500 0 : }
1501 :
1502 0 : #define STEM_BURST (1UL)
1503 0 : #define STEM_LAZY (50UL)
1504 :
1505 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_rpc_tile_t
1506 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_rpc_tile_t)
1507 :
1508 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
1509 0 : #define STEM_CALLBACK_BEFORE_CREDIT before_credit
1510 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
1511 :
1512 : #include "../../disco/stem/fd_stem.c"
1513 :
1514 : #ifndef FD_TILE_TEST
1515 : fd_topo_run_tile_t fd_tile_rpc = {
1516 : .name = "rpc",
1517 : .rlimit_file_cnt_fn = rlimit_file_cnt,
1518 : .populate_allowed_seccomp = populate_allowed_seccomp,
1519 : .populate_allowed_fds = populate_allowed_fds,
1520 : .scratch_align = scratch_align,
1521 : .scratch_footprint = scratch_footprint,
1522 : .loose_footprint = loose_footprint,
1523 : .privileged_init = privileged_init,
1524 : .unprivileged_init = unprivileged_init,
1525 : .run = stem_run,
1526 : };
1527 : #endif
|