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

Generated by: LCOV version 1.14