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