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 "../../flamenco/features/fd_features.h"
7 : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
8 : #include "../../waltz/http/fd_http_server.h"
9 : #include "../../waltz/http/fd_http_server_private.h"
10 : #include "../../ballet/json/cJSON.h"
11 : #include "../../ballet/json/cJSON_alloc.h"
12 : #include "../../ballet/lthash/fd_lthash.h"
13 :
14 : #include <stddef.h>
15 : #include <sys/socket.h>
16 :
17 : #include "generated/fd_rpc_tile_seccomp.h"
18 :
19 0 : #define FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN 8192UL
20 :
21 0 : #define IN_KIND_REPLAY (0)
22 0 : #define IN_KIND_GENESI (0)
23 :
24 0 : #define FD_RPC_COMMITMENT_PROCESSED (0)
25 0 : #define FD_RPC_COMMITMENT_CONFIRMED (1)
26 0 : #define FD_RPC_COMMITMENT_FINALIZED (2)
27 :
28 : #define FD_RPC_ENCODING_BASE58 (0)
29 : #define FD_RPC_ENCODING_BASE64 (1)
30 : #define FD_RPC_ENCODING_BASE64_ZSTD (2)
31 : #define FD_RPC_ENCODING_BINARY (3)
32 : #define FD_RPC_ENCODING_JSON_PARSED (4)
33 :
34 : #define FD_RPC_METHOD_GET_ACCOUNT_INFO ( 0)
35 : #define FD_RPC_METHOD_GET_BALANCE ( 1)
36 : #define FD_RPC_METHOD_GET_BLOCK ( 2)
37 : #define FD_RPC_METHOD_GET_BLOCK_COMMITMENT ( 3)
38 : #define FD_RPC_METHOD_GET_BLOCK_HEIGHT ( 4)
39 : #define FD_RPC_METHOD_GET_BLOCK_PRODUCTION ( 5)
40 : #define FD_RPC_METHOD_GET_BLOCKS ( 6)
41 : #define FD_RPC_METHOD_GET_BLOCKS_WITH_LIMIT ( 7)
42 : #define FD_RPC_METHOD_GET_BLOCK_TIME ( 8)
43 : #define FD_RPC_METHOD_GET_CLUSTER_NODES ( 9)
44 : #define FD_RPC_METHOD_GET_EPOCH_INFO (10)
45 : #define FD_RPC_METHOD_GET_EPOCH_SCHEDULE (11)
46 : #define FD_RPC_METHOD_GET_FEE_FOR_MESSAGE (12)
47 : #define FD_RPC_METHOD_GET_FIRST_AVAILABLE_BLOCK (13)
48 : #define FD_RPC_METHOD_GET_GENESIS_HASH (14)
49 : #define FD_RPC_METHOD_GET_HEALTH (15)
50 : #define FD_RPC_METHOD_GET_HIGHEST_SNAPSHOT_SLOT (16)
51 : #define FD_RPC_METHOD_GET_IDENTITY (17)
52 : #define FD_RPC_METHOD_GET_INFLATION_GOVERNOR (18)
53 : #define FD_RPC_METHOD_GET_INFLATION_RATE (19)
54 : #define FD_RPC_METHOD_GET_INFLATION_REWARD (20)
55 : #define FD_RPC_METHOD_GET_LARGEST_ACCOUNTS (21)
56 : #define FD_RPC_METHOD_GET_LATEST_BLOCKHASH (22)
57 : #define FD_RPC_METHOD_GET_LEADER_SCHEDULE (23)
58 : #define FD_RPC_METHOD_GET_MAX_RETRANSMIT_SLOT (24)
59 : #define FD_RPC_METHOD_GET_MAX_SHRED_INSERT_SLOT (25)
60 : #define FD_RPC_METHOD_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION (26)
61 : #define FD_RPC_METHOD_GET_MULTIPLE_ACCOUNTS (27)
62 : #define FD_RPC_METHOD_GET_PROGRAM_ACCOUNTS (28)
63 : #define FD_RPC_METHOD_GET_RECENT_PERFORMANCE_SAMPLES (29)
64 : #define FD_RPC_METHOD_GET_RECENT_PRIORITIZATION_FEES (30)
65 : #define FD_RPC_METHOD_GET_SIGNATURES_FOR_ADDRESS (31)
66 : #define FD_RPC_METHOD_GET_SIGNATURE_STATUSES (32)
67 : #define FD_RPC_METHOD_GET_SLOT (33)
68 : #define FD_RPC_METHOD_GET_SLOT_LEADER (34)
69 : #define FD_RPC_METHOD_GET_SLOT_LEADERS (35)
70 : #define FD_RPC_METHOD_GET_STAKE_MINIMUM_DELEGATION (36)
71 : #define FD_RPC_METHOD_GET_SUPPLY (37)
72 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNT_BALANCE (38)
73 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_DELEGATE (39)
74 : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_OWNER (40)
75 : #define FD_RPC_METHOD_GET_TOKEN_LARGEST_ACCOUNTS (41)
76 : #define FD_RPC_METHOD_GET_TOKEN_SUPPLY (42)
77 : #define FD_RPC_METHOD_GET_TRANSACTION (43)
78 : #define FD_RPC_METHOD_GET_TRANSACTION_COUNT (44)
79 : #define FD_RPC_METHOD_GET_VERSION (45)
80 : #define FD_RPC_METHOD_GET_VOTE_ACCOUNTS (46)
81 : #define FD_RPC_METHOD_IS_BLOCKHASH_VALID (47)
82 : #define FD_RPC_METHOD_MINIMUM_LEDGER_SLOT (48)
83 : #define FD_RPC_METHOD_REQUEST_AIRDROP (49)
84 : #define FD_RPC_METHOD_SEND_TRANSACTION (50)
85 : #define FD_RPC_METHOD_SIMULATE_TRANSACTION (51)
86 :
87 : // Keep in sync with https://github.com/solana-labs/solana-web3.js/blob/master/src/errors.ts
88 : // and https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/custom_error.rs
89 : #define FD_RPC_ERROR_BLOCK_CLEANED_UP (-32001)
90 : #define FD_RPC_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE (-32002)
91 : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE (-32003)
92 : #define FD_RPC_ERROR_BLOCK_NOT_AVAILABLE (-32004)
93 0 : #define FD_RPC_ERROR_NODE_UNHEALTHY (-32005)
94 : #define FD_RPC_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE (-32006)
95 : #define FD_RPC_ERROR_SLOT_SKIPPED (-32007)
96 0 : #define FD_RPC_ERROR_NO_SNAPSHOT (-32008)
97 : #define FD_RPC_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED (-32009)
98 : #define FD_RPC_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX (-32010)
99 : #define FD_RPC_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE (-32011)
100 : #define FD_RPC_ROR (-32012)
101 : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH (-32013)
102 : #define FD_RPC_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET (-32014)
103 : #define FD_RPC_ERROR_UNSUPPORTED_TRANSACTION_VERSION (-32015)
104 0 : #define FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED (-32016)
105 : #define FD_RPC_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE (-32017)
106 : #define FD_RPC_ERROR_SLOT_NOT_EPOCH_BOUNDARY (-32018)
107 : #define FD_RPC_ERROR_LONG_TERM_STORAGE_UNREACHABLE (-32019)
108 :
109 : static fd_http_server_params_t
110 0 : derive_http_params( fd_topo_tile_t const * tile ) {
111 0 : return (fd_http_server_params_t) {
112 0 : .max_connection_cnt = tile->rpc.max_http_connections,
113 0 : .max_ws_connection_cnt = 0UL,
114 0 : .max_request_len = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
115 0 : .max_ws_recv_frame_len = 0UL,
116 0 : .max_ws_send_frame_cnt = 0UL,
117 0 : .outgoing_buffer_sz = tile->rpc.send_buffer_size_mb * (1UL<<20UL),
118 0 : .compress_websocket = 0,
119 0 : };
120 0 : }
121 :
122 : struct fd_rpc_in {
123 : fd_wksp_t * mem;
124 : ulong chunk0;
125 : ulong wmark;
126 : ulong mtu;
127 : };
128 :
129 : typedef struct fd_rpc_in fd_rpc_in_t;
130 :
131 : struct fd_rpc_out {
132 : ulong idx;
133 : fd_wksp_t * mem;
134 : ulong chunk0;
135 : ulong wmark;
136 : ulong chunk;
137 : };
138 :
139 : typedef struct fd_rpc_out fd_rpc_out_t;
140 :
141 : struct bank_info {
142 : ulong slot;
143 : ulong transaction_count;
144 : uchar block_hash[ 32 ];
145 : ulong block_height;
146 :
147 : struct {
148 : double initial;
149 : double terminal;
150 : double taper;
151 : double foundation;
152 : double foundation_term;
153 : } inflation;
154 :
155 : struct {
156 : ulong lamports_per_uint8_year;
157 : double exemption_threshold;
158 : uchar burn_percent;
159 : } rent;
160 : };
161 :
162 : typedef struct bank_info bank_info_t;
163 :
164 : struct fd_rpc_tile {
165 : fd_http_server_t * http;
166 :
167 : bank_info_t * banks;
168 :
169 : ulong cluster_confirmed_slot;
170 :
171 : ulong processed_idx;
172 : ulong confirmed_idx;
173 : ulong finalized_idx;
174 :
175 : int has_genesis_hash;
176 : uchar genesis_hash[ 32 ];
177 :
178 : long next_poll_deadline;
179 :
180 : char version_string[ 16UL ];
181 :
182 : fd_keyswitch_t * keyswitch;
183 : uchar identity_pubkey[ 32UL ];
184 :
185 : int in_kind[ 64UL ];
186 : fd_rpc_in_t in[ 64UL ];
187 :
188 : fd_rpc_out_t replay_out[1];
189 : };
190 :
191 : typedef struct fd_rpc_tile fd_rpc_tile_t;
192 :
193 : FD_FN_CONST static inline ulong
194 0 : scratch_align( void ) {
195 0 : return alignof( fd_rpc_tile_t );
196 0 : }
197 :
198 : FD_FN_PURE static inline ulong
199 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
200 0 : ulong http_fp = fd_http_server_footprint( derive_http_params( tile ) );
201 0 : if( FD_UNLIKELY( !http_fp ) ) FD_LOG_ERR(( "Invalid [tiles.rpc] config parameters" ));
202 :
203 0 : ulong l = FD_LAYOUT_INIT;
204 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
205 0 : l = FD_LAYOUT_APPEND( l, fd_http_server_align(), http_fp );
206 0 : l = FD_LAYOUT_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
207 0 : l = FD_LAYOUT_APPEND( l, alignof(bank_info_t), tile->rpc.max_live_slots*sizeof(bank_info_t) );
208 0 : return FD_LAYOUT_FINI( l, scratch_align() );
209 0 : }
210 :
211 : FD_FN_PURE static inline ulong
212 0 : loose_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
213 0 : return 256UL * (1UL<<20UL); /* 256MiB of heap space for the cJSON allocator */
214 0 : }
215 :
216 : static inline void
217 0 : during_housekeeping( fd_rpc_tile_t * ctx ) {
218 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
219 0 : fd_memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
220 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
221 0 : }
222 0 : }
223 :
224 : static void
225 : before_credit( fd_rpc_tile_t * ctx,
226 : fd_stem_context_t * stem,
227 0 : int * charge_busy ) {
228 0 : (void)stem;
229 :
230 0 : long now = fd_tickcount();
231 0 : if( FD_UNLIKELY( now>=ctx->next_poll_deadline ) ) {
232 0 : *charge_busy = fd_http_server_poll( ctx->http, 0 );
233 0 : ctx->next_poll_deadline = fd_tickcount() + (long)(fd_tempo_tick_per_ns( NULL )*128L*1000L);
234 0 : }
235 0 : }
236 :
237 : static inline int
238 : returnable_frag( fd_rpc_tile_t * ctx,
239 : ulong in_idx,
240 : ulong seq,
241 : ulong sig,
242 : ulong chunk,
243 : ulong sz,
244 : ulong ctl,
245 : ulong tsorig,
246 : ulong tspub,
247 0 : fd_stem_context_t * stem ) {
248 0 : (void)ctx;
249 0 : (void)in_idx;
250 0 : (void)seq;
251 0 : (void)sig;
252 0 : (void)chunk;
253 0 : (void)sz;
254 0 : (void)ctl;
255 0 : (void)tsorig;
256 0 : (void)tspub;
257 0 : (void)stem;
258 :
259 0 : if( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) {
260 0 : switch( sig ) {
261 0 : case REPLAY_SIG_SLOT_COMPLETED: {
262 0 : fd_replay_slot_completed_t const * slot_completed = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
263 :
264 0 : bank_info_t * bank = &ctx->banks[ slot_completed->bank_idx ];
265 0 : bank->slot = slot_completed->slot;
266 0 : bank->transaction_count = slot_completed->transaction_count;
267 0 : bank->block_height = slot_completed->block_height;
268 0 : fd_memcpy( bank->block_hash, slot_completed->block_hash.uc, 32 );
269 :
270 0 : bank->inflation.initial = slot_completed->inflation.initial;
271 0 : bank->inflation.terminal = slot_completed->inflation.terminal;
272 0 : bank->inflation.taper = slot_completed->inflation.taper;
273 0 : bank->inflation.foundation = slot_completed->inflation.foundation;
274 0 : bank->inflation.foundation_term = slot_completed->inflation.foundation_term;
275 :
276 0 : bank->rent.lamports_per_uint8_year = slot_completed->rent.lamports_per_uint8_year;
277 0 : bank->rent.exemption_threshold = slot_completed->rent.exemption_threshold;
278 0 : bank->rent.burn_percent = slot_completed->rent.burn_percent;
279 :
280 0 : break;
281 0 : }
282 0 : case REPLAY_SIG_RESET: {
283 0 : fd_poh_reset_t const * reset = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
284 :
285 0 : ulong prior_processed_idx = ctx->processed_idx;
286 0 : ctx->processed_idx = reset->bank_idx;
287 :
288 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 );
289 0 : break;
290 0 : }
291 0 : default: break;
292 0 : }
293 0 : } else if( ctx->in_kind[ in_idx ]==IN_KIND_GENESI ) {
294 0 : ctx->has_genesis_hash = 1;
295 0 : uchar const * src = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
296 0 : if( FD_LIKELY( sig==GENESI_SIG_BOOTSTRAP_COMPLETED ) ) {
297 0 : fd_memcpy( ctx->genesis_hash, src+sizeof(fd_lthash_value_t), 32UL );
298 0 : } else {
299 0 : fd_memcpy( ctx->genesis_hash, src, 32UL );
300 0 : }
301 0 : }
302 :
303 0 : return 0;
304 0 : }
305 :
306 : static void
307 0 : jsonp_strip_trailing_comma( fd_http_server_t * http ) {
308 0 : if( FD_LIKELY( !http->stage_err &&
309 0 : http->stage_len>=1UL &&
310 0 : http->oring[ (http->stage_off%http->oring_sz)+http->stage_len-1UL ]==(uchar)',' ) ) {
311 0 : http->stage_len--;
312 0 : }
313 0 : }
314 :
315 : static void
316 : jsonp_open_object( fd_http_server_t * http,
317 0 : char const * key ) {
318 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":{", key );
319 0 : else fd_http_server_printf( http, "{" );
320 0 : }
321 :
322 : static void
323 0 : jsonp_close_object( fd_http_server_t * http ) {
324 0 : jsonp_strip_trailing_comma( http );
325 0 : fd_http_server_printf( http, "}," );
326 0 : }
327 :
328 :
329 : static void FD_FN_UNUSED
330 : jsonp_open_array( 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 FD_FN_UNUSED
337 0 : jsonp_close_array( fd_http_server_t * http ) {
338 0 : jsonp_strip_trailing_comma( http );
339 0 : fd_http_server_printf( http, "]," );
340 0 : }
341 :
342 : static void
343 : jsonp_ulong( fd_http_server_t * http,
344 : char const * key,
345 0 : ulong value ) {
346 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":%lu,", key, value );
347 0 : else fd_http_server_printf( http, "%lu,", value );
348 0 : }
349 :
350 : static void FD_FN_UNUSED
351 : jsonp_long( fd_http_server_t * http,
352 : char const * key,
353 0 : long value ) {
354 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":%ld,", key, value );
355 0 : else fd_http_server_printf( http, "%ld,", value );
356 0 : }
357 :
358 : static void FD_FN_UNUSED
359 : jsonp_double( fd_http_server_t * http,
360 : char const * key,
361 0 : double value ) {
362 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":%.2f,", key, value );
363 0 : else fd_http_server_printf( http, "%.2f,", value );
364 0 : }
365 :
366 : static void
367 : jsonp_string( fd_http_server_t * http,
368 : char const * key,
369 0 : char const * value ) {
370 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":", key );
371 0 : if( FD_LIKELY( value ) ) {
372 0 : ulong value_len = strlen( value );
373 0 : FD_TEST( fd_utf8_verify( value, value_len ) );
374 0 : for( ulong i=0UL; i<value_len; i++ ) FD_TEST( value[ i ]>=0x20 && value[ i ]!='"' && value[ i ]!='\\' );
375 :
376 0 : fd_http_server_printf( http, "\"%s\",", value );
377 0 : } else {
378 0 : fd_http_server_printf( http, "null," );
379 0 : }
380 0 : }
381 :
382 : static void FD_FN_UNUSED
383 : jsonp_bool( fd_http_server_t * http,
384 : char const * key,
385 0 : int value ) {
386 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\":%s,", key, value ? "true" : "false" );
387 0 : else fd_http_server_printf( http, "%s,", value ? "true" : "false" );
388 0 : }
389 :
390 : static void FD_FN_UNUSED
391 : jsonp_null( fd_http_server_t * http,
392 0 : char const * key ) {
393 0 : if( FD_LIKELY( key ) ) fd_http_server_printf( http, "\"%s\": null,", key );
394 0 : else fd_http_server_printf( http, "null," );
395 0 : }
396 :
397 : static void
398 0 : jsonp_open_envelope( fd_http_server_t * http ) {
399 0 : jsonp_open_object( http, NULL );
400 0 : jsonp_string( http, "jsonrpc", "2.0" );
401 0 : jsonp_open_object( http, "result" );
402 0 : }
403 :
404 : static void
405 : jsonp_close_envelope( fd_http_server_t * http,
406 0 : ulong id ) {
407 0 : jsonp_close_object( http );
408 0 : jsonp_ulong( http, "id", id );
409 0 : jsonp_close_object( http );
410 0 : jsonp_strip_trailing_comma( http );
411 0 : }
412 :
413 : #define UNIMPLEMENTED(X) \
414 : static fd_http_server_response_t \
415 : X( fd_rpc_tile_t * ctx, \
416 : ulong request_id, \
417 0 : cJSON const * params ) { \
418 0 : (void)ctx; (void)request_id; (void)params; \
419 0 : return (fd_http_server_response_t){ .status = 501 }; \
420 0 : }
421 :
422 : UNIMPLEMENTED(getAccountInfo) // TODO: Used by solana-exporter
423 : UNIMPLEMENTED(getBalance) // TODO: Used by solana-exporter
424 : UNIMPLEMENTED(getBlock) // TODO: Used by solana-exporter
425 : UNIMPLEMENTED(getBlockCommitment)
426 :
427 : static fd_http_server_response_t
428 : getBlockHeight( fd_rpc_tile_t * ctx,
429 : ulong request_id,
430 0 : cJSON const * params ) {
431 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
432 0 : ulong minContextSlot = ULONG_MAX;
433 :
434 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
435 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
436 :
437 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
438 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
439 :
440 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
441 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
442 :
443 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
444 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
445 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
446 0 : else return (fd_http_server_response_t){ .status = 400 };
447 :
448 0 : const cJSON * _minContextSlot = cJSON_GetObjectItemCaseSensitive( param, "minContextSlot" );
449 0 : if( FD_UNLIKELY( _minContextSlot ) ) {
450 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
451 0 : minContextSlot = _minContextSlot->valueulong;
452 0 : }
453 0 : }
454 :
455 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
456 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
457 :
458 0 : if( FD_UNLIKELY( minContextSlot!=ULONG_MAX && minContextSlot>bank->slot ) ) {
459 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 );
460 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
461 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
462 0 : return response;
463 0 : }
464 :
465 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}\n", bank->block_height, request_id );
466 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
467 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
468 0 : return response;
469 0 : }
470 :
471 : UNIMPLEMENTED(getBlockProduction) // TODO: Used by solana-exporter
472 : UNIMPLEMENTED(getBlocks)
473 : UNIMPLEMENTED(getBlocksWithLimit)
474 : UNIMPLEMENTED(getBlockTime)
475 : UNIMPLEMENTED(getClusterNodes)
476 : UNIMPLEMENTED(getEpochInfo) // TODO: Used by solana-exporter
477 : UNIMPLEMENTED(getEpochSchedule)
478 : UNIMPLEMENTED(getFeeForMessage)
479 : UNIMPLEMENTED(getFirstAvailableBlock) // TODO: Used by solana-exporter
480 :
481 : /* Get the genesis hash of the cluster. Firedancer deviates slightly
482 : from Agave here, as the genesis hash is not always known when RPC
483 : is first booted, it may need to be determined asynchronously in the
484 : background, fetched from a peer node. If the genesis hash is not yet
485 : known, we return an error indicating no snapshot is available. */
486 :
487 : static fd_http_server_response_t
488 : getGenesisHash( fd_rpc_tile_t * ctx,
489 : ulong request_id,
490 0 : cJSON const * params ) {
491 0 : (void)params;
492 :
493 0 : if( FD_UNLIKELY( !ctx->has_genesis_hash ) ) {
494 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 );
495 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
496 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
497 0 : return response;
498 0 : }
499 :
500 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%lu}\n", FD_BASE58_ENC_32_ALLOCA( ctx->genesis_hash ), request_id );
501 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
502 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
503 0 : return response;
504 0 : }
505 :
506 : /* Determines if the node is healthy. Agave defines this as follows,
507 :
508 : - On boot, nodes must go through the entire snapshot slot database
509 : and hash everything, and once it's done verify the hash matches.
510 : While this is ongoing, the node is unhealthy with a "slotsBehind"
511 : value of null.
512 :
513 : - On boot, if the cluster is restarting and we are currently waiting
514 : for a supermajority of stake to join gossip to proceed with
515 : booting, the node is forcibly marked as healthy, to, per Agave,
516 :
517 : > prevent load balancers from removing the node from their list
518 : > of candidates during a manual restart
519 :
520 : - In addition, once booted, there is a period where we do not yet
521 : know the cluster confirmed slot, because we have not yet observed
522 : any (or enough) votes arrive from peers in the cluster. During
523 : this period the node is unhealthy with a "slotsBehind" value of
524 : null.
525 :
526 : - Finally, once the cluster confirmed slot is known, which is the
527 : highest optimistically confirmed slot observed from both gossip,
528 : and votes procesed in blocks, it is compared to our own
529 : optimistically confirmed slot, which is just the highest slot down
530 : the cluster confirmed fork that we have finished replaying
531 : locally. The difference between these two slots is compared, and
532 : if it is less than or equal to 128, the node is healthy, otherwise
533 : it is unhealthy with a "slotsBehind" value equal to the
534 : difference.
535 :
536 : Firedancer currently only implements the final two checks, and does
537 : not forcibly mark the node as healthy while waiting for a
538 : supermajority, nor does it mark a node as unhealthy while hashing the
539 : snapshot database on boot. Firedancer hashes snapshots so quickly
540 : that the node will die on boot if the hash is not valid. */
541 :
542 : static fd_http_server_response_t
543 : getHealth( fd_rpc_tile_t * ctx,
544 : ulong request_id,
545 0 : cJSON const * params ) {
546 0 : (void)params;
547 :
548 : // TODO: We should probably implement the same waiting_for_supermajority
549 : // logic to conform with Agave here.
550 :
551 0 : int unknown = ctx->cluster_confirmed_slot==ULONG_MAX || ctx->confirmed_idx==ULONG_MAX;
552 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 );
553 0 : else {
554 0 : ulong slots_behind = fd_ulong_sat_sub( ctx->cluster_confirmed_slot, ctx->banks[ ctx->confirmed_idx ].slot );
555 0 : if( FD_LIKELY( slots_behind<=128UL ) ) fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":\"ok\",\"id\":%lu}\n", request_id );
556 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 );
557 0 : }
558 :
559 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
560 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
561 0 : return response;
562 0 : }
563 :
564 : static fd_http_server_response_t
565 : getHighestSnapshotSlot( fd_rpc_tile_t * ctx,
566 : ulong request_id,
567 0 : cJSON const * params ) {
568 0 : (void)params;
569 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 );
570 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
571 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
572 0 : return response;
573 0 : }
574 :
575 : static fd_http_server_response_t
576 : getIdentity( fd_rpc_tile_t * ctx,
577 : ulong request_id,
578 0 : cJSON const * params ) {
579 0 : (void)params;
580 :
581 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%lu}\n", FD_BASE58_ENC_32_ALLOCA( ctx->identity_pubkey ), request_id );
582 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
583 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
584 0 : return response;
585 0 : }
586 :
587 : static fd_http_server_response_t
588 : getInflationGovernor( fd_rpc_tile_t * ctx,
589 : ulong request_id,
590 0 : cJSON const * params ) {
591 0 : (void)params;
592 :
593 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
594 :
595 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
596 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
597 :
598 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
599 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
600 :
601 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
602 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
603 :
604 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
605 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
606 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
607 0 : else return (fd_http_server_response_t){ .status = 400 };
608 0 : }
609 :
610 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
611 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
612 :
613 0 : jsonp_open_envelope( ctx->http );
614 0 : jsonp_double( ctx->http, "foundation", bank->inflation.foundation );
615 0 : jsonp_double( ctx->http, "foundationTerm", bank->inflation.foundation_term );
616 0 : jsonp_double( ctx->http, "initial", bank->inflation.initial );
617 0 : jsonp_double( ctx->http, "taper", bank->inflation.taper );
618 0 : jsonp_double( ctx->http, "terminal", bank->inflation.terminal );
619 0 : jsonp_close_envelope( ctx->http, request_id );
620 :
621 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
622 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
623 0 : return response;
624 0 : }
625 :
626 : UNIMPLEMENTED(getInflationRate)
627 : UNIMPLEMENTED(getInflationReward) // TODO: Used by solana-exporter
628 : UNIMPLEMENTED(getLargestAccounts)
629 :
630 : static fd_http_server_response_t
631 : getLatestBlockhash( fd_rpc_tile_t * ctx,
632 : ulong request_id,
633 0 : cJSON const * params ) {
634 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
635 0 : ulong minContextSlot = ULONG_MAX;
636 :
637 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
638 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
639 :
640 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
641 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
642 :
643 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
644 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
645 :
646 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
647 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
648 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
649 0 : else return (fd_http_server_response_t){ .status = 400 };
650 :
651 0 : const cJSON * _minContextSlot = cJSON_GetObjectItemCaseSensitive( param, "minContextSlot" );
652 0 : if( FD_UNLIKELY( _minContextSlot ) ) {
653 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
654 0 : minContextSlot = _minContextSlot->valueulong;
655 0 : }
656 0 : }
657 :
658 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
659 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
660 :
661 0 : if( FD_UNLIKELY( minContextSlot!=ULONG_MAX && minContextSlot>bank->slot ) ) {
662 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 );
663 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
664 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
665 0 : return response;
666 0 : }
667 :
668 0 : jsonp_open_envelope( ctx->http );
669 0 : jsonp_open_object( ctx->http, "context" );
670 0 : jsonp_ulong( ctx->http, "slot", bank->slot );
671 0 : jsonp_close_object( ctx->http );
672 :
673 0 : jsonp_open_object( ctx->http, "value" );
674 0 : jsonp_string( ctx->http, "blockhash", FD_BASE58_ENC_32_ALLOCA( bank->block_hash ) );
675 0 : jsonp_ulong( ctx->http, "lastValidBlockHeight", 0UL /* TODO: Implement */ );
676 0 : jsonp_close_object( ctx->http );
677 0 : jsonp_close_envelope( ctx->http, request_id );
678 :
679 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
680 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
681 0 : return response;
682 0 : }
683 :
684 : UNIMPLEMENTED(getLeaderSchedule) // TODO: Used by solana-exporter
685 : UNIMPLEMENTED(getMaxRetransmitSlot)
686 : UNIMPLEMENTED(getMaxShredInsertSlot)
687 :
688 : static fd_http_server_response_t
689 : getMinimumBalanceForRentExemption( fd_rpc_tile_t * ctx,
690 : ulong request_id,
691 0 : cJSON const * params ) {
692 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
693 :
694 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>2 || !cJSON_GetArraySize( params ) ) ) return (fd_http_server_response_t){ .status = 400 };
695 :
696 0 : const cJSON * data_len = cJSON_GetArrayItem( params, 0 );
697 0 : if( FD_UNLIKELY( !cJSON_IsNumber( data_len ) ) ) return (fd_http_server_response_t){ .status = 400 };
698 :
699 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )==2 ) ) {
700 0 : const cJSON * config = cJSON_GetArrayItem( params, 1 );
701 0 : if( FD_UNLIKELY( !cJSON_IsObject( config ) ) ) return (fd_http_server_response_t){ .status = 400 };
702 :
703 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( config, "commitment" );
704 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
705 :
706 0 : if( FD_UNLIKELY( _commitment ) ) {
707 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
708 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
709 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
710 0 : else return (fd_http_server_response_t){ .status = 400 };
711 0 : }
712 0 : }
713 :
714 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
715 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
716 :
717 0 : fd_rent_t rent = {
718 0 : .lamports_per_uint8_year = bank->rent.lamports_per_uint8_year,
719 0 : .exemption_threshold = bank->rent.exemption_threshold,
720 0 : .burn_percent = bank->rent.burn_percent,
721 0 : };
722 0 : ulong minimum = fd_rent_exempt_minimum_balance( &rent, data_len->valueulong );
723 :
724 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}\n", minimum, request_id );
725 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
726 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
727 0 : return response;
728 0 : }
729 :
730 : UNIMPLEMENTED(getMultipleAccounts)
731 : UNIMPLEMENTED(getProgramAccounts)
732 : UNIMPLEMENTED(getRecentPerformanceSamples)
733 : UNIMPLEMENTED(getRecentPrioritizationFees)
734 : UNIMPLEMENTED(getSignaturesForAddress)
735 : UNIMPLEMENTED(getSignatureStatuses)
736 :
737 : static fd_http_server_response_t
738 : getSlot( fd_rpc_tile_t * ctx,
739 : ulong request_id,
740 0 : cJSON const * params ) {
741 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
742 0 : ulong minContextSlot = ULONG_MAX;
743 :
744 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
745 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
746 :
747 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
748 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
749 :
750 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
751 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
752 :
753 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
754 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
755 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
756 0 : else return (fd_http_server_response_t){ .status = 400 };
757 :
758 0 : const cJSON * _minContextSlot = cJSON_GetObjectItemCaseSensitive( param, "minContextSlot" );
759 0 : if( FD_UNLIKELY( _minContextSlot ) ) {
760 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
761 0 : minContextSlot = _minContextSlot->valueulong;
762 0 : }
763 0 : }
764 :
765 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
766 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
767 :
768 0 : if( FD_UNLIKELY( minContextSlot!=ULONG_MAX && minContextSlot>bank->slot ) ) {
769 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 );
770 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
771 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
772 0 : return response;
773 0 : }
774 :
775 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}\n", bank->slot, request_id );
776 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
777 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
778 0 : return response;
779 0 : }
780 :
781 : UNIMPLEMENTED(getSlotLeader)
782 : UNIMPLEMENTED(getSlotLeaders)
783 : UNIMPLEMENTED(getStakeMinimumDelegation)
784 : UNIMPLEMENTED(getSupply)
785 : UNIMPLEMENTED(getTokenAccountBalance)
786 : UNIMPLEMENTED(getTokenAccountsByDelegate)
787 : UNIMPLEMENTED(getTokenAccountsByOwner)
788 : UNIMPLEMENTED(getTokenLargestAccounts)
789 : UNIMPLEMENTED(getTokenSupply)
790 : UNIMPLEMENTED(getTransaction)
791 :
792 : static fd_http_server_response_t
793 : getTransactionCount( fd_rpc_tile_t * ctx,
794 : ulong request_id,
795 0 : cJSON const * params ) {
796 0 : int commitment = FD_RPC_COMMITMENT_FINALIZED;
797 0 : ulong minContextSlot = ULONG_MAX;
798 :
799 0 : if( FD_UNLIKELY( params && cJSON_GetArraySize( params ) ) ) {
800 0 : if( FD_UNLIKELY( cJSON_GetArraySize( params )>1 ) ) return (fd_http_server_response_t){ .status = 400 };
801 :
802 0 : const cJSON * param = cJSON_GetArrayItem( params, 0 );
803 0 : if( FD_UNLIKELY( !cJSON_IsObject( param ) ) ) return (fd_http_server_response_t){ .status = 400 };
804 :
805 0 : const cJSON * _commitment = cJSON_GetObjectItemCaseSensitive( param, "commitment" );
806 0 : if( FD_UNLIKELY( _commitment && ( !cJSON_IsString( _commitment ) || _commitment->valuestring==NULL ) ) ) return (fd_http_server_response_t){ .status = 400 };
807 :
808 0 : if( FD_LIKELY( !strcmp( _commitment->valuestring, "processed" ) ) ) commitment = FD_RPC_COMMITMENT_PROCESSED;
809 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "confirmed" ) ) ) commitment = FD_RPC_COMMITMENT_CONFIRMED;
810 0 : else if( FD_LIKELY( !strcmp( _commitment->valuestring, "finalized" ) ) ) commitment = FD_RPC_COMMITMENT_FINALIZED;
811 0 : else return (fd_http_server_response_t){ .status = 400 };
812 :
813 0 : const cJSON * _minContextSlot = cJSON_GetObjectItemCaseSensitive( param, "minContextSlot" );
814 0 : if( FD_UNLIKELY( _minContextSlot ) ) {
815 0 : if( FD_UNLIKELY( !cJSON_IsNumber( _minContextSlot ) || _minContextSlot->valueulong==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 400 };
816 0 : minContextSlot = _minContextSlot->valueulong;
817 0 : }
818 0 : }
819 :
820 0 : if( FD_UNLIKELY( commitment!=FD_RPC_COMMITMENT_PROCESSED ) ) return (fd_http_server_response_t){ .status = 400 };
821 0 : bank_info_t const * bank = &ctx->banks[ ctx->processed_idx ];
822 :
823 0 : if( FD_UNLIKELY( minContextSlot!=ULONG_MAX && minContextSlot>bank->slot ) ) {
824 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 );
825 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
826 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
827 0 : return response;
828 0 : }
829 :
830 0 : fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}\n", bank->transaction_count, request_id );
831 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
832 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
833 0 : return response;
834 0 : }
835 :
836 : static fd_http_server_response_t
837 : getVersion( fd_rpc_tile_t * ctx,
838 : ulong request_id,
839 0 : cJSON const * params ) {
840 0 : (void)params;
841 :
842 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 );
843 0 : fd_http_server_response_t response = (fd_http_server_response_t){ .content_type = "application/json", .status = 200, .upgrade_websocket = 0 };
844 0 : FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
845 0 : return response;
846 0 : }
847 :
848 : UNIMPLEMENTED(getVoteAccounts) // TODO: Used by solana-exporter
849 : UNIMPLEMENTED(isBlockhashValid)
850 : UNIMPLEMENTED(minimumLedgerSlot) // TODO: Used by solana-exporter
851 : UNIMPLEMENTED(requestAirdrop)
852 : UNIMPLEMENTED(sendTransaction)
853 : UNIMPLEMENTED(simulateTransaction)
854 :
855 : static fd_http_server_response_t
856 0 : rpc_http_request( fd_http_server_request_t const * request ) {
857 0 : fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)request->ctx;
858 :
859 0 : if( FD_UNLIKELY( request->method!=FD_HTTP_SERVER_METHOD_POST ) ) {
860 0 : return (fd_http_server_response_t){
861 0 : .status = 400,
862 0 : };
863 0 : }
864 :
865 0 : const char * parse_end;
866 0 : cJSON * json = cJSON_ParseWithLengthOpts( (char *)request->post.body, request->post.body_len, &parse_end, 0 );
867 0 : if( FD_UNLIKELY( !json ) ) {
868 0 : return (fd_http_server_response_t){ .status = 400 };
869 0 : }
870 :
871 0 : const cJSON * jsonrpc = cJSON_GetObjectItemCaseSensitive( json, "jsonrpc" );
872 0 : if( FD_UNLIKELY( !cJSON_IsString( jsonrpc ) || strcmp( jsonrpc->valuestring, "2.0" ) ) ) goto bad_request;
873 :
874 0 : const cJSON * id = cJSON_GetObjectItemCaseSensitive( json, "id" );
875 0 : ulong request_id = 0UL;
876 0 : if( FD_UNLIKELY( !cJSON_IsNumber( id ) ) ) goto bad_request;
877 0 : request_id = id->valueulong;
878 :
879 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
880 0 : if( FD_UNLIKELY( params && !cJSON_IsArray( params ) ) ) goto bad_request;
881 :
882 0 : fd_http_server_response_t response;
883 :
884 0 : const cJSON * _method = cJSON_GetObjectItemCaseSensitive( json, "method" );
885 0 : if( FD_LIKELY( !cJSON_IsString( _method ) || _method->valuestring==NULL ) ) goto bad_request;
886 0 : if( FD_LIKELY( !strcmp( _method->valuestring, "getAccountInfo" ) ) ) response = getAccountInfo( ctx, request_id, params );
887 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBalance" ) ) ) response = getBalance( ctx, request_id, params );
888 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlock" ) ) ) response = getBlock( ctx, request_id, params );
889 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockCommitment" ) ) ) response = getBlockCommitment( ctx, request_id, params );
890 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockHeight" ) ) ) response = getBlockHeight( ctx, request_id, params );
891 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockProduction" ) ) ) response = getBlockProduction( ctx, request_id, params );
892 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlocks" ) ) ) response = getBlocks( ctx, request_id, params );
893 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlocksWithLimit" ) ) ) response = getBlocksWithLimit( ctx, request_id, params );
894 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockTime" ) ) ) response = getBlockTime( ctx, request_id, params );
895 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getClusterNodes" ) ) ) response = getClusterNodes( ctx, request_id, params );
896 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getEpochInfo" ) ) ) response = getEpochInfo( ctx, request_id, params );
897 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getEpochSchedule" ) ) ) response = getEpochSchedule( ctx, request_id, params );
898 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getFeeForMessage" ) ) ) response = getFeeForMessage( ctx, request_id, params );
899 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getFirstAvailableBlock" ) ) ) response = getFirstAvailableBlock( ctx, request_id, params );
900 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getGenesisHash" ) ) ) response = getGenesisHash( ctx, request_id, params );
901 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getHealth" ) ) ) response = getHealth( ctx, request_id, params );
902 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getHighestSnapshotSlot" ) ) ) response = getHighestSnapshotSlot( ctx, request_id, params );
903 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getIdentity" ) ) ) response = getIdentity( ctx, request_id, params );
904 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationGovernor" ) ) ) response = getInflationGovernor( ctx, request_id, params );
905 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationRate" ) ) ) response = getInflationRate( ctx, request_id, params );
906 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationReward" ) ) ) response = getInflationReward( ctx, request_id, params );
907 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLargestAccounts" ) ) ) response = getLargestAccounts( ctx, request_id, params );
908 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLatestBlockhash" ) ) ) response = getLatestBlockhash( ctx, request_id, params );
909 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getLeaderSchedule" ) ) ) response = getLeaderSchedule( ctx, request_id, params );
910 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMaxRetransmitSlot" ) ) ) response = getMaxRetransmitSlot( ctx, request_id, params );
911 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMaxShredInsertSlot" ) ) ) response = getMaxShredInsertSlot( ctx, request_id, params );
912 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMinimumBalanceForRentExemption" ) ) ) response = getMinimumBalanceForRentExemption( ctx, request_id, params );
913 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getMultipleAccounts" ) ) ) response = getMultipleAccounts( ctx, request_id, params );
914 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getProgramAccounts" ) ) ) response = getProgramAccounts( ctx, request_id, params );
915 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getRecentPerformanceSamples" ) ) ) response = getRecentPerformanceSamples( ctx, request_id, params );
916 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getRecentPrioritizationFees" ) ) ) response = getRecentPrioritizationFees( ctx, request_id, params );
917 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSignaturesForAddress" ) ) ) response = getSignaturesForAddress( ctx, request_id, params );
918 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSignatureStatuses" ) ) ) response = getSignatureStatuses( ctx, request_id, params );
919 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlot" ) ) ) response = getSlot( ctx, request_id, params );
920 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlotLeader" ) ) ) response = getSlotLeader( ctx, request_id, params );
921 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlotLeaders" ) ) ) response = getSlotLeaders( ctx, request_id, params );
922 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getStakeMinimumDelegation" ) ) ) response = getStakeMinimumDelegation( ctx, request_id, params );
923 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getSupply" ) ) ) response = getSupply( ctx, request_id, params );
924 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountBalance" ) ) ) response = getTokenAccountBalance( ctx, request_id, params );
925 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountsByDelegate" ) ) ) response = getTokenAccountsByDelegate( ctx, request_id, params );
926 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountsByOwner" ) ) ) response = getTokenAccountsByOwner( ctx, request_id, params );
927 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenLargestAccounts" ) ) ) response = getTokenLargestAccounts( ctx, request_id, params );
928 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenSupply" ) ) ) response = getTokenSupply( ctx, request_id, params );
929 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTransaction" ) ) ) response = getTransaction( ctx, request_id, params );
930 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getTransactionCount" ) ) ) response = getTransactionCount( ctx, request_id, params );
931 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getVersion" ) ) ) response = getVersion( ctx, request_id, params );
932 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "getVoteAccounts" ) ) ) response = getVoteAccounts( ctx, request_id, params );
933 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "isBlockhashValid" ) ) ) response = isBlockhashValid( ctx, request_id, params );
934 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "minimumLedgerSlot" ) ) ) response = minimumLedgerSlot( ctx, request_id, params );
935 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "requestAirdrop" ) ) ) response = requestAirdrop( ctx, request_id, params );
936 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "sendTransaction" ) ) ) response = sendTransaction( ctx, request_id, params );
937 0 : else if( FD_LIKELY( !strcmp( _method->valuestring, "simulateTransaction" ) ) ) response = simulateTransaction( ctx, request_id, params );
938 0 : else goto bad_request;
939 :
940 0 : cJSON_Delete( json );
941 0 : return response;
942 :
943 0 : bad_request:
944 0 : cJSON_Delete( json );
945 0 : return (fd_http_server_response_t){ .status = 400 };
946 0 : }
947 :
948 : static void
949 : privileged_init( fd_topo_t * topo,
950 0 : fd_topo_tile_t * tile ) {
951 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
952 :
953 0 : fd_http_server_params_t http_params = derive_http_params( tile );
954 :
955 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
956 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
957 0 : fd_http_server_t * _http = FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(), fd_http_server_footprint( http_params ) );
958 :
959 0 : if( FD_UNLIKELY( !strcmp( tile->rpc.identity_key_path, "" ) ) )
960 0 : FD_LOG_ERR(( "identity_key_path not set" ));
961 :
962 0 : const uchar * identity_key = fd_keyload_load( tile->rpc.identity_key_path, /* pubkey only: */ 1 );
963 0 : fd_memcpy( ctx->identity_pubkey, identity_key, 32UL );
964 :
965 0 : fd_http_server_callbacks_t callbacks = {
966 0 : .request = rpc_http_request,
967 0 : };
968 0 : ctx->http = fd_http_server_join( fd_http_server_new( _http, http_params, callbacks, ctx ) );
969 0 : fd_http_server_listen( ctx->http, tile->rpc.listen_addr, tile->rpc.listen_port );
970 :
971 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 ));
972 0 : }
973 :
974 : extern char const fdctl_version_string[];
975 :
976 : static inline fd_rpc_out_t
977 : out1( fd_topo_t const * topo,
978 : fd_topo_tile_t const * tile,
979 0 : char const * name ) {
980 0 : ulong idx = ULONG_MAX;
981 :
982 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
983 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
984 0 : if( !strcmp( link->name, name ) ) {
985 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 ));
986 0 : idx = i;
987 0 : }
988 0 : }
989 :
990 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_rpc_out_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
991 :
992 :
993 0 : ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
994 0 : if( FD_UNLIKELY( mtu==0UL ) ) return (fd_rpc_out_t){ .idx = idx, .mem = NULL, .chunk0 = ULONG_MAX, .wmark = ULONG_MAX, .chunk = ULONG_MAX };
995 :
996 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
997 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
998 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
999 :
1000 0 : return (fd_rpc_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
1001 0 : }
1002 :
1003 : static void
1004 : unprivileged_init( fd_topo_t * topo,
1005 0 : fd_topo_tile_t * tile ) {
1006 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1007 :
1008 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1009 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
1010 0 : FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(), fd_http_server_footprint( derive_http_params( tile ) ) );
1011 0 : void * _alloc = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
1012 0 : void * _banks = FD_SCRATCH_ALLOC_APPEND( l, alignof(bank_info_t), tile->rpc.max_live_slots*sizeof(bank_info_t) );
1013 :
1014 0 : fd_alloc_t * alloc = fd_alloc_join( fd_alloc_new( _alloc, 1UL ), 1UL );
1015 0 : FD_TEST( alloc );
1016 0 : cJSON_alloc_install( alloc );
1017 :
1018 0 : ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->keyswitch_obj_id ) );
1019 0 : FD_TEST( ctx->keyswitch );
1020 :
1021 0 : ctx->next_poll_deadline = fd_tickcount();
1022 :
1023 0 : ctx->cluster_confirmed_slot = ULONG_MAX;
1024 :
1025 0 : ctx->processed_idx = ULONG_MAX;
1026 0 : ctx->confirmed_idx = ULONG_MAX;
1027 0 : ctx->finalized_idx = ULONG_MAX;
1028 :
1029 0 : ctx->banks = _banks;
1030 :
1031 0 : FD_TEST( fd_cstr_printf_check( ctx->version_string, sizeof( ctx->version_string ), NULL, "%s", fdctl_version_string ) );
1032 :
1033 0 : FD_TEST( tile->in_cnt<=sizeof( ctx->in )/sizeof( ctx->in[ 0 ] ) );
1034 0 : for( ulong i=0; i<tile->in_cnt; i++ ) {
1035 0 : fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ];
1036 0 : fd_topo_wksp_t * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
1037 :
1038 0 : ctx->in[ i ].mem = link_wksp->wksp;
1039 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
1040 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
1041 0 : ctx->in[ i ].mtu = link->mtu;
1042 :
1043 0 : if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
1044 0 : else if( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESI;
1045 0 : else FD_LOG_ERR(( "unexpected link name %s", link->name ));
1046 0 : }
1047 :
1048 0 : *ctx->replay_out = out1( topo, tile, "rpc_replay" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
1049 :
1050 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, 1UL );
1051 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
1052 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
1053 0 : }
1054 :
1055 : static ulong
1056 : populate_allowed_seccomp( fd_topo_t const * topo,
1057 : fd_topo_tile_t const * tile,
1058 : ulong out_cnt,
1059 0 : struct sock_filter * out ) {
1060 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1061 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1062 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
1063 :
1064 0 : populate_sock_filter_policy_fd_rpc_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)fd_http_server_fd( ctx->http ) );
1065 0 : return sock_filter_policy_fd_rpc_tile_instr_cnt;
1066 0 : }
1067 :
1068 : static ulong
1069 : populate_allowed_fds( fd_topo_t const * topo,
1070 : fd_topo_tile_t const * tile,
1071 : ulong out_fds_cnt,
1072 0 : int * out_fds ) {
1073 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1074 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1075 0 : fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
1076 :
1077 0 : if( FD_UNLIKELY( out_fds_cnt<3UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
1078 :
1079 0 : ulong out_cnt = 0UL;
1080 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
1081 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
1082 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
1083 0 : out_fds[ out_cnt++ ] = fd_http_server_fd( ctx->http ); /* rpc listen socket */
1084 0 : return out_cnt;
1085 0 : }
1086 :
1087 : static ulong
1088 : rlimit_file_cnt( fd_topo_t const * topo FD_PARAM_UNUSED,
1089 0 : fd_topo_tile_t const * tile ) {
1090 : /* pipefd, socket, stderr, logfile, and one spare for new accept() connections */
1091 0 : ulong base = 5UL;
1092 0 : return base+tile->rpc.max_http_connections;
1093 0 : }
1094 :
1095 0 : #define STEM_BURST (1UL)
1096 0 : #define STEM_LAZY (50UL)
1097 :
1098 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_rpc_tile_t
1099 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_rpc_tile_t)
1100 :
1101 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
1102 0 : #define STEM_CALLBACK_BEFORE_CREDIT before_credit
1103 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
1104 :
1105 : #include "../../disco/stem/fd_stem.c"
1106 :
1107 : fd_topo_run_tile_t fd_tile_rpc = {
1108 : .name = "rpc",
1109 : .rlimit_file_cnt_fn = rlimit_file_cnt,
1110 : .populate_allowed_seccomp = populate_allowed_seccomp,
1111 : .populate_allowed_fds = populate_allowed_fds,
1112 : .scratch_align = scratch_align,
1113 : .scratch_footprint = scratch_footprint,
1114 : .loose_footprint = loose_footprint,
1115 : .privileged_init = privileged_init,
1116 : .unprivileged_init = unprivileged_init,
1117 : .run = stem_run,
1118 : };
|