LCOV - code coverage report
Current view: top level - discof/rpc - fd_rpc_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 942 0.0 %
Date: 2026-02-13 06:06:24 Functions: 0 148 0.0 %

          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

Generated by: LCOV version 1.14