LCOV - code coverage report
Current view: top level - discof/rpc - fd_rpc_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 2135 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 456 0.0 %

          Line data    Source code
       1             : #include "../replay/fd_replay_tile.h"
       2             : #include "../genesis/fd_genesi_tile.h"
       3             : #include "../votor/fd_votor_tile.h"
       4             : #include "../../choreo/votor/ag_cert.h"
       5             : 
       6             : #include "../../ballet/base64/fd_base64.h"
       7             : #include "../../ballet/json/fd_jtok.h"
       8             : #include "../../disco/topo/fd_topo.h"
       9             : #include "../../util/pod/fd_pod_format.h"
      10             : #include "../../disco/fd_clock_tile.h"
      11             : #include "../../disco/metrics/fd_metrics.h"
      12             : #include "../../disco/keyguard/fd_keyload.h"
      13             : #include "../../disco/keyguard/fd_keyswitch.h"
      14             : #include "../../disco/waker/fd_waker.h"
      15             : #include "../../disco/metrics/fd_metrics.h"
      16             : #include "../../flamenco/features/fd_features.h"
      17             : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
      18             : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
      19             : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
      20             : #include "../../flamenco/runtime/fd_runtime_const.h"
      21             : #include "../../flamenco/accdb/fd_accdb.h"
      22             : #include "../../flamenco/alpenglow/fd_alpenglow.h"
      23             : #include "../../flamenco/accdb/fd_accdb_shmem.h"
      24             : #include "../../tango/fseq/fd_fseq.h"
      25             : #include "../../flamenco/gossip/fd_gossip_message.h"
      26             : #include "../../flamenco/runtime/program/vote/fd_vote_codec_tmpl.h"
      27             : #include "../../util/net/fd_ip4.h"
      28             : #include "../../waltz/http/fd_http_server.h"
      29             : #include "../../waltz/http/fd_http_server_private.h"
      30             : #include "../../waltz/http/fd_url.h"
      31             : 
      32             : #include <stddef.h>
      33             : #include <poll.h>
      34             : #include <sys/socket.h>
      35             : #include <string.h>
      36             : 
      37             : #define ZSTD_STATIC_LINKING_ONLY
      38             : #include <zstd.h>
      39             : 
      40             : #include "../../util/archive/fd_tar.h"
      41             : #include "../../third_party/bzip2/bzlib.h"
      42             : 
      43             : #include <linux/futex.h>
      44             : #include "generated/fd_rpc_tile_seccomp.h"
      45             : 
      46           0 : #define FD_RPC_AGAVE_API_VERSION "4.3.0-rc.0"
      47             : 
      48           0 : #define FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN       8192UL
      49             : 
      50             : /* FD_RPC_ID_MAX_SZ is the longest JSON-RPC request id (as raw JSON
      51             :    text) that is echoed back in responses. */
      52             : #define FD_RPC_ID_MAX_SZ                         (256UL)
      53           0 : #define FD_HTTP_SERVER_RPC_MAX_WS_SEND_FRAME_CNT  128UL
      54             : 
      55           0 : #define IN_KIND_REPLAY      (0)
      56           0 : #define IN_KIND_GENESI      (1)
      57           0 : #define IN_KIND_GOSSIP_OUT  (2)
      58           0 : #define IN_KIND_EPOCH       (5)
      59           0 : #define IN_KIND_VOTOR       (6)
      60             : 
      61             : /* From bzip2 docs:
      62             :       To guarantee that the compressed data will fit in its buffer,
      63             :       allocate an output buffer of size 1% larger than the uncompressed
      64             :       data, plus six hundred extra bytes.
      65             : */
      66             : FD_FN_CONST static inline ulong
      67           0 : fd_rpc_genesis_tar_max_sz( ulong max_message_size ) {
      68           0 :   return max_message_size + 4UL*FD_TAR_BLOCK_SZ;
      69           0 : }
      70             : 
      71             : FD_FN_CONST static inline ulong
      72           0 : fd_rpc_genesis_tar_bz_max_sz( ulong max_message_size ) {
      73           0 :   ulong tar_max_sz = fd_rpc_genesis_tar_max_sz( max_message_size );
      74           0 :   return tar_max_sz + ((tar_max_sz + 100UL - 1UL) / 100UL) + 600UL;
      75           0 : }
      76             : 
      77           0 : #define FD_RPC_BASE58_ENCODED_128_LEN (175UL) /* ceil(128*log58(256)) */
      78           0 : #define FD_RPC_ZSTD_LEVEL 1
      79             : /* levels 4-15 are compiled out of libfd_zstd (third_party/zstd/Local.mk) */
      80             : FD_STATIC_ASSERT( FD_RPC_ZSTD_LEVEL<=3 || FD_RPC_ZSTD_LEVEL>=16, zstd_level );
      81             : 
      82             : #define FD_RPC_COMMITMENT_PROCESSED (0)
      83             : #define FD_RPC_COMMITMENT_CONFIRMED (1)
      84             : #define FD_RPC_COMMITMENT_FINALIZED (2)
      85             : 
      86             : #define FD_RPC_ENCODING_BASE58      (0)
      87             : #define FD_RPC_ENCODING_BASE64      (1)
      88             : #define FD_RPC_ENCODING_BASE64_ZSTD (2)
      89             : #define FD_RPC_ENCODING_BINARY      (3)
      90             : #define FD_RPC_ENCODING_JSON_PARSED (4)
      91             : 
      92           0 : #define FD_RPC_HEALTH_STATUS_OK      (0)
      93           0 : #define FD_RPC_HEALTH_STATUS_BEHIND  (1)
      94           0 : #define FD_RPC_HEALTH_STATUS_UNKNOWN (2)
      95             : 
      96             : /* Matches Agave's default health_check_slot_distance. */
      97             : #define FD_RPC_HEALTH_CHECK_SLOT_DISTANCE (128UL)
      98             : 
      99             : #define FD_RPC_METHOD_GET_ACCOUNT_INFO                       ( 0)
     100             : #define FD_RPC_METHOD_GET_BALANCE                            ( 1)
     101             : #define FD_RPC_METHOD_GET_BLOCK                              ( 2)
     102             : #define FD_RPC_METHOD_GET_BLOCK_COMMITMENT                   ( 3)
     103             : #define FD_RPC_METHOD_GET_BLOCK_HEIGHT                       ( 4)
     104             : #define FD_RPC_METHOD_GET_BLOCK_PRODUCTION                   ( 5)
     105             : #define FD_RPC_METHOD_GET_BLOCKS                             ( 6)
     106             : #define FD_RPC_METHOD_GET_BLOCKS_WITH_LIMIT                  ( 7)
     107             : #define FD_RPC_METHOD_GET_BLOCK_TIME                         ( 8)
     108             : #define FD_RPC_METHOD_GET_CLUSTER_NODES                      ( 9)
     109             : #define FD_RPC_METHOD_GET_EPOCH_INFO                         (10)
     110             : #define FD_RPC_METHOD_GET_EPOCH_SCHEDULE                     (11)
     111             : #define FD_RPC_METHOD_GET_FEE_FOR_MESSAGE                    (12)
     112             : #define FD_RPC_METHOD_GET_FIRST_AVAILABLE_BLOCK              (13)
     113             : #define FD_RPC_METHOD_GET_GENESIS_HASH                       (14)
     114             : #define FD_RPC_METHOD_GET_HEALTH                             (15)
     115             : #define FD_RPC_METHOD_GET_HIGHEST_SNAPSHOT_SLOT              (16)
     116             : #define FD_RPC_METHOD_GET_IDENTITY                           (17)
     117             : #define FD_RPC_METHOD_GET_INFLATION_GOVERNOR                 (18)
     118             : #define FD_RPC_METHOD_GET_INFLATION_RATE                     (19)
     119             : #define FD_RPC_METHOD_GET_INFLATION_REWARD                   (20)
     120             : #define FD_RPC_METHOD_GET_LARGEST_ACCOUNTS                   (21)
     121             : #define FD_RPC_METHOD_GET_LATEST_BLOCKHASH                   (22)
     122             : #define FD_RPC_METHOD_GET_LEADER_SCHEDULE                    (23)
     123             : #define FD_RPC_METHOD_GET_MAX_RETRANSMIT_SLOT                (24)
     124             : #define FD_RPC_METHOD_GET_MAX_SHRED_INSERT_SLOT              (25)
     125             : #define FD_RPC_METHOD_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION (26)
     126             : #define FD_RPC_METHOD_GET_MULTIPLE_ACCOUNTS                  (27)
     127             : #define FD_RPC_METHOD_GET_PROGRAM_ACCOUNTS                   (28)
     128             : #define FD_RPC_METHOD_GET_RECENT_PERFORMANCE_SAMPLES         (29)
     129             : #define FD_RPC_METHOD_GET_RECENT_PRIORITIZATION_FEES         (30)
     130             : #define FD_RPC_METHOD_GET_SIGNATURES_FOR_ADDRESS             (31)
     131             : #define FD_RPC_METHOD_GET_SIGNATURE_STATUSES                 (32)
     132             : #define FD_RPC_METHOD_GET_SLOT                               (33)
     133             : #define FD_RPC_METHOD_GET_SLOT_LEADER                        (34)
     134             : #define FD_RPC_METHOD_GET_SLOT_LEADERS                       (35)
     135             : #define FD_RPC_METHOD_GET_STAKE_MINIMUM_DELEGATION           (36)
     136             : #define FD_RPC_METHOD_GET_SUPPLY                             (37)
     137             : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNT_BALANCE              (38)
     138             : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_DELEGATE         (39)
     139             : #define FD_RPC_METHOD_GET_TOKEN_ACCOUNTS_BY_OWNER            (40)
     140             : #define FD_RPC_METHOD_GET_TOKEN_LARGEST_ACCOUNTS             (41)
     141             : #define FD_RPC_METHOD_GET_TOKEN_SUPPLY                       (42)
     142             : #define FD_RPC_METHOD_GET_TRANSACTION                        (43)
     143             : #define FD_RPC_METHOD_GET_TRANSACTION_COUNT                  (44)
     144             : #define FD_RPC_METHOD_GET_VERSION                            (45)
     145             : #define FD_RPC_METHOD_GET_VOTE_ACCOUNTS                      (46)
     146             : #define FD_RPC_METHOD_IS_BLOCKHASH_VALID                     (47)
     147             : #define FD_RPC_METHOD_MINIMUM_LEDGER_SLOT                    (48)
     148             : #define FD_RPC_METHOD_REQUEST_AIRDROP                        (49)
     149             : #define FD_RPC_METHOD_SEND_TRANSACTION                       (50)
     150             : #define FD_RPC_METHOD_SIMULATE_TRANSACTION                   (51)
     151             : 
     152             : // Keep in sync with https://github.com/solana-labs/solana-web3.js/blob/master/src/errors.ts
     153             : // and https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/custom_error.rs
     154             : #define FD_RPC_ERROR_BLOCK_CLEANED_UP                            (-32001)
     155             : #define FD_RPC_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE          (-32002)
     156             : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE  (-32003)
     157             : #define FD_RPC_ERROR_BLOCK_NOT_AVAILABLE                         (-32004)
     158             : #define FD_RPC_ERROR_NODE_UNHEALTHY                              (-32005)
     159             : #define FD_RPC_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE (-32006)
     160             : #define FD_RPC_ERROR_SLOT_SKIPPED                                (-32007)
     161             : #define FD_RPC_ERROR_NO_SNAPSHOT                                 (-32008)
     162             : #define FD_RPC_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED              (-32009)
     163             : #define FD_RPC_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX           (-32010)
     164             : #define FD_RPC_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE           (-32011)
     165             : #define FD_RPC_ROR                                               (-32012)
     166             : #define FD_RPC_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH          (-32013)
     167             : #define FD_RPC_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET              (-32014)
     168             : #define FD_RPC_ERROR_UNSUPPORTED_TRANSACTION_VERSION             (-32015)
     169             : #define FD_RPC_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED                (-32016)
     170             : #define FD_RPC_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE                 (-32017)
     171             : #define FD_RPC_ERROR_SLOT_NOT_EPOCH_BOUNDARY                     (-32018)
     172             : #define FD_RPC_ERROR_LONG_TERM_STORAGE_UNREACHABLE               (-32019)
     173             : 
     174             : /* fd_rpc_val_t is a JSON value captured from a request as a raw input
     175             :    slice plus its kind.  Kinds mirror the serde names Agave uses in its
     176             :    error messages.  INT is a number literal that fits in a ulong or a
     177             :    long, any other number literal is FLOAT.  NONE denotes an absent
     178             :    value (missing member or array element). */
     179             : 
     180           0 : #define FD_RPC_VAL_NONE  (0)
     181           0 : #define FD_RPC_VAL_STR   (1)
     182           0 : #define FD_RPC_VAL_MAP   (2)
     183           0 : #define FD_RPC_VAL_SEQ   (3)
     184           0 : #define FD_RPC_VAL_BOOL  (4)
     185           0 : #define FD_RPC_VAL_FLOAT (5)
     186           0 : #define FD_RPC_VAL_INT   (6)
     187           0 : #define FD_RPC_VAL_NULL  (7)
     188             : 
     189             : struct fd_rpc_val {
     190             :   int           kind;
     191             :   char const *  raw;
     192             :   ulong         raw_sz;
     193             :   fd_jtok_str_t str; /* STR only: view of the contents between the quotes */
     194             :   int           neg; /* INT only: value is negative */
     195             :   ulong         u;   /* INT only: value if !neg */
     196             : };
     197             : 
     198             : typedef struct fd_rpc_val fd_rpc_val_t;
     199             : 
     200             : static void
     201             : fd_rpc_val_read( fd_jtok_t *    j,
     202           0 :                  fd_rpc_val_t * v ) {
     203           0 :   *v = (fd_rpc_val_t){ .kind = FD_RPC_VAL_NONE };
     204           0 :   int kind = fd_jtok_peek( j );
     205           0 :   char const * raw;
     206           0 :   ulong        raw_sz;
     207           0 :   fd_jtok_raw( j, &raw, &raw_sz );
     208           0 :   if( FD_UNLIKELY( fd_jtok_err( j ) ) ) return;
     209           0 :   v->raw    = raw;
     210           0 :   v->raw_sz = raw_sz;
     211           0 :   switch( kind ) {
     212           0 :   case FD_JTOK_OBJ:  v->kind = FD_RPC_VAL_MAP;   break;
     213           0 :   case FD_JTOK_ARR:  v->kind = FD_RPC_VAL_SEQ;   break;
     214           0 :   case FD_JTOK_BOOL: v->kind = FD_RPC_VAL_BOOL;  break;
     215           0 :   case FD_JTOK_NULL: v->kind = FD_RPC_VAL_NULL;  break;
     216           0 :   case FD_JTOK_NUM:  v->kind = FD_RPC_VAL_FLOAT; break;
     217           0 :   case FD_JTOK_STR:
     218           0 :     v->kind = FD_RPC_VAL_STR;
     219           0 :     v->str  = (fd_jtok_str_t){ .ptr = raw+1, .sz = raw_sz-2UL }; /* raw is the token including both quotes */
     220           0 :     break;
     221           0 :   case FD_JTOK_INT: {
     222           0 :     fd_jtok_t n[1];
     223           0 :     ulong u;
     224           0 :     fd_jtok_ulong( fd_jtok_init( n, raw, raw_sz ), &u );
     225           0 :     if( FD_LIKELY( !fd_jtok_err( n ) ) ) { v->kind = FD_RPC_VAL_INT; v->u = u; break; }
     226           0 :     long l;
     227           0 :     fd_jtok_long( fd_jtok_init( n, raw, raw_sz ), &l );
     228           0 :     if( FD_LIKELY( !fd_jtok_err( n ) ) ) { v->kind = FD_RPC_VAL_INT; v->neg = l<0L; v->u = l<0L ? 0UL : (ulong)l; break; }
     229           0 :     v->kind = FD_RPC_VAL_FLOAT;
     230           0 :     break;
     231           0 :   }
     232           0 :   default: break;
     233           0 :   }
     234           0 : }
     235             : 
     236             : /* fd_rpc_val_cstr decodes a STR value into the out_sz byte buffer at
     237             :    out.  Returns 1 on success, 0 if v is not a string or does not fit
     238             :    (out is then an empty string). */
     239             : 
     240             : static int
     241             : fd_rpc_val_cstr( fd_rpc_val_t const * v,
     242             :                  char *               out,
     243           0 :                  ulong                out_sz ) {
     244           0 :   out[0] = '\0';
     245           0 :   if( v->kind!=FD_RPC_VAL_STR ) return 0;
     246           0 :   fd_jtok_t j[1];
     247           0 :   fd_jtok_cstr( fd_jtok_init( j, v->raw, v->raw_sz ), out, out_sz );
     248           0 :   return !fd_jtok_err( j );
     249           0 : }
     250             : 
     251             : /* fd_rpc_params_t is the "params" member of a request.  kind is the
     252             :    kind of the params value itself.  For a SEQ, cnt is the element
     253             :    count and v holds the first FD_RPC_PARAM_MAX elements (NONE beyond
     254             :    cnt). */
     255             : 
     256           0 : #define FD_RPC_PARAM_MAX (2UL)
     257             : 
     258             : struct fd_rpc_params {
     259             :   int          kind;
     260             :   ulong        cnt;
     261             :   fd_rpc_val_t v[ FD_RPC_PARAM_MAX ];
     262             : };
     263             : 
     264             : typedef struct fd_rpc_params fd_rpc_params_t;
     265             : 
     266             : static void
     267             : fd_rpc_params_read( fd_rpc_val_t const * params,
     268           0 :                     fd_rpc_params_t *    out ) {
     269           0 :   out->kind = params->kind;
     270           0 :   out->cnt  = 0UL;
     271           0 :   for( ulong i=0UL; i<FD_RPC_PARAM_MAX; i++ ) out->v[ i ].kind = FD_RPC_VAL_NONE;
     272           0 :   if( params->kind!=FD_RPC_VAL_SEQ ) return;
     273           0 :   fd_jtok_t j[1];
     274           0 :   fd_jtok_arr_enter( fd_jtok_init( j, params->raw, params->raw_sz ) );
     275           0 :   while( fd_jtok_arr_next( j ) ) {
     276           0 :     if( out->cnt<FD_RPC_PARAM_MAX ) fd_rpc_val_read( j, &out->v[ out->cnt ] );
     277           0 :     out->cnt++;
     278           0 :   }
     279           0 : }
     280             : 
     281             : /* fd_rpc_config_t holds the members of a config object that any
     282             :    handler looks at.  For duplicate members the first occurrence wins,
     283             :    unknown members are skipped. */
     284             : 
     285             : struct fd_rpc_config {
     286             :   fd_rpc_val_t commitment;
     287             :   fd_rpc_val_t encoding;
     288             :   fd_rpc_val_t data_slice;
     289             :   fd_rpc_val_t min_context_slot;
     290             :   fd_rpc_val_t identity;
     291             : };
     292             : 
     293             : typedef struct fd_rpc_config fd_rpc_config_t;
     294             : 
     295             : static void
     296             : fd_rpc_config_read( fd_rpc_val_t const * config,
     297           0 :                     fd_rpc_config_t *    out ) {
     298           0 :   out->commitment.kind       = FD_RPC_VAL_NONE;
     299           0 :   out->encoding.kind         = FD_RPC_VAL_NONE;
     300           0 :   out->data_slice.kind       = FD_RPC_VAL_NONE;
     301           0 :   out->min_context_slot.kind = FD_RPC_VAL_NONE;
     302           0 :   out->identity.kind         = FD_RPC_VAL_NONE;
     303           0 :   if( config->kind!=FD_RPC_VAL_MAP ) return;
     304           0 :   fd_jtok_t j[1];
     305           0 :   fd_jtok_str_t key;
     306           0 :   fd_jtok_obj_enter( fd_jtok_init( j, config->raw, config->raw_sz ) );
     307           0 :   while( fd_jtok_obj_next( j, &key ) ) {
     308           0 :     fd_rpc_val_t * member;
     309           0 :     if(      fd_jtok_str_eq( &key, "commitment"     ) ) member = &out->commitment;
     310           0 :     else if( fd_jtok_str_eq( &key, "encoding"       ) ) member = &out->encoding;
     311           0 :     else if( fd_jtok_str_eq( &key, "dataSlice"      ) ) member = &out->data_slice;
     312           0 :     else if( fd_jtok_str_eq( &key, "minContextSlot" ) ) member = &out->min_context_slot;
     313           0 :     else if( fd_jtok_str_eq( &key, "identity"       ) ) member = &out->identity;
     314           0 :     else continue;
     315           0 :     if( member->kind==FD_RPC_VAL_NONE ) fd_rpc_val_read( j, member );
     316           0 :   }
     317           0 : }
     318             : 
     319             : static fd_http_server_params_t
     320           0 : derive_http_params( fd_topo_tile_t const * tile ) {
     321           0 :   return (fd_http_server_params_t) {
     322           0 :     .max_connection_cnt    = tile->rpc.max_http_connections,
     323           0 :     .max_ws_connection_cnt = tile->rpc.max_websocket_connections,
     324           0 :     .max_request_len       = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
     325           0 :     .max_ws_recv_frame_len = FD_HTTP_SERVER_RPC_MAX_REQUEST_LEN,
     326           0 :     .max_ws_send_frame_cnt = FD_HTTP_SERVER_RPC_MAX_WS_SEND_FRAME_CNT,
     327           0 :     .outgoing_buffer_sz    = tile->rpc.send_buffer_size_mb * (1UL<<20UL),
     328           0 :     .compress_websocket    = 0,
     329           0 :   };
     330           0 : }
     331             : 
     332             : struct fd_rpc_in {
     333             :   fd_wksp_t * mem;
     334             :   ulong       chunk0;
     335             :   ulong       wmark;
     336             :   ulong       mtu;
     337             : };
     338             : 
     339             : typedef struct fd_rpc_in fd_rpc_in_t;
     340             : 
     341             : struct fd_rpc_out {
     342             :   ulong       idx;
     343             :   fd_wksp_t * mem;
     344             :   ulong       chunk0;
     345             :   ulong       wmark;
     346             :   ulong       chunk;
     347             : };
     348             : 
     349             : typedef struct fd_rpc_out fd_rpc_out_t;
     350             : 
     351             : struct bank_info {
     352             :   ulong slot; /* default ULONG_MAX */
     353             :   ulong bank_idx;
     354             :   fd_accdb_fork_id_t accdb_fork_id;
     355             :   ulong epoch;
     356             :   ulong slot_in_epoch;
     357             :   ulong slots_per_epoch;
     358             : 
     359             :   ulong transaction_count;
     360             :   uchar block_hash[ 32 ];
     361             :   ulong block_height;
     362             : 
     363             :   struct {
     364             :     double initial;
     365             :     double terminal;
     366             :     double taper;
     367             :     double foundation;
     368             :     double foundation_term;
     369             :   } inflation;
     370             : 
     371             :   struct {
     372             :     ulong lamports_per_uint8_year;
     373             :     double exemption_threshold;
     374             :     uchar burn_percent;
     375             :   } rent;
     376             : };
     377             : 
     378             : typedef struct bank_info bank_info_t;
     379             : 
     380             : struct fd_rpc_cluster_node {
     381             :   int valid;
     382             :   fd_pubkey_t identity;
     383             :   fd_gossip_contact_info_t ci[ 1 ];
     384             : 
     385             :   struct { ulong prev, next; } dlist;
     386             : };
     387             : 
     388             : typedef struct fd_rpc_cluster_node fd_rpc_cluster_node_t;
     389             : 
     390             : #define DLIST_NAME  fd_rpc_cluster_node_dlist
     391             : #define DLIST_ELE_T fd_rpc_cluster_node_t
     392           0 : #define DLIST_PREV dlist.prev
     393           0 : #define DLIST_NEXT dlist.next
     394             : #include "../../util/tmpl/fd_dlist.c"
     395             : 
     396             : /* fd_rpc_gls_pair_t associates a leader-schedule rotation index with the
     397             :    identity pubkey that leads it, used to group getLeaderSchedule's
     398             :    result by identity (see the handler). */
     399             : struct fd_rpc_gls_pair {
     400             :   fd_pubkey_t id_key; /* leader identity */
     401             :   uint        rot;    /* rotation index into the epoch's leader schedule */
     402             : };
     403             : typedef struct fd_rpc_gls_pair fd_rpc_gls_pair_t;
     404             : 
     405           0 : #define FD_RPC_MLEADERS_CNT       (5UL)
     406           0 : #define FD_RPC_MLEADERS_EPOCH_CNT (FD_RPC_MLEADERS_CNT*MULTI_EPOCH_LEADERS_EPOCH_CNT)
     407             : 
     408             : static inline void
     409           0 : fd_rpc_mleaders_init( fd_multi_epoch_leaders_t * mleaders ) {
     410           0 :   for( ulong i=0UL; i<FD_RPC_MLEADERS_CNT; i++ ) FD_TEST( fd_multi_epoch_leaders_new( &mleaders[ i ] ) );
     411           0 : }
     412             : 
     413             : static inline void
     414             : fd_rpc_mleaders_ingest( fd_multi_epoch_leaders_t *  mleaders,
     415           0 :                         fd_epoch_info_msg_t const * msg ) {
     416           0 :   ulong idx = (msg->epoch / MULTI_EPOCH_LEADERS_EPOCH_CNT) % FD_RPC_MLEADERS_CNT;
     417           0 :   fd_multi_epoch_leaders_epoch_msg_init( &mleaders[ idx ], msg );
     418           0 :   fd_multi_epoch_leaders_epoch_msg_fini( &mleaders[ idx ] );
     419           0 : }
     420             : 
     421             : FD_FN_PURE static inline fd_epoch_leaders_t const *
     422             : fd_rpc_mleaders_get_lsched_for_slot( fd_multi_epoch_leaders_t const * mleaders,
     423           0 :                                      ulong                            slot ) {
     424           0 :   for( ulong i=0UL; i<FD_RPC_MLEADERS_CNT; i++ ) {
     425           0 :     fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( &mleaders[ i ], slot );
     426           0 :     if( FD_LIKELY( lsched ) ) return lsched;
     427           0 :   }
     428           0 :   return NULL;
     429           0 : }
     430             : 
     431             : FD_FN_PURE static inline fd_pubkey_t const *
     432             : fd_rpc_mleaders_get_leader_for_slot( fd_multi_epoch_leaders_t const * mleaders,
     433           0 :                                      ulong                            slot ) {
     434           0 :   fd_epoch_leaders_t const * lsched = fd_rpc_mleaders_get_lsched_for_slot( mleaders, slot );
     435           0 :   if( FD_UNLIKELY( !lsched ) ) return NULL;
     436           0 :   return fd_epoch_leaders_get( lsched, slot );
     437           0 : }
     438             : 
     439             : struct fd_rpc_tile {
     440             :   int delay_startup;
     441             :   int alpenglow;
     442             :   int waker_rearm_pending;
     443             : 
     444             :   fd_http_server_t * http;
     445             : 
     446             :   ulong * ws_subscribers_vote;
     447             :   ulong   ws_subscribers_vote_cnt;
     448             :   ulong * ws_subscribers_slot;
     449             :   ulong   ws_subscribers_slot_cnt;
     450             : 
     451             :   fd_rpc_cluster_node_dlist_t * cluster_nodes_dlist;
     452             :   fd_rpc_cluster_node_t cluster_nodes[ FD_CONTACT_INFO_TABLE_SIZE ];
     453             : 
     454             :   bank_info_t * banks;
     455             :   ulong         max_live_slots;
     456             : 
     457             :   fd_accdb_t * accdb;
     458             : 
     459             :   ulong cluster_confirmed_slot;
     460             : 
     461             :   /* Highest slot relayed to turbine (Agave MaxSlots::retransmit),
     462             :      one fseq per shred tile, written by shred */
     463             :   ulong         shred_rtx_cnt;
     464             :   ulong const * shred_rtx[ FD_TOPO_MAX_TILES ];
     465             : 
     466             :   /* Address of the account holding the alpenglow genesis certificate,
     467             :      for getAgGenesisCert. */
     468             :   fd_pubkey_t ag_genesis_cert_addr[ 1 ];
     469             : 
     470             :   ulong processed_idx;
     471             :   ulong confirmed_idx;
     472             :   ulong finalized_idx;
     473             : 
     474             :   int has_genesis_hash;
     475             :   fd_hash_t genesis_hash[ 1 ];
     476             : 
     477             :   ulong genesis_tar_bz_sz;
     478             :   ulong genesis_max_message_size;
     479             :   ulong genesis_tar_max_sz;
     480             :   ulong genesis_tar_bz_max_sz;
     481             :   uchar * genesis_tar;
     482             :   uchar * genesis_tar_bz;
     483             : 
     484             :   fd_alloc_t * bz2_alloc;
     485             : 
     486             :   fd_clock_tile_t clock[1];
     487             :   ulong in_cnt;
     488             :   ulong idle_cnt;
     489             : 
     490             :   ulong   waker_client_idx;
     491             :   ulong * waker_fseq;
     492             : 
     493             :   fd_keyswitch_t * keyswitch;
     494             :   uchar identity_pubkey[ 32UL ];
     495             : 
     496             :   int in_kind[ 64UL ];
     497             :   fd_rpc_in_t in[ 64UL ];
     498             : 
     499             :   fd_rpc_out_t replay_out[1];
     500             : 
     501             :   fd_histf_t request_duration[ 1 ];
     502             : 
     503             :   fd_multi_epoch_leaders_t mleaders[ FD_RPC_MLEADERS_CNT ];
     504             :   fd_epoch_schedule_t      epoch_schedule;
     505             :   int                      has_epoch_schedule;
     506             : 
     507             :   ZSTD_CCtx * zstd_cctx;
     508             :   uchar compress_buf[ ZSTD_COMPRESSBOUND( FD_RUNTIME_ACC_SZ_MAX ) ];
     509             : 
     510             :   /* Redirect to snapshot server */
     511             :   int    snapshot_server_enabled;
     512             :   char   snapshot_server_url[ FD_URL_MAX ];
     513             : 
     514             :   union {
     515             :     /* Scratch used by getLeaderSchedule to group the epoch's leader
     516             :        schedule by identity. */
     517             :     fd_rpc_gls_pair_t gls_pairs[ (MAX_SLOTS_PER_EPOCH + FD_EPOCH_SLOTS_PER_ROTATION - 1UL) / FD_EPOCH_SLOTS_PER_ROTATION ];
     518             : 
     519             :     /* Scratch buffer for fd_accdb_read_one_nocache: holds the account
     520             :        data bytes returned by the readonly accdb path.  Sized to the
     521             :        runtime account data maximum.  Must not be in accdb shmem. */
     522             :     uchar accdb_data_buf[ FD_RUNTIME_ACC_SZ_MAX ];
     523             :   } scratch;
     524             : };
     525             : 
     526             : typedef struct fd_rpc_tile fd_rpc_tile_t;
     527             : 
     528             : static void
     529             : fd_rpc_ws_subscriber_vote_add( fd_rpc_tile_t * ctx,
     530           0 :                                ulong           ws_conn_id ) {
     531           0 :   for( ulong i=0UL; i<ctx->ws_subscribers_vote_cnt; i++ )
     532           0 :     if( FD_UNLIKELY( ctx->ws_subscribers_vote[ i ]==ws_conn_id ) ) return;
     533             : 
     534           0 :   FD_TEST( ctx->ws_subscribers_vote_cnt<ctx->http->max_ws_conns );
     535           0 :   ctx->ws_subscribers_vote[ ctx->ws_subscribers_vote_cnt++ ] = ws_conn_id;
     536           0 : }
     537             : 
     538             : static int
     539             : fd_rpc_ws_subscriber_vote_remove( fd_rpc_tile_t * ctx,
     540           0 :                                   ulong           ws_conn_id ) {
     541           0 :   for( ulong idx=0UL; idx<ctx->ws_subscribers_vote_cnt; idx++ ) {
     542           0 :     if( FD_LIKELY( ctx->ws_subscribers_vote[ idx ]!=ws_conn_id ) ) continue;
     543             : 
     544           0 :     ctx->ws_subscribers_vote_cnt--;
     545           0 :     ctx->ws_subscribers_vote[ idx ] = ctx->ws_subscribers_vote[ ctx->ws_subscribers_vote_cnt ];
     546           0 :     return 1;
     547           0 :   }
     548             : 
     549           0 :   return 0;
     550           0 : }
     551             : 
     552             : static void
     553             : fd_rpc_ws_subscriber_slot_add( fd_rpc_tile_t * ctx,
     554           0 :                                ulong           ws_conn_id ) {
     555           0 :   for( ulong i=0UL; i<ctx->ws_subscribers_slot_cnt; i++ )
     556           0 :     if( FD_UNLIKELY( ctx->ws_subscribers_slot[ i ]==ws_conn_id ) ) return;
     557             : 
     558           0 :   FD_TEST( ctx->ws_subscribers_slot_cnt<ctx->http->max_ws_conns );
     559           0 :   ctx->ws_subscribers_slot[ ctx->ws_subscribers_slot_cnt++ ] = ws_conn_id;
     560           0 : }
     561             : 
     562             : static int
     563             : fd_rpc_ws_subscriber_slot_remove( fd_rpc_tile_t * ctx,
     564           0 :                                   ulong           ws_conn_id ) {
     565           0 :   for( ulong idx=0UL; idx<ctx->ws_subscribers_slot_cnt; idx++ ) {
     566           0 :     if( FD_LIKELY( ctx->ws_subscribers_slot[ idx ]!=ws_conn_id ) ) continue;
     567             : 
     568           0 :     ctx->ws_subscribers_slot_cnt--;
     569           0 :     ctx->ws_subscribers_slot[ idx ] = ctx->ws_subscribers_slot[ ctx->ws_subscribers_slot_cnt ];
     570           0 :     return 1;
     571           0 :   }
     572             : 
     573           0 :   return 0;
     574           0 : }
     575             : 
     576             : static void *
     577             : bz2_malloc( void * opaque,
     578             :             int    items,
     579           0 :             int    size ) {
     580           0 :   fd_alloc_t * alloc = (fd_alloc_t *)opaque;
     581             : 
     582           0 :   if( FD_UNLIKELY( items<=0 || size<=0 ) ) {
     583           0 :     FD_LOG_WARNING(( "bz2_malloc: invalid items=%d or size=%d", items, size ));
     584           0 :     return NULL;
     585           0 :   }
     586           0 :   void * result = fd_alloc_malloc( alloc, alignof(max_align_t), (ulong)items*(ulong)size );
     587           0 :   if( FD_UNLIKELY( !result ) ) return NULL;
     588           0 :   return result;
     589           0 : }
     590             : 
     591             : static void
     592             : bz2_free( void * opaque,
     593           0 :           void * addr ) {
     594           0 :   fd_alloc_t * alloc = (fd_alloc_t *)opaque;
     595             : 
     596           0 :   if( FD_UNLIKELY( !addr ) ) return;
     597           0 :   fd_alloc_free( alloc, addr );
     598           0 : }
     599             : 
     600             : static inline ulong
     601             : fd_rpc_file_as_tarball( fd_rpc_tile_t * ctx,
     602             :                         char const *    filename_cstr,
     603             :                         uchar const *   data,
     604             :                         ulong           data_sz,
     605             :                         uchar *         scratch,
     606             :                         ulong           scratch_sz,
     607             :                         uchar *         out,
     608           0 :                         ulong           out_sz ) {
     609           0 :   ulong padding_sz = 2UL*FD_TAR_BLOCK_SZ;
     610           0 :   if( FD_LIKELY( data_sz % FD_TAR_BLOCK_SZ ) ) padding_sz += FD_TAR_BLOCK_SZ - (data_sz % FD_TAR_BLOCK_SZ);
     611             : 
     612           0 :   if( FD_UNLIKELY( data_sz>ctx->genesis_max_message_size ) ) {
     613           0 :     FD_LOG_ERR(( "Genesis data exceeds maximum size (data_sz=%lu max=%lu)", data_sz, ctx->genesis_max_message_size ));
     614           0 :   }
     615           0 :   ulong tar_sz = sizeof(fd_tar_meta_t) + data_sz + padding_sz;
     616           0 :   if( FD_UNLIKELY( tar_sz>scratch_sz ) ) {
     617           0 :     FD_LOG_WARNING(( "tar_sz exceeds scratch_sz (tar_sz=%lu scratch_sz=%lu)", tar_sz, scratch_sz ));
     618           0 :     return ULONG_MAX;
     619           0 :   }
     620             : 
     621           0 :   fd_tar_meta_init_file_default( (fd_tar_meta_t *)scratch, filename_cstr, data_sz, fd_log_wallclock() );
     622           0 :   fd_memcpy( scratch+sizeof(fd_tar_meta_t), data, data_sz );
     623           0 :   memset( scratch+sizeof(fd_tar_meta_t)+data_sz, 0, padding_sz );
     624             : 
     625             :   /* NOTE: Agave's genesis.tar also contains a `rocksdb` folder */
     626             : 
     627           0 :   bz_stream bzstrm = {0};
     628           0 :   bzstrm.bzalloc = bz2_malloc;
     629           0 :   bzstrm.bzfree  = bz2_free;
     630           0 :   bzstrm.opaque  = ctx->bz2_alloc;
     631           0 :   int bzerr = BZ2_bzCompressInit( &bzstrm, 1, 0, 0 );
     632           0 :   if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzCompressInit() failed (%d)", bzerr ));
     633             : 
     634           0 :   ulong tar_bz_sz = out_sz;
     635             : 
     636           0 :   bzstrm.next_in   = (char *)scratch;
     637           0 :   bzstrm.avail_in  = (uint)tar_sz;
     638           0 :   bzstrm.next_out  = (char *)out;
     639           0 :   bzstrm.avail_out = (uint)tar_bz_sz;
     640             : 
     641           0 :   for(;;) {
     642           0 :     bzerr = BZ2_bzCompress( &bzstrm, BZ_FINISH );
     643           0 :     if( FD_LIKELY( bzerr==BZ_STREAM_END ) ) break;
     644           0 :     if( FD_UNLIKELY( bzerr>=0 ) ) continue;
     645           0 :     FD_LOG_ERR(( "BZ2_bzCompress(_, BZ_FINISH) failed (%d)", bzerr ));
     646           0 :   }
     647             : 
     648           0 :   tar_bz_sz -= (ulong)bzstrm.avail_out;
     649             : 
     650           0 :   bzerr = BZ2_bzCompressEnd( &bzstrm );
     651           0 :   if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzCompressEnd() failed (%d)", bzerr ));
     652             : 
     653           0 :   return tar_bz_sz;
     654           0 : }
     655             : 
     656             : FD_FN_CONST static inline ulong
     657           0 : scratch_align( void ) {
     658           0 :   ulong a = alignof( fd_rpc_tile_t );
     659           0 :   a = fd_ulong_max( a, fd_http_server_align() );
     660           0 :   a = fd_ulong_max( a, fd_alloc_align() );
     661           0 :   a = fd_ulong_max( a, alignof(bank_info_t) );
     662           0 :   a = fd_ulong_max( a, fd_rpc_cluster_node_dlist_align() );
     663           0 :   a = fd_ulong_max( a, fd_accdb_align() );
     664           0 :   return a;
     665           0 : }
     666             : 
     667             : FD_FN_CONST static inline ulong
     668           0 : loose_footprint( fd_topo_tile_t const * tile ) {
     669           0 :   (void)tile;
     670             :   /* Leftover space for bzip2 allocations */
     671           0 :   return 1UL<<26; /* 64 MiB */
     672           0 : }
     673             : 
     674             : static inline ulong
     675           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
     676           0 :   fd_http_server_params_t http_params = derive_http_params( tile );
     677           0 :   ulong http_fp = fd_http_server_footprint( http_params );
     678           0 :   if( FD_UNLIKELY( !http_fp ) ) FD_LOG_ERR(( "Invalid [tiles.rpc] config parameters" ));
     679             : 
     680           0 :   ulong l = FD_LAYOUT_INIT;
     681           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_rpc_tile_t),            sizeof(fd_rpc_tile_t)                                              );
     682           0 :   l = FD_LAYOUT_APPEND( l, fd_http_server_align(),            http_fp                                                            );
     683           0 :   l = FD_LAYOUT_APPEND( l, fd_alloc_align(),                  fd_alloc_footprint()                                               );
     684           0 :   l = FD_LAYOUT_APPEND( l, alignof(bank_info_t),              tile->rpc.max_live_slots*sizeof(bank_info_t)                       );
     685           0 :   l = FD_LAYOUT_APPEND( l, fd_rpc_cluster_node_dlist_align(), fd_rpc_cluster_node_dlist_footprint()                              );
     686           0 :   l = FD_LAYOUT_APPEND( l, fd_accdb_align(),                  fd_accdb_footprint( tile->rpc.max_live_slots, 0 )                     );
     687           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),                    http_params.max_ws_connection_cnt*sizeof(ulong)                    );
     688           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),                    http_params.max_ws_connection_cnt*sizeof(ulong)                    );
     689           0 :   l = FD_LAYOUT_APPEND( l, alignof(uchar),                    fd_rpc_genesis_tar_max_sz( tile->rpc.genesis_max_message_size )    );
     690           0 :   l = FD_LAYOUT_APPEND( l, alignof(uchar),                    fd_rpc_genesis_tar_bz_max_sz( tile->rpc.genesis_max_message_size ) );
     691           0 :   l = FD_LAYOUT_APPEND( l, 16UL, ZSTD_estimateCCtxSize( FD_RPC_ZSTD_LEVEL ) );
     692           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
     693           0 : }
     694             : 
     695             : static inline void
     696           0 : during_housekeeping( fd_rpc_tile_t * ctx ) {
     697           0 :   if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) {
     698           0 :     fd_clock_tile_recal( ctx->clock );
     699           0 :   }
     700             : 
     701           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
     702           0 :     fd_memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
     703           0 :     fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
     704           0 :   }
     705           0 : }
     706             : 
     707             : static inline void
     708           0 : metrics_write( fd_rpc_tile_t * ctx ) {
     709           0 :   FD_MHIST_COPY( RPC, REQUEST_DURATION_NANOS,             ctx->request_duration                );
     710           0 :   FD_MGAUGE_SET( RPC, CONN_OPEN,                          ctx->http->metrics.connection_cnt    );
     711           0 :   FD_MGAUGE_SET( RPC, WEBSOCKET_CONN_OPEN,                ctx->http->metrics.ws_connection_cnt );
     712           0 :   FD_MGAUGE_SET( RPC, WEBSOCKET_SUBSCRIBER_COUNT_VOTE,    ctx->ws_subscribers_vote_cnt         );
     713           0 :   FD_MGAUGE_SET( RPC, WEBSOCKET_SUBSCRIBER_COUNT_SLOT,    ctx->ws_subscribers_slot_cnt         );
     714           0 :   FD_ACCDB_METRICS_WRITE_RO( RPC, fd_accdb_metrics( ctx->accdb ) );
     715           0 : }
     716             : 
     717             : static void
     718             : before_credit( fd_rpc_tile_t *     ctx,
     719             :                fd_stem_context_t * stem,
     720           0 :                int *               charge_busy ) {
     721           0 :   (void)stem;
     722             : 
     723           0 :   ctx->idle_cnt++;
     724           0 :   if( FD_LIKELY( ctx->idle_cnt<2UL*ctx->in_cnt ) ) return;
     725           0 :   ctx->idle_cnt = 0UL;
     726             : 
     727           0 :   int replay_ready = ctx->confirmed_idx!=ULONG_MAX && ctx->processed_idx!=ULONG_MAX && ctx->finalized_idx!=ULONG_MAX;
     728           0 :   if( FD_UNLIKELY( ctx->delay_startup && !replay_ready ) ) {
     729             :     /* Consume readiness so an early connection does not veto every
     730             :        park, but do not rearm: the listen fd stays readable and would
     731             :        refire on every pass. */
     732           0 :     if( FD_UNLIKELY( fd_fseq_query( ctx->waker_fseq )==1UL ) ) {
     733           0 :       fd_fseq_update( ctx->waker_fseq, 0UL );
     734           0 :       ctx->waker_rearm_pending = 1;
     735           0 :     }
     736           0 :     return;
     737           0 :   }
     738             : 
     739           0 :   if( FD_UNLIKELY( ctx->waker_rearm_pending ) ) {
     740           0 :     ctx->waker_rearm_pending = 0;
     741           0 :     fd_waker_client_rearm( ctx->waker_client_idx );
     742           0 :   }
     743             : 
     744           0 :   if( FD_UNLIKELY( fd_fseq_query( ctx->waker_fseq )==1UL ) ) {
     745           0 :     fd_fseq_update( ctx->waker_fseq, 0UL );
     746           0 :     *charge_busy = fd_http_server_epoll_poll( ctx->http, 1UL );
     747           0 :     fd_waker_client_rearm( ctx->waker_client_idx );
     748           0 :   }
     749           0 : }
     750             : 
     751             : static int
     752             : before_frag( fd_rpc_tile_t *   ctx,
     753             :              ulong             in_idx,
     754             :              ulong             seq FD_PARAM_UNUSED,
     755           0 :              ulong             sig ) {
     756           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) ) {
     757             :     /* To match Agave, voteSubscribe shouldn't work under Alpenglow. */
     758           0 :     if( sig==FD_GOSSIP_UPDATE_TAG_VOTE ) return ctx->alpenglow || !ctx->ws_subscribers_vote_cnt;
     759           0 :     return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO &&
     760           0 :            sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE;
     761           0 :   }
     762             : 
     763           0 :   if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) ) {
     764           0 :     return sig!=REPLAY_SIG_SLOT_COMPLETED && sig!=REPLAY_SIG_OC_ADVANCED &&
     765           0 :            sig!=REPLAY_SIG_ROOT_ADVANCED  && sig!=REPLAY_SIG_DROP_BANK_REF;
     766           0 :   }
     767             : 
     768           0 :   if( ctx->in_kind[ in_idx ]==IN_KIND_VOTOR ) return sig!=FD_VOTOR_SIG_CERTED;
     769             : 
     770           0 :   return 0;
     771           0 : }
     772             : 
     773             : static int
     774             : fd_rpc_extract_vote_notification( fd_gossip_vote_t const * vote,
     775             :                                   fd_pubkey_t *            vote_pubkey,
     776             :                                   fd_hash_t *              hash,
     777             :                                   long *                   timestamp,
     778             :                                   uchar *                  has_timestamp,
     779             :                                   fd_signature_t *         signature,
     780             :                                   ulong *                  slots,
     781           0 :                                   ulong *                  slots_cnt ) {
     782           0 :   uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t))));
     783           0 :   ulong txn_sz = fd_txn_parse( vote->transaction, vote->transaction_len, txn_mem, NULL );
     784           0 :   if( FD_UNLIKELY( !txn_sz ) ) return 0;
     785             : 
     786           0 :   fd_txn_t const * txn = (fd_txn_t const *)txn_mem;
     787           0 :   if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, vote->transaction ) ) ) return 0;
     788           0 :   if( FD_UNLIKELY( txn->instr_cnt!=1UL || txn->signature_cnt<1UL ) ) return 0;
     789             : 
     790           0 :   fd_txn_instr_t const * instr = &txn->instr[ 0 ];
     791           0 :   if( FD_UNLIKELY( !instr->acct_cnt ) ) return 0;
     792           0 :   uchar const * instr_accts = fd_txn_get_instr_accts( instr, vote->transaction );
     793           0 :   uchar vote_pubkey_idx = instr_accts[ 0 ];
     794           0 :   if( FD_UNLIKELY( vote_pubkey_idx>=txn->acct_addr_cnt ) ) return 0;
     795             : 
     796           0 :   fd_pubkey_t const * acct_addrs = (fd_pubkey_t const *)fd_type_pun_const( vote->transaction + txn->acct_addr_off );
     797           0 :   *vote_pubkey = acct_addrs[ vote_pubkey_idx ];
     798             : 
     799           0 :   fd_vote_instruction_t ix[1];
     800           0 :   if( FD_UNLIKELY( !fd_vote_instruction_deserialize( ix, fd_txn_get_instr_data( instr, vote->transaction ), instr->data_sz ) ) ) return 0;
     801             : 
     802           0 :   *slots_cnt = 0UL;
     803           0 :   *has_timestamp = 0;
     804           0 :   *timestamp = 0L;
     805             : 
     806           0 :   switch( ix->discriminant ) {
     807           0 :     case fd_vote_instruction_enum_tower_sync: {
     808           0 :       fd_tower_sync_t const * v = &ix->tower_sync;
     809           0 :       for( ulong i=0UL; i<v->lockouts_cnt; i++ ) slots[ (*slots_cnt)++ ] =
     810           0 :           deq_fd_vote_lockout_t_peek_index_const( v->lockouts, i )->slot;
     811           0 :       *hash = v->hash;
     812           0 :       *has_timestamp = v->has_timestamp;
     813           0 :       *timestamp = v->timestamp;
     814           0 :       break;
     815           0 :     }
     816           0 :     case fd_vote_instruction_enum_tower_sync_switch: {
     817           0 :       fd_tower_sync_t const * v = &ix->tower_sync_switch.tower_sync;
     818           0 :       for( ulong i=0UL; i<v->lockouts_cnt; i++ ) slots[ (*slots_cnt)++ ] =
     819           0 :           deq_fd_vote_lockout_t_peek_index_const( v->lockouts, i )->slot;
     820           0 :       *hash = v->hash;
     821           0 :       *has_timestamp = v->has_timestamp;
     822           0 :       *timestamp = v->timestamp;
     823           0 :       break;
     824           0 :     }
     825           0 :     default:
     826             :       /* Legacy vote instructions not supported */
     827           0 :       return 0;
     828           0 :   }
     829             : 
     830           0 :   *signature = FD_LOAD( fd_signature_t, fd_txn_get_signatures( txn, vote->transaction ) );
     831           0 :   return *slots_cnt>0UL;
     832           0 : }
     833             : 
     834             : static void
     835             : fd_rpc_publish_vote_event( fd_rpc_tile_t *          ctx,
     836           0 :                            fd_gossip_vote_t const * vote ) {
     837           0 :   if( FD_UNLIKELY( !ctx->ws_subscribers_vote_cnt ) ) return;
     838             : 
     839           0 :   fd_pubkey_t vote_pubkey[1];
     840           0 :   fd_hash_t hash[1];
     841           0 :   long timestamp = 0L;
     842           0 :   uchar has_timestamp = 0;
     843           0 :   fd_signature_t signature[1];
     844           0 :   ulong slots[ FD_VOTE_INSTR_MAX_LOCKOUT_OFFSETS_LEN ];
     845           0 :   ulong slots_cnt = 0UL;
     846             : 
     847           0 :   if( FD_UNLIKELY( !fd_rpc_extract_vote_notification( vote, vote_pubkey, hash, &timestamp, &has_timestamp, signature, slots, &slots_cnt ) ) ) return;
     848             : 
     849           0 :   FD_BASE58_ENCODE_32_BYTES( vote_pubkey->uc, vote_pubkey_b58 );
     850           0 :   FD_BASE58_ENCODE_32_BYTES( hash->uc, hash_b58 );
     851           0 :   FD_BASE58_ENCODE_64_BYTES( signature->uc, signature_b58 );
     852             : 
     853           0 :   char txn_base64[ FD_BASE64_ENC_SZ( sizeof(vote->transaction) ) ];
     854           0 :   ulong txn_base64_len = fd_base64_encode( txn_base64, vote->transaction, vote->transaction_len );
     855             : 
     856           0 :   ulong sent_cnt = 0UL;
     857           0 :   for( ulong i=0UL; i<ctx->ws_subscribers_vote_cnt; ) {
     858           0 :     ulong ws_conn_id = ctx->ws_subscribers_vote[ i ];
     859           0 :     fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"method\":\"voteNotification\",\"params\":{\"subscription\":0,\"result\":{\"votePubkey\":\"%s\",\"slots\":[", vote_pubkey_b58 );
     860           0 :     for( ulong j=0UL; j<slots_cnt; j++ ) fd_http_server_printf( ctx->http, "%s%lu", j ? "," : "", slots[ j ] );
     861           0 :     fd_http_server_printf( ctx->http, "],\"hash\":\"%s\",", hash_b58 );
     862           0 :     if( FD_LIKELY( has_timestamp ) ) fd_http_server_printf( ctx->http, "\"timestamp\":%ld,", timestamp );
     863           0 :     else                             fd_http_server_printf( ctx->http, "\"timestamp\":null," );
     864           0 :     fd_http_server_printf( ctx->http, "\"signature\":\"%s\",\"transaction\":[\"%.*s\",\"base64\"]}}}\n", signature_b58, (int)txn_base64_len, txn_base64 );
     865             : 
     866           0 :     if( FD_UNLIKELY( fd_http_server_ws_send( ctx->http, ws_conn_id ) ) ) {
     867           0 :       fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
     868           0 :       fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
     869           0 :       continue;
     870           0 :     }
     871           0 :     if( FD_UNLIKELY( i>=ctx->ws_subscribers_vote_cnt || ctx->ws_subscribers_vote[ i ]!=ws_conn_id ) ) continue;
     872           0 :     sent_cnt++;
     873           0 :     i++;
     874           0 :   }
     875             : 
     876           0 :   FD_MCNT_INC( RPC, WEBSOCKET_NOTIFICATION_PUBLISHED_VOTE, !!sent_cnt );
     877           0 :   FD_MCNT_INC( RPC, WEBSOCKET_NOTIFICATION_TX_VOTE,          sent_cnt );
     878           0 : }
     879             : 
     880             : static void
     881             : fd_rpc_publish_slot_event( fd_rpc_tile_t *                    ctx,
     882           0 :                            fd_replay_slot_completed_t const * slot_completed ) {
     883           0 :   if( FD_UNLIKELY( !ctx->ws_subscribers_slot_cnt ) ) return;
     884             : 
     885           0 :   ulong sent_cnt = 0UL;
     886           0 :   for( ulong i=0UL; i<ctx->ws_subscribers_slot_cnt; ) {
     887           0 :     ulong ws_conn_id = ctx->ws_subscribers_slot[ i ];
     888           0 :     fd_http_server_printf( ctx->http,
     889           0 :                            "{\"jsonrpc\":\"2.0\",\"method\":\"slotNotification\",\"params\":{\"subscription\":0,\"result\":{\"parent\":%lu,\"root\":%lu,\"slot\":%lu}}}\n",
     890           0 :                            slot_completed->parent_slot,
     891           0 :                            slot_completed->root_slot,
     892           0 :                            slot_completed->slot );
     893             : 
     894           0 :     if( FD_UNLIKELY( fd_http_server_ws_send( ctx->http, ws_conn_id ) ) ) {
     895           0 :       fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
     896           0 :       fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
     897           0 :       continue;
     898           0 :     }
     899           0 :     if( FD_UNLIKELY( i>=ctx->ws_subscribers_slot_cnt || ctx->ws_subscribers_slot[ i ]!=ws_conn_id ) ) continue;
     900           0 :     sent_cnt++;
     901           0 :     i++;
     902           0 :   }
     903             : 
     904           0 :   FD_MCNT_INC( RPC, WEBSOCKET_NOTIFICATION_PUBLISHED_SLOT, !!sent_cnt );
     905           0 :   FD_MCNT_INC( RPC, WEBSOCKET_NOTIFICATION_TX_SLOT,          sent_cnt );
     906           0 : }
     907             : 
     908             : static inline void
     909             : fd_rpc_cluster_finalized( fd_rpc_tile_t * ctx,
     910           0 :                           ulong           slot ) {
     911           0 :   ctx->cluster_confirmed_slot = ctx->cluster_confirmed_slot==ULONG_MAX ? slot : fd_ulong_max( ctx->cluster_confirmed_slot, slot );
     912           0 : }
     913             : 
     914             : static inline int
     915             : returnable_frag( fd_rpc_tile_t *     ctx,
     916             :                  ulong               in_idx,
     917             :                  ulong               seq FD_PARAM_UNUSED,
     918             :                  ulong               sig,
     919             :                  ulong               chunk,
     920             :                  ulong               sz FD_PARAM_UNUSED,
     921             :                  ulong               ctl FD_PARAM_UNUSED,
     922             :                  ulong               tsorig FD_PARAM_UNUSED,
     923             :                  ulong               tspub FD_PARAM_UNUSED,
     924           0 :                  fd_stem_context_t * stem ) {
     925             : 
     926           0 :   ctx->idle_cnt = 0UL;
     927             : 
     928           0 :   if( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) {
     929           0 :     switch( sig ) {
     930           0 :       case REPLAY_SIG_SLOT_COMPLETED: {
     931           0 :         fd_replay_slot_completed_t const * slot_completed = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     932             : 
     933           0 :         FD_TEST( slot_completed->bank_idx<ctx->max_live_slots );
     934           0 :         bank_info_t * bank = &ctx->banks[ slot_completed->bank_idx ];
     935           0 :         bank->slot = slot_completed->slot;
     936           0 :         bank->accdb_fork_id = slot_completed->accdb_fork_id;
     937           0 :         bank->epoch = slot_completed->epoch;
     938           0 :         bank->slot_in_epoch = slot_completed->slot_in_epoch;
     939           0 :         bank->slots_per_epoch = slot_completed->slots_per_epoch;
     940           0 :         bank->transaction_count = slot_completed->transaction_count;
     941           0 :         bank->block_height = slot_completed->block_height;
     942           0 :         fd_memcpy( bank->block_hash, slot_completed->block_hash.uc, 32 );
     943             : 
     944           0 :         bank->inflation.initial         = slot_completed->inflation.initial;
     945           0 :         bank->inflation.terminal        = slot_completed->inflation.terminal;
     946           0 :         bank->inflation.taper           = slot_completed->inflation.taper;
     947           0 :         bank->inflation.foundation      = slot_completed->inflation.foundation;
     948           0 :         bank->inflation.foundation_term = slot_completed->inflation.foundation_term;
     949             : 
     950           0 :         bank->rent.lamports_per_uint8_year = slot_completed->rent.lamports_per_uint8_year;
     951           0 :         bank->rent.exemption_threshold     = slot_completed->rent.exemption_threshold;
     952           0 :         bank->rent.burn_percent            = slot_completed->rent.burn_percent;
     953             : 
     954           0 :         fd_rpc_publish_slot_event( ctx, slot_completed );
     955             : 
     956             :         /* In Agave, "processed" confirmation is the bank we've just
     957             :            voted for (handle_votable_bank), which is also guaranteed to
     958             :            have been replayed.
     959             : 
     960             :            Right now tower is not really built out to replicate this
     961             :            exactly, so we use the latest replayed slot, which is
     962             :            slightly more eager than Agave but shouldn't really affect
     963             :            end-users, since any use-cases that assume "processed" means
     964             :            "voted-for" would fail in Agave in cases where a cast vote
     965             :            does not land.
     966             : 
     967             :            Due to bank eviction semantics, it is possible that the RPC
     968             :            can return data about a slot that is not the most recently
     969             :            replayed slot (since slots can be re-replayed).
     970             : 
     971             :            tldr: This isn't strictly conformant with Agave, but doesn't
     972             :            need to be since Agave doesn't provide any guarantees
     973             :            anyways. */
     974           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 );
     975           0 :         ctx->processed_idx = slot_completed->bank_idx;
     976           0 :         break;
     977           0 :       }
     978           0 :       case REPLAY_SIG_OC_ADVANCED: {
     979           0 :         fd_replay_oc_advanced_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     980           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 );
     981           0 :         FD_TEST( msg->bank_idx<ctx->max_live_slots );
     982           0 :         ctx->confirmed_idx = msg->bank_idx;
     983           0 :         break;
     984           0 :       }
     985           0 :       case REPLAY_SIG_ROOT_ADVANCED: {
     986           0 :         fd_replay_root_advanced_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     987           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 );
     988           0 :         FD_TEST( msg->bank_idx<ctx->max_live_slots );
     989           0 :         ctx->finalized_idx = msg->bank_idx;
     990             : 
     991           0 :         if( FD_UNLIKELY( ctx->alpenglow ) ) {
     992           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 );
     993           0 :           ctx->confirmed_idx = msg->bank_idx;
     994             : 
     995           0 :           if( FD_LIKELY( ctx->cluster_confirmed_slot!=ULONG_MAX ) ) fd_rpc_cluster_finalized( ctx, msg->slot );
     996           0 :         }
     997           0 :         break;
     998           0 :       }
     999           0 :       case REPLAY_SIG_DROP_BANK_REF: {
    1000           0 :         fd_replay_drop_bank_ref_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
    1001           0 :         if( FD_UNLIKELY( ctx->processed_idx==msg->bank_idx ) ) {
    1002           0 :           fd_stem_publish( stem, ctx->replay_out->idx, ctx->processed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
    1003           0 :           ctx->processed_idx = ULONG_MAX;
    1004           0 :         }
    1005           0 :         if( FD_UNLIKELY( ctx->confirmed_idx==msg->bank_idx ) ) {
    1006           0 :           fd_stem_publish( stem, ctx->replay_out->idx, ctx->confirmed_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
    1007           0 :           ctx->confirmed_idx = ULONG_MAX;
    1008           0 :         }
    1009           0 :         if( FD_UNLIKELY( ctx->finalized_idx==msg->bank_idx ) ) {
    1010           0 :           fd_stem_publish( stem, ctx->replay_out->idx, ctx->finalized_idx, 0UL, 0UL, 0UL, 0UL, 0UL );
    1011           0 :           ctx->finalized_idx = ULONG_MAX;
    1012           0 :         }
    1013           0 :         break;
    1014           0 :       }
    1015           0 :       default: {
    1016           0 :         break;
    1017           0 :       }
    1018           0 :     }
    1019           0 :   } else if( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) {
    1020           0 :     fd_gossip_update_message_t const * update = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
    1021           0 :     switch( update->tag ) {
    1022           0 :       case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
    1023           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 ));
    1024           0 :         fd_rpc_cluster_node_t * node = &ctx->cluster_nodes[ update->contact_info->idx ];
    1025           0 :         if( FD_LIKELY( node->valid ) ) fd_rpc_cluster_node_dlist_idx_remove( ctx->cluster_nodes_dlist, update->contact_info->idx, ctx->cluster_nodes );
    1026             : 
    1027           0 :         node->valid = 1;
    1028           0 :         node->identity = *(fd_pubkey_t *)update->origin;
    1029           0 :         fd_memcpy( node->ci, update->contact_info->value, sizeof(fd_gossip_contact_info_t) );
    1030             : 
    1031           0 :         fd_rpc_cluster_node_dlist_idx_push_tail( ctx->cluster_nodes_dlist, update->contact_info->idx, ctx->cluster_nodes );
    1032           0 :         break;
    1033           0 :       }
    1034           0 :       case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE: {
    1035           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 ));
    1036           0 :         fd_rpc_cluster_node_t * node = &ctx->cluster_nodes[ update->contact_info_remove->idx ];
    1037           0 :         FD_TEST( node->valid );
    1038           0 :         node->valid = 0;
    1039           0 :         fd_rpc_cluster_node_dlist_idx_remove( ctx->cluster_nodes_dlist, update->contact_info_remove->idx, ctx->cluster_nodes );
    1040           0 :         break;
    1041           0 :       }
    1042           0 :       case FD_GOSSIP_UPDATE_TAG_VOTE: {
    1043           0 :         fd_rpc_publish_vote_event( ctx, update->vote->value );
    1044           0 :         break;
    1045           0 :       }
    1046           0 :       default: break;
    1047           0 :     }
    1048           0 :   } else if( ctx->in_kind[ in_idx ]==IN_KIND_EPOCH ) {
    1049           0 :     fd_epoch_info_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
    1050           0 :     FD_TEST( msg->staked_vote_cnt<=MAX_STAKE_WEIGHTS );
    1051           0 :     ctx->epoch_schedule     = msg->epoch_schedule;
    1052           0 :     ctx->has_epoch_schedule = 1;
    1053             : 
    1054           0 :     fd_rpc_mleaders_ingest( ctx->mleaders, msg );
    1055           0 :   } else if( ctx->in_kind[ in_idx ]==IN_KIND_VOTOR ) {
    1056           0 :     fd_votor_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
    1057           0 :     if( FD_LIKELY( msg->certed.kind==AG_CERT_KIND_FINAL || msg->certed.kind==AG_CERT_KIND_FAST_FINAL ) ) {
    1058           0 :       fd_rpc_cluster_finalized( ctx, msg->certed.slot );
    1059           0 :     }
    1060           0 :   } else if( ctx->in_kind[ in_idx ]==IN_KIND_GENESI ) {
    1061           0 :     ctx->has_genesis_hash = 1;
    1062           0 :     fd_genesis_meta_t const * genesis_meta = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
    1063           0 :     *ctx->genesis_hash = genesis_meta->genesis_hash;
    1064             : 
    1065           0 :     uchar const * blob    = (uchar const *)( genesis_meta+1 );
    1066           0 :     ulong const   blob_sz = genesis_meta->blob_sz;
    1067           0 :     FD_TEST( blob_sz<=ctx->genesis_max_message_size );
    1068             : 
    1069           0 :     ctx->genesis_tar_bz_sz = fd_rpc_file_as_tarball(
    1070           0 :       ctx,
    1071           0 :       "genesis.bin",
    1072           0 :       blob, blob_sz,
    1073           0 :       ctx->genesis_tar, ctx->genesis_tar_max_sz,
    1074           0 :       ctx->genesis_tar_bz, ctx->genesis_tar_bz_max_sz );
    1075           0 :     if( FD_UNLIKELY( ctx->genesis_tar_bz_sz==ULONG_MAX ) ) {
    1076           0 :       FD_LOG_ERR(( "failed to create genesis tarball (blob_sz=%lu)", blob_sz ));
    1077           0 :     }
    1078           0 :   }
    1079             : 
    1080           0 :   return 0;
    1081           0 : }
    1082             : 
    1083           0 : #define STAGE_JSON(__ctx) (__extension__({ \
    1084           0 :   fd_http_server_response_t __res = (fd_http_server_response_t){ .content_type = "application/json", .status = 200 }; \
    1085           0 :   if( FD_UNLIKELY( fd_http_server_stage_body( __ctx->http, &__res ) ) ) { \
    1086           0 :     __res.status = 500; \
    1087           0 :     FD_LOG_WARNING(( "Failed to populate RPC response buffer" )); \
    1088           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 ) )); \
    1089           0 :     FD_LOG_HEXDUMP_WARNING(( "start of buffer:\n%.*s",  __ctx->http->oring,                                                fd_ulong_min( 500UL, __ctx->http->oring_sz ) )); \
    1090           0 :   } \
    1091           0 :   __res; }))
    1092             : 
    1093           0 : #define PRINTF_JSON(__ctx, ...) (__extension__({ \
    1094           0 :   fd_http_server_printf( __ctx->http, __VA_ARGS__ ); \
    1095           0 :   fd_http_server_response_t __res = STAGE_JSON( __ctx ); \
    1096           0 :   __res; }))
    1097             : 
    1098             : 
    1099             : static inline char const *
    1100           0 : fd_rpc_val_kind_cstr( fd_rpc_val_t const * v ) {
    1101           0 :   switch( v->kind ) {
    1102           0 :   case FD_RPC_VAL_STR:   return "string";
    1103           0 :   case FD_RPC_VAL_MAP:   return "map";
    1104           0 :   case FD_RPC_VAL_SEQ:   return "sequence";
    1105           0 :   case FD_RPC_VAL_BOOL:  return "boolean";
    1106           0 :   case FD_RPC_VAL_FLOAT: return "floating point";
    1107           0 :   case FD_RPC_VAL_INT:   return "integer";
    1108           0 :   case FD_RPC_VAL_NULL:  return "null";
    1109           0 :   default: FD_LOG_ERR(( "unreachable" ));
    1110           0 :   }
    1111           0 : }
    1112             : 
    1113             : /* fd_rpc_str_ws_sz returns the size of the whitespace unit at the
    1114             :    start of the JSON string body [p,end), or 0 if it does not start
    1115             :    with one.  A unit is a literal space or an escape that decodes to an
    1116             :    ASCII control character. */
    1117             : 
    1118             : static ulong
    1119             : fd_rpc_str_ws_sz( char const * p,
    1120           0 :                   char const * end ) {
    1121           0 :   if( p[0]==' ' ) return 1UL;
    1122           0 :   if( p[0]!='\\' || p+1>=end ) return 0UL;
    1123           0 :   switch( p[1] ) {
    1124           0 :   case 'b': case 'f': case 'n': case 'r': case 't':
    1125           0 :     return 2UL;
    1126           0 :   case 'u':
    1127           0 :     if( p+6<=end && p[2]=='0' && p[3]=='0' && (p[4]=='0' || p[4]=='1') ) return 6UL;
    1128           0 :     return 0UL;
    1129           0 :   default:
    1130           0 :     return 0UL;
    1131           0 :   }
    1132           0 : }
    1133             : 
    1134             : /* fd_rpc_print_str appends the contents of a request string to the
    1135             :    response.  The bytes between the quotes of a well formed JSON string
    1136             :    are themselves a well formed JSON string body, so they are copied
    1137             :    as-is, escapes included.  Runs of whitespace (literal spaces and the
    1138             :    escapes for control characters) are collapsed into a single space to
    1139             :    keep the error message on one line. */
    1140             : 
    1141             : static void
    1142             : fd_rpc_print_str( fd_rpc_tile_t *       ctx,
    1143           0 :                   fd_jtok_str_t const * s ) {
    1144           0 :   char const * p    = s->ptr;
    1145           0 :   char const * end  = s->ptr + s->sz;
    1146           0 :   char const * span = p;
    1147           0 :   while( p<end ) {
    1148           0 :     ulong ws_sz = fd_rpc_str_ws_sz( p, end );
    1149           0 :     if( !ws_sz ) {
    1150           0 :       p += ( p[0]=='\\' && p+1<end ) ? 2 : 1; /* skip escapes whole */
    1151           0 :       continue;
    1152           0 :     }
    1153           0 :     if( span<p ) fd_http_server_memcpy( ctx->http, (uchar const *)span, (ulong)(p-span) );
    1154           0 :     fd_http_server_memcpy( ctx->http, (uchar const *)" ", 1UL );
    1155           0 :     do p += ws_sz; while( p<end && (ws_sz = fd_rpc_str_ws_sz( p, end )) );
    1156           0 :     span = p;
    1157           0 :   }
    1158           0 :   if( span<p ) fd_http_server_memcpy( ctx->http, (uchar const *)span, (ulong)(p-span) );
    1159           0 : }
    1160             : 
    1161             : /* fd_rpc_print_float formats a double to match serde_json / zmij. */
    1162             : 
    1163             : static inline void
    1164             : fd_rpc_print_float( fd_rpc_tile_t * ctx,
    1165           0 :                     double          d ) {
    1166           0 :   char buf[ 64 ];
    1167           0 :   double test;
    1168           0 :   int ok = 0;
    1169           0 :   if( FD_LIKELY( sprintf( buf, "%1.15g", d )>0 && sscanf( buf, "%lg", &test )==1 && test==d ) ) ok = 1;
    1170           0 :   else if( FD_LIKELY( sprintf( buf, "%1.16g", d )>0 && sscanf( buf, "%lg", &test )==1 && test==d ) ) ok = 1;
    1171           0 :   else if( FD_LIKELY( sprintf( buf, "%1.17g", d )>0 ) ) ok = 1;
    1172           0 :   if( FD_UNLIKELY( !ok ) ) {
    1173           0 :     fd_http_server_printf( ctx->http, " `0.0`" );
    1174           0 :     return;
    1175           0 :   }
    1176           0 :   if( !strchr( buf, '.' ) && !strchr( buf, 'e' ) ) {
    1177           0 :     fd_http_server_printf( ctx->http, " `%s.0`", buf );
    1178           0 :   } else {
    1179           0 :     fd_http_server_printf( ctx->http, " `%s`", buf );
    1180           0 :   }
    1181           0 : }
    1182             : 
    1183             : /* fd_rpc_err_invalid emits an "invalid type" / "invalid value" error
    1184             :    for v in the style of serde.  what is "type" or "value", expected is
    1185             :    the name of the expected type. */
    1186             : 
    1187             : static fd_http_server_response_t
    1188             : fd_rpc_err_invalid( fd_rpc_tile_t *      ctx,
    1189             :                     char const *         id_cstr,
    1190             :                     char const *         what,
    1191             :                     fd_rpc_val_t const * v,
    1192           0 :                     char const *         expected ) {
    1193           0 :   fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid %s: %s", what, fd_rpc_val_kind_cstr( v ) );
    1194           0 :   switch( v->kind ) {
    1195           0 :   case FD_RPC_VAL_STR:
    1196           0 :     fd_http_server_printf( ctx->http, " \\\"" );
    1197           0 :     fd_rpc_print_str( ctx, &v->str );
    1198           0 :     fd_http_server_printf( ctx->http, "\\\"" );
    1199           0 :     break;
    1200           0 :   case FD_RPC_VAL_BOOL:
    1201           0 :   case FD_RPC_VAL_INT:
    1202           0 :     fd_http_server_printf( ctx->http, " `%.*s`", (int)v->raw_sz, v->raw );
    1203           0 :     break;
    1204           0 :   case FD_RPC_VAL_FLOAT: {
    1205           0 :     fd_jtok_t j[1];
    1206           0 :     double d;
    1207           0 :     fd_jtok_double( fd_jtok_init( j, v->raw, v->raw_sz ), &d );
    1208           0 :     if( FD_LIKELY( !fd_jtok_fini( j ) ) ) fd_rpc_print_float( ctx, d );
    1209           0 :     else                                  fd_http_server_printf( ctx->http, " `%.*s`", (int)v->raw_sz, v->raw );
    1210           0 :     break;
    1211           0 :   }
    1212           0 :   default:
    1213           0 :     break;
    1214           0 :   }
    1215           0 :   fd_http_server_printf( ctx->http, ", expected %s.\"},\"id\":%s}\n", expected, id_cstr );
    1216           0 :   return STAGE_JSON( ctx );
    1217           0 : }
    1218             : 
    1219             : static fd_http_server_response_t
    1220             : fd_rpc_err_unknown_encoding( fd_rpc_tile_t *       ctx,
    1221             :                              char const *          id_cstr,
    1222           0 :                              fd_jtok_str_t const * variant ) {
    1223           0 :   fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: unknown variant `" );
    1224           0 :   fd_rpc_print_str( ctx, variant );
    1225           0 :   fd_http_server_printf( ctx->http, "`, expected one of `binary`, `base58`, `base64`, `jsonParsed`, `base64+zstd`.\"},\"id\":%s}\n", id_cstr );
    1226           0 :   return STAGE_JSON( ctx );
    1227           0 : }
    1228             : 
    1229             : static inline int
    1230             : fd_rpc_validate_params( fd_rpc_tile_t *             ctx,
    1231             :                         char const *                id_cstr,
    1232             :                         fd_rpc_params_t const *     params,
    1233             :                         ulong                       min_cnt,
    1234             :                         ulong                       max_cnt,
    1235           0 :                         fd_http_server_response_t * res ) {
    1236           0 :   FD_TEST( min_cnt <= max_cnt );
    1237             :   /* Agave also includes a "data" field in some responses with the
    1238             :      faulty params payload. Instead of printing raw JSON, they print the
    1239             :      representation which we won't replicate.
    1240             : 
    1241             :      e.g. "data" might contain something like
    1242             :       Array([String(\"\"), Object {}])
    1243             : 
    1244             :     instead, we just include the field with an empty string
    1245             :   */
    1246             : 
    1247           0 :   ulong param_cnt = 0UL;
    1248           0 :   switch( params->kind ) {
    1249           0 :   case FD_RPC_VAL_NONE:
    1250           0 :   case FD_RPC_VAL_NULL:
    1251           0 :     break;
    1252           0 :   case FD_RPC_VAL_SEQ:
    1253           0 :     param_cnt = params->cnt;
    1254           0 :     break;
    1255           0 :   case FD_RPC_VAL_MAP:
    1256           0 :     if( max_cnt==0UL ) *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: No parameters were expected\",\"data\":\"\"},\"id\":%s}\n", id_cstr );
    1257           0 :     else                *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"`params` should be an array\"},\"id\":%s}\n", id_cstr );
    1258           0 :     return 0;
    1259           0 :   default:
    1260           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    1261           0 :     return 0;
    1262           0 :   }
    1263             : 
    1264           0 :   if( FD_UNLIKELY( param_cnt>0UL && max_cnt==0UL ) ) {
    1265           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid parameters: No parameters were expected\",\"data\":\"\"},\"id\":%s}\n", id_cstr );
    1266           0 :     return 0;
    1267           0 :   }
    1268           0 :   if( FD_UNLIKELY( param_cnt<min_cnt ) ) {
    1269           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 );
    1270           0 :     return 0;
    1271           0 :   }
    1272           0 :   if( param_cnt>max_cnt ) {
    1273           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 );
    1274           0 :     return 0;
    1275           0 :   }
    1276             : 
    1277           0 :   return 1;
    1278           0 : }
    1279             : 
    1280             : /* TODO: use optimized version of this from fd_base58_tmpl.c */
    1281             : static const char base58_chars[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
    1282             : 
    1283             : static inline int
    1284           0 : fd_rpc_cstr_contains_non_base58(const char *str) {
    1285           0 :     for (; *str; str++) {
    1286           0 :         if (!strchr(base58_chars, *str)) return 1;
    1287           0 :     }
    1288           0 :     return 0;
    1289           0 : }
    1290             : 
    1291             : /* adapted from https://salsa.debian.org/debian/libbase58/-/blob/debian/master/base58.c */
    1292             : static inline int
    1293           0 : fd_rpc_base58_encode_128( char * b58, ulong * b58sz, const void *data, ulong binsz ) {
    1294           0 :   FD_TEST( binsz <= 128UL );
    1295             : 
    1296           0 :   const uchar * bin = data;
    1297           0 :   ulong carry;
    1298           0 :   ulong i, j, high, zcount = 0;
    1299           0 :   ulong size;
    1300             : 
    1301           0 :   while( zcount<binsz && !bin[ zcount ] ) zcount++;
    1302             : 
    1303           0 :   size = (binsz-zcount)*138/100+1; /* strict overestimate */
    1304           0 :   size = fd_ulong_min( size, FD_RPC_BASE58_ENCODED_128_LEN ); /* theoretical max */
    1305           0 :   uchar buf[ FD_RPC_BASE58_ENCODED_128_LEN ] = { 0 };
    1306             : 
    1307           0 :   for( i=zcount, high=size-1UL; i<binsz; i++, high=j ) {
    1308           0 :     for( carry=bin[ i ], j=size-1UL; (j>high) || carry; j-- ) {
    1309           0 :       carry += 256UL * buf[ j ];
    1310           0 :       buf[ j ] = (uchar)(carry%58UL);
    1311           0 :       carry /= 58UL;
    1312           0 :       if( FD_UNLIKELY( !j ) ) break;
    1313           0 :     }
    1314           0 :   }
    1315             : 
    1316           0 :   for( j=0; j<size && !buf[ j ]; j++);
    1317             : 
    1318           0 :   if( *b58sz<zcount+size-j ) {
    1319           0 :     *b58sz = zcount+size-j;
    1320           0 :     return 0;
    1321           0 :   }
    1322             : 
    1323           0 :   if (zcount) memset(b58, '1', zcount);
    1324           0 :   for( i=zcount; j<size; i++, j++) b58[ i ] = base58_chars[ buf[ j ] ];
    1325           0 :   *b58sz = i;
    1326             : 
    1327           0 :   return 1;
    1328           0 : }
    1329             : 
    1330             : /* getInflationGovernor and getMinimumBalanceForRentExemption take a
    1331             :    bare Option<CommitmentConfig> in Agave, so unlike the flattened
    1332             :    configs serde rejects a missing or malformed commitment member. */
    1333             : 
    1334             : static int
    1335             : fd_rpc_validate_commitment_strict( fd_rpc_tile_t *             ctx,
    1336             :                                    char const *                id_cstr,
    1337             :                                    fd_rpc_val_t const *        config,
    1338           0 :                                    fd_http_server_response_t * res ) {
    1339           0 :   if( config->kind!=FD_RPC_VAL_MAP ) return 1; /* type errors are fd_rpc_validate_config's */
    1340           0 :   fd_rpc_config_t cfg[1];
    1341           0 :   fd_rpc_config_read( config, cfg );
    1342           0 :   fd_rpc_val_t const * commitment = &cfg->commitment;
    1343           0 :   if( FD_UNLIKELY( commitment->kind==FD_RPC_VAL_NONE ) ) {
    1344           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `commitment`.\"},\"id\":%s}\n", id_cstr );
    1345           0 :     return 0;
    1346           0 :   }
    1347           0 :   if( FD_UNLIKELY( commitment->kind!=FD_RPC_VAL_STR ) ) {
    1348           0 :     *res = fd_rpc_err_invalid( ctx, id_cstr, "type", commitment, "enum CommitmentLevel" );
    1349           0 :     return 0;
    1350           0 :   }
    1351           0 :   if( FD_UNLIKELY( !fd_jtok_str_eq( &commitment->str, "processed" ) && !fd_jtok_str_eq( &commitment->str, "confirmed" ) && !fd_jtok_str_eq( &commitment->str, "finalized" ) ) ) {
    1352           0 :     fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: unknown variant `" );
    1353           0 :     fd_rpc_print_str( ctx, &commitment->str );
    1354           0 :     fd_http_server_printf( ctx->http, "`, expected one of `processed`, `confirmed`, `finalized`.\"},\"id\":%s}\n", id_cstr );
    1355           0 :     *res = STAGE_JSON( ctx );
    1356           0 :     return 0;
    1357           0 :   }
    1358           0 :   return 1;
    1359           0 : }
    1360             : 
    1361             : static inline int
    1362             : fd_rpc_validate_config( fd_rpc_tile_t *             ctx,
    1363             :                         char const *                id_cstr,
    1364             :                         fd_rpc_val_t const *        config,
    1365             :                         char const *                config_rust_type,
    1366             :                         int                         has_commitment,
    1367             :                         int                         has_encoding,
    1368             :                         int                         has_data_slice,
    1369             :                         int                         has_min_context_slot,
    1370             :                         ulong *                     bank_idx,
    1371             :                         char const **               opt_encoding_cstr,
    1372             :                         ulong *                     opt_slice_length,
    1373             :                         ulong *                     opt_slice_offset,
    1374             :                         fd_rpc_val_t *              opt_identity,
    1375           0 :                         fd_http_server_response_t * res ) {
    1376             : 
    1377           0 :   switch( config->kind ) {
    1378           0 :   case FD_RPC_VAL_INT:
    1379           0 :   case FD_RPC_VAL_FLOAT:
    1380           0 :   case FD_RPC_VAL_BOOL:
    1381           0 :   case FD_RPC_VAL_STR:
    1382           0 :     *res = fd_rpc_err_invalid( ctx, id_cstr, "type", config, config_rust_type );
    1383           0 :     return 0;
    1384           0 :   case FD_RPC_VAL_SEQ:
    1385           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: Positional config params not supported\"},\"id\":%s}\n", id_cstr );
    1386           0 :     return 0;
    1387           0 :   default:
    1388           0 :     break;
    1389           0 :   }
    1390             : 
    1391           0 :   fd_rpc_config_t cfg[1];
    1392           0 :   fd_rpc_config_read( config, cfg );
    1393           0 :   if( opt_identity ) *opt_identity = cfg->identity;
    1394             : 
    1395           0 :   ulong _bank_idx = ULONG_MAX;
    1396           0 :   if( FD_LIKELY( has_commitment ) ) {
    1397           0 :     fd_rpc_val_t const * commitment = &cfg->commitment;
    1398           0 :     char commitment_cstr[ 16 ];
    1399           0 :     if( FD_UNLIKELY( !fd_rpc_val_cstr( commitment, commitment_cstr, sizeof(commitment_cstr) ) ) ) _bank_idx = ctx->finalized_idx;
    1400           0 :     else if( FD_LIKELY( !strcmp( commitment_cstr, "processed" ) ) ) _bank_idx = ctx->processed_idx;
    1401           0 :     else if( FD_LIKELY( !strcmp( commitment_cstr, "confirmed" ) ) ) _bank_idx = ctx->confirmed_idx;
    1402           0 :     else if( FD_LIKELY( !strcmp( commitment_cstr, "finalized" ) ) ) _bank_idx = ctx->finalized_idx;
    1403           0 :     else _bank_idx = ctx->finalized_idx;
    1404           0 :   } else {
    1405           0 :     _bank_idx = ctx->finalized_idx;
    1406           0 :   }
    1407           0 :   if( FD_UNLIKELY( _bank_idx==ULONG_MAX ) ) {
    1408           0 :     *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
    1409           0 :     return 0;
    1410           0 :   }
    1411           0 :   *bank_idx = _bank_idx;
    1412             : 
    1413           0 :   if( FD_LIKELY( has_encoding ) ) {
    1414           0 :     fd_rpc_val_t const * encoding = &cfg->encoding;
    1415             : 
    1416           0 :     if( FD_UNLIKELY( encoding->kind==FD_RPC_VAL_INT || encoding->kind==FD_RPC_VAL_FLOAT || encoding->kind==FD_RPC_VAL_BOOL ) ) {
    1417           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", encoding, "string or map" );
    1418           0 :       return 0;
    1419           0 :     }
    1420             : 
    1421             :     /* An encoding given as a map must be a single key naming the
    1422             :        variant with a null (unit) value, e.g. {"base64":null}. */
    1423           0 :     fd_jtok_str_t variant_key = {0};
    1424           0 :     fd_rpc_val_t  variant_val[1] = {{ .kind = FD_RPC_VAL_NONE }};
    1425           0 :     if( FD_UNLIKELY( encoding->kind==FD_RPC_VAL_MAP ) ) {
    1426           0 :       fd_jtok_t j[1];
    1427           0 :       fd_jtok_str_t key;
    1428           0 :       ulong member_cnt = 0UL;
    1429           0 :       fd_jtok_obj_enter( fd_jtok_init( j, encoding->raw, encoding->raw_sz ) );
    1430           0 :       while( fd_jtok_obj_next( j, &key ) ) {
    1431           0 :         if( !member_cnt ) { variant_key = key; fd_rpc_val_read( j, variant_val ); }
    1432           0 :         member_cnt++;
    1433           0 :       }
    1434           0 :       if( FD_UNLIKELY( member_cnt!=1UL ) ) {
    1435           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 );
    1436           0 :         return 0;
    1437           0 :       }
    1438           0 :     }
    1439           0 :     if( FD_UNLIKELY( encoding->kind==FD_RPC_VAL_SEQ ) ) {
    1440           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", encoding, "string or map" );
    1441           0 :       return 0;
    1442           0 :     }
    1443             : 
    1444           0 :     char const * encoding_cstr;
    1445           0 :     if( FD_UNLIKELY( encoding->kind==FD_RPC_VAL_MAP ) ) {
    1446           0 :       if(      fd_jtok_str_eq( &variant_key, "binary"      ) ) encoding_cstr = "binary";
    1447           0 :       else if( fd_jtok_str_eq( &variant_key, "base58"      ) ) encoding_cstr = "base58";
    1448           0 :       else if( fd_jtok_str_eq( &variant_key, "base64"      ) ) encoding_cstr = "base64";
    1449           0 :       else if( fd_jtok_str_eq( &variant_key, "base64+zstd" ) ) encoding_cstr = "base64+zstd";
    1450           0 :       else if( fd_jtok_str_eq( &variant_key, "jsonParsed"  ) ) encoding_cstr = "jsonParsed";
    1451           0 :       else {
    1452           0 :         *res = fd_rpc_err_unknown_encoding( ctx, id_cstr, &variant_key );
    1453           0 :         return 0;
    1454           0 :       }
    1455           0 :       if( FD_UNLIKELY( variant_val->kind!=FD_RPC_VAL_NULL ) ) {
    1456           0 :         *res = fd_rpc_err_invalid( ctx, id_cstr, "type", variant_val, "unit" );
    1457           0 :         return 0;
    1458           0 :       }
    1459           0 :     } else {
    1460           0 :       char buf[ 16 ];
    1461           0 :       if(      encoding->kind!=FD_RPC_VAL_STR    ) encoding_cstr = "binary";
    1462           0 :       else if( !fd_rpc_val_cstr( encoding, buf, sizeof(buf) ) ) encoding_cstr = "";
    1463           0 :       else if( !strcmp( buf, "binary"      ) ) encoding_cstr = "binary";
    1464           0 :       else if( !strcmp( buf, "base58"      ) ) encoding_cstr = "base58";
    1465           0 :       else if( !strcmp( buf, "base64"      ) ) encoding_cstr = "base64";
    1466           0 :       else if( !strcmp( buf, "base64+zstd" ) ) encoding_cstr = "base64+zstd";
    1467           0 :       else if( !strcmp( buf, "jsonParsed"  ) ) encoding_cstr = "jsonParsed";
    1468           0 :       else encoding_cstr = "";
    1469           0 :     }
    1470             : 
    1471           0 :     if( 0==strcmp( encoding_cstr, "jsonParsed" ) ) {
    1472           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: jsonParsed is unsupported\"},\"id\":%s}\n", id_cstr );
    1473           0 :       return 0;
    1474           0 :     } else if( FD_UNLIKELY( !encoding_cstr[0] ) ) {
    1475           0 :       *res = fd_rpc_err_unknown_encoding( ctx, id_cstr, &encoding->str );
    1476           0 :       return 0;
    1477           0 :     }
    1478             : 
    1479           0 :     if( FD_LIKELY( opt_encoding_cstr ) ) *opt_encoding_cstr = encoding_cstr;
    1480           0 :   }
    1481             : 
    1482           0 :   if( FD_LIKELY( has_data_slice ) ) {
    1483           0 :     fd_rpc_val_t const * dataSlice = &cfg->data_slice;
    1484             : 
    1485           0 :     if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_INT || dataSlice->kind==FD_RPC_VAL_FLOAT || dataSlice->kind==FD_RPC_VAL_BOOL || dataSlice->kind==FD_RPC_VAL_STR ) ) {
    1486           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", dataSlice, "struct UiDataSliceConfig" );
    1487           0 :       return 0;
    1488           0 :     }
    1489             : 
    1490             :     /* A data slice is either {"offset":..,"length":..} or a two
    1491             :        element [offset,length] array. */
    1492           0 :     fd_rpc_val_t _offset[1] = {{ .kind = FD_RPC_VAL_NONE }};
    1493           0 :     fd_rpc_val_t _length[1] = {{ .kind = FD_RPC_VAL_NONE }};
    1494           0 :     ulong        slice_cnt  = 0UL;
    1495           0 :     if( dataSlice->kind==FD_RPC_VAL_MAP ) {
    1496           0 :       fd_jtok_t j[1];
    1497           0 :       fd_jtok_str_t key;
    1498           0 :       fd_jtok_obj_enter( fd_jtok_init( j, dataSlice->raw, dataSlice->raw_sz ) );
    1499           0 :       while( fd_jtok_obj_next( j, &key ) ) {
    1500           0 :         if(      fd_jtok_str_eq( &key, "offset" ) ) { if( _offset->kind==FD_RPC_VAL_NONE ) fd_rpc_val_read( j, _offset ); }
    1501           0 :         else if( fd_jtok_str_eq( &key, "length" ) ) { if( _length->kind==FD_RPC_VAL_NONE ) fd_rpc_val_read( j, _length ); }
    1502           0 :       }
    1503           0 :     } else if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_SEQ ) ) {
    1504           0 :       fd_jtok_t j[1];
    1505           0 :       fd_jtok_arr_enter( fd_jtok_init( j, dataSlice->raw, dataSlice->raw_sz ) );
    1506           0 :       while( fd_jtok_arr_next( j ) ) {
    1507           0 :         if(      slice_cnt==0UL ) fd_rpc_val_read( j, _offset );
    1508           0 :         else if( slice_cnt==1UL ) fd_rpc_val_read( j, _length );
    1509           0 :         slice_cnt++;
    1510           0 :       }
    1511           0 :     }
    1512             : 
    1513           0 :     if( FD_UNLIKELY( _offset->kind==FD_RPC_VAL_BOOL || _offset->kind==FD_RPC_VAL_FLOAT ) ) {
    1514           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _offset, "usize" );
    1515           0 :       return 0;
    1516           0 :     }
    1517           0 :     if( FD_UNLIKELY( _length->kind==FD_RPC_VAL_BOOL || _length->kind==FD_RPC_VAL_FLOAT ) ) {
    1518           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _length, "usize" );
    1519           0 :       return 0;
    1520           0 :     }
    1521             : 
    1522           0 :     if( FD_UNLIKELY( _offset->kind==FD_RPC_VAL_INT && _offset->neg ) ) {
    1523           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "value", _offset, "usize" );
    1524           0 :       return 0;
    1525           0 :     }
    1526           0 :     if( FD_UNLIKELY( _length->kind==FD_RPC_VAL_INT && _length->neg ) ) {
    1527           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "value", _length, "usize" );
    1528           0 :       return 0;
    1529           0 :     }
    1530             : 
    1531           0 :     if( FD_UNLIKELY( _offset->kind==FD_RPC_VAL_STR ) ) {
    1532           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _offset, "usize" );
    1533           0 :       return 0;
    1534           0 :     }
    1535           0 :     if( FD_UNLIKELY( _length->kind==FD_RPC_VAL_STR ) ) {
    1536           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _length, "usize" );
    1537           0 :       return 0;
    1538           0 :     }
    1539             : 
    1540           0 :     if( FD_UNLIKELY( _offset->kind!=FD_RPC_VAL_NONE && _offset->kind!=FD_RPC_VAL_INT ) ) {
    1541           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _offset, "usize" );
    1542           0 :       return 0;
    1543           0 :     }
    1544           0 :     if( FD_UNLIKELY( _length->kind!=FD_RPC_VAL_NONE && _length->kind!=FD_RPC_VAL_INT ) ) {
    1545           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _length, "usize" );
    1546           0 :       return 0;
    1547           0 :     }
    1548             : 
    1549           0 :     if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_MAP && _offset->kind==FD_RPC_VAL_NONE ) ) {
    1550           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `offset`.\"},\"id\":%s}\n", id_cstr );
    1551           0 :       return 0;
    1552           0 :     }
    1553           0 :     if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_MAP && _length->kind==FD_RPC_VAL_NONE ) ) {
    1554           0 :       *res = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `length`.\"},\"id\":%s}\n", id_cstr );
    1555           0 :       return 0;
    1556           0 :     }
    1557             : 
    1558           0 :     if( FD_UNLIKELY( dataSlice->kind==FD_RPC_VAL_SEQ && slice_cnt!=2UL ) ) {
    1559           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", slice_cnt, id_cstr );
    1560           0 :       return 0;
    1561           0 :     }
    1562             : 
    1563           0 :     if( dataSlice->kind==FD_RPC_VAL_MAP || dataSlice->kind==FD_RPC_VAL_SEQ ) {
    1564           0 :       if( FD_LIKELY( opt_slice_offset ) ) *opt_slice_offset = _offset->u;
    1565           0 :       if( FD_LIKELY( opt_slice_length ) ) *opt_slice_length = _length->u;
    1566           0 :     } else {
    1567           0 :       if( FD_LIKELY( opt_slice_offset ) ) *opt_slice_offset = 0UL;
    1568           0 :       if( FD_LIKELY( opt_slice_length ) ) *opt_slice_length = ULONG_MAX;
    1569           0 :     }
    1570           0 :   }
    1571             : 
    1572           0 :   if( FD_LIKELY( has_min_context_slot ) ) {
    1573           0 :     fd_rpc_val_t const * _minContextSlot = &cfg->min_context_slot;
    1574           0 :     if( FD_UNLIKELY( _minContextSlot->kind==FD_RPC_VAL_BOOL || _minContextSlot->kind==FD_RPC_VAL_FLOAT ) ) {
    1575           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _minContextSlot, "u64" );
    1576           0 :       return 0;
    1577           0 :     }
    1578             : 
    1579           0 :     if( FD_UNLIKELY( _minContextSlot->kind==FD_RPC_VAL_INT && _minContextSlot->neg ) ) {
    1580           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "value", _minContextSlot, "u64" );
    1581           0 :       return 0;
    1582           0 :     }
    1583             : 
    1584           0 :     if( FD_UNLIKELY( _minContextSlot->kind==FD_RPC_VAL_STR ) ) {
    1585           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _minContextSlot, "u64" );
    1586           0 :       return 0;
    1587           0 :     }
    1588             : 
    1589           0 :     if( FD_UNLIKELY( _minContextSlot->kind==FD_RPC_VAL_MAP || _minContextSlot->kind==FD_RPC_VAL_SEQ ) ) {
    1590           0 :       *res = fd_rpc_err_invalid( ctx, id_cstr, "type", _minContextSlot, "u64" );
    1591           0 :       return 0;
    1592           0 :     }
    1593             : 
    1594           0 :     ulong minContextSlot = _minContextSlot->kind==FD_RPC_VAL_INT ? _minContextSlot->u : 0UL;
    1595             : 
    1596           0 :     if( _bank_idx!=ULONG_MAX && ctx->banks[ _bank_idx ].slot<minContextSlot ) {
    1597           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 );
    1598           0 :       return 0;
    1599           0 :     }
    1600           0 :   }
    1601             : 
    1602           0 :   return 1;
    1603           0 : }
    1604             : 
    1605             : static int
    1606             : fd_rpc_validate_address( fd_rpc_tile_t *             ctx,
    1607             :                          char const *                id_cstr,
    1608             :                          fd_rpc_val_t const *        address_in,
    1609             :                          fd_pubkey_t *               address_out,
    1610           0 :                          fd_http_server_response_t * response ) {
    1611           0 :   if( FD_UNLIKELY( address_in->kind!=FD_RPC_VAL_STR ) ) {
    1612           0 :     *response = fd_rpc_err_invalid( ctx, id_cstr, "type", address_in, "a string" );
    1613           0 :     return 0;
    1614           0 :   }
    1615           0 :   char address_cstr[ FD_BASE58_ENCODED_32_SZ ];
    1616           0 :   fd_jtok_t s[1];
    1617           0 :   fd_jtok_cstr( fd_jtok_init( s, address_in->raw, address_in->raw_sz ), address_cstr, sizeof(address_cstr) );
    1618           0 :   int fits = !fd_jtok_err( s );
    1619           0 :   int invalid_char;
    1620           0 :   if( FD_LIKELY( fits ) ) invalid_char = fd_rpc_cstr_contains_non_base58( address_cstr );
    1621           0 :   else {
    1622             :     /* Too long to be a pubkey.  A string of only base58 characters is
    1623             :        still WrongSize rather than Invalid. */
    1624           0 :     invalid_char = 0;
    1625           0 :     for( ulong i=0UL; i<address_in->str.sz; i++ ) {
    1626           0 :       if( !strchr( base58_chars, address_in->str.ptr[ i ] ) ) { invalid_char = 1; break; }
    1627           0 :     }
    1628           0 :   }
    1629           0 :   if( FD_UNLIKELY( invalid_char ) ) {
    1630           0 :     *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: Invalid\"},\"id\":%s}\n", id_cstr );
    1631           0 :     return 0;
    1632           0 :   }
    1633           0 :   int valid = fits && !!fd_base58_decode_32( address_cstr, address_out->uc );
    1634           0 :   if( FD_UNLIKELY( !valid ) ) {
    1635           0 :     *response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid param: WrongSize\"},\"id\":%s}\n", id_cstr );
    1636           0 :     return 0;
    1637           0 :   }
    1638             : 
    1639           0 :   return 1;
    1640           0 : }
    1641             : 
    1642             : static inline int
    1643             : fd_rpc_validate_uint_param( fd_rpc_tile_t *             ctx,
    1644             :                             char const *                id_cstr,
    1645             :                             fd_rpc_val_t const *        val,
    1646             :                             char const *                type_cstr,
    1647             :                             ulong *                     out,
    1648           0 :                             fd_http_server_response_t * res ) {
    1649           0 :   if( FD_UNLIKELY( val->kind==FD_RPC_VAL_BOOL || val->kind==FD_RPC_VAL_FLOAT ) ) {
    1650           0 :     *res = fd_rpc_err_invalid( ctx, id_cstr, "type", val, type_cstr );
    1651           0 :     return 0;
    1652           0 :   }
    1653           0 :   if( FD_UNLIKELY( val->kind==FD_RPC_VAL_INT && val->neg ) ) {
    1654           0 :     *res = fd_rpc_err_invalid( ctx, id_cstr, "value", val, type_cstr );
    1655           0 :     return 0;
    1656           0 :   }
    1657           0 :   if( FD_UNLIKELY( val->kind!=FD_RPC_VAL_INT ) ) {
    1658           0 :     *res = fd_rpc_err_invalid( ctx, id_cstr, "type", val, type_cstr );
    1659           0 :     return 0;
    1660           0 :   }
    1661             : 
    1662           0 :   *out = val->u;
    1663           0 :   return 1;
    1664           0 : }
    1665             : 
    1666             : #define UNIMPLEMENTED(X)                               \
    1667             : static fd_http_server_response_t                       \
    1668             : X( fd_rpc_tile_t *         ctx,                        \
    1669             :    char const *            id_cstr,                    \
    1670           0 :    fd_rpc_params_t const * params ) {                     \
    1671           0 :   (void)ctx; (void)id_cstr; (void)params;                 \
    1672           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_UNIMPLEMENTED, 1UL ); \
    1673           0 :   return (fd_http_server_response_t){ .status = 501 };    \
    1674           0 : }
    1675             : 
    1676             : UNIMPLEMENTED(getBlock)
    1677             : UNIMPLEMENTED(getBlockCommitment)
    1678             : 
    1679             : /* fd_rpc_encode_account_data encodes account data fields
    1680             :    (executable, lamports, owner, rentEpoch, space, data) into the http
    1681             :    staging buffer.  Returns 1 on success.  On failure, calls
    1682             :    fd_http_server_unstage and writes an error response into
    1683             :    err_response. */
    1684             : static int
    1685             : fd_rpc_encode_account_data( fd_rpc_tile_t *             ctx,
    1686             :                             uchar const *               acct_data,
    1687             :                             ulong                       acct_data_len,
    1688             :                             uchar const *               acct_owner,
    1689             :                             ulong                       acct_lamports,
    1690             :                             int                         acct_executable,
    1691             :                             char const *                encoding_cstr,
    1692             :                             ulong                       slice_offset,
    1693             :                             ulong                       slice_length,
    1694             :                             char const *                id_cstr,
    1695           0 :                             fd_http_server_response_t * err_response ) {
    1696             : 
    1697           0 :   int is_binary = !strcmp( encoding_cstr, "binary" );
    1698           0 :   int is_base58 = !strcmp( encoding_cstr, "base58" );
    1699           0 :   int is_zstd   = !strcmp( encoding_cstr, "base64+zstd" );
    1700             : 
    1701           0 :   ulong data_sz        = acct_data_len;
    1702           0 :   uchar const * out    = acct_data + fd_ulong_if( slice_offset<data_sz, slice_offset, 0UL );
    1703           0 :   ulong         snip_sz = fd_ulong_min( fd_ulong_if( slice_offset<data_sz, data_sz-slice_offset, 0UL ), slice_length );
    1704           0 :   ulong         out_sz  = snip_sz;
    1705             : 
    1706           0 :   if( FD_UNLIKELY( (is_binary || is_base58) && snip_sz>128UL ) ) {
    1707           0 :     fd_http_server_unstage( ctx->http );
    1708           0 :     *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Encoded binary (base 58) data should be less than 128 bytes, please use Base64 encoding.\"},\"id\":%s}\n", id_cstr );
    1709           0 :     return 0;
    1710           0 :   }
    1711             : 
    1712           0 :   if( is_zstd ) {
    1713           0 :     ulong zstd_res = ZSTD_compressCCtx( ctx->zstd_cctx, ctx->compress_buf, sizeof(ctx->compress_buf), out, snip_sz, FD_RPC_ZSTD_LEVEL );
    1714           0 :     if( ZSTD_isError( zstd_res ) ) {
    1715           0 :       fd_http_server_unstage( ctx->http );
    1716           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 );
    1717           0 :       return 0;
    1718           0 :     }
    1719           0 :     out    = ctx->compress_buf;
    1720           0 :     out_sz = (ulong)zstd_res;
    1721           0 :   }
    1722             : 
    1723           0 :   FD_BASE58_ENCODE_32_BYTES( acct_owner, owner_b58 );
    1724           0 :   fd_http_server_printf( ctx->http,
    1725           0 :       "{\"executable\":%s,\"lamports\":%lu,\"owner\":\"%s\",\"rentEpoch\":18446744073709551615,\"space\":%lu,\"data\":",
    1726           0 :       acct_executable ? "true" : "false",
    1727           0 :       acct_lamports,
    1728           0 :       owner_b58,
    1729           0 :       data_sz );
    1730             : 
    1731           0 :   ulong encoded_sz = fd_ulong_if( is_base58 || is_binary, FD_RPC_BASE58_ENCODED_128_LEN, FD_BASE64_ENC_SZ( out_sz ) );
    1732           0 :   if( FD_UNLIKELY( is_binary ) ) {
    1733           0 :     fd_http_server_printf( ctx->http, "\"" );
    1734           0 :   } else {
    1735           0 :     fd_http_server_printf( ctx->http, "[\"" );
    1736           0 :   }
    1737             : 
    1738           0 :   uchar * encoded = fd_http_server_append_start( ctx->http, encoded_sz );
    1739           0 :   if( FD_UNLIKELY( !encoded ) ) {
    1740           0 :     fd_http_server_unstage( ctx->http );
    1741           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 );
    1742           0 :     return 0;
    1743           0 :   }
    1744             : 
    1745           0 :   if( FD_UNLIKELY( is_base58 || is_binary ) ) {
    1746           0 :     if( FD_UNLIKELY( !fd_rpc_base58_encode_128( (char *)encoded, &encoded_sz, out, out_sz ) ) ) {
    1747           0 :       fd_http_server_unstage( ctx->http );
    1748           0 :       FD_LOG_WARNING(( "base58 encode failed out_sz=%lu", out_sz ));
    1749           0 :       *err_response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: base58 encode failed\"},\"id\":%s}\n", id_cstr );
    1750           0 :       return 0;
    1751           0 :     }
    1752           0 :   } else {
    1753           0 :     encoded_sz = fd_base64_encode( (char *)encoded, out, out_sz );
    1754           0 :   }
    1755             : 
    1756           0 :   fd_http_server_append_end( ctx->http, encoded_sz );
    1757             : 
    1758           0 :   if( FD_UNLIKELY( is_binary ) ) fd_http_server_printf( ctx->http, "\"}" );
    1759           0 :   else                           fd_http_server_printf( ctx->http, "\",\"%s\"]}", encoding_cstr );
    1760             : 
    1761           0 :   return 1;
    1762           0 : }
    1763             : 
    1764             : static fd_http_server_response_t
    1765             : getAccountInfo( fd_rpc_tile_t *         ctx,
    1766             :                 char const *            id_cstr,
    1767           0 :                 fd_rpc_params_t const * params ) {
    1768           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_ACCOUNT_INFO, 1UL );
    1769             : 
    1770           0 :   fd_http_server_response_t response;
    1771           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 2, &response ) ) ) return response;
    1772             : 
    1773           0 :   fd_pubkey_t address;
    1774           0 :   fd_rpc_val_t const * acct_pubkey = &params->v[ 0 ];
    1775           0 :   if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id_cstr, acct_pubkey, &address, &response ) ) ) return response;
    1776             : 
    1777           0 :   ulong bank_idx = ULONG_MAX;
    1778           0 :   char const * encoding_cstr = NULL;
    1779           0 :   ulong slice_length = ULONG_MAX;
    1780           0 :   ulong slice_offset = 0;
    1781           0 :   fd_rpc_val_t const * config = &params->v[ 1 ];
    1782           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcAccountInfoConfig",
    1783           0 :                                              1, /* has_commitment */
    1784           0 :                                              1, /* has_encoding */
    1785           0 :                                              1, /* has_data_slice */
    1786           0 :                                              1, /* has_min_context_slot */
    1787           0 :                                              &bank_idx,
    1788           0 :                                              &encoding_cstr,
    1789           0 :                                              &slice_length,
    1790           0 :                                              &slice_offset,
    1791           0 :                                              NULL,
    1792           0 :                                              &response );
    1793           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1794             : 
    1795           0 :   bank_info_t * info = &ctx->banks[ bank_idx ];
    1796           0 :   ulong acct_lamports;
    1797           0 :   int   acct_executable;
    1798           0 :   uchar acct_owner[ 32UL ];
    1799           0 :   ulong acct_data_len;
    1800           0 :   fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, address.uc,
    1801           0 :                              &acct_lamports, &acct_executable, acct_owner,
    1802           0 :                              ctx->scratch.accdb_data_buf, &acct_data_len );
    1803           0 :   if( FD_UNLIKELY( !acct_lamports ) ) {
    1804           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":%lu},\"value\":null},\"id\":%s}\n", info->slot, id_cstr );
    1805           0 :   }
    1806             : 
    1807           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 );
    1808             : 
    1809           0 :   fd_http_server_response_t err_response;
    1810           0 :   if( FD_UNLIKELY( !fd_rpc_encode_account_data( ctx, ctx->scratch.accdb_data_buf, acct_data_len, acct_owner, acct_lamports, acct_executable, encoding_cstr, slice_offset, slice_length, id_cstr, &err_response ) ) ) {
    1811           0 :     return err_response;
    1812           0 :   }
    1813             : 
    1814           0 :   fd_http_server_printf( ctx->http, "}}\n" );
    1815           0 :   return STAGE_JSON( ctx );
    1816           0 : }
    1817             : 
    1818             : static fd_http_server_response_t
    1819             : getBalance( fd_rpc_tile_t *         ctx,
    1820             :             char const *            id_cstr,
    1821           0 :             fd_rpc_params_t const * params ) {
    1822           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_BALANCE, 1UL );
    1823             : 
    1824           0 :   fd_http_server_response_t response;
    1825           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 2, &response ) ) ) return response;
    1826             : 
    1827           0 :   fd_pubkey_t address;
    1828           0 :   fd_rpc_val_t const * acct_pubkey = &params->v[ 0 ];
    1829           0 :   if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id_cstr, acct_pubkey, &address, &response ) ) ) return response;
    1830             : 
    1831           0 :   ulong bank_idx = ULONG_MAX;
    1832           0 :   fd_rpc_val_t const * config = &params->v[ 1 ];
    1833           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
    1834           0 :                                              1, /* has_commitment */
    1835           0 :                                              0, /* has_encoding */
    1836           0 :                                              0, /* has_data_slice */
    1837           0 :                                              1, /* has_min_context_slot */
    1838           0 :                                              &bank_idx,
    1839           0 :                                              NULL,
    1840           0 :                                              NULL,
    1841           0 :                                              NULL,
    1842           0 :                                              NULL,
    1843           0 :                                              &response );
    1844           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1845             : 
    1846           0 :   ulong balance = fd_accdb_lamports( ctx->accdb, ctx->banks[ bank_idx ].accdb_fork_id, address.uc );
    1847             : 
    1848           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 );
    1849           0 : }
    1850             : 
    1851             : static fd_http_server_response_t
    1852             : getBlockHeight( fd_rpc_tile_t *         ctx,
    1853             :                 char const *            id_cstr,
    1854           0 :                 fd_rpc_params_t const * params ) {
    1855           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_BLOCK_HEIGHT, 1UL );
    1856             : 
    1857           0 :   fd_http_server_response_t response;
    1858           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
    1859             : 
    1860           0 :   ulong bank_idx = ULONG_MAX;
    1861           0 :   fd_rpc_val_t const * config = &params->v[ 0 ];
    1862           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
    1863           0 :                                              1, /* has_commitment */
    1864           0 :                                              0, /* has_encoding */
    1865           0 :                                              0, /* has_data_slice */
    1866           0 :                                              1, /* has_min_context_slot */
    1867           0 :                                              &bank_idx,
    1868           0 :                                              NULL,
    1869           0 :                                              NULL,
    1870           0 :                                              NULL,
    1871           0 :                                              NULL,
    1872           0 :                                              &response );
    1873           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1874             : 
    1875           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", ctx->banks[ bank_idx ].block_height, id_cstr );
    1876           0 : }
    1877             : 
    1878             : UNIMPLEMENTED(getBlockProduction) // TODO: Used by solana-exporter
    1879             : UNIMPLEMENTED(getBlocks)
    1880             : UNIMPLEMENTED(getBlocksWithLimit)
    1881             : UNIMPLEMENTED(getBlockTime)
    1882             : 
    1883             : static fd_http_server_response_t
    1884             : getClusterNodes( fd_rpc_tile_t *         ctx,
    1885             :                  char const *            id_cstr,
    1886           0 :                  fd_rpc_params_t const * params ) {
    1887           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_CLUSTER_NODES, 1UL );
    1888             : 
    1889           0 :   fd_http_server_response_t response;
    1890           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    1891             : 
    1892           0 :   fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":[" );
    1893             : 
    1894           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 );
    1895           0 :        !fd_rpc_cluster_node_dlist_iter_done( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes );
    1896           0 :        iter = fd_rpc_cluster_node_dlist_iter_rev_next( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes ) ) {
    1897           0 :     fd_rpc_cluster_node_t * ele = fd_rpc_cluster_node_dlist_iter_ele( iter, ctx->cluster_nodes_dlist, ctx->cluster_nodes );
    1898           0 :     FD_BASE58_ENCODE_32_BYTES( ele->identity.uc, identity_cstr );
    1899           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 );
    1900             : 
    1901           0 :     fd_http_server_printf( ctx->http, "{\"featureSet\":%u,", ele->ci->version.feature_set );
    1902             : 
    1903           0 :     for( ulong i=0UL; i<FD_GOSSIP_CONTACT_INFO_SOCKET_CNT; i++ ) {
    1904           0 :       char const * name;
    1905           0 :       switch( i ) {
    1906           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP:            name = "gossip"; break;
    1907           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR_QUIC: name = NULL; break;
    1908           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_RPC:               name = "rpc"; break;
    1909           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_RPC_PUBSUB:        name = "pubsub"; break;
    1910           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR:      name = "serveRepair"; break;
    1911             :         /* Even though Agave does not support "tpu" and "tpuForwards"
    1912             :            (hardcoded null), Firedancer still supports
    1913             :            UDP-TPU. */
    1914           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU:               name = "tpu"; break;
    1915           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_FORWARDS:      name = "tpuForwards"; break;
    1916           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_FORWARDS_QUIC: name = "tpuForwardsQuic"; break;
    1917           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_QUIC:          name = "tpuQuic"; break;
    1918           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE:          name = "tpuVote"; break;
    1919           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TVU:               name = "tvu"; break;
    1920           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TVU_QUIC:          name = NULL; break;
    1921           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE_QUIC:     name = NULL; break;
    1922           0 :         case FD_GOSSIP_CONTACT_INFO_SOCKET_ALPENGLOW:         name = NULL; break;
    1923           0 :         default: FD_LOG_ERR(( "unreachable "));
    1924           0 :       }
    1925           0 :       if( FD_UNLIKELY( !name ) ) continue;
    1926             : 
    1927           0 :       uint ip4 = ele->ci->sockets[ i ].is_ipv6 ? 0U : ele->ci->sockets[ i ].ip4;
    1928           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 ) );
    1929           0 :       else                                                     fd_http_server_printf( ctx->http, "\"%s\":null,", name );
    1930           0 :     }
    1931           0 :     fd_http_server_printf( ctx->http, "\"pubkey\":\"%s\",", identity_cstr );
    1932           0 :     fd_http_server_printf( ctx->http, "\"shredVersion\":%u,", ele->ci->shred_version );
    1933             : 
    1934           0 :     char version[ 64UL ];
    1935           0 :     FD_TEST( fd_gossip_version_cstr( ele->ci->version.major, ele->ci->version.minor, ele->ci->version.patch, version, sizeof( version ) ) );
    1936           0 :     fd_http_server_printf( ctx->http, "\"version\":\"%s\",", version );
    1937             : 
    1938           0 :     char const * client_id;
    1939           0 :     switch( ele->ci->version.client ) {
    1940           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_SOLANA_LABS:            client_id = "SolanaLabs";              break;
    1941           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_JITO_LABS:              client_id = "JitoLabs";                break;
    1942           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_FRANKENDANCER:          client_id = "Frankendancer";           break;
    1943           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE:                  client_id = "Agave";                   break;
    1944           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE_PALADIN:          client_id = "AgavePaladin";            break;
    1945           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_FIREDANCER:             client_id = "Firedancer";              break;
    1946           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_AGAVE_BAM:              client_id = "AgaveBam";                break;
    1947           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_SIG:                    client_id = "Sig";                     break;
    1948           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_RAKURAI:                client_id = "Rakurai";                 break;
    1949           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_HARMONIC_FIREDANCER:    client_id = "HarmonicFiredancer";      break;
    1950           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_HARMONIC_AGAVE:         client_id = "HarmonicAgave";           break;
    1951           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_HARMONIC_FRANKENDANCER: client_id = "HarmonicFrankendancer";  break;
    1952           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_FIRE_BAM:               client_id = "FireBAM";                 break;
    1953           0 :       case FD_GOSSIP_CONTACT_INFO_CLIENT_RAIKU:                  client_id = "Raiku";                   break;
    1954           0 :       default:                                                   client_id = NULL;                      break;
    1955           0 :     }
    1956           0 :     if( FD_LIKELY( client_id ) ) fd_http_server_printf( ctx->http, "\"clientId\":\"%s\"", client_id );
    1957           0 :     else                         fd_http_server_printf( ctx->http, "\"clientId\":\"Unknown(%hu)\"", ele->ci->version.client );
    1958             : 
    1959           0 :     if( FD_UNLIKELY( is_last ) ) fd_http_server_printf( ctx->http, "}" );
    1960           0 :     else                         fd_http_server_printf( ctx->http, "}," );
    1961           0 :   }
    1962             : 
    1963           0 :   fd_http_server_printf( ctx->http, "],\"id\":%s}\n", id_cstr );
    1964           0 :   return STAGE_JSON( ctx );
    1965           0 : }
    1966             : 
    1967             : static fd_http_server_response_t
    1968             : getEpochInfo( fd_rpc_tile_t *         ctx,
    1969             :               char const *            id_cstr,
    1970           0 :               fd_rpc_params_t const * params ) {
    1971           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_EPOCH_INFO, 1UL );
    1972             : 
    1973           0 :   fd_http_server_response_t response;
    1974           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
    1975             : 
    1976           0 :   ulong bank_idx = ULONG_MAX;
    1977           0 :   fd_rpc_val_t const * config = &params->v[ 0 ];
    1978           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
    1979           0 :                                               1, /* has_commitment */
    1980           0 :                                               0, /* has_encoding */
    1981           0 :                                               0, /* has_data_slice */
    1982           0 :                                               1, /* has_min_context_slot */
    1983           0 :                                               &bank_idx,
    1984           0 :                                               NULL,
    1985           0 :                                               NULL,
    1986           0 :                                               NULL,
    1987           0 :                                               NULL,
    1988           0 :                                               &response );
    1989           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    1990             : 
    1991           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 );
    1992           0 : }
    1993             : 
    1994             : UNIMPLEMENTED(getEpochSchedule)
    1995             : UNIMPLEMENTED(getFeeForMessage)
    1996             : UNIMPLEMENTED(getFirstAvailableBlock) // TODO: Used by solana-exporter
    1997             : 
    1998             : /* Get the genesis hash of the cluster.  Firedancer deviates slightly
    1999             :    from Agave here, as the genesis hash is not always known when RPC
    2000             :    is first booted, it may need to be determined asynchronously in the
    2001             :    background, fetched from a peer node.  If the genesis hash is not yet
    2002             :    known, we return an error indicating no snapshot is available. */
    2003             : 
    2004             : static fd_http_server_response_t
    2005             : getGenesisHash( fd_rpc_tile_t *         ctx,
    2006             :                 char const *            id_cstr,
    2007           0 :                 fd_rpc_params_t const * params ) {
    2008           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_GENESIS_HASH, 1UL );
    2009             : 
    2010           0 :   fd_http_server_response_t response;
    2011           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    2012             : 
    2013           0 :   if( FD_UNLIKELY( !ctx->has_genesis_hash ) ) {
    2014           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 );
    2015           0 :   }
    2016             : 
    2017           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, genesis_hash_b58 );
    2018           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%s}\n", genesis_hash_b58, id_cstr );
    2019           0 : }
    2020             : 
    2021             : /* getAgGenesisCert returns the alpenglow genesis certificate, or null
    2022             :    if Tower consensus is active still.
    2023             : 
    2024             :    The account holds a wincode encoded WireBlockCertMessage,
    2025             : 
    2026             :      u64   block.slot
    2027             :      [u8;32]  block.block_id
    2028             :      [u8;192] signature.signature   (uncompressed BLS signature)
    2029             :      u64   signature.bitmap_len
    2030             :      [u8]  signature.bitmap
    2031             : 
    2032             :    and the result is that struct as JSON, with the fixed size byte
    2033             :    arrays and the bitmap encoded as arrays of numbers, or null if the
    2034             :    account is empty or does not exist. */
    2035             : 
    2036           0 : #define FD_RPC_AG_GENESIS_CERT_HDR_SZ (8UL+32UL+FD_BLS_SIG_SZ+8UL)
    2037             : 
    2038             : static fd_http_server_response_t
    2039             : getAgGenesisCert( fd_rpc_tile_t *         ctx,
    2040             :                   char const *            id_cstr,
    2041           0 :                   fd_rpc_params_t const * params ) {
    2042           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_AG_GENESIS_CERT, 1UL );
    2043             : 
    2044           0 :   fd_http_server_response_t response;
    2045           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    2046             : 
    2047           0 :   if( FD_UNLIKELY( ctx->finalized_idx==ULONG_MAX ) ) {
    2048           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
    2049           0 :   }
    2050             : 
    2051           0 :   bank_info_t * info = &ctx->banks[ ctx->finalized_idx ];
    2052           0 :   ulong acct_lamports;
    2053           0 :   int   acct_executable;
    2054           0 :   uchar acct_owner[ 32UL ];
    2055           0 :   ulong acct_data_len;
    2056           0 :   fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, ctx->ag_genesis_cert_addr->uc,
    2057           0 :                              &acct_lamports, &acct_executable, acct_owner,
    2058           0 :                              ctx->scratch.accdb_data_buf, &acct_data_len );
    2059           0 :   if( FD_UNLIKELY( !acct_lamports || !acct_data_len ) ) {
    2060           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":%s}\n", id_cstr );
    2061           0 :   }
    2062             : 
    2063           0 :   uchar const * data       = ctx->scratch.accdb_data_buf;
    2064           0 :   ulong         bitmap_sz  = 0UL;
    2065           0 :   int           malformed  = acct_data_len<FD_RPC_AG_GENESIS_CERT_HDR_SZ;
    2066           0 :   if( FD_LIKELY( !malformed ) ) {
    2067           0 :     bitmap_sz = FD_LOAD( ulong, data+FD_RPC_AG_GENESIS_CERT_HDR_SZ-8UL );
    2068           0 :     malformed = bitmap_sz>acct_data_len-FD_RPC_AG_GENESIS_CERT_HDR_SZ;
    2069           0 :   }
    2070           0 :   if( FD_UNLIKELY( malformed ) ) {
    2071           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32603,\"message\":\"Firedancer Error: malformed genesis certificate\"},\"id\":%s}\n", id_cstr );
    2072           0 :   }
    2073             : 
    2074           0 :   uchar const * block_id  = data+8UL;
    2075           0 :   uchar const * signature = block_id+32UL;
    2076           0 :   uchar const * bitmap    = data+FD_RPC_AG_GENESIS_CERT_HDR_SZ;
    2077             : 
    2078           0 :   fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":{\"block\":{\"slot\":%lu,\"blockId\":[", FD_LOAD( ulong, data ) );
    2079           0 :   for( ulong i=0UL; i<32UL;         i++ ) fd_http_server_printf( ctx->http, i ? ",%u" : "%u", (uint)block_id [ i ] );
    2080           0 :   fd_http_server_printf( ctx->http, "]},\"signature\":{\"signature\":[" );
    2081           0 :   for( ulong i=0UL; i<FD_BLS_SIG_SZ; i++ ) fd_http_server_printf( ctx->http, i ? ",%u" : "%u", (uint)signature[ i ] );
    2082           0 :   fd_http_server_printf( ctx->http, "],\"bitmap\":[" );
    2083           0 :   for( ulong i=0UL; i<bitmap_sz;    i++ ) fd_http_server_printf( ctx->http, i ? ",%u" : "%u", (uint)bitmap   [ i ] );
    2084           0 :   fd_http_server_printf( ctx->http, "]}},\"id\":%s}\n", id_cstr );
    2085           0 :   return STAGE_JSON( ctx );
    2086           0 : }
    2087             : 
    2088             : /* Determines if the node is healthy.  Agave defines this as follows,
    2089             : 
    2090             :     - On boot, nodes must go through the entire snapshot slot database
    2091             :       and hash everything, and once it's done verify the hash matches.
    2092             :       While this is ongoing, the node is unhealthy with a "slotsBehind"
    2093             :       value of null.
    2094             : 
    2095             :     - On boot, if the cluster is restarting and we are currently waiting
    2096             :       for a supermajority of stake to join gossip to proceed with
    2097             :       booting, the node is forcibly marked as healthy, to, per Agave,
    2098             : 
    2099             :        > prevent load balancers from removing the node from their list
    2100             :        > of candidates during a manual restart
    2101             : 
    2102             :     - In addition, once booted, there is a period where we do not yet
    2103             :       know the cluster confirmed slot, because we have not yet observed
    2104             :       any (or enough) votes arrive from peers in the cluster.  During
    2105             :       this period the node is unhealthy with a "slotsBehind" value of
    2106             :       null.
    2107             : 
    2108             :     - Finally, once the cluster confirmed slot is known, which is the
    2109             :       highest optimistically confirmed slot observed from both gossip,
    2110             :       and votes processed in blocks, it is compared to our own
    2111             :       optimistically confirmed slot, which is just the highest slot down
    2112             :       the cluster confirmed fork that we have finished replaying
    2113             :       locally.  The difference between these two slots is compared, and
    2114             :       if it is less than or equal to 128, the node is healthy, otherwise
    2115             :       it is unhealthy with a "slotsBehind" value equal to the
    2116             :       difference.
    2117             : 
    2118             :    Under alpenglow there are no optimistic confirmations.  The cluster
    2119             :    slot is instead the highest slot for which votor has observed a
    2120             :    finalization certificate (slow or fast), and the node stays Unknown
    2121             :    until it has seen one.
    2122             : 
    2123             :    Firedancer currently only implements the final two checks, and does
    2124             :    not forcibly mark the node as healthy while waiting for a
    2125             :    supermajority, nor does it mark a node as unhealthy while hashing the
    2126             :    snapshot database on boot.  Firedancer hashes snapshots so quickly
    2127             :    that the node will die on boot if the hash is not valid. */
    2128             : 
    2129             : static inline int
    2130           0 : _getHealth( fd_rpc_tile_t * ctx ) {
    2131             :   /* Requests are not served until RPC has initialized banks */
    2132           0 :   if( FD_UNLIKELY( ctx->confirmed_idx==ULONG_MAX ) ) return FD_RPC_HEALTH_STATUS_UNKNOWN;
    2133           0 :   if( FD_UNLIKELY( ctx->cluster_confirmed_slot==ULONG_MAX ) ) return FD_RPC_HEALTH_STATUS_UNKNOWN;
    2134             : 
    2135           0 :   ulong slots_behind = fd_ulong_sat_sub( ctx->cluster_confirmed_slot, ctx->banks[ ctx->confirmed_idx ].slot );
    2136           0 :   if( FD_LIKELY( slots_behind<=FD_RPC_HEALTH_CHECK_SLOT_DISTANCE ) ) return FD_RPC_HEALTH_STATUS_OK;
    2137           0 :   else                                                               return FD_RPC_HEALTH_STATUS_BEHIND;
    2138           0 : }
    2139             : 
    2140             : static fd_http_server_response_t
    2141             : getHealth( fd_rpc_tile_t *         ctx,
    2142             :            char const *            id_cstr,
    2143           0 :            fd_rpc_params_t const * params ) {
    2144           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_HEALTH, 1UL );
    2145             : 
    2146           0 :   fd_http_server_response_t response;
    2147           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    2148             : 
    2149             :   // TODO: We should probably implement the same waiting_for_supermajority
    2150             :   // logic to conform with Agave here.
    2151             : 
    2152           0 :   int health_status = _getHealth( ctx );
    2153             : 
    2154           0 :   switch( health_status ) {
    2155           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 );
    2156           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 );
    2157           0 :     case FD_RPC_HEALTH_STATUS_OK:      return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"ok\",\"id\":%s}\n", id_cstr );
    2158           0 :     default: FD_LOG_ERR(( "unknown health status" ));
    2159           0 :   }
    2160           0 : }
    2161             : 
    2162             : UNIMPLEMENTED(getHighestSnapshotSlot)
    2163             : 
    2164             : static fd_http_server_response_t
    2165             : getIdentity( fd_rpc_tile_t *         ctx,
    2166             :              char const *            id_cstr,
    2167           0 :              fd_rpc_params_t const * params ) {
    2168           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_IDENTITY, 1UL );
    2169             : 
    2170           0 :   fd_http_server_response_t response;
    2171           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    2172             : 
    2173           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->identity_pubkey, identity_pubkey_b58 );
    2174           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":{\"identity\":\"%s\"},\"id\":%s}\n", identity_pubkey_b58, id_cstr );
    2175           0 : }
    2176             : 
    2177             : static fd_http_server_response_t
    2178             : getInflationGovernor( fd_rpc_tile_t *         ctx,
    2179             :                       char const *            id_cstr,
    2180           0 :                       fd_rpc_params_t const * params ) {
    2181           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_INFLATION_GOVERNOR, 1UL );
    2182             : 
    2183           0 :   fd_http_server_response_t response;
    2184           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
    2185             : 
    2186           0 :   ulong bank_idx = ULONG_MAX;
    2187           0 :   fd_rpc_val_t const * config = &params->v[ 0 ];
    2188           0 :   if( FD_UNLIKELY( !fd_rpc_validate_commitment_strict( ctx, id_cstr, config, &response ) ) ) return response;
    2189           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct CommitmentConfig",
    2190           0 :                                              1, /* has_commitment */
    2191           0 :                                              0, /* has_encoding */
    2192           0 :                                              0, /* has_data_slice */
    2193           0 :                                              0, /* has_min_context_slot */
    2194           0 :                                              &bank_idx,
    2195           0 :                                              NULL,
    2196           0 :                                              NULL,
    2197           0 :                                              NULL,
    2198           0 :                                              NULL,
    2199           0 :                                              &response );
    2200           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    2201             : 
    2202           0 :   bank_info_t const * bank = &ctx->banks[ bank_idx ];
    2203           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",
    2204           0 :                            bank->inflation.foundation, bank->inflation.foundation==0 ? ".0" : "",
    2205           0 :                            bank->inflation.foundation_term, bank->inflation.foundation_term==0 ? ".0" : "",
    2206           0 :                            bank->inflation.initial, bank->inflation.initial==0 ? ".0" : "",
    2207           0 :                            bank->inflation.taper, bank->inflation.taper==0 ? ".0" : "",
    2208           0 :                            bank->inflation.terminal, bank->inflation.terminal==0 ? ".0" : "",
    2209           0 :                            id_cstr );
    2210           0 : }
    2211             : 
    2212             : UNIMPLEMENTED(getInflationRate)
    2213             : UNIMPLEMENTED(getInflationReward) // TODO: Used by solana-exporter
    2214             : UNIMPLEMENTED(getLargestAccounts)
    2215             : 
    2216             : static fd_http_server_response_t
    2217             : getLatestBlockhash( fd_rpc_tile_t *         ctx,
    2218             :                     char const *            id_cstr,
    2219           0 :                     fd_rpc_params_t const * params ) {
    2220           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_LATEST_BLOCKHASH, 1UL );
    2221             : 
    2222           0 :   if( FD_UNLIKELY( ctx->processed_idx==ULONG_MAX ) ) {
    2223           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32065,\"message\":\"Firedancer Error: banks uninitialized\"},\"id\":%s}\n", id_cstr );
    2224           0 :   }
    2225             : 
    2226           0 :   fd_http_server_response_t response;
    2227           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
    2228             : 
    2229           0 :   ulong bank_idx = ULONG_MAX;
    2230           0 :   fd_rpc_val_t const * config = &params->v[ 0 ];
    2231           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
    2232           0 :                                              1, /* has_commitment */
    2233           0 :                                              0, /* has_encoding */
    2234           0 :                                              0, /* has_data_slice */
    2235           0 :                                              1, /* has_min_context_slot */
    2236           0 :                                              &bank_idx,
    2237           0 :                                              NULL,
    2238           0 :                                              NULL,
    2239           0 :                                              NULL,
    2240           0 :                                              NULL,
    2241           0 :                                              &response );
    2242           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    2243             : 
    2244           0 :   bank_info_t * bank = &ctx->banks[ bank_idx ];
    2245           0 :   FD_BASE58_ENCODE_32_BYTES( bank->block_hash, block_hash_b58 );
    2246             : 
    2247           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 );
    2248           0 : }
    2249             : 
    2250             : /* Order getLeaderSchedule pairs by identity, breaking ties by rotation
    2251             :    so each identity's rotations end up ascending (matching Agave, which
    2252             :    folds the per-slot leaders into the result in slot order). */
    2253             : #define SORT_NAME        sort_gls_pairs_by_id
    2254           0 : #define SORT_KEY_T       fd_rpc_gls_pair_t
    2255           0 : #define SORT_BEFORE(a,b) ( memcmp( (a).id_key.uc, (b).id_key.uc, sizeof(fd_pubkey_t) )<0 ? 1 : \
    2256           0 :                            memcmp( (a).id_key.uc, (b).id_key.uc, sizeof(fd_pubkey_t) )>0 ? 0 : \
    2257           0 :                            (a).rot<(b).rot )
    2258             : #include "../../util/tmpl/fd_sort.c"
    2259             : 
    2260             : static fd_http_server_response_t
    2261             : getLeaderSchedule( fd_rpc_tile_t *         ctx,
    2262             :                    char const *            id_cstr,
    2263           0 :                    fd_rpc_params_t const * params ) {
    2264           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_LEADER_SCHEDULE, 1UL );
    2265             : 
    2266           0 :   fd_http_server_response_t response;
    2267           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 2, &response ) ) ) return response;
    2268             : 
    2269           0 :   fd_rpc_val_t const * p0             = &params->v[ 0 ];
    2270           0 :   fd_rpc_val_t const * config         = &params->v[ 1 ];
    2271           0 :   ulong                req_slot       = 0UL;
    2272           0 :   int                  have_slot      = 0;
    2273           0 :   int                  wrapper_config = 0;
    2274           0 :   if( p0->kind==FD_RPC_VAL_NONE || p0->kind==FD_RPC_VAL_NULL ) {
    2275             :     /* config stays the second param */
    2276           0 :   } else if( p0->kind==FD_RPC_VAL_INT && !p0->neg ) {
    2277           0 :     req_slot  = p0->u;
    2278           0 :     have_slot = 1;
    2279           0 :   } else if( p0->kind==FD_RPC_VAL_MAP ) {
    2280           0 :     config         = p0;
    2281           0 :     wrapper_config = 1;
    2282             : 
    2283             :     /* Clients can pass a second config object, which also needs
    2284             :        validation. */
    2285           0 :     fd_rpc_val_t const * fallback_config = &params->v[ 1 ];
    2286           0 :     if( fallback_config->kind!=FD_RPC_VAL_NONE ) {
    2287           0 :       ulong        fallback_bank_idx = ULONG_MAX;
    2288           0 :       fd_rpc_val_t fallback_identity[1];
    2289           0 :       int fallback_config_valid = fd_rpc_validate_config( ctx, id_cstr, fallback_config, "struct RpcLeaderScheduleConfig",
    2290           0 :                                                           1, /* has_commitment */
    2291           0 :                                                           0, /* has_encoding */
    2292           0 :                                                           0, /* has_data_slice */
    2293           0 :                                                           0, /* has_min_context_slot */
    2294           0 :                                                           &fallback_bank_idx,
    2295           0 :                                                           NULL,
    2296           0 :                                                           NULL,
    2297           0 :                                                           NULL,
    2298           0 :                                                           fallback_identity,
    2299           0 :                                                           &response );
    2300           0 :       if( FD_UNLIKELY( !fallback_config_valid ) ) return response;
    2301             : 
    2302           0 :       if( FD_UNLIKELY( fallback_identity->kind!=FD_RPC_VAL_NONE && fallback_identity->kind!=FD_RPC_VAL_NULL && fallback_identity->kind!=FD_RPC_VAL_STR ) ) {
    2303           0 :         return fd_rpc_err_invalid( ctx, id_cstr, "type", fallback_identity, "a string" );
    2304           0 :       }
    2305           0 :     }
    2306           0 :   } else {
    2307           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: data did not match any variant of untagged enum RpcLeaderScheduleConfigWrapper.\"},\"id\":%s}\n", id_cstr );
    2308           0 :   }
    2309             : 
    2310           0 :   ulong        bank_idx = ULONG_MAX;
    2311           0 :   fd_rpc_val_t identity[1];
    2312           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcLeaderScheduleConfig",
    2313           0 :                                              1, /* has_commitment */
    2314           0 :                                              0, /* has_encoding */
    2315           0 :                                              0, /* has_data_slice */
    2316           0 :                                              0, /* has_min_context_slot */
    2317           0 :                                              &bank_idx,
    2318           0 :                                              NULL,
    2319           0 :                                              NULL,
    2320           0 :                                              NULL,
    2321           0 :                                              identity,
    2322           0 :                                              &response );
    2323           0 :   if( FD_UNLIKELY( !config_valid ) ) {
    2324           0 :     if( !wrapper_config ) return response;
    2325           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: data did not match any variant of untagged enum RpcLeaderScheduleConfigWrapper.\"},\"id\":%s}\n", id_cstr );
    2326           0 :   }
    2327             : 
    2328             :   /* Optional identity filter: restrict the result to a single validator. */
    2329           0 :   int         have_identity = 0;
    2330           0 :   fd_pubkey_t identity_filter[ 1 ];
    2331           0 :   if( identity->kind!=FD_RPC_VAL_NONE && identity->kind!=FD_RPC_VAL_NULL ) {
    2332           0 :     if( FD_UNLIKELY( wrapper_config && identity->kind!=FD_RPC_VAL_STR ) ) {
    2333           0 :       return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: data did not match any variant of untagged enum RpcLeaderScheduleConfigWrapper.\"},\"id\":%s}\n", id_cstr );
    2334           0 :     }
    2335           0 :     if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id_cstr, identity, identity_filter, &response ) ) ) return response;
    2336           0 :     have_identity = 1;
    2337           0 :   }
    2338             : 
    2339           0 :   ulong slot = have_slot ? req_slot : ctx->banks[ bank_idx ].slot;
    2340           0 :   fd_epoch_leaders_t const * lsched = fd_rpc_mleaders_get_lsched_for_slot( ctx->mleaders, slot );
    2341           0 :   if( FD_UNLIKELY( !lsched ) ) {
    2342             :     /* Unlike getSlotLeader(s), Agave returns a null result (not an
    2343             :        error) when the requested epoch's schedule is unavailable. */
    2344           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":%s}\n", id_cstr );
    2345           0 :   }
    2346             : 
    2347           0 :   ulong pub_cnt   = lsched->pub_cnt;
    2348           0 :   ulong sched_cnt = lsched->sched_cnt;
    2349           0 :   ulong slot_cnt  = lsched->slot_cnt;
    2350           0 :   FD_TEST( pub_cnt<=MAX_STAKE_WEIGHTS );
    2351           0 :   FD_TEST( sched_cnt<=(sizeof(ctx->scratch.gls_pairs)/sizeof(ctx->scratch.gls_pairs[0])) );
    2352             : 
    2353           0 :   fd_rpc_gls_pair_t * pairs    = ctx->scratch.gls_pairs;
    2354           0 :   ulong               pair_cnt = 0UL;
    2355           0 :   for( ulong r=0UL; r<sched_cnt; r++ ) {
    2356           0 :     ulong idx = lsched->sched[ r ];
    2357           0 :     if( FD_UNLIKELY( idx>=pub_cnt ) ) continue; /* indeterminate leader; not reported */
    2358           0 :     if( have_identity && 0!=memcmp( lsched->pub[ idx ].uc, identity_filter->uc, sizeof(fd_pubkey_t) ) ) continue;
    2359           0 :     pairs[ pair_cnt ].id_key = lsched->pub[ idx ];
    2360           0 :     pairs[ pair_cnt ].rot    = (uint)r;
    2361           0 :     pair_cnt++;
    2362           0 :   }
    2363           0 :   sort_gls_pairs_by_id_inplace( pairs, pair_cnt );
    2364             : 
    2365           0 :   fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":{" );
    2366           0 :   int first = 1;
    2367           0 :   for( ulong i=0UL; i<pair_cnt; ) {
    2368             :     /* [i,j) is the run of pairs sharing pairs[i].id_key (one identity). */
    2369           0 :     ulong j = i+1UL;
    2370           0 :     while( j<pair_cnt && 0==memcmp( pairs[ j ].id_key.uc, pairs[ i ].id_key.uc, sizeof(fd_pubkey_t) ) ) j++;
    2371             : 
    2372           0 :     FD_BASE58_ENCODE_32_BYTES( pairs[ i ].id_key.uc, pub_b58 );
    2373           0 :     fd_http_server_printf( ctx->http, "%s\"%s\":[", first ? "" : ",", pub_b58 );
    2374           0 :     first = 0;
    2375             : 
    2376           0 :     int first_slot = 1;
    2377           0 :     for( ulong b=i; b<j; b++ ) {
    2378           0 :       ulong r = pairs[ b ].rot;
    2379           0 :       for( ulong k=0UL; k<FD_EPOCH_SLOTS_PER_ROTATION; k++ ) {
    2380           0 :         ulong rel = r*FD_EPOCH_SLOTS_PER_ROTATION + k;
    2381           0 :         if( FD_UNLIKELY( rel>=slot_cnt ) ) break;
    2382           0 :         fd_http_server_printf( ctx->http, "%s%lu", first_slot ? "" : ",", rel );
    2383           0 :         first_slot = 0;
    2384           0 :       }
    2385           0 :     }
    2386           0 :     fd_http_server_printf( ctx->http, "]" );
    2387           0 :     i = j;
    2388           0 :   }
    2389           0 :   fd_http_server_printf( ctx->http, "},\"id\":%s}\n", id_cstr );
    2390           0 :   return STAGE_JSON( ctx );
    2391           0 : }
    2392             : 
    2393             : static fd_http_server_response_t
    2394             : getMaxRetransmitSlot( fd_rpc_tile_t *         ctx,
    2395             :                       char const *            id_cstr,
    2396           0 :                       fd_rpc_params_t const * params ) {
    2397           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_MAX_RETRANSMIT_SLOT, 1UL );
    2398             : 
    2399           0 :   fd_http_server_response_t response;
    2400           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    2401             : 
    2402           0 :   ulong max_retransmit_slot = 0UL;
    2403           0 :   for( ulong i=0UL; i<ctx->shred_rtx_cnt; i++ ) {
    2404           0 :     ulong slot = fd_fseq_query( ctx->shred_rtx[ i ] );
    2405           0 :     if( FD_LIKELY( slot!=ULONG_MAX ) ) max_retransmit_slot = fd_ulong_max( max_retransmit_slot, slot );
    2406           0 :   }
    2407           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", max_retransmit_slot, id_cstr );
    2408           0 : }
    2409             : 
    2410             : UNIMPLEMENTED(getMaxShredInsertSlot)
    2411             : 
    2412             : static fd_http_server_response_t
    2413             : getMinimumBalanceForRentExemption( fd_rpc_tile_t *         ctx,
    2414             :                                    char const *            id_cstr,
    2415           0 :                                    fd_rpc_params_t const * params ) {
    2416           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_MINIMUM_BALANCE_FOR_RENT_EXEMPTION, 1UL );
    2417             : 
    2418           0 :   fd_http_server_response_t response;
    2419           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 2, &response ) ) ) return response;
    2420             : 
    2421           0 :   ulong bank_idx = ULONG_MAX;
    2422           0 :   fd_rpc_val_t const * config = &params->v[ 1 ];
    2423           0 :   if( FD_UNLIKELY( !fd_rpc_validate_commitment_strict( ctx, id_cstr, config, &response ) ) ) return response;
    2424           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct CommitmentConfig",
    2425           0 :                                              1, /* has_commitment */
    2426           0 :                                              0, /* has_encoding */
    2427           0 :                                              0, /* has_data_slice */
    2428           0 :                                              0, /* has_min_context_slot */
    2429           0 :                                              &bank_idx,
    2430           0 :                                              NULL,
    2431           0 :                                              NULL,
    2432           0 :                                              NULL,
    2433           0 :                                              NULL,
    2434           0 :                                              &response );
    2435           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    2436             : 
    2437           0 :   fd_rpc_val_t const * acct_sz = &params->v[ 0 ];
    2438           0 :   ulong acct_sz_val;
    2439           0 :   if( FD_UNLIKELY( !fd_rpc_validate_uint_param( ctx, id_cstr, acct_sz, "usize", &acct_sz_val, &response ) ) ) return response;
    2440           0 :   if( FD_UNLIKELY( acct_sz_val>FD_RUNTIME_ACC_SZ_MAX ) ) {
    2441           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2442           0 :   }
    2443             : 
    2444           0 :   bank_info_t const * bank = &ctx->banks[ bank_idx ];
    2445             : 
    2446           0 :   fd_rent_t rent = {
    2447           0 :     .lamports_per_uint8_year = bank->rent.lamports_per_uint8_year,
    2448           0 :     .exemption_threshold = bank->rent.exemption_threshold,
    2449           0 :     .burn_percent = bank->rent.burn_percent,
    2450           0 :   };
    2451           0 :   ulong minimum = fd_rent_exempt_minimum_balance( &rent, acct_sz_val );
    2452           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", minimum, id_cstr );
    2453           0 : }
    2454             : 
    2455             : static fd_http_server_response_t
    2456             : getMultipleAccounts( fd_rpc_tile_t *         ctx,
    2457             :                      char const *            id_cstr,
    2458           0 :                      fd_rpc_params_t const * params ) {
    2459           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_MULTIPLE_ACCOUNTS, 1UL );
    2460             : 
    2461           0 :   fd_http_server_response_t response;
    2462           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 2, &response ) ) ) return response;
    2463             : 
    2464           0 :   fd_rpc_val_t const * keys_arr = &params->v[ 0 ];
    2465           0 :   if( FD_UNLIKELY( keys_arr->kind!=FD_RPC_VAL_SEQ ) ) {
    2466           0 :     return fd_rpc_err_invalid( ctx, id_cstr, "type", keys_arr, "a sequence" );
    2467           0 :   }
    2468             : 
    2469           0 :   fd_rpc_val_t keys[ 100UL ];
    2470           0 :   ulong        cnt = 0UL;
    2471           0 :   fd_jtok_t j[1];
    2472           0 :   fd_jtok_arr_enter( fd_jtok_init( j, keys_arr->raw, keys_arr->raw_sz ) );
    2473           0 :   while( fd_jtok_arr_next( j ) ) {
    2474           0 :     if( cnt<100UL ) fd_rpc_val_read( j, &keys[ cnt ] );
    2475           0 :     cnt++;
    2476           0 :   }
    2477           0 :   if( FD_UNLIKELY( cnt>100UL ) ) {
    2478           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Too many inputs provided; max 100\"},\"id\":%s}\n", id_cstr );
    2479           0 :   }
    2480             : 
    2481           0 :   ulong bank_idx = ULONG_MAX;
    2482           0 :   char const * encoding_cstr = NULL;
    2483           0 :   ulong slice_length = ULONG_MAX;
    2484           0 :   ulong slice_offset = 0;
    2485           0 :   fd_rpc_val_t const * config = &params->v[ 1 ];
    2486           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcAccountInfoConfig",
    2487           0 :                                              1, 1, 1, 1,
    2488           0 :                                              &bank_idx, &encoding_cstr,
    2489           0 :                                              &slice_length, &slice_offset,
    2490           0 :                                              NULL,
    2491           0 :                                              &response );
    2492           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    2493             : 
    2494             :   /* Agave defaults getMultipleAccounts to base64 (getAccountInfo to binary) */
    2495           0 :   fd_rpc_config_t cfg[1];
    2496           0 :   fd_rpc_config_read( config, cfg );
    2497           0 :   if( cfg->encoding.kind==FD_RPC_VAL_NONE || cfg->encoding.kind==FD_RPC_VAL_NULL ) encoding_cstr = "base64";
    2498             : 
    2499           0 :   bank_info_t * info = &ctx->banks[ bank_idx ];
    2500             : 
    2501           0 :   fd_pubkey_t addresses[ 100UL ];
    2502           0 :   for( ulong i=0UL; i<cnt; i++ ) {
    2503           0 :     if( FD_UNLIKELY( !fd_rpc_validate_address( ctx, id_cstr, &keys[ i ], &addresses[ i ], &response ) ) ) return response;
    2504           0 :   }
    2505             : 
    2506           0 :   fd_http_server_printf( ctx->http,
    2507           0 :       "{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":{\"context\":{\"apiVersion\":\"%s\",\"slot\":%lu},\"value\":[",
    2508           0 :       id_cstr, FD_RPC_AGAVE_API_VERSION, info->slot );
    2509             : 
    2510           0 :   for( ulong i=0UL; i<cnt; i++ ) {
    2511           0 :     if( i>0UL ) fd_http_server_printf( ctx->http, "," );
    2512             : 
    2513           0 :     ulong acct_lamports;
    2514           0 :     int   acct_executable;
    2515           0 :     uchar acct_owner[ 32UL ];
    2516           0 :     ulong acct_data_len;
    2517           0 :     fd_accdb_read_one_nocache( ctx->accdb, info->accdb_fork_id, addresses[i].uc,
    2518           0 :                                &acct_lamports, &acct_executable, acct_owner,
    2519           0 :                                ctx->scratch.accdb_data_buf, &acct_data_len );
    2520           0 :     if( FD_UNLIKELY( !acct_lamports ) ) {
    2521           0 :       fd_http_server_printf( ctx->http, "null" );
    2522           0 :       continue;
    2523           0 :     }
    2524             : 
    2525           0 :     fd_http_server_response_t err_response;
    2526           0 :     if( FD_UNLIKELY( !fd_rpc_encode_account_data( ctx, ctx->scratch.accdb_data_buf, acct_data_len, acct_owner, acct_lamports, acct_executable, encoding_cstr, slice_offset, slice_length, id_cstr, &err_response ) ) ) {
    2527           0 :       return err_response;
    2528           0 :     }
    2529           0 :   }
    2530             : 
    2531           0 :   fd_http_server_printf( ctx->http, "]}}\n" );
    2532           0 :   return STAGE_JSON( ctx );
    2533           0 : }
    2534             : 
    2535             : UNIMPLEMENTED(getProgramAccounts)
    2536             : UNIMPLEMENTED(getRecentPerformanceSamples)
    2537             : UNIMPLEMENTED(getRecentPrioritizationFees)
    2538             : UNIMPLEMENTED(getSignaturesForAddress)
    2539             : UNIMPLEMENTED(getSignatureStatuses)
    2540             : 
    2541             : static fd_http_server_response_t
    2542             : getSlot( fd_rpc_tile_t *         ctx,
    2543             :          char const *            id_cstr,
    2544           0 :          fd_rpc_params_t const * params ) {
    2545           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_SLOT, 1UL );
    2546             : 
    2547           0 :   fd_http_server_response_t response;
    2548           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
    2549             : 
    2550           0 :   ulong bank_idx = ULONG_MAX;
    2551           0 :   fd_rpc_val_t const * config = &params->v[ 0 ];
    2552           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
    2553           0 :                                              1, /* has_commitment */
    2554           0 :                                              0, /* has_encoding */
    2555           0 :                                              0, /* has_data_slice */
    2556           0 :                                              1, /* has_min_context_slot */
    2557           0 :                                              &bank_idx,
    2558           0 :                                              NULL,
    2559           0 :                                              NULL,
    2560           0 :                                              NULL,
    2561           0 :                                              NULL,
    2562           0 :                                              &response );
    2563           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    2564             : 
    2565           0 :   bank_info_t * bank = &ctx->banks[ bank_idx ];
    2566           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", bank->slot, id_cstr );
    2567           0 : }
    2568             : 
    2569             : static fd_http_server_response_t
    2570             : getSlotLeader( fd_rpc_tile_t *         ctx,
    2571             :                char const *            id_cstr,
    2572           0 :                fd_rpc_params_t const * params ) {
    2573           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_SLOT_LEADER, 1UL );
    2574             : 
    2575           0 :   fd_http_server_response_t response;
    2576           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
    2577             : 
    2578           0 :   ulong bank_idx = ULONG_MAX;
    2579           0 :   fd_rpc_val_t const * config = &params->v[ 0 ];
    2580           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
    2581           0 :                                              1, /* has_commitment */
    2582           0 :                                              0, /* has_encoding */
    2583           0 :                                              0, /* has_data_slice */
    2584           0 :                                              1, /* has_min_context_slot */
    2585           0 :                                              &bank_idx,
    2586           0 :                                              NULL,
    2587           0 :                                              NULL,
    2588           0 :                                              NULL,
    2589           0 :                                              NULL,
    2590           0 :                                              &response );
    2591           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    2592             : 
    2593           0 :   ulong slot = ctx->banks[ bank_idx ].slot;
    2594           0 :   fd_pubkey_t const * leader = fd_rpc_mleaders_get_leader_for_slot( ctx->mleaders, slot );
    2595           0 :   if( FD_UNLIKELY( !leader ) ) {
    2596           0 :     ulong epoch = ctx->has_epoch_schedule ? fd_slot_to_epoch( &ctx->epoch_schedule, slot, NULL ) : 0UL;
    2597           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid slot range: leader schedule for epoch %lu is unavailable\"},\"id\":%s}\n", epoch, id_cstr );
    2598           0 :   }
    2599             : 
    2600           0 :   FD_BASE58_ENCODE_32_BYTES( leader->uc, leader_b58 );
    2601           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":\"%s\",\"id\":%s}\n", leader_b58, id_cstr );
    2602           0 : }
    2603             : 
    2604             : static fd_http_server_response_t
    2605             : getSlotLeaders( fd_rpc_tile_t *         ctx,
    2606             :                 char const *            id_cstr,
    2607           0 :                 fd_rpc_params_t const * params ) {
    2608           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_SLOT_LEADERS, 1UL );
    2609             : 
    2610           0 :   fd_http_server_response_t response;
    2611           0 :   if( FD_UNLIKELY( params->kind==FD_RPC_VAL_NULL ) ) {
    2612           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: null, expected a tuple of size 2.\"},\"id\":%s}\n", id_cstr );
    2613           0 :   }
    2614           0 :   if( FD_UNLIKELY( params->kind==FD_RPC_VAL_MAP ) ) {
    2615           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: map, expected a tuple of size 2.\"},\"id\":%s}\n", id_cstr );
    2616           0 :   }
    2617           0 :   fd_rpc_val_t const * p0 = &params->v[ 0 ];
    2618           0 :   fd_rpc_val_t const * p1 = &params->v[ 1 ];
    2619           0 :   if( FD_UNLIKELY( params->kind==FD_RPC_VAL_SEQ && params->cnt!=2UL ) ) {
    2620           0 :     ulong param_cnt = params->cnt;
    2621           0 :     ulong unused;
    2622           0 :     if( FD_UNLIKELY( param_cnt>=1UL && !fd_rpc_validate_uint_param( ctx, id_cstr, p0, "u64", &unused, &response ) ) ) return response;
    2623           0 :     if( FD_UNLIKELY( param_cnt>=2UL && !fd_rpc_validate_uint_param( ctx, id_cstr, p1, "u64", &unused, &response ) ) ) return response;
    2624           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid length %lu, expected a tuple of size 2.\"},\"id\":%s}\n", param_cnt, id_cstr );
    2625           0 :   }
    2626           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 2, 2, &response ) ) ) return response;
    2627             : 
    2628           0 :   ulong start_slot;
    2629           0 :   ulong limit;
    2630           0 :   if( FD_UNLIKELY( !fd_rpc_validate_uint_param( ctx, id_cstr, p0, "u64", &start_slot, &response ) ) ) return response;
    2631           0 :   if( FD_UNLIKELY( !fd_rpc_validate_uint_param( ctx, id_cstr, p1, "u64", &limit,      &response ) ) ) return response;
    2632             : 
    2633           0 :   if( FD_UNLIKELY( limit>5000UL ) ) {
    2634           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid limit; max 5000\"},\"id\":%s}\n", id_cstr );
    2635           0 :   }
    2636             : 
    2637           0 :   fd_http_server_printf( ctx->http, "{\"jsonrpc\":\"2.0\",\"result\":[" );
    2638           0 :   for( ulong i=0UL; i<limit; i++ ) {
    2639           0 :     ulong slot = start_slot + i;
    2640           0 :     fd_pubkey_t const * leader = FD_LIKELY( slot>=start_slot ) ? fd_rpc_mleaders_get_leader_for_slot( ctx->mleaders, slot ) : NULL;
    2641           0 :     if( FD_UNLIKELY( !leader ) ) {
    2642           0 :       fd_http_server_unstage( ctx->http );
    2643           0 :       ulong epoch = ctx->has_epoch_schedule ? fd_slot_to_epoch( &ctx->epoch_schedule, slot, NULL ) : 0UL;
    2644           0 :       return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid slot range: leader schedule for epoch %lu is unavailable\"},\"id\":%s}\n", epoch, id_cstr );
    2645           0 :     }
    2646           0 :     FD_BASE58_ENCODE_32_BYTES( leader->uc, leader_b58 );
    2647           0 :     fd_http_server_printf( ctx->http, "%s\"%s\"", i ? "," : "", leader_b58 );
    2648           0 :   }
    2649           0 :   fd_http_server_printf( ctx->http, "],\"id\":%s}\n", id_cstr );
    2650           0 :   return STAGE_JSON( ctx );
    2651           0 : }
    2652             : UNIMPLEMENTED(getStakeMinimumDelegation)
    2653             : UNIMPLEMENTED(getSupply)
    2654             : UNIMPLEMENTED(getTokenAccountBalance)
    2655             : UNIMPLEMENTED(getTokenAccountsByDelegate)
    2656             : UNIMPLEMENTED(getTokenAccountsByOwner)
    2657             : UNIMPLEMENTED(getTokenLargestAccounts)
    2658             : UNIMPLEMENTED(getTokenSupply)
    2659             : UNIMPLEMENTED(getTransaction)
    2660             : 
    2661             : static fd_http_server_response_t
    2662             : getTransactionCount( fd_rpc_tile_t *         ctx,
    2663             :                      char const *            id_cstr,
    2664           0 :                      fd_rpc_params_t const * params ) {
    2665           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_TRANSACTION_COUNT, 1UL );
    2666             : 
    2667           0 :   fd_http_server_response_t response;
    2668           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 1, &response ) ) ) return response;
    2669             : 
    2670           0 :   ulong bank_idx = ULONG_MAX;
    2671           0 :   fd_rpc_val_t const * config = &params->v[ 0 ];
    2672           0 :   int config_valid = fd_rpc_validate_config( ctx, id_cstr, config, "struct RpcContextConfig",
    2673           0 :                                              1, /* has_commitment */
    2674           0 :                                              0, /* has_encoding */
    2675           0 :                                              0, /* has_data_slice */
    2676           0 :                                              1, /* has_min_context_slot */
    2677           0 :                                              &bank_idx,
    2678           0 :                                              NULL,
    2679           0 :                                              NULL,
    2680           0 :                                              NULL,
    2681           0 :                                              NULL,
    2682           0 :                                              &response );
    2683           0 :   if( FD_UNLIKELY( !config_valid ) ) return response;
    2684             : 
    2685           0 :   bank_info_t * bank = &ctx->banks[ bank_idx ];
    2686           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%s}\n", bank->transaction_count, id_cstr );
    2687           0 : }
    2688             : 
    2689             : static fd_http_server_response_t
    2690             : getVersion( fd_rpc_tile_t *         ctx,
    2691             :             char const *            id_cstr,
    2692           0 :             fd_rpc_params_t const * params ) {
    2693           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_GET_VERSION, 1UL );
    2694             : 
    2695           0 :   fd_http_server_response_t response;
    2696           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    2697             : 
    2698           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 );
    2699           0 : }
    2700             : 
    2701             : static fd_http_server_response_t
    2702             : voteSubscribe( fd_rpc_tile_t *         ctx,
    2703             :                char const *            id_cstr,
    2704             :                fd_rpc_params_t const * params,
    2705           0 :                ulong                   ws_conn_id ) {
    2706           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_VOTE_SUBSCRIBE, 1UL );
    2707             : 
    2708           0 :   fd_http_server_response_t response;
    2709           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    2710             : 
    2711           0 :   if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
    2712           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    2713           0 :   }
    2714           0 :   FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
    2715             : 
    2716           0 :   fd_rpc_ws_subscriber_vote_add( ctx, ws_conn_id );
    2717             : 
    2718           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":%s}\n", id_cstr );
    2719           0 : }
    2720             : 
    2721             : static fd_http_server_response_t
    2722             : slotSubscribe( fd_rpc_tile_t *         ctx,
    2723             :                char const *            id_cstr,
    2724             :                fd_rpc_params_t const * params,
    2725           0 :                ulong                   ws_conn_id ) {
    2726           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_SLOT_SUBSCRIBE, 1UL );
    2727             : 
    2728           0 :   fd_http_server_response_t response;
    2729           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 0, 0, &response ) ) ) return response;
    2730             : 
    2731           0 :   if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
    2732           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    2733           0 :   }
    2734           0 :   FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
    2735             : 
    2736           0 :   fd_rpc_ws_subscriber_slot_add( ctx, ws_conn_id );
    2737             : 
    2738           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":0,\"id\":%s}\n", id_cstr );
    2739           0 : }
    2740             : 
    2741             : static fd_http_server_response_t
    2742             : voteUnsubscribe( fd_rpc_tile_t *         ctx,
    2743             :                  char const *            id_cstr,
    2744             :                  fd_rpc_params_t const * params,
    2745           0 :                  ulong                   ws_conn_id ) {
    2746           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_VOTE_UNSUBSCRIBE, 1UL );
    2747             : 
    2748           0 :   fd_http_server_response_t response;
    2749           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 1, &response ) ) ) return response;
    2750             : 
    2751           0 :   if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
    2752           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    2753           0 :   }
    2754           0 :   FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
    2755             : 
    2756           0 :   fd_rpc_val_t const * subscription = &params->v[ 0 ];
    2757           0 :   if( FD_UNLIKELY( subscription->kind!=FD_RPC_VAL_INT || subscription->neg ) ) {
    2758           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_val_kind_cstr( subscription ), id_cstr );
    2759           0 :   }
    2760             : 
    2761           0 :   int unsubscribed = 0;
    2762           0 :   if( FD_LIKELY( subscription->u==0UL ) )
    2763           0 :     unsubscribed = fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
    2764             : 
    2765           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%s,\"id\":%s}\n", unsubscribed ? "true" : "false", id_cstr );
    2766           0 : }
    2767             : 
    2768             : static fd_http_server_response_t
    2769             : slotUnsubscribe( fd_rpc_tile_t *         ctx,
    2770             :                  char const *            id_cstr,
    2771             :                  fd_rpc_params_t const * params,
    2772           0 :                  ulong                   ws_conn_id ) {
    2773           0 :   FD_MCNT_INC( RPC, REQUEST_HANDLED_SLOT_UNSUBSCRIBE, 1UL );
    2774             : 
    2775           0 :   fd_http_server_response_t response;
    2776           0 :   if( FD_UNLIKELY( !fd_rpc_validate_params( ctx, id_cstr, params, 1, 1, &response ) ) ) return response;
    2777             : 
    2778           0 :   if( FD_UNLIKELY( ws_conn_id==ULONG_MAX ) ) {
    2779           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    2780           0 :   }
    2781           0 :   FD_CHECK_CRIT( ws_conn_id < ctx->http->max_ws_conns, "OOB ws_conn_id" );
    2782             : 
    2783           0 :   fd_rpc_val_t const * subscription = &params->v[ 0 ];
    2784           0 :   if( FD_UNLIKELY( subscription->kind!=FD_RPC_VAL_INT || subscription->neg ) ) {
    2785           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid type: %s, expected usize.\"},\"id\":%s}\n", fd_rpc_val_kind_cstr( subscription ), id_cstr );
    2786           0 :   }
    2787             : 
    2788           0 :   int unsubscribed = 0;
    2789           0 :   if( FD_LIKELY( subscription->u==0UL ) )
    2790           0 :     unsubscribed = fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
    2791             : 
    2792           0 :   return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"result\":%s,\"id\":%s}\n", unsubscribed ? "true" : "false", id_cstr );
    2793           0 : }
    2794             : 
    2795             : UNIMPLEMENTED(getVoteAccounts) // TODO: Used by solana-exporter
    2796             : UNIMPLEMENTED(isBlockhashValid)
    2797             : UNIMPLEMENTED(minimumLedgerSlot) // TODO: Used by solana-exporter
    2798             : UNIMPLEMENTED(requestAirdrop)
    2799             : UNIMPLEMENTED(sendTransaction)
    2800             : UNIMPLEMENTED(simulateTransaction)
    2801             : 
    2802             : static fd_http_server_response_t
    2803             : rpc_json_request( fd_rpc_tile_t * ctx,
    2804             :                   uchar const *   body,
    2805             :                   ulong           body_len,
    2806             :                   ulong           ws_conn_id );
    2807             : 
    2808             : static fd_http_server_response_t
    2809             : rpc_http_request1( fd_rpc_tile_t *                  ctx,
    2810           0 :                    fd_http_server_request_t const * request ) {
    2811           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET &&
    2812           0 :                    request->headers.upgrade_websocket &&
    2813           0 :                    (!strcmp( request->path, "/" ) || !strcmp( request->path, "/websocket" )) ) ) {
    2814           0 :     if( FD_UNLIKELY( !ctx->http->max_ws_conns ) ) return (fd_http_server_response_t){ .status = 404 };
    2815           0 :     return (fd_http_server_response_t){
    2816           0 :       .status            = 200,
    2817           0 :       .upgrade_websocket = 1,
    2818           0 :     };
    2819           0 :   }
    2820             : 
    2821           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET && !strcmp( request->path, "/health" ) ) ) {
    2822           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_HEALTH, 1UL );
    2823           0 :     int health_status = _getHealth( ctx );
    2824             : 
    2825           0 :     switch( health_status ) {
    2826           0 :       case FD_RPC_HEALTH_STATUS_UNKNOWN: return PRINTF_JSON( ctx, "unknown" );
    2827           0 :       case FD_RPC_HEALTH_STATUS_BEHIND:  return PRINTF_JSON( ctx, "behind" );
    2828           0 :       case FD_RPC_HEALTH_STATUS_OK:      return PRINTF_JSON( ctx, "ok" );
    2829           0 :       default: FD_LOG_ERR(( "unknown health status" ));
    2830           0 :     }
    2831           0 :   }
    2832             : 
    2833           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET && !strcmp( request->path, "/genesis.tar.bz2" ) ) ) {
    2834           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_GENESIS, 1UL );
    2835           0 :     if( FD_UNLIKELY( ctx->genesis_tar_bz_sz==ULONG_MAX ) ) return (fd_http_server_response_t){ .status = 404 };
    2836             : 
    2837           0 :     return (fd_http_server_response_t) {
    2838           0 :       .status          = 200,
    2839           0 :       .static_body     = ctx->genesis_tar_bz,
    2840           0 :       .static_body_len = ctx->genesis_tar_bz_sz,
    2841           0 :     };
    2842           0 :   }
    2843             : 
    2844           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET &&
    2845           0 :                    ( !strncmp( request->path, "/snapshot",             9UL ) ||
    2846           0 :                      !strncmp( request->path, "/incremental-snapshot", 21UL ) ) ) ) {
    2847           0 :     if( FD_UNLIKELY( !ctx->snapshot_server_enabled ) ) {
    2848           0 :       return (fd_http_server_response_t){ .status = 403 }; /* forbidden */
    2849           0 :     }
    2850           0 :     return (fd_http_server_response_t){
    2851           0 :       .status       = 302,
    2852           0 :       .location     = { ctx->snapshot_server_url, request->path_raw },
    2853           0 :       .location_len = { strlen( ctx->snapshot_server_url ), request->path_len },
    2854           0 :     };
    2855           0 :   }
    2856             : 
    2857             : 
    2858           0 :   if( FD_UNLIKELY( request->method==FD_HTTP_SERVER_METHOD_GET ) ) {
    2859           0 :     return (fd_http_server_response_t){ .status = 404 };
    2860           0 :   }
    2861             : 
    2862           0 :   if( FD_UNLIKELY( request->method!=FD_HTTP_SERVER_METHOD_POST ) ) {
    2863           0 :     return (fd_http_server_response_t){ .status = 405 };
    2864           0 :   }
    2865             : 
    2866           0 :   return rpc_json_request( ctx, request->post.body, request->post.body_len, ULONG_MAX );
    2867           0 : }
    2868             : 
    2869             : static fd_http_server_response_t
    2870             : rpc_json_request( fd_rpc_tile_t * ctx,
    2871             :                   uchar const *   body,
    2872             :                   ulong           body_len,
    2873           0 :                   ulong           ws_conn_id ) { /* ULONG_MAX implies HTTP */
    2874           0 :   fd_jtok_t j[1];
    2875           0 :   fd_jtok_init( j, body, body_len );
    2876           0 :   int kind = fd_jtok_peek( j );
    2877             : 
    2878           0 :   if( FD_UNLIKELY( kind==FD_JTOK_ARR ) ) {
    2879             :     /* A bug in Agave ¯\_(ツ)_/¯ */
    2880           0 :     fd_jtok_arr_enter( j );
    2881           0 :     if( FD_LIKELY( !fd_jtok_arr_next( j ) && !fd_jtok_fini( j ) ) ) return (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
    2882           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2883           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
    2884           0 :   }
    2885             : 
    2886           0 :   if( FD_UNLIKELY( kind!=FD_JTOK_OBJ ) ) {
    2887           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2888           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
    2889           0 :   }
    2890             : 
    2891             :   /* For duplicate members the first occurrence wins, later ones are
    2892             :      skipped. */
    2893           0 :   fd_rpc_val_t id     [1] = {{ .kind = FD_RPC_VAL_NONE }};
    2894           0 :   fd_rpc_val_t jsonrpc[1] = {{ .kind = FD_RPC_VAL_NONE }};
    2895           0 :   fd_rpc_val_t _method[1] = {{ .kind = FD_RPC_VAL_NONE }};
    2896           0 :   fd_rpc_val_t _params[1] = {{ .kind = FD_RPC_VAL_NONE }};
    2897           0 :   int unknown_member = 0;
    2898             : 
    2899           0 :   fd_jtok_str_t key;
    2900           0 :   fd_jtok_obj_enter( j );
    2901           0 :   while( fd_jtok_obj_next( j, &key ) ) {
    2902           0 :     fd_rpc_val_t * member;
    2903           0 :     if(      fd_jtok_str_eq( &key, "jsonrpc" ) ) member = jsonrpc;
    2904           0 :     else if( fd_jtok_str_eq( &key, "id"      ) ) member = id;
    2905           0 :     else if( fd_jtok_str_eq( &key, "method"  ) ) member = _method;
    2906           0 :     else if( fd_jtok_str_eq( &key, "params"  ) ) member = _params;
    2907           0 :     else { unknown_member = 1; continue; }
    2908           0 :     if( member->kind==FD_RPC_VAL_NONE ) fd_rpc_val_read( j, member );
    2909           0 :   }
    2910           0 :   if( FD_UNLIKELY( fd_jtok_fini( j ) ) ) {
    2911           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2912           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
    2913           0 :   }
    2914             : 
    2915             :   /* Echo the id back verbatim. */
    2916           0 :   char id_buf[ FD_RPC_ID_MAX_SZ+1UL ];
    2917           0 :   char const * id_cstr = "null";
    2918           0 :   if( id->kind!=FD_RPC_VAL_NONE ) {
    2919           0 :     if( FD_UNLIKELY( id->raw_sz>FD_RPC_ID_MAX_SZ ) ) {
    2920           0 :       FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2921           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
    2922           0 :     }
    2923           0 :     fd_memcpy( id_buf, id->raw, id->raw_sz );
    2924           0 :     id_buf[ id->raw_sz ] = '\0';
    2925           0 :     id_cstr = id_buf;
    2926           0 :   }
    2927             : 
    2928           0 :   if( FD_UNLIKELY( unknown_member ) ) {
    2929           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2930           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2931           0 :   }
    2932             : 
    2933           0 :   if( FD_UNLIKELY( _method->kind!=FD_RPC_VAL_NONE && id->kind==FD_RPC_VAL_NONE ) ) {
    2934             :     /* A bug in Agave ¯\_(ツ)_/¯ */
    2935           0 :     return (fd_http_server_response_t){ .content_type = "application/json", .status = 200 };
    2936           0 :   }
    2937             : 
    2938           0 :   if( FD_UNLIKELY( jsonrpc->kind==FD_RPC_VAL_NONE && _method->kind!=FD_RPC_VAL_NONE ) ) {
    2939           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2940           0 :     return PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":%s}\n", id_cstr );
    2941           0 :   }
    2942             : 
    2943           0 :   if( FD_UNLIKELY( jsonrpc->kind==FD_RPC_VAL_NONE || _method->kind==FD_RPC_VAL_NONE ) ) {
    2944           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2945           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2946           0 :   }
    2947             : 
    2948           0 :   if( FD_UNLIKELY( !(id->kind==FD_RPC_VAL_INT && !id->neg) && id->kind!=FD_RPC_VAL_STR && id->kind!=FD_RPC_VAL_NULL ) ) {
    2949           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2950           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}\n" );
    2951           0 :   }
    2952             : 
    2953           0 :   if( FD_UNLIKELY( jsonrpc->kind==FD_RPC_VAL_NULL ) ) {
    2954           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2955           0 :     return PRINTF_JSON( ctx, "{\"error\":{\"code\":-32600,\"message\":\"Unsupported JSON-RPC protocol version\"},\"id\":%s}\n", id_cstr );
    2956           0 :   }
    2957             : 
    2958           0 :   if( FD_UNLIKELY( jsonrpc->kind!=FD_RPC_VAL_STR || !fd_jtok_str_eq( &jsonrpc->str, "2.0" ) ) ) {
    2959           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2960           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2961           0 :   }
    2962             : 
    2963           0 :   fd_http_server_response_t response;
    2964             : 
    2965           0 :   if( FD_UNLIKELY( _method->kind!=FD_RPC_VAL_STR ) ) {
    2966           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_INVALID, 1UL );
    2967           0 :     return PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid request\"},\"id\":%s}\n", id_cstr );
    2968           0 :   }
    2969           0 :   char method[ 64 ];
    2970           0 :   fd_rpc_val_cstr( _method, method, sizeof(method) ); /* too long or undecodable is an unknown method */
    2971             : 
    2972           0 :   fd_rpc_params_t params[1];
    2973           0 :   fd_rpc_params_read( _params, params );
    2974             : 
    2975           0 :   if( FD_LIKELY(      !strcmp( method, "getAccountInfo"                    ) ) ) response = getAccountInfo( ctx, id_cstr, params );
    2976           0 :   else if( FD_LIKELY( !strcmp( method, "getBalance"                        ) ) ) response = getBalance( ctx, id_cstr, params );
    2977           0 :   else if( FD_LIKELY( !strcmp( method, "getBlock"                          ) ) ) response = getBlock( ctx, id_cstr, params );
    2978           0 :   else if( FD_LIKELY( !strcmp( method, "getBlockCommitment"                ) ) ) response = getBlockCommitment( ctx, id_cstr, params );
    2979           0 :   else if( FD_LIKELY( !strcmp( method, "getBlockHeight"                    ) ) ) response = getBlockHeight( ctx, id_cstr, params );
    2980           0 :   else if( FD_LIKELY( !strcmp( method, "getBlockProduction"                ) ) ) response = getBlockProduction( ctx, id_cstr, params );
    2981           0 :   else if( FD_LIKELY( !strcmp( method, "getBlocks"                         ) ) ) response = getBlocks( ctx, id_cstr, params );
    2982           0 :   else if( FD_LIKELY( !strcmp( method, "getBlocksWithLimit"                ) ) ) response = getBlocksWithLimit( ctx, id_cstr, params );
    2983           0 :   else if( FD_LIKELY( !strcmp( method, "getBlockTime"                      ) ) ) response = getBlockTime( ctx, id_cstr, params );
    2984           0 :   else if( FD_LIKELY( !strcmp( method, "getClusterNodes"                   ) ) ) response = getClusterNodes( ctx, id_cstr, params );
    2985           0 :   else if( FD_LIKELY( !strcmp( method, "getEpochInfo"                      ) ) ) response = getEpochInfo( ctx, id_cstr, params );
    2986           0 :   else if( FD_LIKELY( !strcmp( method, "getEpochSchedule"                  ) ) ) response = getEpochSchedule( ctx, id_cstr, params );
    2987           0 :   else if( FD_LIKELY( !strcmp( method, "getFeeForMessage"                  ) ) ) response = getFeeForMessage( ctx, id_cstr, params );
    2988           0 :   else if( FD_LIKELY( !strcmp( method, "getFirstAvailableBlock"            ) ) ) response = getFirstAvailableBlock( ctx, id_cstr, params );
    2989           0 :   else if( FD_LIKELY( !strcmp( method, "getGenesisHash"                    ) ) ) response = getGenesisHash( ctx, id_cstr, params );
    2990           0 :   else if( FD_LIKELY( !strcmp( method, "getHealth"                         ) ) ) response = getHealth( ctx, id_cstr, params );
    2991           0 :   else if( FD_LIKELY( !strcmp( method, "getHighestSnapshotSlot"            ) ) ) response = getHighestSnapshotSlot( ctx, id_cstr, params );
    2992           0 :   else if( FD_LIKELY( !strcmp( method, "getIdentity"                       ) ) ) response = getIdentity( ctx, id_cstr, params );
    2993           0 :   else if( FD_LIKELY( !strcmp( method, "getInflationGovernor"              ) ) ) response = getInflationGovernor( ctx, id_cstr, params );
    2994           0 :   else if( FD_LIKELY( !strcmp( method, "getInflationRate"                  ) ) ) response = getInflationRate( ctx, id_cstr, params );
    2995           0 :   else if( FD_LIKELY( !strcmp( method, "getInflationReward"                ) ) ) response = getInflationReward( ctx, id_cstr, params );
    2996           0 :   else if( FD_LIKELY( !strcmp( method, "getLargestAccounts"                ) ) ) response = getLargestAccounts( ctx, id_cstr, params );
    2997           0 :   else if( FD_LIKELY( !strcmp( method, "getLatestBlockhash"                ) ) ) response = getLatestBlockhash( ctx, id_cstr, params );
    2998           0 :   else if( FD_LIKELY( !strcmp( method, "getLeaderSchedule"                 ) ) ) response = getLeaderSchedule( ctx, id_cstr, params );
    2999           0 :   else if( FD_LIKELY( !strcmp( method, "getMaxRetransmitSlot"              ) ) ) response = getMaxRetransmitSlot( ctx, id_cstr, params );
    3000           0 :   else if( FD_LIKELY( !strcmp( method, "getMaxShredInsertSlot"             ) ) ) response = getMaxShredInsertSlot( ctx, id_cstr, params );
    3001           0 :   else if( FD_LIKELY( !strcmp( method, "getMinimumBalanceForRentExemption" ) ) ) response = getMinimumBalanceForRentExemption( ctx, id_cstr, params );
    3002           0 :   else if( FD_LIKELY( !strcmp( method, "getMultipleAccounts"               ) ) ) response = getMultipleAccounts( ctx, id_cstr, params );
    3003           0 :   else if( FD_LIKELY( !strcmp( method, "getProgramAccounts"                ) ) ) response = getProgramAccounts( ctx, id_cstr, params );
    3004           0 :   else if( FD_LIKELY( !strcmp( method, "getRecentPerformanceSamples"       ) ) ) response = getRecentPerformanceSamples( ctx, id_cstr, params );
    3005           0 :   else if( FD_LIKELY( !strcmp( method, "getRecentPrioritizationFees"       ) ) ) response = getRecentPrioritizationFees( ctx, id_cstr, params );
    3006           0 :   else if( FD_LIKELY( !strcmp( method, "getSignaturesForAddress"           ) ) ) response = getSignaturesForAddress( ctx, id_cstr, params );
    3007           0 :   else if( FD_LIKELY( !strcmp( method, "getSignatureStatuses"              ) ) ) response = getSignatureStatuses( ctx, id_cstr, params );
    3008           0 :   else if( FD_LIKELY( !strcmp( method, "getSlot"                           ) ) ) response = getSlot( ctx, id_cstr, params );
    3009           0 :   else if( FD_LIKELY( !strcmp( method, "getSlotLeader"                     ) ) ) response = getSlotLeader( ctx, id_cstr, params );
    3010           0 :   else if( FD_LIKELY( !strcmp( method, "getSlotLeaders"                    ) ) ) response = getSlotLeaders( ctx, id_cstr, params );
    3011           0 :   else if( FD_LIKELY( !strcmp( method, "getStakeMinimumDelegation"         ) ) ) response = getStakeMinimumDelegation( ctx, id_cstr, params );
    3012           0 :   else if( FD_LIKELY( !strcmp( method, "getSupply"                         ) ) ) response = getSupply( ctx, id_cstr, params );
    3013           0 :   else if( FD_LIKELY( !strcmp( method, "getTokenAccountBalance"            ) ) ) response = getTokenAccountBalance( ctx, id_cstr, params );
    3014           0 :   else if( FD_LIKELY( !strcmp( method, "getTokenAccountsByDelegate"        ) ) ) response = getTokenAccountsByDelegate( ctx, id_cstr, params );
    3015           0 :   else if( FD_LIKELY( !strcmp( method, "getTokenAccountsByOwner"           ) ) ) response = getTokenAccountsByOwner( ctx, id_cstr, params );
    3016           0 :   else if( FD_LIKELY( !strcmp( method, "getTokenLargestAccounts"           ) ) ) response = getTokenLargestAccounts( ctx, id_cstr, params );
    3017           0 :   else if( FD_LIKELY( !strcmp( method, "getTokenSupply"                    ) ) ) response = getTokenSupply( ctx, id_cstr, params );
    3018           0 :   else if( FD_LIKELY( !strcmp( method, "getTransaction"                    ) ) ) response = getTransaction( ctx, id_cstr, params );
    3019           0 :   else if( FD_LIKELY( !strcmp( method, "getTransactionCount"               ) ) ) response = getTransactionCount( ctx, id_cstr, params );
    3020           0 :   else if( FD_LIKELY( !strcmp( method, "getVersion"                        ) ) ) response = getVersion( ctx, id_cstr, params );
    3021           0 :   else if( FD_LIKELY( !strcmp( method, "getVoteAccounts"                   ) ) ) response = getVoteAccounts( ctx, id_cstr, params );
    3022           0 :   else if( FD_LIKELY( !strcmp( method, "slotSubscribe"                     ) ) ) response = slotSubscribe( ctx, id_cstr, params, ws_conn_id );
    3023           0 :   else if( FD_LIKELY( !strcmp( method, "slotUnsubscribe"                   ) ) ) response = slotUnsubscribe( ctx, id_cstr, params, ws_conn_id );
    3024           0 :   else if( FD_LIKELY( !strcmp( method, "voteSubscribe"                     ) ) ) response = voteSubscribe( ctx, id_cstr, params, ws_conn_id );
    3025           0 :   else if( FD_LIKELY( !strcmp( method, "voteUnsubscribe"                   ) ) ) response = voteUnsubscribe( ctx, id_cstr, params, ws_conn_id );
    3026           0 :   else if( FD_LIKELY( !strcmp( method, "isBlockhashValid"                  ) ) ) response = isBlockhashValid( ctx, id_cstr, params );
    3027           0 :   else if( FD_LIKELY( !strcmp( method, "minimumLedgerSlot"                 ) ) ) response = minimumLedgerSlot( ctx, id_cstr, params );
    3028           0 :   else if( FD_LIKELY( !strcmp( method, "requestAirdrop"                    ) ) ) response = requestAirdrop( ctx, id_cstr, params );
    3029           0 :   else if( FD_LIKELY( !strcmp( method, "sendTransaction"                   ) ) ) response = sendTransaction( ctx, id_cstr, params );
    3030           0 :   else if( FD_LIKELY( !strcmp( method, "simulateTransaction"               ) ) ) response = simulateTransaction( ctx, id_cstr, params );
    3031           0 :   else if( FD_LIKELY( !strcmp( method, "getAgGenesisCert"                  ) ) ) response = getAgGenesisCert( ctx, id_cstr, params );
    3032           0 :   else {
    3033           0 :     FD_MCNT_INC( RPC, REQUEST_HANDLED_UNKNOWN, 1UL );
    3034           0 :     response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    3035           0 :   }
    3036             : 
    3037             :   /* Agave pubsub answers non-subscription methods with -32601 and
    3038             :      keeps the socket; a bare 501 would close it and drop subscriptions */
    3039           0 :   if( FD_UNLIKELY( ws_conn_id!=ULONG_MAX && response.status==501UL ) ) {
    3040           0 :     response = PRINTF_JSON( ctx, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":%s}\n", id_cstr );
    3041           0 :   }
    3042             : 
    3043           0 :   return response;
    3044           0 : }
    3045             : 
    3046             : static fd_http_server_response_t
    3047           0 : rpc_http_request( fd_http_server_request_t const * request ) {
    3048           0 :   fd_rpc_tile_t * ctx = request->ctx;
    3049           0 :   long dt = -fd_tickcount();
    3050           0 :   fd_http_server_response_t response = rpc_http_request1( ctx, request );
    3051           0 :   dt += fd_tickcount();
    3052           0 :   fd_histf_sample( ctx->request_duration, (ulong)dt );
    3053           0 :   return response;
    3054           0 : }
    3055             : 
    3056             : static void
    3057             : rpc_ws_open( ulong  ws_conn_id,
    3058           0 :              void * ctx ) {
    3059           0 :   (void)ws_conn_id; (void)ctx;
    3060           0 : }
    3061             : 
    3062             : static void
    3063             : rpc_ws_close( ulong  ws_conn_id,
    3064             :               int    reason     FD_PARAM_UNUSED,
    3065           0 :               void * _ctx ) {
    3066           0 :   fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)_ctx;
    3067           0 :   if( FD_UNLIKELY( ws_conn_id>=ctx->http->max_ws_conns ) ) return;
    3068           0 :   fd_rpc_ws_subscriber_vote_remove( ctx, ws_conn_id );
    3069           0 :   fd_rpc_ws_subscriber_slot_remove( ctx, ws_conn_id );
    3070           0 : }
    3071             : 
    3072             : static void
    3073             : rpc_ws_message( ulong         ws_conn_id,
    3074             :                 uchar const * data,
    3075             :                 ulong         data_len,
    3076           0 :                 void *        _ctx ) {
    3077           0 :   fd_rpc_tile_t * ctx = (fd_rpc_tile_t *)_ctx;
    3078             : 
    3079           0 :   fd_http_server_unstage( ctx->http );
    3080             : 
    3081           0 :   long dt = -fd_tickcount();
    3082           0 :   fd_http_server_response_t response = rpc_json_request( ctx, data, data_len, ws_conn_id );
    3083           0 :   dt += fd_tickcount();
    3084           0 :   fd_histf_sample( ctx->request_duration, (ulong)dt );
    3085             : 
    3086             :   /* fd_http_server_reserve may evict this connection while staging the response. */
    3087           0 :   if( FD_UNLIKELY( ctx->http->pollfds[ ctx->http->max_conns+ws_conn_id ].fd==-1 ) ) return;
    3088             : 
    3089           0 :   if( FD_UNLIKELY( response.status!=200UL ) ) {
    3090           0 :     fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
    3091           0 :     return;
    3092           0 :   }
    3093             : 
    3094           0 :   if( FD_LIKELY( response._body_len ) ) {
    3095           0 :     ulong response_body_end = response._body_off + response._body_len;
    3096             : 
    3097           0 :     ctx->http->stage_off      = response._body_off;
    3098           0 :     ctx->http->stage_len      = response._body_len;
    3099           0 :     ctx->http->stage_comp_len = 0UL;
    3100             : 
    3101           0 :     int err = fd_http_server_ws_send( ctx->http, ws_conn_id );
    3102           0 :     if( FD_UNLIKELY( ctx->http->stage_off<response_body_end ) ) ctx->http->stage_off = response_body_end;
    3103           0 :     if( FD_UNLIKELY( err ) )
    3104           0 :       fd_http_server_ws_close( ctx->http, ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW );
    3105           0 :   }
    3106           0 : }
    3107             : 
    3108             : static void
    3109             : privileged_init( fd_topo_t const *      topo,
    3110           0 :                  fd_topo_tile_t const * tile ) {
    3111           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    3112             : 
    3113           0 :   fd_http_server_params_t http_params = derive_http_params( tile );
    3114             : 
    3115           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    3116           0 :   fd_rpc_tile_t * ctx      = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
    3117           0 :   fd_http_server_t * _http = FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(),   fd_http_server_footprint( http_params ) );
    3118             : 
    3119           0 :   fd_memset( ctx, 0, sizeof(fd_rpc_tile_t) );
    3120             : 
    3121           0 :   if( FD_UNLIKELY( !strcmp( tile->rpc.identity_key_path, "" ) ) )
    3122           0 :     FD_LOG_ERR(( "identity_key_path not set" ));
    3123             : 
    3124           0 :   const uchar * identity_key = fd_keyload_load( tile->rpc.identity_key_path, /* pubkey only: */ 1 );
    3125           0 :   fd_memcpy( ctx->identity_pubkey, identity_key, 32UL );
    3126             : 
    3127           0 :   fd_http_server_callbacks_t callbacks = {
    3128           0 :     .request    = rpc_http_request,
    3129           0 :     .ws_open    = rpc_ws_open,
    3130           0 :     .ws_close   = rpc_ws_close,
    3131           0 :     .ws_message = rpc_ws_message,
    3132           0 :   };
    3133           0 :   ctx->http = fd_http_server_join( fd_http_server_new( _http, http_params, callbacks, ctx ) );
    3134           0 :   ctx->snapshot_server_enabled = tile->rpc.snapshot_server_enabled;
    3135           0 :   if( FD_LIKELY( ctx->snapshot_server_enabled ) ) {
    3136           0 :     FD_TEST( fd_cstr_printf_check(
    3137           0 :         ctx->snapshot_server_url, sizeof(ctx->snapshot_server_url), NULL,
    3138           0 :         "http://%s:%u", tile->rpc.snapshot_server_host,
    3139           0 :         tile->rpc.snapshot_server_port ) );
    3140           0 :   }
    3141           0 :   ctx->waker_client_idx = tile->waker_client_idx;
    3142           0 :   FD_TEST( ctx->waker_client_idx!=ULONG_MAX );
    3143           0 :   fd_http_server_listen6( ctx->http, FD_WAKER_INNER_FD( ctx->waker_client_idx ), &tile->rpc.listen_addr, tile->rpc.listen_port );
    3144           0 :   char listen_addr_cstr[ FD_IP6_ADDR_CSTR_MAX ]; fd_ip6_addr_cstr( listen_addr_cstr, &tile->rpc.listen_addr );
    3145           0 :   FD_LOG_NOTICE(( "rpc server listening at %shttp://%s:%u%s", fd_log_style_bold(), listen_addr_cstr, tile->rpc.listen_port, fd_log_style_normal() ));
    3146           0 : }
    3147             : 
    3148             : static inline fd_rpc_out_t
    3149             : out1( fd_topo_t const *      topo,
    3150             :       fd_topo_tile_t const * tile,
    3151           0 :       char const *           name ) {
    3152           0 :   ulong idx = ULONG_MAX;
    3153             : 
    3154           0 :   for( ulong i=0UL; i<tile->out_cnt; i++ ) {
    3155           0 :     fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
    3156           0 :     if( !strcmp( link->name, name ) ) {
    3157           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 ));
    3158           0 :       idx = i;
    3159           0 :     }
    3160           0 :   }
    3161             : 
    3162           0 :   if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_rpc_out_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
    3163             : 
    3164             : 
    3165           0 :   ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
    3166           0 :   if( FD_UNLIKELY( mtu==0UL ) ) return (fd_rpc_out_t){ .idx = idx, .mem = NULL, .chunk0 = ULONG_MAX, .wmark = ULONG_MAX, .chunk = ULONG_MAX };
    3167             : 
    3168           0 :   void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
    3169           0 :   ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
    3170           0 :   ulong wmark  = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
    3171             : 
    3172           0 :   return (fd_rpc_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
    3173           0 : }
    3174             : 
    3175             : static void
    3176             : unprivileged_init( fd_topo_t const *      topo,
    3177           0 :                    fd_topo_tile_t const * tile ) {
    3178           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    3179             : 
    3180           0 :   fd_http_server_params_t http_params = derive_http_params( tile );
    3181             : 
    3182           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    3183           0 :   fd_rpc_tile_t * ctx    = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rpc_tile_t),            sizeof(fd_rpc_tile_t)                                              );
    3184           0 :                            FD_SCRATCH_ALLOC_APPEND( l, fd_http_server_align(),            fd_http_server_footprint( http_params )                            );
    3185           0 :   void * _bz2_alloc      = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(),                  fd_alloc_footprint()                                               );
    3186           0 :   void * _banks          = FD_SCRATCH_ALLOC_APPEND( l, alignof(bank_info_t),              tile->rpc.max_live_slots*sizeof(bank_info_t)                       );
    3187           0 :   void * _nodes_dlist    = FD_SCRATCH_ALLOC_APPEND( l, fd_rpc_cluster_node_dlist_align(), fd_rpc_cluster_node_dlist_footprint()                              );
    3188           0 :   void * _accdb_join     = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(),                  fd_accdb_footprint( tile->rpc.max_live_slots, 0 )                     );
    3189           0 :   void * _ws_sub_vote    = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),                    http_params.max_ws_connection_cnt*sizeof(ulong)                    );
    3190           0 :   void * _ws_sub_slot    = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),                    http_params.max_ws_connection_cnt*sizeof(ulong)                    );
    3191           0 :   void * _genesis_tar    = FD_SCRATCH_ALLOC_APPEND( l, alignof(uchar),                    fd_rpc_genesis_tar_max_sz( tile->rpc.genesis_max_message_size )    );
    3192           0 :   void * _genesis_tar_bz = FD_SCRATCH_ALLOC_APPEND( l, alignof(uchar),                    fd_rpc_genesis_tar_bz_max_sz( tile->rpc.genesis_max_message_size ) );
    3193           0 :   ulong  zstd_wksp_sz = ZSTD_estimateCCtxSize( FD_RPC_ZSTD_LEVEL );
    3194           0 :   void * _zstd_wksp   = FD_SCRATCH_ALLOC_APPEND( l, 16UL,                     zstd_wksp_sz                                           );
    3195             : 
    3196           0 :   ctx->delay_startup       = tile->rpc.delay_startup;
    3197           0 :   ctx->alpenglow           = tile->rpc.alpenglow;
    3198           0 :   fd_alpenglow_pda( "carlgration", ctx->ag_genesis_cert_addr );
    3199           0 :   ctx->waker_rearm_pending = 0;
    3200           0 :   ctx->ws_subscribers_vote = _ws_sub_vote;
    3201           0 :   ctx->ws_subscribers_vote_cnt = 0UL;
    3202           0 :   ctx->ws_subscribers_slot = _ws_sub_slot;
    3203           0 :   ctx->ws_subscribers_slot_cnt = 0UL;
    3204             : 
    3205           0 :   ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
    3206           0 :   FD_TEST( ctx->keyswitch );
    3207             : 
    3208           0 :   ctx->bz2_alloc = fd_alloc_join( fd_alloc_new( _bz2_alloc, 1UL ), 1UL );
    3209           0 :   FD_TEST( ctx->bz2_alloc );
    3210             : 
    3211           0 :   ctx->zstd_cctx = ZSTD_initStaticCCtx( _zstd_wksp, zstd_wksp_sz );
    3212           0 :   FD_CHECK_ERR( ctx->zstd_cctx, "ZSTD_initStaticCCtx failed" );
    3213             : 
    3214           0 :   fd_clock_tile_init( ctx->clock );
    3215           0 :   ctx->in_cnt          = tile->in_cnt;
    3216           0 :   ctx->idle_cnt        = 0UL;
    3217             : 
    3218           0 :   FD_TEST( ctx->waker_client_idx!=ULONG_MAX );
    3219           0 :   ctx->waker_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->waker_fseq_obj_id ) );
    3220           0 :   FD_TEST( ctx->waker_fseq );
    3221             : 
    3222           0 :   ctx->cluster_confirmed_slot = ULONG_MAX;
    3223           0 :   ctx->genesis_max_message_size = tile->rpc.genesis_max_message_size;
    3224           0 :   ctx->genesis_tar_max_sz = fd_rpc_genesis_tar_max_sz( tile->rpc.genesis_max_message_size );
    3225           0 :   ctx->genesis_tar_bz_max_sz = fd_rpc_genesis_tar_bz_max_sz( tile->rpc.genesis_max_message_size );
    3226           0 :   ctx->genesis_tar = _genesis_tar;
    3227           0 :   ctx->genesis_tar_bz = _genesis_tar_bz;
    3228           0 :   ctx->genesis_tar_bz_sz = ULONG_MAX;
    3229             : 
    3230           0 :   ctx->processed_idx = ULONG_MAX;
    3231           0 :   ctx->confirmed_idx = ULONG_MAX;
    3232           0 :   ctx->finalized_idx = ULONG_MAX;
    3233             : 
    3234           0 :   ctx->cluster_nodes_dlist = fd_rpc_cluster_node_dlist_join( fd_rpc_cluster_node_dlist_new( _nodes_dlist ) );
    3235           0 :   ctx->banks = _banks;
    3236           0 :   ctx->max_live_slots = tile->rpc.max_live_slots;
    3237           0 :   for( ulong i=0UL; i<ctx->max_live_slots; i++ ) ctx->banks[ i ].slot = ULONG_MAX;
    3238             : 
    3239           0 :   fd_rpc_mleaders_init( ctx->mleaders );
    3240           0 :   ctx->has_epoch_schedule = 0;
    3241             : 
    3242           0 :   FD_TEST( tile->in_cnt<=sizeof( ctx->in )/sizeof( ctx->in[ 0 ] ) );
    3243           0 :   for( ulong i=0; i<tile->in_cnt; i++ ) {
    3244           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    3245           0 :     fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
    3246             : 
    3247           0 :     ctx->in[ i ].mem    = link_wksp->wksp;
    3248           0 :     ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    3249           0 :     ctx->in[ i ].wmark  = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
    3250           0 :     ctx->in[ i ].mtu    = link->mtu;
    3251             : 
    3252           0 :     if     ( FD_LIKELY( !strcmp( link->name, "replay_slot"  ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
    3253           0 :     else if( FD_LIKELY( !strcmp( link->name, "genesi_out"   ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESI;
    3254           0 :     else if( FD_LIKELY( !strcmp( link->name, "gossip_ciaddr" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
    3255           0 :     else if( FD_LIKELY( !strcmp( link->name, "gossip_vote"  ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
    3256           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH;
    3257           0 :     else if( FD_UNLIKELY( !strcmp( link->name, "votor_out"  ) ) ) ctx->in_kind[ i ] = IN_KIND_VOTOR;
    3258           0 :     else FD_LOG_ERR(( "unexpected link name %s", link->name ));
    3259           0 :   }
    3260             : 
    3261           0 :   for( ctx->shred_rtx_cnt=0UL; ctx->shred_rtx_cnt<FD_TOPO_MAX_TILES; ctx->shred_rtx_cnt++ ) {
    3262           0 :     ulong rtx_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "shred_rtx.%lu", ctx->shred_rtx_cnt );
    3263           0 :     if( FD_UNLIKELY( rtx_obj_id==ULONG_MAX ) ) break;
    3264           0 :     ctx->shred_rtx[ ctx->shred_rtx_cnt ] = fd_fseq_join( fd_topo_obj_laddr( topo, rtx_obj_id ) );
    3265           0 :     FD_TEST( ctx->shred_rtx[ ctx->shred_rtx_cnt ] );
    3266           0 :   }
    3267             : 
    3268           0 :   *ctx->replay_out = out1( topo, tile, "rpc_replay" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
    3269             : 
    3270             :   /* Read-only join to accdb.  The accdb workspace is mapped
    3271             :      PROT_READ in this tile (see topology); the only writable
    3272             :      external mapping is our private epoch fseq.  fd FD_ACCDB_FD_RO is
    3273             :      the O_RDONLY dup of the accdb data file. */
    3274           0 :   void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->rpc.accdb_obj_id );
    3275           0 :   fd_accdb_shmem_t * accdb_shmem_ro = fd_accdb_shmem_join( _accdb_shmem );
    3276           0 :   FD_TEST( accdb_shmem_ro );
    3277           0 :   ulong * epoch_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->rpc.accdb_epoch_fseq_obj_id ) );
    3278           0 :   FD_TEST( epoch_fseq );
    3279           0 :   ctx->accdb = fd_accdb_join_readonly( _accdb_join, accdb_shmem_ro, epoch_fseq, FD_ACCDB_FD_RO );
    3280           0 :   FD_TEST( ctx->accdb );
    3281             : 
    3282           0 :   fd_histf_join( fd_metrics_histf_new_ns( ctx->request_duration, FD_MHIST_EDGES_NS( RPC, REQUEST_DURATION_NANOS ) ) );
    3283             : 
    3284           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    3285           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    3286           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    3287           0 : }
    3288             : 
    3289             : static ulong
    3290             : populate_allowed_seccomp( fd_topo_t const *      topo,
    3291             :                           fd_topo_tile_t const * tile,
    3292             :                           ulong                  out_cnt,
    3293           0 :                           struct sock_filter *   out ) {
    3294           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    3295           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    3296           0 :   fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
    3297             : 
    3298           0 :   uint epoll_inner_fd = (uint)FD_WAKER_INNER_FD( tile->waker_client_idx );
    3299           0 :   uint epoll_outer_fd = (uint)FD_WAKER_OUTER_FD;
    3300             : 
    3301           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, epoll_inner_fd, epoll_outer_fd );
    3302           0 :   return sock_filter_policy_fd_rpc_tile_instr_cnt;
    3303           0 : }
    3304             : 
    3305             : static ulong
    3306             : populate_allowed_fds( fd_topo_t const *      topo,
    3307             :                       fd_topo_tile_t const * tile,
    3308             :                       ulong                  out_fds_cnt,
    3309           0 :                       int *                  out_fds ) {
    3310           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    3311           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    3312           0 :   fd_rpc_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_rpc_tile_t ), sizeof( fd_rpc_tile_t ) );
    3313             : 
    3314           0 :   if( FD_UNLIKELY( out_fds_cnt<6UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    3315             : 
    3316           0 :   ulong out_cnt = 0UL;
    3317           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    3318           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    3319           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    3320           0 :   out_fds[ out_cnt++ ] = fd_http_server_fd( ctx->http ); /* rpc listen socket */
    3321           0 :   out_fds[ out_cnt++ ] = FD_ACCDB_FD_RO; /* accounts db readonly fd */
    3322           0 :   out_fds[ out_cnt++ ] = FD_WAKER_OUTER_FD;
    3323           0 :   out_fds[ out_cnt++ ] = FD_WAKER_INNER_FD( tile->waker_client_idx );
    3324             : 
    3325           0 :   return out_cnt;
    3326           0 : }
    3327             : 
    3328             : static ulong
    3329             : rlimit_file_cnt( fd_topo_t const *      topo FD_PARAM_UNUSED,
    3330           0 :                  fd_topo_tile_t const * tile ) {
    3331             :   /* pipefd, socket, stderr, logfile, and one spare for new accept() connections */
    3332           0 :   ulong base = 5UL;
    3333           0 :   return base + tile->rpc.max_http_connections + tile->rpc.max_websocket_connections;
    3334           0 : }
    3335             : 
    3336           0 : #define STEM_BURST (3UL)
    3337             : 
    3338             : /* The default STEM_LAZY is based on cr_max, which is the minimum depth
    3339             :    across all output links that have at least one reliable consumer.
    3340             :    RPC has one tiny output link used to release banks, with a
    3341             :    significantly slower line rate than assumed in the formula for the
    3342             :    default STEM_LAZY value.
    3343             : 
    3344             :    Instead, lazy just needs to be frequent enough to relinquish credits
    3345             :    to upstream producers faster than they are exhausted. 384us is a
    3346             :    reasonable default used in many other non-critical tiles. */
    3347           0 : #define STEM_LAZY  (128L*3000L)
    3348             : 
    3349           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_rpc_tile_t
    3350           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_rpc_tile_t)
    3351             : 
    3352           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    3353           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    3354           0 : #define STEM_CALLBACK_BEFORE_CREDIT       before_credit
    3355           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    3356           0 : #define STEM_CALLBACK_RETURNABLE_FRAG     returnable_frag
    3357             : 
    3358             : #include "../../disco/stem/fd_stem.c"
    3359             : 
    3360             : #ifndef FD_TILE_TEST
    3361             : fd_topo_run_tile_t fd_tile_rpc = {
    3362             :   .name                     = "rpc",
    3363             :   .rlimit_file_cnt_fn       = rlimit_file_cnt,
    3364             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    3365             :   .populate_allowed_fds     = populate_allowed_fds,
    3366             :   .scratch_align            = scratch_align,
    3367             :   .scratch_footprint        = scratch_footprint,
    3368             :   .loose_footprint          = loose_footprint,
    3369             :   .privileged_init          = privileged_init,
    3370             :   .unprivileged_init        = unprivileged_init,
    3371             :   .run                      = stem_run,
    3372             : };
    3373             : #endif

Generated by: LCOV version 1.14