LCOV - code coverage report
Current view: top level - discof/rpc - fd_rpc_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1604 0.0 %
Date: 2026-06-29 05:51:35 Functions: 0 388 0.0 %

          Line data    Source code
       1             : #include "../replay/fd_replay_tile.h"
       2             : #include "../genesis/fd_genesi_tile.h"
       3             : 
       4             : #include "../../ballet/json/cJSON_alloc.h"
       5             : #include "../../ballet/base64/fd_base64.h"
       6             : #include "../../ballet/json/cJSON.h"
       7             : #include "../../disco/topo/fd_topo.h"
       8             : #include "../../disco/metrics/fd_metrics.h"
       9             : #include "../../disco/keyguard/fd_keyload.h"
      10             : #include "../../disco/keyguard/fd_keyswitch.h"
      11             : #include "../../disco/metrics/fd_metrics.h"
      12             : #include "../../flamenco/features/fd_features.h"
      13             : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
      14             : #include "../../flamenco/runtime/fd_runtime_const.h"
      15             : #include "../../flamenco/accdb/fd_accdb.h"
      16             : #include "../../flamenco/accdb/fd_accdb_shmem.h"
      17             : #include "../../tango/fseq/fd_fseq.h"
      18             : #include "../../flamenco/gossip/fd_gossip_message.h"
      19             : #include "../../flamenco/genesis/fd_genesis_parse.h"
      20             : #include "../../flamenco/runtime/program/vote/fd_vote_codec.h"
      21             : #include "../../util/net/fd_ip4.h"
      22             : #include "../../waltz/http/fd_http_server.h"
      23             : #include "../../waltz/http/fd_http_server_private.h"
      24             : 
      25             : #include <stddef.h>
      26             : #include <sys/socket.h>
      27             : #include <math.h> /* floor, isfinite */
      28             : #include <string.h>
      29             : 
      30             : #if FD_HAS_ZSTD
      31             : #include <zstd.h>
      32             : #endif
      33             : 
      34             : #include "../../util/archive/fd_tar.h"
      35             : #include "../../ballet/bzip2/bzlib.h"
      36             : 
      37             : #include "generated/fd_rpc_tile_seccomp.h"
      38             : 
      39           0 : #define FD_RPC_AGAVE_API_VERSION "4.0.0-beta.6"
      40             : 
      41           0 : #define FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN       8192UL
      42           0 : #define FD_HTTP_SERVER_RPC_MAX_WS_SEND_FRAME_CNT  128UL
      43             : 
      44           0 : #define IN_KIND_REPLAY      (0)
      45           0 : #define IN_KIND_GENESI      (1)
      46           0 : #define IN_KIND_GOSSIP_OUT  (2)
      47             : 
      48             : /* From bzip2 docs:
      49             :       To guarantee that the compressed data will fit in its buffer,
      50             :       allocate an output buffer of size 1% larger than the uncompressed
      51             :       data, plus six hundred extra bytes.
      52             : */
      53             : #define FD_RPC_TAR_SZ (FD_GENESIS_MAX_MESSAGE_SIZE + 4UL*512UL)
      54             : #define FD_RPC_TAR_BZ_SZ (FD_RPC_TAR_SZ + ((FD_RPC_TAR_SZ + 100UL - 1UL) / 100UL) + 600UL)
      55             : 
      56           0 : #define FD_RPC_BASE58_ENCODED_128_LEN (175UL) /* ceil(128*log58(256)) */
      57             : 
      58             : #define FD_RPC_COMMITMENT_PROCESSED (0)
      59             : #define FD_RPC_COMMITMENT_CONFIRMED (1)
      60             : #define FD_RPC_COMMITMENT_FINALIZED (2)
      61             : 
      62             : #define FD_RPC_ENCODING_BASE58      (0)
      63             : #define FD_RPC_ENCODING_BASE64      (1)
      64             : #define FD_RPC_ENCODING_BASE64_ZSTD (2)
      65             : #define FD_RPC_ENCODING_BINARY      (3)
      66             : #define FD_RPC_ENCODING_JSON_PARSED (4)
      67             : 
      68           0 : #define FD_RPC_HEALTH_STATUS_OK      (0)
      69           0 : #define FD_RPC_HEALTH_STATUS_BEHIND  (1)
      70           0 : #define FD_RPC_HEALTH_STATUS_UNKNOWN (2)
      71             : 
      72             : #define FD_RPC_METHOD_GET_ACCOUNT_INFO                       ( 0)
      73             : #define FD_RPC_METHOD_GET_BALANCE                            ( 1)
      74             : #define FD_RPC_METHOD_GET_BLOCK                              ( 2)
      75             : #define FD_RPC_METHOD_GET_BLOCK_COMMITMENT                   ( 3)
      76             : #define FD_RPC_METHOD_GET_BLOCK_HEIGHT                       ( 4)
      77             : #define FD_RPC_METHOD_GET_BLOCK_PRODUCTION                   ( 5)
      78             : #define FD_RPC_METHOD_GET_BLOCKS                             ( 6)
      79             : #define FD_RPC_METHOD_GET_BLOCKS_WITH_LIMIT                  ( 7)
      80             : #define FD_RPC_METHOD_GET_BLOCK_TIME                         ( 8)
      81             : #define FD_RPC_METHOD_GET_CLUSTER_NODES                      ( 9)
      82             : #define FD_RPC_METHOD_GET_EPOCH_INFO                         (10)
      83             : #define FD_RPC_METHOD_GET_EPOCH_SCHEDULE                     (11)
      84             : #define FD_RPC_METHOD_GET_FEE_FOR_MESSAGE                    (12)
      85             : #define FD_RPC_METHOD_GET_FIRST_AVAILABLE_BLOCK              (13)
      86             : #define FD_RPC_METHOD_GET_GENESIS_HASH                       (14)
      87             : #define FD_RPC_METHOD_GET_HEALTH                             (15)
      88             : #define FD_RPC_METHOD_GET_HIGHEST_SNAPSHOT_SLOT              (16)
      89             : #define FD_RPC_METHOD_GET_IDENTITY                           (17)
      90             : #define FD_RPC_METHOD_GET_INFLATION_GOVERNOR                 (18)
      91             : #define FD_RPC_METHOD_GET_INFLATION_RATE                     (19)
      92             : #define FD_RPC_METHOD_GET_INFLATION_REWARD                   (20)
      93             : #define FD_RPC_METHOD_GET_LARGEST_ACCOUNTS                   (21)
      94             : #define FD_RPC_METHOD_GET_LATEST_BLOCKHASH                   (22)
      95             : #define FD_RPC_METHOD_GET_LEADER_SCHEDULE                    (23)
      96             : #define FD_RPC_METHOD_GET_MAX_RETRANSMIT_SLOT                (24)
      97             : #define FD_RPC_METHOD_GET_MAX_SHRED_INSERT_SLOT              (25)
      98             : #define FD_RPC_METHOD_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION (26)
      99             : #define FD_RPC_METHOD_GET_MULTIPLE_ACCOUNTS                  (27)
     100             : #define FD_RPC_METHOD_GET_PROGRAM_ACCOUNTS                   (28)
     101             : #define FD_RPC_METHOD_GET_RECENT_PERFORMANCE_SAMPLES         (29)
     102             : #define FD_RPC_METHOD_GET_RECENT_PRIORITIZATION_FEES         (30)
     103             : #define FD_RPC_METHOD_GET_SIGNATURES_FOR_ADDRESS             (31)
     104             : #define FD_RPC_METHOD_GET_SIGNATURE_STATUSES                 (32)
     105             : #define FD_RPC_METHOD_GET_SLOT                               (33)
     106             : #define FD_RPC_METHOD_GET_SLOT_LEADER                        (34)
     107             : #define FD_RPC_METHOD_GET_SLOT_LEADERS                       (35)
     108             : #define FD_RPC_METHOD_GET_STAKE_MINIMUM_DELEGATION           (36)
     109             : #define FD_RPC_METHOD_GET_SUPPLY                             (37)
     110             : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNT_BALANCE              (38)
     111             : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_DELEGATE         (39)
     112             : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_OWNER            (40)
     113             : #define FD_RPC_METHOD_GET_TOKEN_LARGEST_ACCOUNTS             (41)
     114             : #define FD_RPC_METHOD_GET_TOKEN_SUPPLY                       (42)
     115             : #define FD_RPC_METHOD_GET_TRANSACTION                        (43)
     116             : #define FD_RPC_METHOD_GET_TRANSACTION_COUNT                  (44)
     117             : #define FD_RPC_METHOD_GET_VERSION                            (45)
     118             : #define FD_RPC_METHOD_GET_VOTE_ACCOUNTS                      (46)
     119             : #define FD_RPC_METHOD_IS_BLOCKHASH_VALID                     (47)
     120             : #define FD_RPC_METHOD_MINIMUM_LEDGER_SLOT                    (48)
     121             : #define FD_RPC_METHOD_REQUEST_AIRDROP                        (49)
     122             : #define FD_RPC_METHOD_SEND_TRANSACTION                       (50)
     123             : #define FD_RPC_METHOD_SIMULATE_TRANSACTION                   (51)
     124             : 
     125             : // Keep in sync with https://github.com/solana-labs/solana-web3.js/blob/master/src/errors.ts
     126             : // and https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/custom_error.rs
     127             : #define FD_RPC_ERROR_BLOCK_CLEANED_UP                            (-32001)
     128             : #define FD_RPC_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE          (-32002)
     129             : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE  (-32003)
     130             : #define FD_RPC_ERROR_BLOCK_NOT_AVAILABLE                         (-32004)
     131             : #define FD_RPC_ERROR_NODE_UNHEALTHY                              (-32005)
     132             : #define FD_RPC_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE (-32006)
     133             : #define FD_RPC_ERROR_SLOT_SKIPPED                                (-32007)
     134             : #define FD_RPC_ERROR_NO_SNAPSHOT                                 (-32008)
     135             : #define FD_RPC_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED              (-32009)
     136             : #define FD_RPC_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX           (-32010)
     137             : #define FD_RPC_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE           (-32011)
     138             : #define FD_RPC_ROR                                               (-32012)
     139             : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH          (-32013)
     140             : #define FD_RPC_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET              (-32014)
     141             : #define FD_RPC_ERROR_UNSUPPORTED_TRANSACTION_VERSION             (-32015)
     142             : #define FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED                (-32016)
     143             : #define FD_RPC_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE                 (-32017)
     144             : #define FD_RPC_ERROR_SLOT_NOT_EPOCH_BOUNDARY                     (-32018)
     145             : #define FD_RPC_ERROR_LONG_TERM_STORAGE_UNREACHABLE               (-32019)
     146             : 
     147           0 : static void fd_rpc_cstr_cJSON_free( char ** p ) { cJSON_free( *p ); }
     148           0 : #define CSTR_JSON(__json, __out) __attribute__((cleanup(fd_rpc_cstr_cJSON_free))) char * __out = cJSON_PrintUnformatted( __json );
     149             : 
     150             : /* Like CSTR_JSON, but strips the surrounding quotes from a string
     151             :    node's JSON representation. */
     152             : #define CSTR_JSON_UNQUOTED(__json, __out)                                  \
     153           0 :   CSTR_JSON( (__json), __out##_quoted_ );                                  \
     154           0 :   ulong __out##_len_ = __out##_quoted_ ? strlen( __out##_quoted_ ) : 0;    \
     155           0 :   if( FD_LIKELY( __out##_len_>=2 ) )                                       \
     156           0 :     __out##_quoted_[ __out##_len_ - 1 ] = '\0';                            \
     157           0 :   char const * (__out) = __out##_len_>=2 ? __out##_quoted_ + 1 : ""
     158             : 
     159             : static fd_http_server_params_t
     160           0 : derive_http_params( fd_topo_tile_t const * tile ) {
     161           0 :   return (fd_http_server_params_t) {
     162           0 :     .max_connection_cnt    = tile->rpc.max_http_connections,
     163           0 :     .max_ws_connection_cnt = tile->rpc.max_websocket_connections,
     164           0 :     .max_request_len       = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
     165           0 :     .max_ws_recv_frame_len = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
     166           0 :     .max_ws_send_frame_cnt = FD_HTTP_SERVER_RPC_MAX_WS_SEND_FRAME_CNT,
     167           0 :     .outgoing_buffer_sz    = tile->rpc.send_buffer_size_mb * (1UL<<20UL),
     168           0 :     .compress_websocket    = 0,
     169           0 :   };
     170           0 : }
     171             : 
     172             : struct fd_rpc_in {
     173             :   fd_wksp_t * mem;
     174             :   ulong       chunk0;
     175             :   ulong       wmark;
     176             :   ulong       mtu;
     177             : };
     178             : 
     179             : typedef struct fd_rpc_in fd_rpc_in_t;
     180             : 
     181             : struct fd_rpc_out {
     182             :   ulong       idx;
     183             :   fd_wksp_t * mem;
     184             :   ulong       chunk0;
     185             :   ulong       wmark;
     186             :   ulong       chunk;
     187             : };
     188             : 
     189             : typedef struct fd_rpc_out fd_rpc_out_t;
     190             : 
     191             : struct bank_info {
     192             :   ulong slot; /* default ULONG_MAX */
     193             :   ulong bank_idx;
     194             :   fd_accdb_fork_id_t accdb_fork_id;
     195             :   ulong epoch;
     196             :   ulong slot_in_epoch;
     197             :   ulong slots_per_epoch;
     198             : 
     199             :   ulong transaction_count;
     200             :   uchar block_hash[ 32 ];
     201             :   ulong block_height;
     202             : 
     203             :   struct {
     204             :     double initial;
     205             :     double terminal;
     206             :     double taper;
     207             :     double foundation;
     208             :     double foundation_term;
     209             :   } inflation;
     210             : 
     211             :   struct {
     212             :     ulong lamports_per_uint8_year;
     213             :     double exemption_threshold;
     214             :     uchar burn_percent;
     215             :   } rent;
     216             : };
     217             : 
     218             : typedef struct bank_info bank_info_t;
     219             : 
     220             : struct fd_rpc_cluster_node {
     221             :   int valid;
     222             :   fd_pubkey_t identity;
     223             :   fd_gossip_contact_info_t ci[ 1 ];
     224             : 
     225             :   struct { ulong prev, next; } dlist;
     226             : };
     227             : 
     228             : typedef struct fd_rpc_cluster_node fd_rpc_cluster_node_t;
     229             : 
     230             : #define DLIST_NAME  fd_rpc_cluster_node_dlist
     231             : #define DLIST_ELE_T fd_rpc_cluster_node_t
     232           0 : #define DLIST_PREV dlist.prev
     233           0 : #define DLIST_NEXT dlist.next
     234             : #include "../../util/tmpl/fd_dlist.c"
     235             : 
     236             : struct fd_rpc_tile {
     237             :   int delay_startup;
     238             :   fd_http_server_t * http;
     239             : 
     240             :   ulong * ws_subscribers_vote;
     241             :   ulong   ws_subscribers_vote_cnt;
     242             :   ulong * ws_subscribers_slot;
     243             :   ulong   ws_subscribers_slot_cnt;
     244             : 
     245             :   fd_rpc_cluster_node_dlist_t * cluster_nodes_dlist;
     246             :   fd_rpc_cluster_node_t cluster_nodes[ FD_CONTACT_INFO_TABLE_SIZE ];
     247             : 
     248             :   bank_info_t * banks;
     249             :   ulong         max_live_slots;
     250             : 
     251             :   fd_accdb_t * accdb;
     252             : 
     253             :   ulong cluster_confirmed_slot;
     254             : 
     255             :   ulong processed_idx;
     256             :   ulong confirmed_idx;
     257             :   ulong finalized_idx;
     258             : 
     259             :   int has_genesis_hash;
     260             :   fd_hash_t genesis_hash[ 1 ];
     261             : 
     262             :   uchar genesis_tar[ FD_RPC_TAR_SZ ];
     263             :   uchar genesis_tar_bz[ FD_RPC_TAR_BZ_SZ ];
     264             :   ulong genesis_tar_bz_sz;
     265             : 
     266             :   fd_alloc_t * bz2_alloc;
     267             : 
     268             :   long next_poll_deadline;
     269             : 
     270             :   fd_keyswitch_t * keyswitch;
     271             :   uchar identity_pubkey[ 32UL ];
     272             : 
     273             :   int in_kind[ 64UL ];
     274             :   fd_rpc_in_t in[ 64UL ];
     275             : 
     276             :   fd_rpc_out_t replay_out[1];
     277             : 
     278             :   fd_histf_t request_duration[ 1 ];
     279             : 
     280             : # if FD_HAS_ZSTD
     281             :   uchar compress_buf[ ZSTD_COMPRESSBOUND( FD_RUNTIME_ACC_SZ_MAX ) ];
     282             : # endif
     283             : 
     284             :   /* Scratch buffer for fd_accdb_read_one_nocache: holds the account
     285             :      data bytes returned by the readonly accdb path.  Sized to the
     286             :      runtime account data maximum.  Must not be in accdb shmem. */
     287             :   uchar accdb_data_buf[ FD_RUNTIME_ACC_SZ_MAX ];
     288             : };
     289             : 
     290             : typedef struct fd_rpc_tile fd_rpc_tile_t;
     291             : 
     292             : static void
     293             : fd_rpc_ws_subscriber_vote_add( fd_rpc_tile_t * ctx,
     294           0 :                                ulong           ws_conn_id ) {
     295           0 :   for( ulong i=0UL; i<ctx->ws_subscribers_vote_cnt; i++ )
     296           0 :     if( FD_UNLIKELY( ctx->ws_subscribers_vote[ i ]==ws_conn_id ) ) return;
     297             : 
     298           0 :   FD_TEST( ctx->ws_subscribers_vote_cnt<ctx->http->max_ws_conns );
     299           0 :   ctx->ws_subscribers_vote[ ctx->ws_subscribers_vote_cnt++ ] = ws_conn_id;
     300           0 : }
     301             : 
     302             : static int
     303             : fd_rpc_ws_subscriber_vote_remove( fd_rpc_tile_t * ctx,
     304           0 :                                   ulong           ws_conn_id ) {
     305           0 :   for( ulong idx=0UL; idx<ctx->ws_subscribers_vote_cnt; idx++ ) {
     306           0 :     if( FD_LIKELY( ctx->ws_subscribers_vote[ idx ]!=ws_conn_id ) ) continue;
     307             : 
     308           0 :     ctx->ws_subscribers_vote_cnt--;
     309           0 :     ctx->ws_subscribers_vote[ idx ] = ctx->ws_subscribers_vote[ ctx->ws_subscribers_vote_cnt ];
     310           0 :     return 1;
     311           0 :   }
     312             : 
     313           0 :   return 0;
     314           0 : }
     315             : 
     316             : static void
     317             : fd_rpc_ws_subscriber_slot_add( fd_rpc_tile_t * ctx,
     318           0 :                                ulong           ws_conn_id ) {
     319           0 :   for( ulong i=0UL; i<ctx->ws_subscribers_slot_cnt; i++ )
     320           0 :     if( FD_UNLIKELY( ctx->ws_subscribers_slot[ i ]==ws_conn_id ) ) return;
     321             : 
     322           0 :   FD_TEST( ctx->ws_subscribers_slot_cnt<ctx->http->max_ws_conns );
     323           0 :   ctx->ws_subscribers_slot[ ctx->ws_subscribers_slot_cnt++ ] = ws_conn_id;
     324           0 : }
     325             : 
     326             : static int
     327             : fd_rpc_ws_subscriber_slot_remove( fd_rpc_tile_t * ctx,
     328           0 :                                   ulong           ws_conn_id ) {
     329           0 :   for( ulong idx=0UL; idx<ctx->ws_subscribers_slot_cnt; idx++ ) {
     330           0 :     if( FD_LIKELY( ctx->ws_subscribers_slot[ idx ]!=ws_conn_id ) ) continue;
     331             : 
     332           0 :     ctx->ws_subscribers_slot_cnt--;
     333           0 :     ctx->ws_subscribers_slot[ idx ] = ctx->ws_subscribers_slot[ ctx->ws_subscribers_slot_cnt ];
     334           0 :     return 1;
     335           0 :   }
     336             : 
     337           0 :   return 0;
     338           0 : }
     339             : 
     340             : static void *
     341             : bz2_malloc( void * opaque,
     342             :             int    items,
     343           0 :             int    size ) {
     344           0 :   fd_alloc_t * alloc = (fd_alloc_t *)opaque;
     345             : 
     346           0 :   if( FD_UNLIKELY( items<=0 || size<=0 ) ) {
     347           0 :     FD_LOG_WARNING(( "bz2_malloc: invalid items=%d or size=%d", items, size ));
     348           0 :     return NULL;
     349           0 :   }
     350           0 :   void * result = fd_alloc_malloc( alloc, alignof(max_align_t), (ulong)items*(ulong)size );
     351           0 :   if( FD_UNLIKELY( !result ) ) return NULL;
     352           0 :   return result;
     353           0 : }
     354             : 
     355             : static void
     356             : bz2_free( void * opaque,
     357           0 :           void * addr ) {
     358           0 :   fd_alloc_t * alloc = (fd_alloc_t *)opaque;
     359             : 
     360           0 :   if( FD_UNLIKELY( !addr ) ) return;
     361           0 :   fd_alloc_free( alloc, addr );
     362           0 : }
     363             : 
     364             : static inline ulong
     365             : fd_rpc_file_as_tarball( fd_rpc_tile_t * ctx,
     366             :                         char const *    filename_cstr,
     367             :                         uchar const *   data,
     368             :                         ulong           data_sz,
     369             :                         uchar *         scratch,
     370             :                         ulong           scratch_sz,
     371             :                         uchar *         out,
     372           0 :                         ulong           out_sz ) {
     373           0 :   ulong padding_sz = 2*512UL;
     374           0 :   if( FD_LIKELY( data_sz % 512UL ) ) padding_sz += 512UL - (data_sz % 512UL);
     375             : 
     376           0 :   if( FD_UNLIKELY( data_sz>FD_GENESIS_MAX_MESSAGE_SIZE ) ) {
     377           0 :     FD_LOG_ERR(( "Genesis data exceeds maximum size (data_sz=%lu max=%lu)", data_sz, (ulong)FD_GENESIS_MAX_MESSAGE_SIZE ));
     378           0 :   }
     379           0 :   ulong tar_sz = sizeof(fd_tar_meta_t) + data_sz + padding_sz;
     380           0 :   if( FD_UNLIKELY( tar_sz>scratch_sz ) ) {
     381           0 :     FD_LOG_WARNING(( "tar_sz exceeds scratch_sz (tar_sz=%lu scratch_sz=%lu)", tar_sz, scratch_sz ));
     382           0 :     return ULONG_MAX;
     383           0 :   }
     384             : 
     385           0 :   fd_tar_meta_init_file_default( (fd_tar_meta_t *)scratch, filename_cstr, data_sz, fd_log_wallclock() );
     386           0 :   fd_memcpy( scratch+sizeof(fd_tar_meta_t), data, data_sz );
     387           0 :   memset( scratch+sizeof(fd_tar_meta_t)+data_sz, 0, padding_sz );
     388             : 
     389             :   /* NOTE: Agave's genesis.tar also contains a `rocksdb` folder */
     390             : 
     391           0 :   bz_stream bzstrm = {0};
     392           0 :   bzstrm.bzalloc = bz2_malloc;
     393           0 :   bzstrm.bzfree  = bz2_free;
     394           0 :   bzstrm.opaque  = ctx->bz2_alloc;
     395           0 :   int bzerr = BZ2_bzCompressInit( &bzstrm, 1, 0, 0 );
     396           0 :   if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzCompressInit() failed (%d)", bzerr ));
     397             : 
     398           0 :   ulong tar_bz_sz = out_sz;
     399             : 
     400           0 :   bzstrm.next_in   = (char *)scratch;
     401           0 :   bzstrm.avail_in  = (uint)tar_sz;
     402           0 :   bzstrm.next_out  = (char *)out;
     403           0 :   bzstrm.avail_out = (uint)tar_bz_sz;
     404             : 
     405           0 :   for(;;) {
     406           0 :     bzerr = BZ2_bzCompress( &bzstrm, BZ_FINISH );
     407           0 :     if( FD_LIKELY( bzerr==BZ_STREAM_END ) ) break;
     408           0 :     if( FD_UNLIKELY( bzerr>=0 ) ) continue;
     409           0 :     FD_LOG_ERR(( "BZ2_bzCompress(_, BZ_FINISH) failed (%d)", bzerr ));
     410           0 :   }
     411             : 
     412           0 :   tar_bz_sz -= (ulong)bzstrm.avail_out;
     413             : 
     414           0 :   bzerr = BZ2_bzCompressEnd( &bzstrm );
     415           0 :   if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzCompressEnd() failed (%d)", bzerr ));
     416             : 
     417           0 :   return tar_bz_sz;
     418           0 : }
     419             : 
     420             : FD_FN_CONST static inline ulong
     421           0 : scratch_align( void ) {
     422           0 :   ulong a = alignof( fd_rpc_tile_t );
     423           0 :   a = fd_ulong_max( a, fd_http_server_align() );
     424           0 :   a = fd_ulong_max( a, fd_alloc_align() );
     425           0 :   a = fd_ulong_max( a, alignof(bank_info_t) );
     426           0 :   a = fd_ulong_max( a, fd_rpc_cluster_node_dlist_align() );
     427           0 :   a = fd_ulong_max( a, fd_accdb_align() );
     428           0 :   return a;
     429           0 : }
     430             : 
     431             : static inline ulong
     432           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
     433           0 :   fd_http_server_params_t http_params = derive_http_params( tile );
     434           0 :   ulong http_fp = fd_http_server_footprint( http_params );
     435           0 :   if( FD_UNLIKELY( !http_fp ) ) FD_LOG_ERR(( "Invalid [tiles.rpc] config parameters" ));
     436             : 
     437           0 :   ulong l = FD_LAYOUT_INIT;
     438           0 :   l = FD_LAYOUT_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t )                         );
     439           0 :   l = FD_LAYOUT_APPEND( l, fd_http_server_align(),   http_fp                                         );
     440           0 :   l = FD_LAYOUT_APPEND( l, fd_alloc_align(),         fd_alloc_footprint()                            );
     441           0 :   l = FD_LAYOUT_APPEND( l, fd_alloc_align(),         fd_alloc_footprint()                            );
     442           0 :   l = FD_LAYOUT_APPEND( l, alignof(bank_info_t),     tile->rpc.max_live_slots*sizeof(bank_info_t)    );
     443           0 :   l = FD_LAYOUT_APPEND( l, fd_rpc_cluster_node_dlist_align(), fd_rpc_cluster_node_dlist_footprint()  );
     444           0 :   l = FD_LAYOUT_APPEND( l, fd_accdb_align(),         fd_accdb_footprint( tile->rpc.max_live_slots )  );
     445           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),           http_params.max_ws_connection_cnt*sizeof(ulong) );
     446           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),           http_params.max_ws_connection_cnt*sizeof(ulong) );
     447           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
     448           0 : }
     449             : 
     450             : FD_FN_PURE static inline ulong
     451           0 : loose_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
     452           0 :   return 256UL * (1UL<<20UL); /* 256MiB of heap space for the cJSON allocator */
     453           0 : }
     454             : 
     455             : static inline void
     456           0 : during_housekeeping( fd_rpc_tile_t * ctx ) {
     457           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
     458           0 :     fd_memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
     459           0 :     fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
     460           0 :   }
     461           0 : }
     462             : 
     463             : static inline void
     464           0 : metrics_write( fd_rpc_tile_t * ctx ) {
     465           0 :   FD_MHIST_COPY( RPC, REQUEST_DURATION_SECONDS,           ctx->request_duration                );
     466           0 :   FD_MGAUGE_SET( RPC, CONN_ACTIVE,                        ctx->http->metrics.connection_cnt    );
     467           0 :   FD_MGAUGE_SET( RPC, WEBSOCKET_CONN_ACTIVE,              ctx->http->metrics.ws_connection_cnt );
     468           0 :   FD_MGAUGE_SET( RPC, WEBSOCKET_SUBSCRIPTION_ACTIVE_VOTE, ctx->ws_subscribers_vote_cnt         );
     469           0 :   FD_MGAUGE_SET( RPC, WEBSOCKET_SUBSCRIPTION_ACTIVE_SLOT, ctx->ws_subscribers_slot_cnt         );
     470           0 :   FD_ACCDB_METRICS_WRITE_RO( RPC, fd_accdb_metrics( ctx->accdb ) );
     471           0 : }
     472             : 
     473             : static void
     474             : before_credit( fd_rpc_tile_t *     ctx,
     475             :                fd_stem_context_t * stem,
     476           0 :                int *               charge_busy ) {
     477           0 :   (void)stem;
     478             : 
     479           0 :   long now = fd_tickcount();
     480           0 :   int replay_ready = ctx->confirmed_idx!=ULONG_MAX && ctx->processed_idx!=ULONG_MAX && ctx->finalized_idx!=ULONG_MAX;
     481           0 :   if( FD_UNLIKELY( (!ctx->delay_startup || replay_ready) && now>=ctx->next_poll_deadline ) ) {
     482           0 :     *charge_busy = fd_http_server_poll( ctx->http, 0 );
     483           0 :     ctx->next_poll_deadline = fd_tickcount() + (long)(fd_tempo_tick_per_ns( NULL )*128L*1000L);
     484           0 :   }
     485           0 : }
     486             : 
     487             : static int
     488             : before_frag( fd_rpc_tile_t *   ctx,
     489             :              ulong             in_idx,
     490             :              ulong             seq FD_PARAM_UNUSED,
     491           0 :              ulong             sig ) {
     492           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) ) {
     493           0 :     return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO &&
     494           0 :            sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE &&
     495           0 :            sig!=FD_GOSSIP_UPDATE_TAG_VOTE;
     496           0 :   }
     497             : 
     498           0 :   return 0;
     499           0 : }
     500             : 
     501             : static int
     502             : fd_rpc_extract_vote_notification( fd_gossip_vote_t const * vote,
     503             :                                   fd_pubkey_t *            vote_pubkey,
     504             :                                   fd_hash_t *              hash,
     505             :                                   long *                   timestamp,
     506             :                                   uchar *                  has_timestamp,
     507             :                                   fd_signature_t *         signature,
     508             :                                   ulong *                  slots,
     509           0 :                                   ulong *                  slots_cnt ) {
     510           0 :   uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t))));
     511           0 :   ulong txn_sz = fd_txn_parse( vote->transaction, vote->transaction_len, txn_mem, NULL );
     512           0 :   if( FD_UNLIKELY( !txn_sz ) ) return 0;
     513             : 
     514           0 :   fd_txn_t const * txn = (fd_txn_t const *)txn_mem;
     515           0 :   if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, vote->transaction ) ) ) return 0;
     516           0 :   if( FD_UNLIKELY( txn->instr_cnt!=1UL || txn->signature_cnt<1UL ) ) return 0;
     517             : 
     518           0 :   fd_txn_instr_t const * instr = &txn->instr[ 0 ];
     519           0 :   if( FD_UNLIKELY( !instr->acct_cnt ) ) return 0;
     520           0 :   uchar const * instr_accts = fd_txn_get_instr_accts( instr, vote->transaction );
     521           0 :   uchar vote_pubkey_idx = instr_accts[ 0 ];
     522           0 :   if( FD_UNLIKELY( vote_pubkey_idx>=txn->acct_addr_cnt ) ) return 0;
     523             : 
     524           0 :   fd_pubkey_t const * acct_addrs = (fd_pubkey_t const *)fd_type_pun_const( vote->transaction + txn->acct_addr_off );
     525           0 :   *vote_pubkey = acct_addrs[ vote_pubkey_idx ];
     526             : 
     527           0 :   fd_vote_instruction_t ix[1];
     528           0 :   if( FD_UNLIKELY( !fd_vote_instruction_deserialize( ix, fd_txn_get_instr_data( instr, vote->transaction ), instr->data_sz ) ) ) return 0;
     529             : 
     530           0 :   *slots_cnt = 0UL;
     531           0 :   *has_timestamp = 0;
     532           0 :   *timestamp = 0L;
     533             : 
     534           0 :   switch( ix->discriminant ) {
     535           0 :     case fd_vote_instruction_enum_tower_sync: {
     536           0 :       fd_tower_sync_t const * v = &ix->tower_sync;
     537           0 :       for( ulong i=0UL; i<v->lockouts_cnt; i++ ) slots[ (*slots_cnt)++ ] =
     538           0 :           deq_fd_vote_lockout_t_peek_index_const( v->lockouts, i )->slot;
     539           0 :       *hash = v->hash;
     540           0 :       *has_timestamp = v->has_timestamp;
     541           0 :       *timestamp = v->timestamp;
     542           0 :       break;
     543           0 :     }
     544           0 :     case fd_vote_instruction_enum_tower_sync_switch: {
     545           0 :       fd_tower_sync_t const * v = &ix->tower_sync_switch.tower_sync;
     546           0 :       for( ulong i=0UL; i<v->lockouts_cnt; i++ ) slots[ (*slots_cnt)++ ] =
     547           0 :           deq_fd_vote_lockout_t_peek_index_const( v->lockouts, i )->slot;
     548           0 :       *hash = v->hash;
     549           0 :       *has_timestamp = v->has_timestamp;
     550           0 :       *timestamp = v->timestamp;
     551           0 :       break;
     552           0 :     }
     553           0 :     default:
     554             :       /* Legacy vote instructions not supported */
     555           0 :       return 0;
     556           0 :   }
     557             : 
     558           0 :   *signature = FD_LOAD( fd_signature_t, fd_txn_get_signatures( txn, vote->transaction ) );
     559           0 :   return *slots_cnt>0UL;
     560           0 : }
     561             : 
     562             : static void
     563             : fd_rpc_publish_vote_event( fd_rpc_tile_t *          ctx,
     564           0 :                            fd_gossip_vote_t const * vote ) {
     565           0 :   if( FD_UNLIKELY( !ctx->ws_subscribers_vote_cnt ) ) return;
     566             : 
     567           0 :   fd_pubkey_t vote_pubkey[1];
     568           0 :   fd_hash_t hash[1];
     569           0 :   long timestamp = 0L;
     570           0 :   uchar has_timestamp = 0;
     571           0 :   fd_signature_t signature[1];
     572           0 :   ulong slots[ FD_VOTE_INSTR_MAX_LOCKOUT_OFFSETS_LEN ];
     573           0 :   ulong slots_cnt = 0UL;
     574             : 
     575           0 :   if( FD_UNLIKELY( !fd_rpc_extract_vote_notification( vote, vote_pubkey, hash, &timestamp, &has_timestamp, signature, slots, &slots_cnt ) ) ) return;
     576             : 
     577           0 :   FD_BASE58_ENCODE_32_BYTES( vote_pubkey->uc, vote_pubkey_b58 );
     578           0 :   FD_BASE58_ENCODE_32_BYTES( hash->uc, hash_b58 );
     579           0 :   FD_BASE58_ENCODE_64_BYTES( signature->uc, signature_b58 );
     580             : 
     581           0 :   char txn_base64[ FD_BASE64_ENC_SZ( sizeof(vote->transaction) ) ];
     582           0 :   ulong txn_base64_len = fd_base64_encode( txn_base64, vote->transaction, vote->transaction_len );
     583             : 
     584           0 :   ulong sent_cnt = 0UL;
     585           0 :   for( ulong i=0UL; i<ctx->ws_subscribers_vote_cnt; ) {
     586           0 :     ulong ws_conn_id = ctx->ws_subscribers_vote[ i ];
     587           0 :     fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"method\":\"voteNotification\",\"params\":{\"subscription\":0,\"result\":{\"votePubkey\":\"%s\",\"slots\":[", vote_pubkey_b58 );
     588           0 :     for( ulong j=0UL; j<slots_cnt; j++ ) fd_http_server_printf( ctx->http, "%s%lu", j ? "," : "", slots[ j ] );
     589           0 :     fd_http_server_printf( ctx->http, "],\"hash\":\"%s\",", hash_b58 );
     590           0 :     if( FD_LIKELY( has_timestamp ) ) fd_http_server_printf( ctx->http, "\"timestamp\":%ld,", timestamp );
     591           0 :     else                             fd_http_server_printf( ctx->http, "\"timestamp\":null," );
     592           0 :     fd_http_server_printf( ctx->http, "\"signature\":\"%s\",\"transaction\":[\"%.*s\",\"base64\"]}}}\n", signature_b58, (int)txn_base64_len, txn_base64 );
     593             : 
     594           0 :     if( FD_UNLIKELY( fd_http_server_ws_send( ctx->http, ws_conn_id ) ) ) {
     595           0 :       fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
     596           0 :       fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
     597           0 :       continue;
     598           0 :     }
     599           0 :     if( FD_UNLIKELY( i>=ctx->ws_subscribers_vote_cnt || ctx->ws_subscribers_vote[ i ]!=ws_conn_id ) ) continue;
     600           0 :     sent_cnt++;
     601           0 :     i++;
     602           0 :   }
     603             : 
     604           0 :   FD_MCNT_INC( RPC, WEBSOCKET_EVENT_UNIQUE_SENT_VOTE, !!sent_cnt );
     605           0 :   FD_MCNT_INC( RPC, WEBSOCKET_EVENT_SENT_VOTE,          sent_cnt );
     606           0 : }
     607             : 
     608             : static void
     609             : fd_rpc_publish_slot_event( fd_rpc_tile_t *                    ctx,
     610           0 :                            fd_replay_slot_completed_t const * slot_completed ) {
     611           0 :   if( FD_UNLIKELY( !ctx->ws_subscribers_slot_cnt ) ) return;
     612             : 
     613           0 :   ulong sent_cnt = 0UL;
     614           0 :   for( ulong i=0UL; i<ctx->ws_subscribers_slot_cnt; ) {
     615           0 :     ulong ws_conn_id = ctx->ws_subscribers_slot[ i ];
     616           0 :     fd_http_server_printf( ctx->http,
     617           0 :                            "{\"jsonrpc\":\"2.0\",\"method\":\"slotNotification\",\"params\":{\"subscription\":0,\"result\":{\"parent\":%lu,\"root\":%lu,\"slot\":%lu}}}\n",
     618           0 :                            slot_completed->parent_slot,
     619           0 :                            slot_completed->root_slot,
     620           0 :                            slot_completed->slot );
     621             : 
     622           0 :     if( FD_UNLIKELY( fd_http_server_ws_send( ctx->http, ws_conn_id ) ) ) {
     623           0 :       fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
     624           0 :       fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
     625           0 :       continue;
     626           0 :     }
     627           0 :     if( FD_UNLIKELY( i>=ctx->ws_subscribers_slot_cnt || ctx->ws_subscribers_slot[ i ]!=ws_conn_id ) ) continue;
     628           0 :     sent_cnt++;
     629           0 :     i++;
     630           0 :   }
     631             : 
     632           0 :   FD_MCNT_INC( RPC, WEBSOCKET_EVENT_UNIQUE_SENT_SLOT, !!sent_cnt );
     633           0 :   FD_MCNT_INC( RPC, WEBSOCKET_EVENT_SENT_SLOT,          sent_cnt );
     634           0 : }
     635             : 
     636             : static inline int
     637             : returnable_frag( fd_rpc_tile_t *     ctx,
     638             :                  ulong               in_idx,
     639             :                  ulong               seq FD_PARAM_UNUSED,
     640             :                  ulong               sig,
     641             :                  ulong               chunk,
     642             :                  ulong               sz FD_PARAM_UNUSED,
     643             :                  ulong               ctl FD_PARAM_UNUSED,
     644             :                  ulong               tsorig FD_PARAM_UNUSED,
     645             :                  ulong               tspub FD_PARAM_UNUSED,
     646           0 :                  fd_stem_context_t * stem ) {
     647             : 
     648           0 :   if( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) {
     649           0 :     switch( sig ) {
     650           0 :       case REPLAY_SIG_SLOT_COMPLETED: {
     651           0 :         fd_replay_slot_completed_t const * slot_completed = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     652             : 
     653           0 :         FD_TEST( slot_completed->bank_idx<ctx->max_live_slots );
     654           0 :         bank_info_t * bank = &ctx->banks[ slot_completed->bank_idx ];
     655           0 :         bank->slot = slot_completed->slot;
     656           0 :         bank->accdb_fork_id = slot_completed->accdb_fork_id;
     657           0 :         bank->epoch = slot_completed->epoch;
     658           0 :         bank->slot_in_epoch = slot_completed->slot_in_epoch;
     659           0 :         bank->slots_per_epoch = slot_completed->slots_per_epoch;
     660           0 :         bank->transaction_count = slot_completed->transaction_count;
     661           0 :         bank->block_height = slot_completed->block_height;
     662           0 :         fd_memcpy( bank->block_hash, slot_completed->block_hash.uc, 32 );
     663             : 
     664           0 :         bank->inflation.initial         = slot_completed->inflation.initial;
     665           0 :         bank->inflation.terminal        = slot_completed->inflation.terminal;
     666           0 :         bank->inflation.taper           = slot_completed->inflation.taper;
     667           0 :         bank->inflation.foundation      = slot_completed->inflation.foundation;
     668           0 :         bank->inflation.foundation_term = slot_completed->inflation.foundation_term;
     669             : 
     670           0 :         bank->rent.lamports_per_uint8_year = slot_completed->rent.lamports_per_uint8_year;
     671           0 :         bank->rent.exemption_threshold     = slot_completed->rent.exemption_threshold;
     672           0 :         bank->rent.burn_percent            = slot_completed->rent.burn_percent;
     673             : 
     674           0 :         fd_rpc_publish_slot_event( ctx, slot_completed );
     675             : 
     676             :         /* In Agave, "processed" confirmation is the bank we've just
     677             :            voted for (handle_votable_bank), which is also guaranteed to
     678             :            have been replayed.
     679             : 
     680             :            Right now tower is not really built out to replicate this
     681             :            exactly, so we use the latest replayed slot, which is
     682             :            slightly more eager than Agave but shouldn't really affect
     683             :            end-users, since any use-cases that assume "processed" means
     684             :            "voted-for" would fail in Agave in cases where a cast vote
     685             :            does not land.
     686             : 
     687             :            tldr: This isn't strictly conformant with Agave, but doesn't
     688             :            need to be since Agave doesn't provide any guarantees anyways. */
     689           0 :         if( FD_LIKELY( ctx->processed_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->processed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
     690           0 :         ctx->processed_idx = slot_completed->bank_idx;
     691           0 :         break;
     692           0 :       }
     693           0 :       case REPLAY_SIG_OC_ADVANCED: {
     694           0 :         fd_replay_oc_advanced_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     695           0 :         if( FD_LIKELY( ctx->confirmed_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->confirmed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
     696           0 :         FD_TEST( msg->bank_idx<ctx->max_live_slots );
     697           0 :         ctx->confirmed_idx = msg->bank_idx;
     698           0 :         ctx->cluster_confirmed_slot = msg->slot;
     699           0 :         break;
     700           0 :       }
     701           0 :       case REPLAY_SIG_ROOT_ADVANCED: {
     702           0 :         fd_replay_root_advanced_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     703           0 :         if( FD_LIKELY( ctx->finalized_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->finalized_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
     704           0 :         FD_TEST( msg->bank_idx<ctx->max_live_slots );
     705           0 :         ctx->finalized_idx = msg->bank_idx;
     706           0 :         break;
     707           0 :       }
     708           0 :       default: {
     709           0 :         break;
     710           0 :       }
     711           0 :     }
     712           0 :   } else if( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) {
     713           0 :     fd_gossip_update_message_t const * update = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     714           0 :     switch( update->tag ) {
     715           0 :       case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
     716           0 :         if( FD_UNLIKELY( update->contact_info->idx>=FD_CONTACT_INFO_TABLE_SIZE ) ) FD_LOG_ERR(( "unexpected contact_info_idx %lu >= %lu", update->contact_info->idx, FD_CONTACT_INFO_TABLE_SIZE ));
     717           0 :         fd_rpc_cluster_node_t * node = &ctx->cluster_nodes[ update->contact_info->idx ];
     718           0 :         if( FD_LIKELY( node->valid ) ) fd_rpc_cluster_node_dlist_idx_remove( ctx->cluster_nodes_dlist, update->contact_info->idx, ctx->cluster_nodes );
     719             : 
     720           0 :         node->valid = 1;
     721           0 :         node->identity = *(fd_pubkey_t *)update->origin;
     722           0 :         fd_memcpy( node->ci, update->contact_info->value, sizeof(fd_gossip_contact_info_t) );
     723             : 
     724           0 :         fd_rpc_cluster_node_dlist_idx_push_tail( ctx->cluster_nodes_dlist, update->contact_info->idx, ctx->cluster_nodes );
     725           0 :         break;
     726           0 :       }
     727           0 :       case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE: {
     728           0 :         if( FD_UNLIKELY( update->contact_info_remove->idx>=FD_CONTACT_INFO_TABLE_SIZE ) ) FD_LOG_ERR(( "unexpected remove_contact_info_idx %lu >= %lu", update->contact_info_remove->idx, FD_CONTACT_INFO_TABLE_SIZE ));
     729           0 :         fd_rpc_cluster_node_t * node = &ctx->cluster_nodes[ update->contact_info_remove->idx ];
     730           0 :         FD_TEST( node->valid );
     731           0 :         node->valid = 0;
     732           0 :         fd_rpc_cluster_node_dlist_idx_remove( ctx->cluster_nodes_dlist, update->contact_info_remove->idx, ctx->cluster_nodes );
     733           0 :         break;
     734           0 :       }
     735           0 :       case FD_GOSSIP_UPDATE_TAG_VOTE: {
     736           0 :         fd_rpc_publish_vote_event( ctx, update->vote->value );
     737           0 :         break;
     738           0 :       }
     739           0 :       default: break;
     740           0 :     }
     741           0 :   } else if( ctx->in_kind[ in_idx ]==IN_KIND_GENESI ) {
     742           0 :     ctx->has_genesis_hash = 1;
     743           0 :     fd_genesis_meta_t const * genesis_meta = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     744           0 :     *ctx->genesis_hash = genesis_meta->genesis_hash;
     745             : 
     746           0 :     uchar const * blob    = (uchar const *)( genesis_meta+1 );
     747           0 :     ulong const   blob_sz = genesis_meta->blob_sz;
     748           0 :     FD_TEST( blob_sz<=FD_GENESIS_MAX_MESSAGE_SIZE );
     749             : 
     750           0 :     ctx->genesis_tar_bz_sz = fd_rpc_file_as_tarball(
     751           0 :       ctx,
     752           0 :       "genesis.bin",
     753           0 :       blob, blob_sz,
     754           0 :       ctx->genesis_tar, sizeof(ctx->genesis_tar),
     755           0 :       ctx->genesis_tar_bz, sizeof(ctx->genesis_tar_bz) );
     756           0 :     if( FD_UNLIKELY( ctx->genesis_tar_bz_sz==ULONG_MAX ) ) {
     757           0 :       FD_LOG_ERR(( "failed to create genesis tarball (blob_sz=%lu)", blob_sz ));
     758           0 :     }
     759           0 :   }
     760             : 
     761           0 :   return 0;
     762           0 : }
     763             : 
     764             : /* Silence warnings due gcc not recognizing nan-infinity-disabled
     765             :    pragma, which is required by clang */
     766             : #pragma GCC diagnostic push
     767             : #pragma GCC diagnostic ignored "-Wpragmas"
     768             : #pragma GCC diagnostic ignored "-Wunknown-warning-option"
     769             : #pragma GCC diagnostic ignored "-Wnan-infinity-disabled"
     770             : 
     771             : static inline int
     772           0 : fd_rpc_cjson_is_integer( const cJSON * item ) {
     773           0 :   return cJSON_IsNumber(item)
     774           0 :       && isfinite(item->valuedouble)
     775           0 :       && floor(item->valuedouble) == item->valuedouble;
     776           0 : }
     777             : 
     778             : #pragma GCC diagnostic pop
     779             : 
     780             : static inline char const *
     781           0 : fd_rpc_cjson_type_to_cstr( cJSON const * elt ) {
     782           0 :   FD_TEST( elt );
     783           0 :   if( cJSON_IsString( elt ) ) return "string";
     784           0 :   if( cJSON_IsObject( elt ) ) return "map";
     785           0 :   if( cJSON_IsArray ( elt ) ) return "sequence";
     786           0 :   if( cJSON_IsBool  ( elt ) ) return "boolean";
     787           0 :   if( cJSON_IsNumber( elt ) && !fd_rpc_cjson_is_integer( elt ) ) return "floating point";
     788           0 :   if( cJSON_IsNumber( elt ) ) return "integer";
     789           0 :   if( cJSON_IsNull  ( elt ) ) return "null";
     790           0 :   CSTR_JSON( elt, elt_cstr );
     791           0 :   FD_LOG_ERR(( "unreachable %s", elt_cstr ));
     792           0 : }
     793             : 
     794           0 : #define STAGE_JSON(__ctx) (__extension__({ \
     795           0 :   fd_http_server_response_t __res = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 }; \
     796           0 :   if( FD_UNLIKELY( fd_http_server_stage_body( __ctx->http, &__res ) ) ) { \
     797           0 :     __res.status = 500; \
     798           0 :     FD_LOG_WARNING(( "Failed to populate RPC response buffer" )); \
     799           0 :     FD_LOG_HEXDUMP_WARNING(( "start of message:\n%.*s", __ctx->http->oring+(__ctx->http->stage_off%__ctx->http->oring_sz), fd_ulong_min( 500UL, __ctx->http->oring_sz-(__ctx->http->stage_off%__ctx->http->oring_sz)-1UL ) )); \
     800           0 :     FD_LOG_HEXDUMP_WARNING(( "start of buffer:\n%.*s",  __ctx->http->oring,                                                fd_ulong_min( 500UL, __ctx->http->oring_sz ) )); \
     801           0 :   } \
     802           0 :   __res; }))
     803             : 
     804           0 : #define PRINTF_JSON(__ctx, ...) (__extension__({ \
     805           0 :   fd_http_server_printf( __ctx->http, __VA_ARGS__ ); \
     806           0 :   fd_http_server_response_t __res = STAGE_JSON( __ctx ); \
     807           0 :   __res; }))
     808             : 
     809             : 
     810             : static inline int
     811             : fd_rpc_validate_params( fd_rpc_tile_t *             ctx,
     812             :                         cJSON const *               id,
     813             :                         cJSON const *               params,
     814             :                         ulong                       min_cnt,
     815             :                         ulong                       max_cnt,
     816           0 :                         fd_http_server_response_t * res ) {
     817           0 :   FD_TEST( min_cnt <= max_cnt );
     818             :   /* Agave also includes a "data" field in some responses with the
     819             :      faulty params payload. Instead of printing raw JSON, they print the
     820             :      representation which we won't replicate.
     821             : 
     822             :      e.g. "data" might contain something like
     823             :       Array([String(\"\"), Object {}])
     824             : 
     825             :     instead, we just include the field with an empty string
     826             :   */
     827             : 
     828           0 :   ulong param_cnt;
     829           0 :   if( FD_UNLIKELY( !params ) ) param_cnt = 0UL;
     830           0 :   else if( FD_UNLIKELY( cJSON_IsNumber( params ) || cJSON_IsString( params ) || cJSON_IsBool( params ) ) ) {
     831           0 :     CSTR_JSON( id, id_cstr );
     832           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
     833           0 :     return 0;
     834           0 :   }
     835           0 :   else if( FD_UNLIKELY( cJSON_IsObject( params ) && max_cnt==0UL ) ) {
     836           0 :     CSTR_JSON( id, id_cstr );
     837           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: No parameters were expected\",\"data\":\"\"},\"id\":%s}\n", id_cstr );
     838           0 :     return 0;
     839           0 :   }
     840           0 :   else if( FD_UNLIKELY( cJSON_IsObject( params ) && max_cnt>0UL ) ) {
     841           0 :     CSTR_JSON( id, id_cstr );
     842           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"`params` should be an array\"},\"id\":%s}\n", id_cstr );
     843           0 :     return 0;
     844           0 :   }
     845           0 :   else if( FD_UNLIKELY( cJSON_IsNull( params ) ) ) param_cnt = 0UL;
     846           0 :   else if( FD_UNLIKELY( cJSON_IsArray( params ) ) ) param_cnt = (ulong)cJSON_GetArraySize( params );
     847           0 :   else FD_LOG_ERR(("unreachable"));
     848             : 
     849           0 :   if( FD_UNLIKELY( param_cnt>0UL && max_cnt==0UL ) ) {
     850           0 :     CSTR_JSON( id, id_cstr );
     851           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: No parameters were expected\",\"data\":\"\"},\"id\":%s}\n", id_cstr );
     852           0 :     return 0;
     853           0 :   }
     854           0 :   if( FD_UNLIKELY( param_cnt<min_cnt ) ) {
     855           0 :     CSTR_JSON( id, id_cstr );
     856           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"`params` should have at least %lu argument(s)\"},\"id\":%s}\n", min_cnt, id_cstr );
     857           0 :     return 0;
     858           0 :   }
     859           0 :   if( param_cnt>max_cnt ) {
     860           0 :     CSTR_JSON( id, id_cstr );
     861           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: Expected from %lu to %lu parameters.\",\"data\":\"\\\"Got: %lu\\\"\"},\"id\":%s}\n", min_cnt, max_cnt, param_cnt, id_cstr );
     862           0 :     return 0;
     863           0 :   }
     864             : 
     865           0 :   return 1;
     866           0 : }
     867             : 
     868             : /* TODO: use optimized version of this from fd_base58_tmpl.c */
     869             : static const char base58_chars[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
     870             : 
     871             : static inline int
     872           0 : fd_rpc_cstr_contains_non_base58(const char *str) {
     873           0 :     for (; *str; str++) {
     874           0 :         if (!strchr(base58_chars, *str)) return 1;
     875           0 :     }
     876           0 :     return 0;
     877           0 : }
     878             : 
     879             : /* adapted from https://salsa.debian.org/debian/libbase58/-/blob/debian/master/base58.c */
     880             : static inline int
     881           0 : fd_rpc_base58_encode_128( char * b58, ulong * b58sz, const void *data, ulong binsz ) {
     882           0 :   FD_TEST( binsz <= 128UL );
     883             : 
     884           0 :   const uchar * bin = data;
     885           0 :   ulong carry;
     886           0 :   ulong i, j, high, zcount = 0;
     887           0 :   ulong size;
     888             : 
     889           0 :   while( zcount<binsz && !bin[ zcount ] ) zcount++;
     890             : 
     891           0 :   size = (binsz-zcount)*138/100+1; /* strict overestimate */
     892           0 :   size = fd_ulong_min( size, FD_RPC_BASE58_ENCODED_128_LEN ); /* theoretical max */
     893           0 :   uchar buf[ FD_RPC_BASE58_ENCODED_128_LEN ] = { 0 };
     894             : 
     895           0 :   for( i=zcount, high=size-1UL; i<binsz; i++, high=j ) {
     896           0 :     for( carry=bin[ i ], j=size-1UL; (j>high) || carry; j-- ) {
     897           0 :       carry += 256UL * buf[ j ];
     898           0 :       buf[ j ] = (uchar)(carry%58UL);
     899           0 :       carry /= 58UL;
     900           0 :       if( FD_UNLIKELY( !j ) ) break;
     901           0 :     }
     902           0 :   }
     903             : 
     904           0 :   for( j=0; j<size && !buf[ j ]; j++);
     905             : 
     906           0 :   if( *b58sz<zcount+size-j ) {
     907           0 :     *b58sz = zcount+size-j;
     908           0 :     return 0;
     909           0 :   }
     910             : 
     911           0 :   if (zcount) memset(b58, '1', zcount);
     912           0 :   for( i=zcount; j<size; i++, j++) b58[ i ] = base58_chars[ buf[ j ] ];
     913           0 :   *b58sz = i;
     914             : 
     915           0 :   return 1;
     916           0 : }
     917             : 
     918             : static inline int
     919             : fd_rpc_validate_config( fd_rpc_tile_t *             ctx,
     920             :                         cJSON const *               id,
     921             :                         cJSON const *               config,
     922             :                         char const *                config_rust_type,
     923             :                         int                         has_commitment,
     924             :                         int                         has_encoding,
     925             :                         int                         has_data_slice,
     926             :                         int                         has_min_context_slot,
     927             :                         ulong *                     bank_idx,
     928             :                         char const **               opt_encoding_cstr,
     929             :                         ulong *                     opt_slice_length,
     930             :                         ulong *                     opt_slice_offset,
     931           0 :                         fd_http_server_response_t * res ) {
     932             : 
     933           0 :   if( FD_UNLIKELY( config && (cJSON_IsNumber( config ) || cJSON_IsBool( config )) ) ) {
     934           0 :     CSTR_JSON( id, id_cstr ); CSTR_JSON( config, config_cstr );
     935           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected %s.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( config ), config_cstr, config_rust_type, id_cstr );
     936           0 :     return 0;
     937           0 :   }
     938           0 :   if( FD_UNLIKELY( config && cJSON_IsString( config ) ) ) {
     939           0 :     CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( config, config_esc );
     940           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected %s.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( config ), config_esc, config_rust_type, id_cstr );
     941           0 :     return 0;
     942           0 :   }
     943           0 :   if( FD_UNLIKELY( cJSON_IsArray( config ) ) ) {
     944           0 :     CSTR_JSON( id, id_cstr );
     945           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: Positional config params not supported\"},\"id\":%s}\n", id_cstr );
     946           0 :     return 0;
     947           0 :   }
     948           0 :   if( FD_UNLIKELY( config && !(cJSON_IsNull( config ) || cJSON_IsObject( config )) ) ) {
     949           0 :     CSTR_JSON( id, id_cstr );
     950           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected %s.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( config ), config_rust_type, id_cstr );
     951           0 :     return 0;
     952           0 :   }
     953             : 
     954           0 :   ulong _bank_idx = ULONG_MAX;
     955           0 :   cJSON const * commitment = NULL;
     956           0 :   if( FD_LIKELY( has_commitment ) ) {
     957           0 :     commitment = cJSON_GetObjectItemCaseSensitive( config, "commitment" );
     958           0 :     if( FD_UNLIKELY( !commitment || !cJSON_IsString( commitment ) ) ) _bank_idx = ctx->finalized_idx;
     959           0 :     else if( FD_LIKELY( !strcmp( commitment->valuestring, "processed" ) ) ) _bank_idx = ctx->processed_idx;
     960           0 :     else if( FD_LIKELY( !strcmp( commitment->valuestring, "confirmed" ) ) ) _bank_idx = ctx->confirmed_idx;
     961           0 :     else if( FD_LIKELY( !strcmp( commitment->valuestring, "finalized" ) ) ) _bank_idx = ctx->finalized_idx;
     962           0 :     else _bank_idx = ctx->finalized_idx;
     963           0 :   } else {
     964           0 :     _bank_idx = ctx->finalized_idx;
     965           0 :   }
     966           0 :   if( FD_UNLIKELY( _bank_idx==ULONG_MAX ) ) {
     967           0 :     CSTR_JSON( id, id_cstr );
     968           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
     969           0 :     return 0;
     970           0 :   }
     971           0 :   *bank_idx = _bank_idx;
     972             : 
     973           0 :   if( FD_LIKELY( has_encoding ) ) {
     974           0 :     cJSON const * encoding = cJSON_GetObjectItemCaseSensitive( config, "encoding" );
     975             : 
     976           0 :     if( FD_UNLIKELY( cJSON_IsNumber( encoding ) || cJSON_IsBool( encoding ) ) ) {
     977           0 :       CSTR_JSON( id, id_cstr );
     978           0 :       CSTR_JSON( encoding, encoding_cstr );
     979           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected string or map.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding ), encoding_cstr, id_cstr );
     980           0 :       return 0;
     981           0 :     }
     982           0 :     if( FD_UNLIKELY( cJSON_IsObject( encoding ) && !(encoding->child && encoding->child->next==NULL) ) ) {
     983           0 :       CSTR_JSON( id, id_cstr );
     984           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: map, expected map with a single key.\"},\"id\":%s}\n", id_cstr );
     985           0 :       return 0;
     986           0 :     }
     987           0 :     if( FD_UNLIKELY( encoding && !cJSON_IsString( encoding ) && !cJSON_IsNull( encoding ) && !cJSON_IsObject( encoding ) ) ) {
     988           0 :       CSTR_JSON( id, id_cstr );
     989           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected string or map.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding ), id_cstr );
     990           0 :       return 0;
     991           0 :     }
     992             : 
     993           0 :     char const * encoding_cstr;
     994           0 :     if( FD_UNLIKELY( cJSON_IsObject( encoding ) ) ) {
     995           0 :       if( cJSON_HasObjectItem( encoding, "binary" ) ) encoding_cstr = "binary";
     996           0 :       else if( cJSON_HasObjectItem( encoding, "base58" ) ) encoding_cstr = "base58";
     997           0 :       else if( cJSON_HasObjectItem( encoding, "base64" ) ) encoding_cstr = "base64";
     998           0 :       else if( cJSON_HasObjectItem( encoding, "base64+zstd" ) ) encoding_cstr = "base64+zstd";
     999           0 :       else if( cJSON_HasObjectItem( encoding, "jsonParsed" ) ) encoding_cstr = "jsonParsed";
    1000           0 :       else {
    1001           0 :         cJSON * _key_node = cJSON_CreateString( encoding->child->string );
    1002           0 :         CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( _key_node, key_esc );
    1003           0 :         *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: unknown variant `%s`, expected one of `binary`, `base58`, `base64`, `jsonParsed`, `base64+zstd`.\"},\"id\":%s}\n", key_esc, id_cstr );
    1004           0 :         cJSON_Delete( _key_node );
    1005           0 :         return 0;
    1006           0 :       }
    1007           0 :     } else {
    1008           0 :       encoding_cstr = encoding && cJSON_IsString( encoding ) ? encoding->valuestring : "binary";
    1009           0 :     }
    1010             : 
    1011           0 :     if( FD_UNLIKELY( cJSON_IsObject( encoding ) && (cJSON_IsNumber( encoding->child ) || cJSON_IsBool( encoding->child )) ) ) {
    1012           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON( encoding->child, child_cstr );
    1013           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected unit.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding->child ), child_cstr, id_cstr );
    1014           0 :       return 0;
    1015           0 :     }
    1016           0 :     if( FD_UNLIKELY( cJSON_IsObject( encoding ) && cJSON_IsString( encoding->child ) ) ) {
    1017           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( encoding->child, encoding_child_esc );
    1018           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected unit.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding->child ), encoding_child_esc, id_cstr );
    1019           0 :       return 0;
    1020           0 :     }
    1021           0 :     if( FD_UNLIKELY( cJSON_IsObject( encoding ) && !cJSON_IsNull( encoding->child ) ) ) {
    1022           0 :       CSTR_JSON( id, id_cstr );
    1023           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected unit.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( encoding->child ), id_cstr );
    1024           0 :       return 0;
    1025           0 :     }
    1026             : 
    1027           0 :     if( 0==strcmp( encoding_cstr, "jsonParsed" ) ) {
    1028           0 :       CSTR_JSON( id, id_cstr );
    1029           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: jsonParsed is unsupported\"},\"id\":%s}\n", id_cstr );
    1030           0 :       return 0;
    1031           0 :     } else if( 0!=strcmp( encoding_cstr, "binary" ) && 0!=strcmp( encoding_cstr, "base58" ) && 0!=strcmp( encoding_cstr, "base64" ) && 0!=strcmp( encoding_cstr, "base64+zstd" ) ) {
    1032           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( encoding, encoding_esc );
    1033           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: unknown variant `%s`, expected one of `binary`, `base58`, `base64`, `jsonParsed`, `base64+zstd`.\"},\"id\":%s}\n", encoding_esc, id_cstr );
    1034           0 :       return 0;
    1035           0 :     }
    1036             : 
    1037           0 :     if( FD_LIKELY( opt_encoding_cstr ) ) *opt_encoding_cstr = encoding_cstr;
    1038           0 :   }
    1039             : 
    1040           0 :   if( FD_LIKELY( has_data_slice ) ) {
    1041           0 :     const cJSON * dataSlice = cJSON_GetObjectItemCaseSensitive( config, "dataSlice" );
    1042             : 
    1043           0 :     if( FD_UNLIKELY( cJSON_IsNumber( dataSlice ) || cJSON_IsBool( dataSlice ) ) ) {
    1044           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON( dataSlice, data_slice_cstr );
    1045           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected struct UiDataSliceConfig.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( dataSlice ), data_slice_cstr, id_cstr );
    1046           0 :       return 0;
    1047           0 :     }
    1048           0 :     if( FD_UNLIKELY( cJSON_IsString( dataSlice ) ) ) {
    1049           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( dataSlice, data_slice_esc );
    1050           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected struct UiDataSliceConfig.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( dataSlice ), data_slice_esc, id_cstr );
    1051           0 :       return 0;
    1052           0 :     }
    1053           0 :     if( FD_UNLIKELY( dataSlice && !cJSON_IsObject( dataSlice ) && !cJSON_IsNull( dataSlice ) && !cJSON_IsArray( dataSlice ) ) ) {
    1054           0 :       CSTR_JSON( id, id_cstr );
    1055           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected struct UiDataSliceConfig.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( dataSlice ), id_cstr );
    1056           0 :       return 0;
    1057           0 :     }
    1058             : 
    1059           0 :     int has_offset = cJSON_IsObject( dataSlice ) && cJSON_HasObjectItem( dataSlice, "offset" );
    1060           0 :     int has_length = cJSON_IsObject( dataSlice ) && cJSON_HasObjectItem( dataSlice, "length" );
    1061             : 
    1062           0 :     cJSON const * _length = NULL;
    1063           0 :     cJSON const * _offset = NULL;
    1064           0 :     if( cJSON_IsObject( dataSlice ) ) {
    1065           0 :       _length = cJSON_GetObjectItemCaseSensitive( dataSlice, "length" );
    1066           0 :       _offset = cJSON_GetObjectItemCaseSensitive( dataSlice, "offset" );
    1067           0 :     } else if( FD_UNLIKELY( cJSON_IsArray( dataSlice ) ) ) {
    1068           0 :       _offset = cJSON_GetArrayItem( dataSlice, 0 );
    1069           0 :       _length = cJSON_GetArrayItem( dataSlice, 1 );
    1070           0 :     }
    1071             : 
    1072           0 :     if( FD_UNLIKELY( cJSON_IsBool( _offset ) || (cJSON_IsNumber( _offset ) && !fd_rpc_cjson_is_integer( _offset )) ) ) {
    1073           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON( _offset, offset_cstr );
    1074           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _offset ), offset_cstr, id_cstr );
    1075           0 :       return 0;
    1076           0 :     }
    1077           0 :     if( FD_UNLIKELY( cJSON_IsBool( _length ) || (cJSON_IsNumber( _length ) && !fd_rpc_cjson_is_integer( _length )) ) ) {
    1078           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON( _length, length_cstr );
    1079           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _length ), length_cstr, id_cstr );
    1080           0 :       return 0;
    1081           0 :     }
    1082             : 
    1083           0 :     if( FD_UNLIKELY( cJSON_IsNumber( _offset ) && _offset->valueint<0 ) ) {
    1084           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON( _offset, offset_cstr );
    1085           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _offset ), offset_cstr, id_cstr );
    1086           0 :       return 0;
    1087           0 :     }
    1088           0 :     if( FD_UNLIKELY( cJSON_IsNumber( _length ) && _length->valueint<0 ) ) {
    1089           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON( _length, length_cstr );
    1090           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _length ), length_cstr, id_cstr );
    1091           0 :       return 0;
    1092           0 :     }
    1093             : 
    1094           0 :     if( FD_UNLIKELY( cJSON_IsString( _offset ) ) ) {
    1095           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( _offset, offset_esc );
    1096           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _offset ), offset_esc, id_cstr );
    1097           0 :       return 0;
    1098           0 :     }
    1099           0 :     if( FD_UNLIKELY( cJSON_IsString( _length ) ) ) {
    1100           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( _length, length_esc );
    1101           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _length ), length_esc, id_cstr );
    1102           0 :       return 0;
    1103           0 :     }
    1104             : 
    1105           0 :     if( FD_UNLIKELY( _offset && !fd_rpc_cjson_is_integer( _offset ) ) ) {
    1106           0 :       CSTR_JSON( id, id_cstr );
    1107           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _offset ), id_cstr );
    1108           0 :       return 0;
    1109           0 :     }
    1110           0 :     if( FD_UNLIKELY( _length && !fd_rpc_cjson_is_integer( _length ) ) ) {
    1111           0 :       CSTR_JSON( id, id_cstr );
    1112           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _length ), id_cstr );
    1113           0 :       return 0;
    1114           0 :     }
    1115             : 
    1116           0 :     if( FD_UNLIKELY( cJSON_IsObject( dataSlice ) && !has_offset ) ) {
    1117           0 :       CSTR_JSON( id, id_cstr );
    1118           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `offset`.\"},\"id\":%s}\n", id_cstr );
    1119           0 :       return 0;
    1120           0 :     }
    1121           0 :     if( FD_UNLIKELY( cJSON_IsObject( dataSlice ) && !has_length ) ) {
    1122           0 :       CSTR_JSON( id, id_cstr );
    1123           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `length`.\"},\"id\":%s}\n", id_cstr );
    1124           0 :       return 0;
    1125           0 :     }
    1126             : 
    1127           0 :     if( FD_UNLIKELY( cJSON_IsArray( dataSlice ) && cJSON_GetArraySize( dataSlice )!=2 ) ) {
    1128           0 :       CSTR_JSON( id, id_cstr );
    1129           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid length %lu, expected struct UiDataSliceConfig with 2 elements.\"},\"id\":%s}\n", (ulong)cJSON_GetArraySize( dataSlice ), id_cstr );
    1130           0 :       return 0;
    1131           0 :     }
    1132             : 
    1133           0 :     if( dataSlice && !cJSON_IsNull( dataSlice ) ) {
    1134           0 :       if( FD_LIKELY( opt_slice_offset ) ) *opt_slice_offset = _offset ? _offset->valueulong : 0UL;
    1135           0 :       if( FD_LIKELY( opt_slice_length ) ) *opt_slice_length = _length ? _length->valueulong : ULONG_MAX;
    1136           0 :     } else {
    1137           0 :       if( FD_LIKELY( opt_slice_offset ) ) *opt_slice_offset = 0UL;
    1138           0 :       if( FD_LIKELY( opt_slice_length ) ) *opt_slice_length = ULONG_MAX;
    1139           0 :     }
    1140           0 :   }
    1141             : 
    1142           0 :   if( FD_LIKELY( has_min_context_slot ) ) {
    1143           0 :     ulong minContextSlot = 0UL;
    1144           0 :     cJSON const * _minContextSlot = cJSON_GetObjectItemCaseSensitive( config, "minContextSlot" );
    1145           0 :     if( FD_UNLIKELY( cJSON_IsBool( _minContextSlot ) || (cJSON_IsNumber( _minContextSlot ) && !fd_rpc_cjson_is_integer( _minContextSlot )) ) ) {
    1146           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON( _minContextSlot, min_context_slot_cstr );
    1147           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected u64.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _minContextSlot ), min_context_slot_cstr, id_cstr );
    1148           0 :       return 0;
    1149           0 :     }
    1150             : 
    1151           0 :     if( FD_UNLIKELY( cJSON_IsNumber( _minContextSlot ) && _minContextSlot->valueint<0 ) ) {
    1152           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON( _minContextSlot, min_context_slot_cstr );
    1153           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: %s `%s`, expected u64.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _minContextSlot ), min_context_slot_cstr, id_cstr );
    1154           0 :       return 0;
    1155           0 :     }
    1156             : 
    1157           0 :     if( FD_UNLIKELY( cJSON_IsString( _minContextSlot ) ) ) {
    1158           0 :       CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( _minContextSlot, min_ctx_slot_esc );
    1159           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected u64.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _minContextSlot ), min_ctx_slot_esc, id_cstr );
    1160           0 :       return 0;
    1161           0 :     }
    1162             : 
    1163           0 :     if( FD_UNLIKELY( _minContextSlot && !cJSON_IsNull( _minContextSlot ) && !fd_rpc_cjson_is_integer( _minContextSlot ) ) ) {
    1164           0 :       CSTR_JSON( id, id_cstr );
    1165           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected u64.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( _minContextSlot ), id_cstr );
    1166           0 :       return 0;
    1167           0 :     }
    1168             : 
    1169           0 :     minContextSlot = _minContextSlot && fd_rpc_cjson_is_integer( _minContextSlot ) ? _minContextSlot->valueulong : 0UL;
    1170             : 
    1171           0 :     if( _bank_idx!=ULONG_MAX && ctx->banks[ _bank_idx ].slot<minContextSlot ) {
    1172           0 :       CSTR_JSON( id, id_cstr );
    1173           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Minimum context slot has not been reached\",\"data\":{\"contextSlot\":%lu}},\"id\":%s}\n", FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, ctx->banks[ _bank_idx ].slot, id_cstr );
    1174           0 :       return 0;
    1175           0 :     }
    1176           0 :   }
    1177             : 
    1178           0 :   return 1;
    1179           0 : }
    1180             : 
    1181             : static int
    1182             : fd_rpc_validate_address( fd_rpc_tile_t *             ctx,
    1183             :                          cJSON const *               id,
    1184             :                          cJSON const *               address_in,
    1185             :                          fd_pubkey_t *               address_out,
    1186           0 :                          fd_http_server_response_t * response ) {
    1187           0 :   FD_TEST( address_in );
    1188           0 :   if( FD_UNLIKELY( cJSON_IsNumber( address_in ) || cJSON_IsBool( address_in ) ) ) {
    1189           0 :     CSTR_JSON( id, id_cstr ); CSTR_JSON( address_in, address_in_cstr );
    1190           0 :     *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected a string.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( address_in ), address_in_cstr, id_cstr );
    1191           0 :     return 0;
    1192           0 :   }
    1193           0 :   if( FD_UNLIKELY( !cJSON_IsString( address_in ) ) ) {
    1194           0 :     CSTR_JSON( id, id_cstr );
    1195           0 :     *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected a string.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( address_in ), id_cstr );
    1196           0 :     return 0;
    1197           0 :   }
    1198           0 :   int invalid_char = fd_rpc_cstr_contains_non_base58( address_in->valuestring );
    1199           0 :   if( FD_UNLIKELY( invalid_char ) ) {
    1200           0 :     CSTR_JSON( id, id_cstr );
    1201           0 :     *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: Invalid\"},\"id\":%s}\n", id_cstr );
    1202           0 :     return 0;
    1203           0 :   }
    1204           0 :   int valid = !!fd_base58_decode_32( address_in->valuestring, address_out->uc );
    1205           0 :   if( FD_UNLIKELY( !valid ) ) {
    1206           0 :     CSTR_JSON( id, id_cstr );
    1207           0 :     *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: WrongSize\"},\"id\":%s}\n", id_cstr );
    1208           0 :     return 0;
    1209           0 :   }
    1210             : 
    1211           0 :   return 1;
    1212           0 : }
    1213             : 
    1214             : #define UNIMPLEMENTED(X)                               \
    1215             : static fd_http_server_response_t                       \
    1216             : X( fd_rpc_tile_t * ctx,                                \
    1217             :    cJSON const *   id,                                 \
    1218           0 :    cJSON const *   params ) {                          \
    1219           0 :   (void)ctx; (void)id; (void)params;                   \
    1220           0 :   return (fd_http_server_response_t){ .status = 501 }; \
    1221           0 : }
    1222             : 
    1223             : UNIMPLEMENTED(getBlock)
    1224             : UNIMPLEMENTED(getBlockCommitment)
    1225             : 
    1226             : /* fd_rpc_encode_account_data encodes account data fields
    1227             :    (executable, lamports, owner, rentEpoch, space, data) into the http
    1228             :    staging buffer.  Returns 1 on success.  On failure, calls
    1229             :    fd_http_server_unstage and writes an error response into
    1230             :    err_response. */
    1231             : static int
    1232             : fd_rpc_encode_account_data( fd_rpc_tile_t *             ctx,
    1233             :                             uchar const *               acct_data,
    1234             :                             ulong                       acct_data_len,
    1235             :                             uchar const *               acct_owner,
    1236             :                             ulong                       acct_lamports,
    1237             :                             int                         acct_executable,
    1238             :                             char const *                encoding_cstr,
    1239             :                             ulong                       slice_offset,
    1240             :                             ulong                       slice_length,
    1241             :                             char const *                id_cstr,
    1242           0 :                             fd_http_server_response_t * err_response ) {
    1243             : 
    1244           0 :   int is_binary = !strcmp( encoding_cstr, "binary" );
    1245           0 :   int is_base58 = !strcmp( encoding_cstr, "base58" );
    1246           0 :   int is_zstd   = !strcmp( encoding_cstr, "base64+zstd" );
    1247             : 
    1248           0 :   ulong data_sz        = acct_data_len;
    1249           0 :   uchar const * out    = acct_data + fd_ulong_if( slice_offset<data_sz, slice_offset, 0UL );
    1250           0 :   ulong         snip_sz = fd_ulong_min( fd_ulong_if( slice_offset<data_sz, data_sz-slice_offset, 0UL ), slice_length );
    1251           0 :   ulong         out_sz  = snip_sz;
    1252             : 
    1253           0 :   if( FD_UNLIKELY( (is_binary || is_base58) && snip_sz>128UL ) ) {
    1254           0 :     fd_http_server_unstage( ctx->http );
    1255           0 :     *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Encoded binary (base 58) data should be less than {MAX_BASE58_BYTES} bytes, please use Base64 encoding.\"},\"id\":%s}\n", id_cstr );
    1256           0 :     return 0;
    1257           0 :   }
    1258             : 
    1259           0 : # if FD_HAS_ZSTD
    1260           0 :   if( is_zstd ) {
    1261           0 :     ulong zstd_res = ZSTD_compress( ctx->compress_buf, sizeof(ctx->compress_buf), out, snip_sz, 0 );
    1262           0 :     if( ZSTD_isError( zstd_res ) ) {
    1263           0 :       fd_http_server_unstage( ctx->http );
    1264           0 :       *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: zstandard compression failed (%s)\"},\"id\":%s}\n", ZSTD_getErrorName( zstd_res ), id_cstr );
    1265           0 :       return 0;
    1266           0 :     }
    1267           0 :     out    = ctx->compress_buf;
    1268           0 :     out_sz = (ulong)zstd_res;
    1269           0 :   }
    1270             : # else
    1271             :   if( is_zstd ) {
    1272             :     fd_http_server_unstage( ctx->http );
    1273             :     *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: zstandard is disabled\"},\"id\":%s}\n", id_cstr );
    1274             :     return 0;
    1275             :   }
    1276             : # endif
    1277             : 
    1278           0 :   FD_BASE58_ENCODE_32_BYTES( acct_owner, owner_b58 );
    1279           0 :   fd_http_server_printf( ctx->http,
    1280           0 :       "{\"executable\":%s,\"lamports\":%lu,\"owner\":\"%s\",\"rentEpoch\":18446744073709551615,\"space\":%lu,\"data\":",
    1281           0 :       acct_executable ? "true" : "false",
    1282           0 :       acct_lamports,
    1283           0 :       owner_b58,
    1284           0 :       data_sz );
    1285             : 
    1286           0 :   ulong encoded_sz = fd_ulong_if( is_base58 || is_binary, FD_RPC_BASE58_ENCODED_128_LEN, FD_BASE64_ENC_SZ( out_sz ) );
    1287           0 :   if( FD_UNLIKELY( is_binary ) ) {
    1288           0 :     fd_http_server_printf( ctx->http, "\"" );
    1289           0 :   } else {
    1290           0 :     fd_http_server_printf( ctx->http, "[\"" );
    1291           0 :   }
    1292             : 
    1293           0 :   uchar * encoded = fd_http_server_append_start( ctx->http, encoded_sz );
    1294           0 :   if( FD_UNLIKELY( !encoded ) ) {
    1295           0 :     fd_http_server_unstage( ctx->http );
    1296           0 :     *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: response encoding buffer overflow (account data too large for response buffer)\"},\"id\":%s}\n", id_cstr );
    1297           0 :     return 0;
    1298           0 :   }
    1299             : 
    1300           0 :   if( FD_UNLIKELY( is_base58 || is_binary ) ) {
    1301           0 :     if( FD_UNLIKELY( !fd_rpc_base58_encode_128( (char *)encoded, &encoded_sz, out, out_sz ) ) ) {
    1302           0 :       fd_http_server_unstage( ctx->http );
    1303           0 :       FD_LOG_WARNING(( "base58 encode failed out_sz=%lu", out_sz ));
    1304           0 :       *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: base58 encode failed\"},\"id\":%s}\n", id_cstr );
    1305           0 :       return 0;
    1306           0 :     }
    1307           0 :   } else {
    1308           0 :     encoded_sz = fd_base64_encode( (char *)encoded, out, out_sz );
    1309           0 :   }
    1310             : 
    1311           0 :   fd_http_server_append_end( ctx->http, encoded_sz );
    1312             : 
    1313           0 :   if( FD_UNLIKELY( is_binary ) ) fd_http_server_printf( ctx->http, "\"}" );
    1314           0 :   else                           fd_http_server_printf( ctx->http, "\",\"%s\"]}", encoding_cstr );
    1315             : 
    1316           0 :   return 1;
    1317           0 : }
    1318             : 
    1319             : static fd_http_server_response_t
    1320             : getAccountInfo( fd_rpc_tile_t * ctx,
    1321             :                 cJSON const *   id,
    1322           0 :                 cJSON const *   params ) {
    1323           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_ACCOUNT_INFO, 1UL );
    1324             : 
    1325           0 :   fd_http_server_response_t response;
    1326           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 2, &response ) ) ) return response;
    1327             : 
    1328           0 :   fd_pubkey_t address;
    1329           0 :   cJSON const * acct_pubkey = cJSON_GetArrayItem( params, 0 );
    1330           0 :   if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id, acct_pubkey, &address, &response ) ) ) return response;
    1331             : 
    1332           0 :   ulong bank_idx = ULONG_MAX;
    1333           0 :   char const * encoding_cstr = NULL;
    1334           0 :   ulong slice_length = ULONG_MAX;
    1335           0 :   ulong slice_offset = 0;
    1336           0 :   cJSON const * config = cJSON_GetArrayItem( params, 1 );
    1337           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcAccountInfoConfig",
    1338           0 :                                              1, /* has_commitment */
    1339           0 :                                              1, /* has_encoding */
    1340           0 :                                              1, /* has_data_slice */
    1341           0 :                                              1, /* has_min_context_slot */
    1342           0 :                                              &bank_idx,
    1343           0 :                                              &encoding_cstr,
    1344           0 :                                              &slice_length,
    1345           0 :                                              &slice_offset,
    1346           0 :                                              &response );
    1347           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1348             : 
    1349           0 :   bank_info_t * info = &ctx->banks[ bank_idx ];
    1350           0 :   ulong acct_lamports;
    1351           0 :   int   acct_executable;
    1352           0 :   uchar acct_owner[ 32UL ];
    1353           0 :   ulong acct_data_len;
    1354           0 :   fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, address.uc,
    1355           0 :                              &acct_lamports, &acct_executable, acct_owner,
    1356           0 :                              ctx->accdb_data_buf, &acct_data_len );
    1357           0 :   if( FD_UNLIKELY( !acct_lamports ) ) {
    1358           0 :     CSTR_JSON( id, id_cstr );
    1359           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":%lu},\"value\":null},\"id\":%s}\n", info->slot, id_cstr );
    1360           0 :   }
    1361             : 
    1362           0 :   CSTR_JSON( id, id_cstr );
    1363           0 :   fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":{\"context\":{\"apiVersion\":\"%s\",\"slot\":%lu},\"value\":", id_cstr, FD_RPC_AGAVE_API_VERSION, info->slot );
    1364             : 
    1365           0 :   fd_http_server_response_t err_response;
    1366           0 :   if( FD_UNLIKELY( !fd_rpc_encode_account_data( ctx, ctx->accdb_data_buf, acct_data_len, acct_owner, acct_lamports, acct_executable, encoding_cstr, slice_offset, slice_length, id_cstr, &err_response ) ) ) {
    1367           0 :     return err_response;
    1368           0 :   }
    1369             : 
    1370           0 :   fd_http_server_printf( ctx->http, "}}\n" );
    1371           0 :   return STAGE_JSON( ctx );
    1372           0 : }
    1373             : 
    1374             : static fd_http_server_response_t
    1375             : getBalance( fd_rpc_tile_t * ctx,
    1376             :             cJSON const *   id,
    1377           0 :             cJSON const *   params ) {
    1378           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_BALANCE, 1UL );
    1379             : 
    1380           0 :   fd_http_server_response_t response;
    1381           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 2, &response ) ) ) return response;
    1382             : 
    1383           0 :   fd_pubkey_t address;
    1384           0 :   cJSON const * acct_pubkey = cJSON_GetArrayItem( params, 0 );
    1385           0 :   if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id, acct_pubkey, &address, &response ) ) ) return response;
    1386             : 
    1387           0 :   ulong bank_idx = ULONG_MAX;
    1388           0 :   cJSON const * config = cJSON_GetArrayItem( params, 1 );
    1389           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcContextConfig",
    1390           0 :                                              1, /* has_commitment */
    1391           0 :                                              0, /* has_encoding */
    1392           0 :                                              0, /* has_data_slice */
    1393           0 :                                              1, /* has_min_context_slot */
    1394           0 :                                              &bank_idx,
    1395           0 :                                              NULL,
    1396           0 :                                              NULL,
    1397           0 :                                              NULL,
    1398           0 :                                              &response );
    1399           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1400             : 
    1401           0 :   ulong balance = fd_accdb_lamports( ctx->accdb, ctx->banks[ bank_idx ].accdb_fork_id, address.uc );
    1402             : 
    1403           0 :   CSTR_JSON( id, id_cstr );
    1404           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"%s\",\"slot\":%lu},\"value\":%lu},\"id\":%s}\n", FD_RPC_AGAVE_API_VERSION, ctx->banks[ bank_idx ].slot, balance, id_cstr );
    1405           0 : }
    1406             : 
    1407             : static fd_http_server_response_t
    1408             : getBlockHeight( fd_rpc_tile_t * ctx,
    1409             :                 cJSON const *   id,
    1410           0 :                 cJSON const *   params ) {
    1411           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_BLOCK_HEIGHT, 1UL );
    1412             : 
    1413           0 :   fd_http_server_response_t response;
    1414           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
    1415             : 
    1416           0 :   ulong bank_idx = ULONG_MAX;
    1417           0 :   cJSON const * config = cJSON_GetArrayItem( params, 0 );
    1418           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcContextConfig",
    1419           0 :                                              1, /* has_commitment */
    1420           0 :                                              0, /* has_encoding */
    1421           0 :                                              0, /* has_data_slice */
    1422           0 :                                              1, /* has_min_context_slot */
    1423           0 :                                              &bank_idx,
    1424           0 :                                              NULL,
    1425           0 :                                              NULL,
    1426           0 :                                              NULL,
    1427           0 :                                              &response );
    1428           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1429             : 
    1430           0 :   CSTR_JSON( id, id_cstr );
    1431           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", ctx->banks[ bank_idx ].block_height, id_cstr );
    1432           0 : }
    1433             : 
    1434             : UNIMPLEMENTED(getBlockProduction) // TODO: Used by solana-exporter
    1435             : UNIMPLEMENTED(getBlocks)
    1436             : UNIMPLEMENTED(getBlocksWithLimit)
    1437             : UNIMPLEMENTED(getBlockTime)
    1438             : 
    1439             : static fd_http_server_response_t
    1440             : getClusterNodes( fd_rpc_tile_t * ctx,
    1441             :                  cJSON const *   id,
    1442           0 :                  cJSON const *   params ) {
    1443           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_CLUSTER_NODES, 1UL );
    1444             : 
    1445           0 :   fd_http_server_response_t response;
    1446           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
    1447             : 
    1448           0 :   fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":[" );
    1449             : 
    1450           0 :   for( fd_rpc_cluster_node_dlist_iter_t iter = fd_rpc_cluster_node_dlist_iter_rev_init( ctx->cluster_nodes_dlist, ctx->cluster_nodes );
    1451           0 :        !fd_rpc_cluster_node_dlist_iter_done( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes );
    1452           0 :        iter = fd_rpc_cluster_node_dlist_iter_rev_next( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes ) ) {
    1453           0 :     fd_rpc_cluster_node_t * ele = fd_rpc_cluster_node_dlist_iter_ele( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes );
    1454           0 :     FD_BASE58_ENCODE_32_BYTES( ele->identity.uc, identity_cstr );
    1455           0 :     int is_last = fd_rpc_cluster_node_dlist_iter_done( fd_rpc_cluster_node_dlist_iter_rev_next( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes ), ctx->cluster_nodes_dlist, ctx->cluster_nodes );
    1456             : 
    1457           0 :     fd_http_server_printf( ctx->http, "{\"featureSet\":%u,", ele->ci->version.feature_set );
    1458             : 
    1459           0 :     for( ulong i=0UL; i<FD_GOSSIP_CONTACT_INFO_SOCKET_CNT; i++ ) {
    1460           0 :       char const * name;
    1461           0 :       switch( i ) {
    1462           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP:            name = "gossip"; break;
    1463           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR_QUIC: name = NULL; break;
    1464           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_RPC:               name = "rpc"; break;
    1465           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_RPC_PUBSUB:        name = "pubsub"; break;
    1466           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR:      name = "serveRepair"; break;
    1467             :         /* Even though Agave does not support "tpu" and "tpuForwards"
    1468             :            (hardcoded null), frankendacer/firedancer still support
    1469             :            UDP-TPU. */
    1470           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU:               name = "tpu"; break;
    1471           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_FORWARDS:      name = "tpuForwards"; break;
    1472           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_FORWARDS_QUIC: name = "tpuForwardsQuic"; break;
    1473           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_QUIC:          name = "tpuQuic"; break;
    1474           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE:          name = "tpuVote"; break;
    1475           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TVU:               name = "tvu"; break;
    1476           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TVU_QUIC:          name = NULL; break;
    1477           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE_QUIC:     name = NULL; break;
    1478           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_ALPENGLOW:         name = NULL; break;
    1479           0 :         default: FD_LOG_ERR(( "unreachable "));
    1480           0 :       }
    1481           0 :       if( FD_UNLIKELY( !name ) ) continue;
    1482             : 
    1483           0 :       uint ip4 = ele->ci->sockets[ i ].is_ipv6 ? 0U : ele->ci->sockets[ i ].ip4;
    1484           0 :       if( FD_LIKELY( !!ip4 || !!ele->ci->sockets[ i ].port ) ) fd_http_server_printf( ctx->http, "\"%s\":\"" FD_IP4_ADDR_FMT ":%hu\",", name, FD_IP4_ADDR_FMT_ARGS( ip4 ), fd_ushort_bswap( ele->ci->sockets[ i ].port ) );
    1485           0 :       else                                                     fd_http_server_printf( ctx->http, "\"%s\":null,", name );
    1486           0 :     }
    1487           0 :     fd_http_server_printf( ctx->http, "\"pubkey\":\"%s\",", identity_cstr );
    1488           0 :     fd_http_server_printf( ctx->http, "\"shredVersion\":%u,", ele->ci->shred_version );
    1489             : 
    1490           0 :     char version[ 64UL ];
    1491           0 :     FD_TEST( fd_gossip_version_cstr( ele->ci->version.major, ele->ci->version.minor, ele->ci->version.patch, version, sizeof( version ) ) );
    1492           0 :     fd_http_server_printf( ctx->http, "\"version\":\"%s\",", version );
    1493             : 
    1494           0 :     char const * client_id;
    1495           0 :     switch( ele->ci->version.client ) {
    1496           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_SOLANA_LABS:   client_id = "SolanaLabs";     break;
    1497           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_JITO_LABS:     client_id = "JitoLabs";       break;
    1498           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_FRANKENDANCER: client_id = "Frankendancer";  break;
    1499           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE:         client_id = "Agave";          break;
    1500           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE_PALADIN: client_id = "AgavePaladin";   break;
    1501           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_FIREDANCER:    client_id = "Firedancer";     break;
    1502           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE_BAM:     client_id = "AgaveBam";       break;
    1503           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_SIG:           client_id = "Sig";            break;
    1504           0 :       default:                                          client_id = NULL;             break;
    1505           0 :     }
    1506           0 :     if( FD_LIKELY( client_id ) ) fd_http_server_printf( ctx->http, "\"clientId\":\"%s\"", client_id );
    1507           0 :     else                         fd_http_server_printf( ctx->http, "\"clientId\":\"Unknown(%hu)\"", ele->ci->version.client );
    1508             : 
    1509           0 :     if( FD_UNLIKELY( is_last ) ) fd_http_server_printf( ctx->http, "}" );
    1510           0 :     else                         fd_http_server_printf( ctx->http, "}," );
    1511           0 :   }
    1512             : 
    1513           0 :   CSTR_JSON( id, id_cstr );
    1514           0 :   fd_http_server_printf( ctx->http, "],\"id\":%s}\n", id_cstr );
    1515           0 :   return STAGE_JSON( ctx );
    1516           0 : }
    1517             : 
    1518             : static fd_http_server_response_t
    1519             : getEpochInfo( fd_rpc_tile_t * ctx,
    1520             :               cJSON const *   id,
    1521           0 :               cJSON const *   params ) {
    1522           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_EPOCH_INFO, 1UL );
    1523             : 
    1524           0 :   fd_http_server_response_t response;
    1525           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
    1526             : 
    1527           0 :   ulong bank_idx = ULONG_MAX;
    1528           0 :   cJSON const * config = cJSON_GetArrayItem( params, 0 );
    1529           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcContextConfig",
    1530           0 :                                               1, /* has_commitment */
    1531           0 :                                               0, /* has_encoding */
    1532           0 :                                               0, /* has_data_slice */
    1533           0 :                                               1, /* has_min_context_slot */
    1534           0 :                                               &bank_idx,
    1535           0 :                                               NULL,
    1536           0 :                                               NULL,
    1537           0 :                                               NULL,
    1538           0 :                                               &response );
    1539           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1540             : 
    1541           0 :   CSTR_JSON( id, id_cstr );
    1542           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"absoluteSlot\":%lu,\"blockHeight\":%lu,\"epoch\":%lu,\"slotIndex\":%lu,\"slotsInEpoch\":%lu,\"transactionCount\":%lu},\"id\":%s}\n", ctx->banks[ bank_idx ].slot, ctx->banks[ bank_idx ].block_height, ctx->banks[ bank_idx ].epoch, ctx->banks[ bank_idx ].slot_in_epoch, ctx->banks[ bank_idx ].slots_per_epoch, ctx->banks[ bank_idx ].transaction_count, id_cstr );
    1543           0 : }
    1544             : 
    1545             : UNIMPLEMENTED(getEpochSchedule)
    1546             : UNIMPLEMENTED(getFeeForMessage)
    1547             : UNIMPLEMENTED(getFirstAvailableBlock) // TODO: Used by solana-exporter
    1548             : 
    1549             : /* Get the genesis hash of the cluster.  Firedancer deviates slightly
    1550             :    from Agave here, as the genesis hash is not always known when RPC
    1551             :    is first booted, it may need to be determined asynchronously in the
    1552             :    background, fetched from a peer node.  If the genesis hash is not yet
    1553             :    known, we return an error indicating no snapshot is available. */
    1554             : 
    1555             : static fd_http_server_response_t
    1556             : getGenesisHash( fd_rpc_tile_t * ctx,
    1557             :                 cJSON const *   id,
    1558           0 :                 cJSON const *   params ) {
    1559           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_GENESIS_HASH, 1UL );
    1560             : 
    1561           0 :   fd_http_server_response_t response;
    1562           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
    1563             : 
    1564           0 :   if( FD_UNLIKELY( !ctx->has_genesis_hash ) ) {
    1565           0 :     CSTR_JSON( id, id_cstr );
    1566           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Firedancer Error: No genesis hash\"},\"id\":%s}\n", FD_RPC_ERROR_NO_SNAPSHOT, id_cstr );
    1567           0 :   }
    1568             : 
    1569           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, genesis_hash_b58 );
    1570           0 :   CSTR_JSON( id, id_cstr );
    1571           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%s}\n", genesis_hash_b58, id_cstr );
    1572           0 : }
    1573             : 
    1574             : /* Determines if the node is healthy.  Agave defines this as follows,
    1575             : 
    1576             :     - On boot, nodes must go through the entire snapshot slot database
    1577             :       and hash everything, and once it's done verify the hash matches.
    1578             :       While this is ongoing, the node is unhealthy with a "slotsBehind"
    1579             :       value of null.
    1580             : 
    1581             :     - On boot, if the cluster is restarting and we are currently waiting
    1582             :       for a supermajority of stake to join gossip to proceed with
    1583             :       booting, the node is forcibly marked as healthy, to, per Agave,
    1584             : 
    1585             :        > prevent load balancers from removing the node from their list
    1586             :        > of candidates during a manual restart
    1587             : 
    1588             :     - In addition, once booted, there is a period where we do not yet
    1589             :       know the cluster confirmed slot, because we have not yet observed
    1590             :       any (or enough) votes arrive from peers in the cluster.  During
    1591             :       this period the node is unhealthy with a "slotsBehind" value of
    1592             :       null.
    1593             : 
    1594             :     - Finally, once the cluster confirmed slot is known, which is the
    1595             :       highest optimistically confirmed slot observed from both gossip,
    1596             :       and votes procesed in blocks, it is compared to our own
    1597             :       optimistically confirmed slot, which is just the highest slot down
    1598             :       the cluster confirmed fork that we have finished replaying
    1599             :       locally.  The difference between these two slots is compared, and
    1600             :       if it is less than or equal to 128, the node is healthy, otherwise
    1601             :       it is unhealthy with a "slotsBehind" value equal to the
    1602             :       difference.
    1603             : 
    1604             :    Firedancer currently only implements the final two checks, and does
    1605             :    not forcibly mark the node as healthy while waiting for a
    1606             :    supermajority, nor does it mark a node as unhealthy while hashing the
    1607             :    snapshot database on boot.  Firedancer hashes snapshots so quickly
    1608             :    that the node will die on boot if the hash is not valid. */
    1609             : 
    1610             : static inline int
    1611           0 : _getHealth( fd_rpc_tile_t * ctx ) {
    1612           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_HEALTH, 1UL );
    1613             : 
    1614             :   /* fd_http_server_listen is not called until after RPC has initialized banks */
    1615           0 :   if( FD_UNLIKELY( ctx->confirmed_idx==ULONG_MAX ) ) return FD_RPC_HEALTH_STATUS_UNKNOWN;
    1616           0 :   if( FD_UNLIKELY( ctx->cluster_confirmed_slot==ULONG_MAX ) ) return FD_RPC_HEALTH_STATUS_UNKNOWN;
    1617             : 
    1618           0 :   ulong slots_behind = fd_ulong_sat_sub( ctx->cluster_confirmed_slot, ctx->banks[ ctx->confirmed_idx ].slot );
    1619           0 :   if( FD_LIKELY( slots_behind<=128UL ) ) return FD_RPC_HEALTH_STATUS_OK;
    1620           0 :   else                                   return FD_RPC_HEALTH_STATUS_BEHIND;
    1621           0 : }
    1622             : 
    1623             : static fd_http_server_response_t
    1624             : getHealth( fd_rpc_tile_t * ctx,
    1625             :            cJSON const *   id,
    1626           0 :            cJSON const *   params ) {
    1627           0 :   fd_http_server_response_t response;
    1628           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
    1629             : 
    1630             :   // TODO: We should probably implement the same waiting_for_supermajority
    1631             :   // logic to conform with Agave here.
    1632             : 
    1633           0 :   int health_status = _getHealth( ctx );
    1634             : 
    1635           0 :   CSTR_JSON( id, id_cstr );
    1636           0 :   switch( health_status ) {
    1637           0 :     case FD_RPC_HEALTH_STATUS_UNKNOWN: return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Node is unhealthy\",\"data\":{\"slotsBehind\":null}},\"id\":%s}\n", FD_RPC_ERROR_NODE_UNHEALTHY, id_cstr );
    1638           0 :     case FD_RPC_HEALTH_STATUS_BEHIND:  return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":\"Node is unhealthy\",\"data\":{\"slotsBehind\":%lu}},\"id\":%s}\n", FD_RPC_ERROR_NODE_UNHEALTHY, fd_ulong_sat_sub( ctx->cluster_confirmed_slot, ctx->banks[ ctx->confirmed_idx ].slot ), id_cstr );
    1639           0 :     case FD_RPC_HEALTH_STATUS_OK:      return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"ok\",\"id\":%s}\n", id_cstr );
    1640           0 :     default: FD_LOG_ERR(( "unknown health status" ));
    1641           0 :   }
    1642           0 : }
    1643             : 
    1644             : UNIMPLEMENTED(getHighestSnapshotSlot)
    1645             : 
    1646             : static fd_http_server_response_t
    1647             : getIdentity( fd_rpc_tile_t * ctx,
    1648             :              cJSON const *   id,
    1649           0 :              cJSON const *   params ) {
    1650           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_IDENTITY, 1UL );
    1651             : 
    1652           0 :   fd_http_server_response_t response;
    1653           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
    1654             : 
    1655           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->identity_pubkey, identity_pubkey_b58 );
    1656           0 :   CSTR_JSON( id, id_cstr );
    1657           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"identity\":\"%s\"},\"id\":%s}\n", identity_pubkey_b58, id_cstr );
    1658           0 : }
    1659             : 
    1660             : static fd_http_server_response_t
    1661             : getInflationGovernor( fd_rpc_tile_t * ctx,
    1662             :                      cJSON const *   id,
    1663           0 :                      cJSON const *   params ) {
    1664           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_INFLATION_GOVERNOR, 1UL );
    1665             : 
    1666           0 :   fd_http_server_response_t response;
    1667           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
    1668             : 
    1669           0 :   ulong bank_idx = ULONG_MAX;
    1670           0 :   cJSON const * config = cJSON_GetArrayItem( params, 0 );
    1671           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
    1672           0 :                                              1, /* has_commitment */
    1673           0 :                                              0, /* has_encoding */
    1674           0 :                                              0, /* has_data_slice */
    1675           0 :                                              0, /* has_min_context_slot */
    1676           0 :                                              &bank_idx,
    1677           0 :                                              NULL,
    1678           0 :                                              NULL,
    1679           0 :                                              NULL,
    1680           0 :                                              &response );
    1681           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1682             : 
    1683           0 :   bank_info_t const * bank = &ctx->banks[ bank_idx ];
    1684           0 :   CSTR_JSON( id, id_cstr );
    1685           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"foundation\":%g%s,\"foundationTerm\":%g%s,\"initial\":%g%s,\"taper\":%g%s,\"terminal\":%g%s},\"id\":%s}\n",
    1686           0 :                            bank->inflation.foundation, bank->inflation.foundation==0 ? ".0" : "",
    1687           0 :                            bank->inflation.foundation_term, bank->inflation.foundation_term==0 ? ".0" : "",
    1688           0 :                            bank->inflation.initial, bank->inflation.initial==0 ? ".0" : "",
    1689           0 :                            bank->inflation.taper, bank->inflation.taper==0 ? ".0" : "",
    1690           0 :                            bank->inflation.terminal, bank->inflation.terminal==0 ? ".0" : "",
    1691           0 :                            id_cstr );
    1692           0 : }
    1693             : 
    1694             : UNIMPLEMENTED(getInflationRate)
    1695             : UNIMPLEMENTED(getInflationReward) // TODO: Used by solana-exporter
    1696             : UNIMPLEMENTED(getLargestAccounts)
    1697             : 
    1698             : static fd_http_server_response_t
    1699             : getLatestBlockhash( fd_rpc_tile_t * ctx,
    1700             :                     cJSON const *   id,
    1701           0 :                     cJSON const *   params ) {
    1702           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_LATEST_BLOCKHASH, 1UL );
    1703             : 
    1704           0 :   if( FD_UNLIKELY( ctx->processed_idx==ULONG_MAX ) ) {
    1705           0 :     CSTR_JSON( id, id_cstr );
    1706           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
    1707           0 :   }
    1708             : 
    1709           0 :   fd_http_server_response_t response;
    1710           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
    1711             : 
    1712           0 :   ulong bank_idx = ULONG_MAX;
    1713           0 :   cJSON const * config = cJSON_GetArrayItem( params, 0 );
    1714           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
    1715           0 :                                              1, /* has_commitment */
    1716           0 :                                              0, /* has_encoding */
    1717           0 :                                              0, /* has_data_slice */
    1718           0 :                                              1, /* has_min_context_slot */
    1719           0 :                                              &bank_idx,
    1720           0 :                                              NULL,
    1721           0 :                                              NULL,
    1722           0 :                                              NULL,
    1723           0 :                                              &response );
    1724           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1725             : 
    1726           0 :   bank_info_t * bank = &ctx->banks[ bank_idx ];
    1727           0 :   FD_BASE58_ENCODE_32_BYTES( bank->block_hash, block_hash_b58 );
    1728             : 
    1729           0 :   CSTR_JSON( id, id_cstr );
    1730           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":%lu,\"apiVersion\":\"%s\"},\"value\":{\"blockhash\":\"%s\",\"lastValidBlockHeight\":%lu}},\"id\":%s}\n", bank->slot, FD_RPC_AGAVE_API_VERSION, block_hash_b58, bank->block_height + 150UL, id_cstr );
    1731           0 : }
    1732             : 
    1733             : UNIMPLEMENTED(getLeaderSchedule) // TODO: Used by solana-exporter
    1734             : UNIMPLEMENTED(getMaxRetransmitSlot)
    1735             : UNIMPLEMENTED(getMaxShredInsertSlot)
    1736             : 
    1737             : static fd_http_server_response_t
    1738             : getMinimumBalanceForRentExemption( fd_rpc_tile_t * ctx,
    1739             :                                    cJSON const *   id,
    1740           0 :                                    cJSON const *   params ) {
    1741           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION, 1UL );
    1742             : 
    1743           0 :   fd_http_server_response_t response;
    1744           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 2, &response ) ) ) return response;
    1745             : 
    1746           0 :   ulong bank_idx = ULONG_MAX;
    1747           0 :   cJSON const * config = cJSON_GetArrayItem( params, 1 );
    1748           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
    1749           0 :                                              1, /* has_commitment */
    1750           0 :                                              0, /* has_encoding */
    1751           0 :                                              0, /* has_data_slice */
    1752           0 :                                              0, /* has_min_context_slot */
    1753           0 :                                              &bank_idx,
    1754           0 :                                              NULL,
    1755           0 :                                              NULL,
    1756           0 :                                              NULL,
    1757           0 :                                              &response );
    1758           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1759             : 
    1760           0 :   cJSON const * acct_sz = cJSON_GetArrayItem( params, 0 );
    1761             : 
    1762           0 :   if( FD_UNLIKELY( cJSON_IsBool( acct_sz ) || (cJSON_IsNumber( acct_sz ) && !fd_rpc_cjson_is_integer( acct_sz )) ) ) {
    1763           0 :     CSTR_JSON( id, id_cstr );
    1764           0 :     CSTR_JSON( acct_sz, acct_sz_cstr );
    1765           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( acct_sz ), acct_sz_cstr, id_cstr );
    1766           0 :   }
    1767           0 :   if( FD_UNLIKELY( cJSON_IsNumber( acct_sz ) && acct_sz->valueint<0 ) ) {
    1768           0 :     CSTR_JSON( id, id_cstr );
    1769           0 :     CSTR_JSON( acct_sz, acct_sz_cstr );
    1770           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid value: %s `%s`, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( acct_sz ), acct_sz_cstr, id_cstr );
    1771           0 :   }
    1772           0 :   if( FD_UNLIKELY( cJSON_IsString( acct_sz ) ) ) {
    1773           0 :     CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( acct_sz, acct_sz_esc );
    1774           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( acct_sz ), acct_sz_esc, id_cstr );
    1775           0 :   }
    1776           0 :   if( FD_UNLIKELY( acct_sz && !fd_rpc_cjson_is_integer( acct_sz ) ) ) {
    1777           0 :     CSTR_JSON( id, id_cstr );
    1778           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( acct_sz ), id_cstr );
    1779           0 :   }
    1780             : 
    1781           0 :   bank_info_t const * bank = &ctx->banks[ bank_idx ];
    1782             : 
    1783           0 :   fd_rent_t rent = {
    1784           0 :     .lamports_per_uint8_year = bank->rent.lamports_per_uint8_year,
    1785           0 :     .exemption_threshold = bank->rent.exemption_threshold,
    1786           0 :     .burn_percent = bank->rent.burn_percent,
    1787           0 :   };
    1788           0 :   ulong minimum = fd_rent_exempt_minimum_balance( &rent, acct_sz->valueulong );
    1789           0 :   CSTR_JSON( id, id_cstr );
    1790           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", minimum, id_cstr );
    1791           0 : }
    1792             : 
    1793             : static fd_http_server_response_t
    1794             : getMultipleAccounts( fd_rpc_tile_t * ctx,
    1795             :                      cJSON const *   id,
    1796           0 :                      cJSON const *   params ) {
    1797           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_MULTIPLE_ACCOUNTS, 1UL );
    1798             : 
    1799           0 :   fd_http_server_response_t response;
    1800           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 2, &response ) ) ) return response;
    1801             : 
    1802           0 :   cJSON const * keys_arr = cJSON_GetArrayItem( params, 0 );
    1803           0 :   if( FD_UNLIKELY( cJSON_IsNumber( keys_arr ) || cJSON_IsBool( keys_arr ) ) ) {
    1804           0 :     CSTR_JSON( id, id_cstr ); CSTR_JSON( keys_arr, keys_arr_cstr );
    1805           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s `%s`, expected a sequence.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( keys_arr ), keys_arr_cstr, id_cstr );
    1806           0 :   }
    1807           0 :   if( FD_UNLIKELY( cJSON_IsString( keys_arr ) ) ) {
    1808           0 :     CSTR_JSON( id, id_cstr ); CSTR_JSON_UNQUOTED( keys_arr, keys_arr_esc );
    1809           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s \\\"%s\\\", expected a sequence.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( keys_arr ), keys_arr_esc, id_cstr );
    1810           0 :   }
    1811           0 :   if( FD_UNLIKELY( !cJSON_IsArray( keys_arr ) ) ) {
    1812           0 :     CSTR_JSON( id, id_cstr );
    1813           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected a sequence.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( keys_arr ), id_cstr );
    1814           0 :   }
    1815             : 
    1816           0 :   int cnt = cJSON_GetArraySize( keys_arr );
    1817           0 :   if( FD_UNLIKELY( cnt<0 || cnt>100 ) ) {
    1818           0 :     CSTR_JSON( id, id_cstr );
    1819           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Too many accounts provided; max 100\"},\"id\":%s}\n", id_cstr );
    1820           0 :   }
    1821             : 
    1822           0 :   ulong bank_idx = ULONG_MAX;
    1823           0 :   char const * encoding_cstr = NULL;
    1824           0 :   ulong slice_length = ULONG_MAX;
    1825           0 :   ulong slice_offset = 0;
    1826           0 :   cJSON const * config = cJSON_GetArrayItem( params, 1 );
    1827           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct RpcAccountInfoConfig",
    1828           0 :                                              1, 1, 1, 1,
    1829           0 :                                              &bank_idx, &encoding_cstr,
    1830           0 :                                              &slice_length, &slice_offset,
    1831           0 :                                              &response );
    1832           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1833             : 
    1834           0 :   bank_info_t * info = &ctx->banks[ bank_idx ];
    1835             : 
    1836           0 :   fd_pubkey_t addresses[ 100UL ];
    1837           0 :   for( ulong i=0UL; i<(ulong)cnt; i++ ) {
    1838           0 :     cJSON const * key_json = cJSON_GetArrayItem( keys_arr, (int)i );
    1839           0 :     if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id, key_json, &addresses[ i ], &response ) ) ) return response;
    1840           0 :   }
    1841             : 
    1842           0 :   CSTR_JSON( id, id_cstr );
    1843           0 :   fd_http_server_printf( ctx->http,
    1844           0 :       "{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":{\"context\":{\"apiVersion\":\"%s\",\"slot\":%lu},\"value\":[",
    1845           0 :       id_cstr, FD_RPC_AGAVE_API_VERSION, info->slot );
    1846             : 
    1847           0 :   for( ulong i=0; i<(ulong)cnt; i++ ) {
    1848           0 :     if( i>0 ) fd_http_server_printf( ctx->http, "," );
    1849             : 
    1850           0 :     ulong acct_lamports;
    1851           0 :     int   acct_executable;
    1852           0 :     uchar acct_owner[ 32UL ];
    1853           0 :     ulong acct_data_len;
    1854           0 :     fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, addresses[i].uc,
    1855           0 :                                &acct_lamports, &acct_executable, acct_owner,
    1856           0 :                                ctx->accdb_data_buf, &acct_data_len );
    1857           0 :     if( FD_UNLIKELY( !acct_lamports ) ) {
    1858           0 :       fd_http_server_printf( ctx->http, "null" );
    1859           0 :       continue;
    1860           0 :     }
    1861             : 
    1862           0 :     fd_http_server_response_t err_response;
    1863           0 :     if( FD_UNLIKELY( !fd_rpc_encode_account_data( ctx, ctx->accdb_data_buf, acct_data_len, acct_owner, acct_lamports, acct_executable, encoding_cstr, slice_offset, slice_length, id_cstr, &err_response ) ) ) {
    1864           0 :       return err_response;
    1865           0 :     }
    1866           0 :   }
    1867             : 
    1868           0 :   fd_http_server_printf( ctx->http, "]}}\n" );
    1869           0 :   return STAGE_JSON( ctx );
    1870           0 : }
    1871             : 
    1872             : UNIMPLEMENTED(getProgramAccounts)
    1873             : UNIMPLEMENTED(getRecentPerformanceSamples)
    1874             : UNIMPLEMENTED(getRecentPrioritizationFees)
    1875             : UNIMPLEMENTED(getSignaturesForAddress)
    1876             : UNIMPLEMENTED(getSignatureStatuses)
    1877             : 
    1878             : static fd_http_server_response_t
    1879             : getSlot( fd_rpc_tile_t * ctx,
    1880             :          cJSON const *   id,
    1881           0 :          cJSON const *   params ) {
    1882           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_SLOT, 1UL );
    1883             : 
    1884           0 :   fd_http_server_response_t response;
    1885           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
    1886             : 
    1887           0 :   ulong bank_idx = ULONG_MAX;
    1888           0 :   cJSON const * config = cJSON_GetArrayItem( params, 0 );
    1889           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
    1890           0 :                                              1, /* has_commitment */
    1891           0 :                                              0, /* has_encoding */
    1892           0 :                                              0, /* has_data_slice */
    1893           0 :                                              1, /* has_min_context_slot */
    1894           0 :                                              &bank_idx,
    1895           0 :                                              NULL,
    1896           0 :                                              NULL,
    1897           0 :                                              NULL,
    1898           0 :                                              &response );
    1899           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1900             : 
    1901           0 :   bank_info_t * bank = &ctx->banks[ bank_idx ];
    1902           0 :   CSTR_JSON( id, id_cstr );
    1903           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", bank->slot, id_cstr );
    1904           0 : }
    1905             : 
    1906             : UNIMPLEMENTED(getSlotLeader)
    1907             : UNIMPLEMENTED(getSlotLeaders)
    1908             : UNIMPLEMENTED(getStakeMinimumDelegation)
    1909             : UNIMPLEMENTED(getSupply)
    1910             : UNIMPLEMENTED(getTokenAccountBalance)
    1911             : UNIMPLEMENTED(getTokenAccountsByDelegate)
    1912             : UNIMPLEMENTED(getTokenAccountsByOwner)
    1913             : UNIMPLEMENTED(getTokenLargestAccounts)
    1914             : UNIMPLEMENTED(getTokenSupply)
    1915             : UNIMPLEMENTED(getTransaction)
    1916             : 
    1917             : static fd_http_server_response_t
    1918             : getTransactionCount( fd_rpc_tile_t * ctx,
    1919             :                      cJSON const *   id,
    1920           0 :                      cJSON const *   params ) {
    1921           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_TRANSACTION_COUNT, 1UL );
    1922             : 
    1923           0 :   fd_http_server_response_t response;
    1924           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 1, &response ) ) ) return response;
    1925             : 
    1926           0 :   ulong bank_idx = ULONG_MAX;
    1927           0 :   cJSON const * config = cJSON_GetArrayItem( params, 0 );
    1928           0 :   int config_valid = fd_rpc_validate_config( ctx, id, config, "struct CommitmentConfig",
    1929           0 :                                              1, /* has_commitment */
    1930           0 :                                              0, /* has_encoding */
    1931           0 :                                              0, /* has_data_slice */
    1932           0 :                                              1, /* has_min_context_slot */
    1933           0 :                                              &bank_idx,
    1934           0 :                                              NULL,
    1935           0 :                                              NULL,
    1936           0 :                                              NULL,
    1937           0 :                                              &response );
    1938           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1939             : 
    1940           0 :   bank_info_t * bank = &ctx->banks[ bank_idx ];
    1941           0 :   CSTR_JSON( id, id_cstr );
    1942           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", bank->transaction_count, id_cstr );
    1943           0 : }
    1944             : 
    1945             : static fd_http_server_response_t
    1946             : getVersion( fd_rpc_tile_t * ctx,
    1947             :             cJSON const *   id,
    1948           0 :             cJSON const *   params ) {
    1949           0 :   FD_MCNT_INC( RPC, REQUEST_SERVED_GET_VERSION, 1UL );
    1950             : 
    1951           0 :   fd_http_server_response_t response;
    1952           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
    1953             : 
    1954           0 :   CSTR_JSON( id, id_cstr );
    1955           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"solana-core\":\"%s\",\"feature-set\":%u},\"id\":%s}\n", fd_version_cstr, FD_FEATURE_SET_ID, id_cstr );
    1956           0 : }
    1957             : 
    1958             : static fd_http_server_response_t
    1959             : voteSubscribe( fd_rpc_tile_t * ctx,
    1960             :                cJSON const *   id,
    1961             :                cJSON const *   params,
    1962           0 :                ulong           ws_conn_id ) {
    1963           0 :   fd_http_server_response_t response;
    1964           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
    1965             : 
    1966           0 :   CSTR_JSON( id, id_cstr );
    1967           0 :   if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
    1968           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    1969           0 :   }
    1970           0 :   FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
    1971             : 
    1972           0 :   fd_rpc_ws_subscriber_vote_add( ctx, ws_conn_id );
    1973             : 
    1974           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":%s}\n", id_cstr );
    1975           0 : }
    1976             : 
    1977             : static fd_http_server_response_t
    1978             : slotSubscribe( fd_rpc_tile_t * ctx,
    1979             :                cJSON const *   id,
    1980             :                cJSON const *   params,
    1981           0 :                ulong           ws_conn_id ) {
    1982           0 :   fd_http_server_response_t response;
    1983           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 0, 0, &response ) ) ) return response;
    1984             : 
    1985           0 :   CSTR_JSON( id, id_cstr );
    1986           0 :   if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
    1987           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    1988           0 :   }
    1989           0 :   FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
    1990             : 
    1991           0 :   fd_rpc_ws_subscriber_slot_add( ctx, ws_conn_id );
    1992             : 
    1993           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":%s}\n", id_cstr );
    1994           0 : }
    1995             : 
    1996             : static fd_http_server_response_t
    1997             : voteUnsubscribe( fd_rpc_tile_t * ctx,
    1998             :                  cJSON const *   id,
    1999             :                  cJSON const *   params,
    2000           0 :                  ulong           ws_conn_id ) {
    2001           0 :   fd_http_server_response_t response;
    2002           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 1, &response ) ) ) return response;
    2003             : 
    2004           0 :   CSTR_JSON( id, id_cstr );
    2005           0 :   if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
    2006           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    2007           0 :   }
    2008           0 :   FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
    2009             : 
    2010           0 :   cJSON const * subscription = cJSON_GetArrayItem( params, 0 );
    2011           0 :   if( FD_UNLIKELY( !fd_rpc_cjson_is_integer( subscription ) || subscription->valueint<0 ) ) {
    2012           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( subscription ), id_cstr );
    2013           0 :   }
    2014             : 
    2015           0 :   int unsubscribed = 0;
    2016           0 :   if( FD_LIKELY( subscription->valueint==0 ) )
    2017           0 :     unsubscribed = fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
    2018             : 
    2019           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%s,\"id\":%s}\n", unsubscribed ? "true" : "false", id_cstr );
    2020           0 : }
    2021             : 
    2022             : static fd_http_server_response_t
    2023             : slotUnsubscribe( fd_rpc_tile_t * ctx,
    2024             :                  cJSON const *   id,
    2025             :                  cJSON const *   params,
    2026           0 :                  ulong           ws_conn_id ) {
    2027           0 :   fd_http_server_response_t response;
    2028           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id, params, 1, 1, &response ) ) ) return response;
    2029             : 
    2030           0 :   CSTR_JSON( id, id_cstr );
    2031           0 :   if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
    2032           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    2033           0 :   }
    2034           0 :   FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
    2035             : 
    2036           0 :   cJSON const * subscription = cJSON_GetArrayItem( params, 0 );
    2037           0 :   if( FD_UNLIKELY( !fd_rpc_cjson_is_integer( subscription ) || subscription->valueint<0 ) ) {
    2038           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_cjson_type_to_cstr( subscription ), id_cstr );
    2039           0 :   }
    2040             : 
    2041           0 :   int unsubscribed = 0;
    2042           0 :   if( FD_LIKELY( subscription->valueint==0 ) )
    2043           0 :     unsubscribed = fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
    2044             : 
    2045           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%s,\"id\":%s}\n", unsubscribed ? "true" : "false", id_cstr );
    2046           0 : }
    2047             : 
    2048             : UNIMPLEMENTED(getVoteAccounts) // TODO: Used by solana-exporter
    2049             : UNIMPLEMENTED(isBlockhashValid)
    2050             : UNIMPLEMENTED(minimumLedgerSlot) // TODO: Used by solana-exporter
    2051             : UNIMPLEMENTED(requestAirdrop)
    2052             : UNIMPLEMENTED(sendTransaction)
    2053             : UNIMPLEMENTED(simulateTransaction)
    2054             : 
    2055             : static fd_http_server_response_t
    2056             : rpc_json_request( fd_rpc_tile_t * ctx,
    2057             :                   uchar const *   body,
    2058             :                   ulong           body_len,
    2059             :                   ulong           ws_conn_id );
    2060             : 
    2061             : static fd_http_server_response_t
    2062             : rpc_http_request1( fd_rpc_tile_t *                  ctx,
    2063           0 :                    fd_http_server_request_t const * request ) {
    2064           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET &&
    2065           0 :                    request->headers.upgrade_websocket &&
    2066           0 :                    (!strcmp( request->path, "/" ) || !strcmp( request->path, "/websocket" )) ) ) {
    2067           0 :     if( FD_UNLIKELY( !ctx->http->max_ws_conns ) ) return (fd_http_server_response_t){ .status = 404 };
    2068           0 :     return (fd_http_server_response_t){
    2069           0 :       .status            = 200,
    2070           0 :       .upgrade_websocket = 1,
    2071           0 :     };
    2072           0 :   }
    2073             : 
    2074           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET && !strcmp( request->path, "/health" ) ) ) {
    2075           0 :     int health_status = _getHealth( ctx );
    2076             : 
    2077           0 :     switch( health_status ) {
    2078           0 :       case FD_RPC_HEALTH_STATUS_UNKNOWN: return PRINTF_JSON( ctx, "unknown" );
    2079           0 :       case FD_RPC_HEALTH_STATUS_BEHIND:  return PRINTF_JSON( ctx, "behind" );
    2080           0 :       case FD_RPC_HEALTH_STATUS_OK:      return PRINTF_JSON( ctx, "ok" );
    2081           0 :       default: FD_LOG_ERR(( "unknown health status" ));
    2082           0 :     }
    2083           0 :   }
    2084             : 
    2085           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET && !strcmp( request->path, "/genesis.tar.bz2" ) ) ) {
    2086           0 :     FD_MCNT_INC( RPC, REQUEST_SERVED_GENESIS, 1UL );
    2087           0 :     if( FD_UNLIKELY( ctx->genesis_tar_bz_sz==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 404 };
    2088             : 
    2089           0 :     fd_http_server_response_t response = (fd_http_server_response_t){ .status = 200 };
    2090           0 :     fd_http_server_memcpy( ctx->http, ctx->genesis_tar_bz, ctx->genesis_tar_bz_sz );
    2091           0 :     FD_TEST( !fd_http_server_stage_body( ctx->http, &response ) );
    2092           0 :     return response;
    2093           0 :   }
    2094             : 
    2095           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET ) ) {
    2096           0 :     return (fd_http_server_response_t){ .status = 404 };
    2097           0 :   }
    2098             : 
    2099           0 :   if( FD_UNLIKELY( request->method!=FD_HTTP_SERVER_METHOD_POST ) ) {
    2100           0 :     return (fd_http_server_response_t){ .status = 405 };
    2101           0 :   }
    2102             : 
    2103           0 :   return rpc_json_request( ctx, request->post.body, request->post.body_len, ULONG_MAX );
    2104           0 : }
    2105             : 
    2106             : static fd_http_server_response_t
    2107             : rpc_json_request( fd_rpc_tile_t * ctx,
    2108             :                   uchar const *   body,
    2109             :                   ulong           body_len,
    2110           0 :                   ulong           ws_conn_id ) { /* ULONG_MAX implies HTTP */
    2111           0 :   const char * parse_end;
    2112           0 :   cJSON * json = cJSON_ParseWithLengthOpts( (char const *)body, body_len, &parse_end, 0 );
    2113             : 
    2114           0 :   if( FD_UNLIKELY( cJSON_IsArray( json ) && cJSON_GetArraySize( json )==0UL ) ) {
    2115             :     /* A bug in Agave ¯\_(ツ)_/¯ */
    2116           0 :     cJSON_Delete( json );
    2117           0 :     return (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
    2118           0 :   }
    2119             : 
    2120           0 :   if( FD_UNLIKELY( !json || !cJSON_IsObject( json ) ) ) {
    2121           0 :     cJSON_Delete( json );
    2122           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
    2123           0 :   }
    2124           0 :   const cJSON * id = cJSON_GetObjectItemCaseSensitive( json, "id" );
    2125             : 
    2126           0 :   cJSON * item = json->child;
    2127           0 :   while( item ) {
    2128           0 :     if( FD_UNLIKELY( strcmp( item->string, "jsonrpc" ) && strcmp( item->string, "id" ) && strcmp( item->string, "method" ) && strcmp( item->string, "params" ) ) ) {
    2129           0 :       fd_http_server_response_t res;
    2130           0 :       if( FD_LIKELY( id ) ) {
    2131           0 :         CSTR_JSON( id, id_cstr );
    2132           0 :         res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2133           0 :       } else {
    2134           0 :         res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":null}\n" );
    2135           0 :       }
    2136           0 :       cJSON_Delete( json );
    2137           0 :       return res;
    2138           0 :     }
    2139           0 :     item = item->next;
    2140           0 :   }
    2141             : 
    2142           0 :   if( FD_UNLIKELY( cJSON_HasObjectItem( json, "method") && !cJSON_HasObjectItem( json, "id") ) ) {
    2143             :     /* A bug in Agave ¯\_(ツ)_/¯ */
    2144           0 :     cJSON_Delete( json );
    2145           0 :     return (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
    2146           0 :   }
    2147             : 
    2148           0 :   const cJSON * jsonrpc = cJSON_GetObjectItemCaseSensitive( json, "jsonrpc" );
    2149           0 :   if( FD_UNLIKELY( !cJSON_HasObjectItem( json, "jsonrpc" ) && cJSON_HasObjectItem( json, "method" ) ) ) {
    2150           0 :     fd_http_server_response_t res;
    2151           0 :     if( FD_LIKELY( id ) ) {
    2152           0 :       CSTR_JSON( id, id_cstr );
    2153           0 :       res = PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":%s}\n", id_cstr );
    2154           0 :     } else {
    2155           0 :       res = PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":null}\n" );;
    2156           0 :     }
    2157           0 :     cJSON_Delete( json );
    2158           0 :     return res;
    2159           0 :   }
    2160             : 
    2161           0 :   if( FD_UNLIKELY( cJSON_IsObject( json ) && (!cJSON_HasObjectItem( json, "jsonrpc" ) || !cJSON_HasObjectItem( json, "method" )) ) ) {
    2162           0 :     fd_http_server_response_t res;
    2163           0 :     if( FD_LIKELY( id ) ) {
    2164           0 :       CSTR_JSON( id, id_cstr );
    2165           0 :       res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2166           0 :     } else {
    2167           0 :       res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":null}\n" );
    2168           0 :     }
    2169           0 :     cJSON_Delete( json );
    2170           0 :     return res;
    2171           0 :   }
    2172             : 
    2173           0 :   if( FD_UNLIKELY( !(id && fd_rpc_cjson_is_integer( id ) && id->valueint >= 0) && !cJSON_IsString( id ) && !cJSON_IsNull( id ) ) ) {
    2174           0 :     cJSON_Delete( json );
    2175           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
    2176           0 :   }
    2177             : 
    2178           0 :   if( FD_UNLIKELY( !cJSON_HasObjectItem( json, "jsonrpc" ) || cJSON_IsNull( jsonrpc ) ) ) {
    2179           0 :     CSTR_JSON( id, id_cstr );
    2180           0 :     cJSON_Delete( json );
    2181           0 :     return PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":%s}\n", id_cstr );
    2182           0 :   }
    2183             : 
    2184           0 :   if( FD_UNLIKELY( !cJSON_IsString( jsonrpc ) || strcmp( jsonrpc->valuestring, "2.0" ) ) ) {
    2185           0 :     CSTR_JSON( id, id_cstr );
    2186           0 :     cJSON_Delete( json );
    2187           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2188           0 :   }
    2189             : 
    2190           0 :   const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
    2191           0 :   fd_http_server_response_t response;
    2192             : 
    2193           0 :   const cJSON * _method = cJSON_GetObjectItemCaseSensitive( json, "method" );
    2194           0 :   if( FD_UNLIKELY( !cJSON_IsString( _method ) || _method->valuestring==NULL ) ) {
    2195           0 :     CSTR_JSON( id, id_cstr );
    2196           0 :     cJSON_Delete( json );
    2197           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2198           0 :   }
    2199             : 
    2200           0 :   if( FD_LIKELY(      !strcmp( _method->valuestring, "getAccountInfo"                    ) ) ) response = getAccountInfo( ctx, id, params );
    2201           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getBalance"                        ) ) ) response = getBalance( ctx, id, params );
    2202           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlock"                          ) ) ) response = getBlock( ctx, id, params );
    2203           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockCommitment"                ) ) ) response = getBlockCommitment( ctx, id, params );
    2204           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockHeight"                    ) ) ) response = getBlockHeight( ctx, id, params );
    2205           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockProduction"                ) ) ) response = getBlockProduction( ctx, id, params );
    2206           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlocks"                         ) ) ) response = getBlocks( ctx, id, params );
    2207           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlocksWithLimit"                ) ) ) response = getBlocksWithLimit( ctx, id, params );
    2208           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getBlockTime"                      ) ) ) response = getBlockTime( ctx, id, params );
    2209           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getClusterNodes"                   ) ) ) response = getClusterNodes( ctx, id, params );
    2210           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getEpochInfo"                      ) ) ) response = getEpochInfo( ctx, id, params );
    2211           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getEpochSchedule"                  ) ) ) response = getEpochSchedule( ctx, id, params );
    2212           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getFeeForMessage"                  ) ) ) response = getFeeForMessage( ctx, id, params );
    2213           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getFirstAvailableBlock"            ) ) ) response = getFirstAvailableBlock( ctx, id, params );
    2214           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getGenesisHash"                    ) ) ) response = getGenesisHash( ctx, id, params );
    2215           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getHealth"                         ) ) ) response = getHealth( ctx, id, params );
    2216           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getHighestSnapshotSlot"            ) ) ) response = getHighestSnapshotSlot( ctx, id, params );
    2217           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getIdentity"                       ) ) ) response = getIdentity( ctx, id, params );
    2218           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationGovernor"              ) ) ) response = getInflationGovernor( ctx, id, params );
    2219           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationRate"                  ) ) ) response = getInflationRate( ctx, id, params );
    2220           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getInflationReward"                ) ) ) response = getInflationReward( ctx, id, params );
    2221           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getLargestAccounts"                ) ) ) response = getLargestAccounts( ctx, id, params );
    2222           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getLatestBlockhash"                ) ) ) response = getLatestBlockhash( ctx, id, params );
    2223           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getLeaderSchedule"                 ) ) ) response = getLeaderSchedule( ctx, id, params );
    2224           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getMaxRetransmitSlot"              ) ) ) response = getMaxRetransmitSlot( ctx, id, params );
    2225           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getMaxShredInsertSlot"             ) ) ) response = getMaxShredInsertSlot( ctx, id, params );
    2226           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getMinimumBalanceForRentExemption" ) ) ) response = getMinimumBalanceForRentExemption( ctx, id, params );
    2227           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getMultipleAccounts"               ) ) ) response = getMultipleAccounts( ctx, id, params );
    2228           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getProgramAccounts"                ) ) ) response = getProgramAccounts( ctx, id, params );
    2229           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getRecentPerformanceSamples"       ) ) ) response = getRecentPerformanceSamples( ctx, id, params );
    2230           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getRecentPrioritizationFees"       ) ) ) response = getRecentPrioritizationFees( ctx, id, params );
    2231           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getSignaturesForAddress"           ) ) ) response = getSignaturesForAddress( ctx, id, params );
    2232           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getSignatureStatuses"              ) ) ) response = getSignatureStatuses( ctx, id, params );
    2233           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlot"                           ) ) ) response = getSlot( ctx, id, params );
    2234           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlotLeader"                     ) ) ) response = getSlotLeader( ctx, id, params );
    2235           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getSlotLeaders"                    ) ) ) response = getSlotLeaders( ctx, id, params );
    2236           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getStakeMinimumDelegation"         ) ) ) response = getStakeMinimumDelegation( ctx, id, params );
    2237           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getSupply"                         ) ) ) response = getSupply( ctx, id, params );
    2238           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountBalance"            ) ) ) response = getTokenAccountBalance( ctx, id, params );
    2239           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountsByDelegate"        ) ) ) response = getTokenAccountsByDelegate( ctx, id, params );
    2240           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenAccountsByOwner"           ) ) ) response = getTokenAccountsByOwner( ctx, id, params );
    2241           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenLargestAccounts"           ) ) ) response = getTokenLargestAccounts( ctx, id, params );
    2242           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getTokenSupply"                    ) ) ) response = getTokenSupply( ctx, id, params );
    2243           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getTransaction"                    ) ) ) response = getTransaction( ctx, id, params );
    2244           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getTransactionCount"               ) ) ) response = getTransactionCount( ctx, id, params );
    2245           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getVersion"                        ) ) ) response = getVersion( ctx, id, params );
    2246           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "getVoteAccounts"                   ) ) ) response = getVoteAccounts( ctx, id, params );
    2247           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "slotSubscribe"                     ) ) ) response = slotSubscribe( ctx, id, params, ws_conn_id );
    2248           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "slotUnsubscribe"                   ) ) ) response = slotUnsubscribe( ctx, id, params, ws_conn_id );
    2249           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "voteSubscribe"                     ) ) ) response = voteSubscribe( ctx, id, params, ws_conn_id );
    2250           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "voteUnsubscribe"                   ) ) ) response = voteUnsubscribe( ctx, id, params, ws_conn_id );
    2251           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "isBlockhashValid"                  ) ) ) response = isBlockhashValid( ctx, id, params );
    2252           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "minimumLedgerSlot"                 ) ) ) response = minimumLedgerSlot( ctx, id, params );
    2253           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "requestAirdrop"                    ) ) ) response = requestAirdrop( ctx, id, params );
    2254           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "sendTransaction"                   ) ) ) response = sendTransaction( ctx, id, params );
    2255           0 :   else if( FD_LIKELY( !strcmp( _method->valuestring, "simulateTransaction"               ) ) ) response = simulateTransaction( ctx, id, params );
    2256           0 :   else {
    2257           0 :     FD_MCNT_INC( RPC, REQUEST_SERVED_UNKNOWN, 1UL );
    2258           0 :     CSTR_JSON( id, id_cstr );
    2259           0 :     response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    2260           0 :   }
    2261             : 
    2262           0 :   cJSON_Delete( json );
    2263           0 :   return response;
    2264           0 : }
    2265             : 
    2266             : static fd_http_server_response_t
    2267           0 : rpc_http_request( fd_http_server_request_t const * request ) {
    2268           0 :   fd_rpc_tile_t * ctx = request->ctx;
    2269           0 :   long dt = -fd_tickcount();
    2270           0 :   fd_http_server_response_t response = rpc_http_request1( ctx, request );
    2271           0 :   dt += fd_tickcount();
    2272           0 :   fd_histf_sample( ctx->request_duration, (ulong)dt );
    2273           0 :   return response;
    2274           0 : }
    2275             : 
    2276             : static void
    2277             : rpc_ws_open( ulong  ws_conn_id,
    2278           0 :              void * ctx ) {
    2279           0 :   (void)ws_conn_id; (void)ctx;
    2280           0 : }
    2281             : 
    2282             : static void
    2283             : rpc_ws_close( ulong  ws_conn_id,
    2284             :               int    reason     FD_PARAM_UNUSED,
    2285           0 :               void * _ctx ) {
    2286           0 :   fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)_ctx;
    2287           0 :   if( FD_UNLIKELY( ws_conn_id>=ctx->http->max_ws_conns ) ) return;
    2288           0 :   fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
    2289           0 :   fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
    2290           0 : }
    2291             : 
    2292             : static void
    2293             : rpc_ws_message( ulong         ws_conn_id,
    2294             :                 uchar const * data,
    2295             :                 ulong         data_len,
    2296           0 :                 void *        _ctx ) {
    2297           0 :   fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)_ctx;
    2298             : 
    2299           0 :   fd_http_server_unstage( ctx->http );
    2300             : 
    2301           0 :   long dt = -fd_tickcount();
    2302           0 :   fd_http_server_response_t response = rpc_json_request( ctx, data, data_len, ws_conn_id );
    2303           0 :   dt += fd_tickcount();
    2304           0 :   fd_histf_sample( ctx->request_duration, (ulong)dt );
    2305             : 
    2306           0 :   if( FD_UNLIKELY( response.status!=200UL ) ) {
    2307           0 :     fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
    2308           0 :     return;
    2309           0 :   }
    2310             : 
    2311           0 :   if( FD_LIKELY( response._body_len ) ) {
    2312           0 :     ulong response_body_end = response._body_off + response._body_len;
    2313             : 
    2314           0 :     ctx->http->stage_off      = response._body_off;
    2315           0 :     ctx->http->stage_len      = response._body_len;
    2316           0 :     ctx->http->stage_comp_len = 0UL;
    2317             : 
    2318           0 :     int err = fd_http_server_ws_send( ctx->http, ws_conn_id );
    2319           0 :     if( FD_UNLIKELY( ctx->http->stage_off<response_body_end ) ) ctx->http->stage_off = response_body_end;
    2320           0 :     if( FD_UNLIKELY( err ) )
    2321           0 :       fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
    2322           0 :   }
    2323           0 : }
    2324             : 
    2325             : static void
    2326             : privileged_init( fd_topo_t const *      topo,
    2327           0 :                  fd_topo_tile_t const * tile ) {
    2328           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2329             : 
    2330           0 :   fd_http_server_params_t http_params = derive_http_params( tile );
    2331             : 
    2332           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2333           0 :   fd_rpc_tile_t * ctx      = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
    2334           0 :   fd_http_server_t * _http = FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(),   fd_http_server_footprint( http_params ) );
    2335             : 
    2336           0 :   fd_memset( ctx, 0, sizeof(fd_rpc_tile_t) );
    2337             : 
    2338           0 :   if( FD_UNLIKELY( !strcmp( tile->rpc.identity_key_path, "" ) ) )
    2339           0 :     FD_LOG_ERR(( "identity_key_path not set" ));
    2340             : 
    2341           0 :   const uchar * identity_key = fd_keyload_load( tile->rpc.identity_key_path, /* pubkey only: */ 1 );
    2342           0 :   fd_memcpy( ctx->identity_pubkey, identity_key, 32UL );
    2343             : 
    2344           0 :   fd_http_server_callbacks_t callbacks = {
    2345           0 :     .request    = rpc_http_request,
    2346           0 :     .ws_open    = rpc_ws_open,
    2347           0 :     .ws_close   = rpc_ws_close,
    2348           0 :     .ws_message = rpc_ws_message,
    2349           0 :   };
    2350           0 :   ctx->http = fd_http_server_join( fd_http_server_new( _http, http_params, callbacks, ctx ) );
    2351           0 :   fd_http_server_listen( ctx->http, tile->rpc.listen_addr, tile->rpc.listen_port );
    2352           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 ));
    2353           0 : }
    2354             : 
    2355             : static inline fd_rpc_out_t
    2356             : out1( fd_topo_t const *      topo,
    2357             :       fd_topo_tile_t const * tile,
    2358           0 :       char const *           name ) {
    2359           0 :   ulong idx = ULONG_MAX;
    2360             : 
    2361           0 :   for( ulong i=0UL; i<tile->out_cnt; i++ ) {
    2362           0 :     fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
    2363           0 :     if( !strcmp( link->name, name ) ) {
    2364           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 ));
    2365           0 :       idx = i;
    2366           0 :     }
    2367           0 :   }
    2368             : 
    2369           0 :   if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_rpc_out_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
    2370             : 
    2371             : 
    2372           0 :   ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
    2373           0 :   if( FD_UNLIKELY( mtu==0UL ) ) return (fd_rpc_out_t){ .idx = idx, .mem = NULL, .chunk0 = ULONG_MAX, .wmark = ULONG_MAX, .chunk = ULONG_MAX };
    2374             : 
    2375           0 :   void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
    2376           0 :   ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
    2377           0 :   ulong wmark  = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
    2378             : 
    2379           0 :   return (fd_rpc_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
    2380           0 : }
    2381             : 
    2382             : static void
    2383             : unprivileged_init( fd_topo_t const *      topo,
    2384           0 :                    fd_topo_tile_t const * tile ) {
    2385           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2386             : 
    2387           0 :   fd_http_server_params_t http_params = derive_http_params( tile );
    2388             : 
    2389           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2390           0 :   fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t )                                );
    2391           0 :                         FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(),   fd_http_server_footprint( http_params )                );
    2392           0 :   void * _alloc       = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(),         fd_alloc_footprint()                                   );
    2393           0 :   void * _bz2_alloc   = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(),         fd_alloc_footprint()                                   );
    2394           0 :   void * _banks       = FD_SCRATCH_ALLOC_APPEND( l, alignof(bank_info_t),     tile->rpc.max_live_slots*sizeof(bank_info_t)           );
    2395           0 :   void * _nodes_dlist = FD_SCRATCH_ALLOC_APPEND( l, fd_rpc_cluster_node_dlist_align(), fd_rpc_cluster_node_dlist_footprint() );
    2396           0 :   void * _accdb_join  = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(),         fd_accdb_footprint( tile->rpc.max_live_slots )         );
    2397           0 :   void * _ws_sub_vote = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),           http_params.max_ws_connection_cnt*sizeof(ulong)        );
    2398           0 :   void * _ws_sub_slot = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),           http_params.max_ws_connection_cnt*sizeof(ulong)        );
    2399             : 
    2400           0 :   fd_alloc_t * alloc = fd_alloc_join( fd_alloc_new( _alloc, 1UL ), 1UL );
    2401           0 :   FD_TEST( alloc );
    2402           0 :   cJSON_alloc_install( alloc );
    2403             : 
    2404           0 :   ctx->delay_startup = tile->rpc.delay_startup;
    2405           0 :   ctx->ws_subscribers_vote = _ws_sub_vote;
    2406           0 :   ctx->ws_subscribers_vote_cnt = 0UL;
    2407           0 :   ctx->ws_subscribers_slot = _ws_sub_slot;
    2408           0 :   ctx->ws_subscribers_slot_cnt = 0UL;
    2409             : 
    2410           0 :   ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
    2411           0 :   FD_TEST( ctx->keyswitch );
    2412             : 
    2413           0 :   ctx->bz2_alloc = fd_alloc_join( fd_alloc_new( _bz2_alloc, 1UL ), 1UL );
    2414           0 :   FD_TEST( ctx->bz2_alloc );
    2415             : 
    2416           0 :   ctx->next_poll_deadline = fd_tickcount();
    2417             : 
    2418           0 :   ctx->cluster_confirmed_slot = ULONG_MAX;
    2419           0 :   ctx->genesis_tar_bz_sz = ULONG_MAX;
    2420             : 
    2421           0 :   ctx->processed_idx = ULONG_MAX;
    2422           0 :   ctx->confirmed_idx = ULONG_MAX;
    2423           0 :   ctx->finalized_idx = ULONG_MAX;
    2424             : 
    2425           0 :   ctx->cluster_nodes_dlist = fd_rpc_cluster_node_dlist_join( fd_rpc_cluster_node_dlist_new( _nodes_dlist ) );
    2426           0 :   ctx->banks = _banks;
    2427           0 :   ctx->max_live_slots = tile->rpc.max_live_slots;
    2428           0 :   for( ulong i=0UL; i<ctx->max_live_slots; i++ ) ctx->banks[ i ].slot = ULONG_MAX;
    2429             : 
    2430           0 :   FD_TEST( tile->in_cnt<=sizeof( ctx->in )/sizeof( ctx->in[ 0 ] ) );
    2431           0 :   for( ulong i=0; i<tile->in_cnt; i++ ) {
    2432           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    2433           0 :     fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
    2434             : 
    2435           0 :     ctx->in[ i ].mem    = link_wksp->wksp;
    2436           0 :     ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    2437           0 :     ctx->in[ i ].wmark  = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
    2438           0 :     ctx->in[ i ].mtu    = link->mtu;
    2439             : 
    2440           0 :     if     ( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
    2441           0 :     else if( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESI;
    2442           0 :     else if( FD_LIKELY( !strcmp( link->name, "gossip_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
    2443           0 :     else FD_LOG_ERR(( "unexpected link name %s", link->name ));
    2444           0 :   }
    2445             : 
    2446           0 :   *ctx->replay_out = out1( topo, tile, "rpc_replay" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
    2447             : 
    2448             :   /* Read-only join to accdb.  The accdb workspace is mapped
    2449             :      PROT_READ in this tile (see topology); the only writable
    2450             :      external mapping is our private epoch fseq.  fd FD_ACCDB_FD_RO is
    2451             :      the O_RDONLY dup of the accdb data file. */
    2452           0 :   void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->rpc.accdb_obj_id );
    2453           0 :   fd_accdb_shmem_t * accdb_shmem_ro = fd_accdb_shmem_join( _accdb_shmem );
    2454           0 :   FD_TEST( accdb_shmem_ro );
    2455           0 :   ulong * epoch_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->rpc.accdb_epoch_fseq_obj_id ) );
    2456           0 :   FD_TEST( epoch_fseq );
    2457           0 :   ctx->accdb = fd_accdb_join_readonly( _accdb_join, accdb_shmem_ro, epoch_fseq, FD_ACCDB_FD_RO );
    2458           0 :   FD_TEST( ctx->accdb );
    2459             : 
    2460           0 :   fd_histf_join( fd_histf_new( ctx->request_duration, FD_MHIST_SECONDS_MIN( RPC, REQUEST_DURATION_SECONDS ),
    2461           0 :                                                       FD_MHIST_SECONDS_MAX( RPC, REQUEST_DURATION_SECONDS ) ) );
    2462             : 
    2463           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    2464           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    2465           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    2466           0 : }
    2467             : 
    2468             : static ulong
    2469             : populate_allowed_seccomp( fd_topo_t const *      topo,
    2470             :                           fd_topo_tile_t const * tile,
    2471             :                           ulong                  out_cnt,
    2472           0 :                           struct sock_filter *   out ) {
    2473           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2474           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2475           0 :   fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
    2476             : 
    2477           0 :   populate_sock_filter_policy_fd_rpc_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)fd_http_server_fd( ctx->http ), (uint)FD_ACCDB_FD_RO );
    2478           0 :   return sock_filter_policy_fd_rpc_tile_instr_cnt;
    2479           0 : }
    2480             : 
    2481             : static ulong
    2482             : populate_allowed_fds( fd_topo_t const *      topo,
    2483             :                       fd_topo_tile_t const * tile,
    2484             :                       ulong                  out_fds_cnt,
    2485           0 :                       int *                  out_fds ) {
    2486           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2487           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2488           0 :   fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
    2489             : 
    2490           0 :   if( FD_UNLIKELY( out_fds_cnt<4UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    2491             : 
    2492           0 :   ulong out_cnt = 0UL;
    2493           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    2494           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    2495           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    2496           0 :   out_fds[ out_cnt++ ] = fd_http_server_fd( ctx->http ); /* rpc listen socket */
    2497           0 :   out_fds[ out_cnt++ ] = FD_ACCDB_FD_RO; /* accounts db readonly fd */
    2498             : 
    2499           0 :   return out_cnt;
    2500           0 : }
    2501             : 
    2502             : static ulong
    2503             : rlimit_file_cnt( fd_topo_t const *      topo FD_PARAM_UNUSED,
    2504           0 :                  fd_topo_tile_t const * tile ) {
    2505             :   /* pipefd, socket, stderr, logfile, and one spare for new accept() connections */
    2506           0 :   ulong base = 5UL;
    2507           0 :   return base + tile->rpc.max_http_connections + tile->rpc.max_websocket_connections;
    2508           0 : }
    2509             : 
    2510           0 : #define STEM_BURST (1UL)
    2511             : 
    2512             : /* The default STEM_LAZY is based on cr_max, which is the minimum depth
    2513             :    across all output links that have at least one reliable consumer.
    2514             :    RPC has one tiny output link used to release banks, with a
    2515             :    significantly slower line rate than assumed in the formula for the
    2516             :    default STEM_LAZY value.
    2517             : 
    2518             :    Instead, lazy just needs to be frequent enough to relinquish credits
    2519             :    to upstream producers faster than they are exhausted. 384us is a
    2520             :    reasonable default used in many other non-critical tiles. */
    2521           0 : #define STEM_LAZY  (128L*3000L)
    2522             : 
    2523           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_rpc_tile_t
    2524           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_rpc_tile_t)
    2525             : 
    2526           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    2527           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    2528           0 : #define STEM_CALLBACK_BEFORE_CREDIT       before_credit
    2529           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    2530           0 : #define STEM_CALLBACK_RETURNABLE_FRAG     returnable_frag
    2531             : 
    2532             : #include "../../disco/stem/fd_stem.c"
    2533             : 
    2534             : #ifndef FD_TILE_TEST
    2535             : fd_topo_run_tile_t fd_tile_rpc = {
    2536             :   .name                     = "rpc",
    2537             :   .rlimit_file_cnt_fn       = rlimit_file_cnt,
    2538             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    2539             :   .populate_allowed_fds     = populate_allowed_fds,
    2540             :   .scratch_align            = scratch_align,
    2541             :   .scratch_footprint        = scratch_footprint,
    2542             :   .loose_footprint          = loose_footprint,
    2543             :   .privileged_init          = privileged_init,
    2544             :   .unprivileged_init        = unprivileged_init,
    2545             :   .run                      = stem_run,
    2546             : };
    2547             : #endif

Generated by: LCOV version 1.14