diff --git a/config/extra/with-rpcserver.mk b/config/extra/with-rpcserver.mk
new file mode 100644
index 0000000000..3a25227280
--- /dev/null
+++ b/config/extra/with-rpcserver.mk
@@ -0,0 +1 @@
+FD_WITH_RPCSERVER:=1
diff --git a/src/app/rpcserver/Local.mk b/src/app/rpcserver/Local.mk
new file mode 100644
index 0000000000..55a0a50d12
--- /dev/null
+++ b/src/app/rpcserver/Local.mk
@@ -0,0 +1,10 @@
+ifdef FD_WITH_RPCSERVER
+
+LDFLAGS+=-Wl,--push-state,-Bstatic -lmicrohttpd -Wl,--pop-state -lgmp -lcrypto
+
+$(call make-bin,fd_rpcserver,main fd_block_to_json fd_methods fd_rpc_service fd_webserver json_lex keywords,fd_flamenco fd_ballet fd_reedsol fd_disco fd_funk fd_shred fd_tango fd_choreo fd_waltz fd_util, $(SECP256K1_LIBS))
+
+$(call make-unit-test,test_rpc_keywords,test_keywords keywords,fd_util)
+$(call fuzz-test,fuzz_json_lex,fuzz_json_lex,fd_util)
+
+endif
diff --git a/src/app/rpcserver/deps.sh b/src/app/rpcserver/deps.sh
new file mode 100755
index 0000000000..433264bb20
--- /dev/null
+++ b/src/app/rpcserver/deps.sh
@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# Install prefix
+cd ../../..
+PREFIX="$(pwd)/opt"
+
+checkout_gnuweb () {
+ # Skip if dir already exists
+ if [[ -d ./opt/gnuweb/"$1" ]]; then
+ echo "[~] Skipping $1 fetch as \"$(pwd)/opt/gnuweb/$1\" already exists"
+ else
+ echo "[+] Cloning $1 from $2/$3.tar.gz"
+ curl -o - -L "$2/$3.tar.gz" | gunzip | tar xf - -C ./opt/gnuweb
+ mv ./opt/gnuweb/$3 ./opt/gnuweb/$1
+ echo
+ fi
+}
+
+fetch () {
+ mkdir -pv ./opt/gnuweb
+ checkout_gnuweb libmicrohttpd https://ftp.gnu.org/gnu/libmicrohttpd/ "libmicrohttpd-0.9.77"
+}
+
+install_libmicrohttpd () {
+ cd ./opt/gnuweb/libmicrohttpd/
+ ./configure \
+ --prefix="$PREFIX" \
+ --disable-https \
+ --disable-curl \
+ --disable-dauth \
+ --with-pic
+ make -j
+ make install
+
+ echo "[+] Successfully installed libmicrohttpd"
+}
+
+install () {
+ CC="$(command -v gcc)"
+ cc="$CC"
+ export CC
+ export cc
+
+ ( install_libmicrohttpd )
+
+ echo "[~] Done!"
+}
+
+ACTION=0
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ fetch)
+ shift
+ fetch
+ ACTION=1
+ ;;
+ install)
+ shift
+ install
+ ACTION=1
+ ;;
+ *)
+ echo "Unknown command: $1" >&2
+ exit 1
+ ;;
+ esac
+done
+
+if [[ $ACTION == 0 ]]; then
+ fetch
+ install
+fi
diff --git a/src/app/rpcserver/fd_block_to_json.c b/src/app/rpcserver/fd_block_to_json.c
new file mode 100644
index 0000000000..2781cd2a7f
--- /dev/null
+++ b/src/app/rpcserver/fd_block_to_json.c
@@ -0,0 +1,513 @@
+#include
+#include
+#include "../../util/fd_util.h"
+#include "../../flamenco/nanopb/pb_decode.h"
+#include "fd_webserver.h"
+#include "../../ballet/txn/fd_txn.h"
+#include "../../ballet/block/fd_microblock.h"
+#include "../../ballet/base58/fd_base58.h"
+#include "../../flamenco/types/fd_types.h"
+#include "../../flamenco/types/fd_solana_block.pb.h"
+#include "../../flamenco/runtime/fd_blockstore.h"
+#include "fd_block_to_json.h"
+
+#define EMIT_SIMPLE(_str_) fd_textstream_append(ts, _str_, sizeof(_str_)-1)
+
+void fd_tokenbalance_to_json( fd_textstream_t * ts, struct _fd_solblock_TokenBalance * b ) {
+ fd_textstream_sprintf(ts, "{\"accountIndex\":%u,\"mint\":\"%s\",\"owner\":\"%s\",\"programId\":\"%s\",\"uiTokenAmount\":{",
+ b->account_index, b->mint, b->owner, b->program_id);
+ fd_textstream_sprintf(ts, "\"amount\":\"%s\",", b->ui_token_amount.amount);
+ int dec;
+ if (b->ui_token_amount.has_decimals) {
+ fd_textstream_sprintf(ts, "\"decimals\":%u,", b->ui_token_amount.decimals);
+ dec = (int)b->ui_token_amount.decimals;
+ } else
+ dec = 0;
+ if (b->ui_token_amount.has_ui_amount)
+ fd_textstream_sprintf(ts, "\"uiAmount\":%.*f,", dec, b->ui_token_amount.ui_amount);
+ fd_textstream_sprintf(ts, "\"uiAmountString\":\"%s\"}}", b->ui_token_amount.ui_amount_string);
+}
+
+void fd_error_to_json( fd_textstream_t * ts,
+ const uchar* bytes,
+ ulong size ) {
+ /* I worked this out by brute force examination of actual cases */
+
+ const uchar* orig_bytes = bytes;
+ ulong orig_size = size;
+
+#define INSTRUCTION_ERROR 8
+ if (size < sizeof(uint) || *(const uint*)bytes != INSTRUCTION_ERROR) /* Always the same? */
+ goto dump_as_hex;
+ bytes += sizeof(uint);
+ size -= sizeof(uint);
+
+ if (size < 1)
+ goto dump_as_hex;
+ uint index = *(bytes++); /* Instruction index */
+ size--;
+
+ if (size < sizeof(uint))
+ goto dump_as_hex;
+ uint cnum = *(const uint*)bytes;
+ bytes += sizeof(uint);
+ size -= sizeof(uint);
+
+ switch (cnum) {
+ case 25: { /* "Custom" */
+ if (size < sizeof(uint))
+ goto dump_as_hex;
+ uint code = *(const uint*)bytes; /* Custom code? */
+ fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,{\"Custom\":%u}]}", index, code);
+ return;
+ }
+
+ case 0: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"GenericError\"]}", index); return;
+ case 1: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"InvalidArgument\"]}", index); return;
+ case 2: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"InvalidInstructionData\"]}", index); return;
+ case 3: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"InvalidAccountData\"]}", index); return;
+ case 4: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"AccountDataTooSmall\"]}", index); return;
+ case 5: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"InsufficientFunds\"]}", index); return;
+ case 6: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"IncorrectProgramId\"]}", index); return;
+ case 7: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"MissingRequiredSignature\"]}", index); return;
+ case 8: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"AccountAlreadyInitialized\"]}", index); return;
+ case 9: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"UninitializedAccount\"]}", index); return;
+ case 10: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"UnbalancedInstruction\"]}", index); return;
+ case 11: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ModifiedProgramId\"]}", index); return;
+ case 12: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ExternalAccountLamportSpend\"]}", index); return;
+ case 13: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ExternalAccountDataModified\"]}", index); return;
+ case 14: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ReadonlyLamportChange\"]}", index); return;
+ case 15: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ReadonlyDataModified\"]}", index); return;
+ case 16: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"DuplicateAccountIndex\"]}", index); return;
+ case 17: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ExecutableModified\"]}", index); return;
+ case 18: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"RentEpochModified\"]}", index); return;
+ case 19: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"NotEnoughAccountKeys\"]}", index); return;
+ case 20: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"AccountDataSizeChanged\"]}", index); return;
+ case 21: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"AccountNotExecutable\"]}", index); return;
+ case 22: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"AccountBorrowFailed\"]}", index); return;
+ case 23: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"AccountBorrowOutstanding\"]}", index); return;
+ case 24: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"DuplicateAccountOutOfSync\"]}", index); return;
+ case 26: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"InvalidError\"]}", index); return;
+ case 27: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ExecutableDataModified\"]}", index); return;
+ case 28: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ExecutableLamportChange\"]}", index); return;
+ case 29: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ExecutableAccountNotRentExempt\"]}", index); return;
+ case 30: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"UnsupportedProgramId\"]}", index); return;
+ case 31: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"CallDepth\"]}", index); return;
+ case 32: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"MissingAccount\"]}", index); return;
+ case 33: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ReentrancyNotAllowed\"]}", index); return;
+ case 34: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"MaxSeedLengthExceeded\"]}", index); return;
+ case 35: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"InvalidSeeds\"]}", index); return;
+ case 36: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"InvalidRealloc\"]}", index); return;
+ case 37: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ComputationalBudgetExceeded\"]}", index); return;
+ case 38: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"PrivilegeEscalation\"]}", index); return;
+ case 39: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ProgramEnvironmentSetupFailure\"]}", index); return;
+ case 40: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ProgramFailedToComplete\"]}", index); return;
+ case 41: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ProgramFailedToCompile\"]}", index); return;
+ case 42: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"Immutable\"]}", index); return;
+ case 43: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"IncorrectAuthority\"]}", index); return;
+ case 44: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"BorshIoError(String::new())\"]}", index); return;
+ case 45: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"AccountNotRentExempt\"]}", index); return;
+ case 46: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"InvalidAccountOwner\"]}", index); return;
+ case 47: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"ArithmeticOverflow\"]}", index); return;
+ case 48: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"UnsupportedSysvar\"]}", index); return;
+ case 49: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"IllegalOwner\"]}", index); return;
+ case 50: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"MaxAccountsDataSizeExceeded\"]}", index); return;
+ case 51: fd_textstream_sprintf(ts, "{\"InstructionError\":[%u,\"MaxAccountsExceeded\"]}", index); return;
+ }
+
+ dump_as_hex:
+ EMIT_SIMPLE("\"");
+ fd_textstream_encode_hex(ts, orig_bytes, orig_size);
+ EMIT_SIMPLE("\"");
+}
+
+void fd_inner_instructions_to_json( fd_textstream_t * ts,
+ struct _fd_solblock_InnerInstructions * insts ) {
+ fd_textstream_sprintf(ts, "{\"index\":%u,\"instructions\":[", insts->index);
+ for ( pb_size_t i = 0; i < insts->instructions_count; ++i ) {
+ struct _fd_solblock_InnerInstruction * inst = insts->instructions + i;
+ fd_textstream_sprintf(ts, "%s{\"data\":\"", (i == 0 ? "" : ","));
+ fd_textstream_encode_base58(ts, inst->data->bytes, inst->data->size);
+ fd_textstream_sprintf(ts, "\",\"programIdIndex:\":%u}", inst->program_id_index);
+ }
+ EMIT_SIMPLE("]}");
+}
+
+int fd_txn_meta_to_json( fd_textstream_t * ts,
+ const void * meta_raw,
+ ulong meta_raw_sz ) {
+ fd_solblock_TransactionStatusMeta txn_status = {0};
+ pb_istream_t stream = pb_istream_from_buffer( meta_raw, meta_raw_sz );
+ if( FD_UNLIKELY( !pb_decode( &stream, fd_solblock_TransactionStatusMeta_fields, &txn_status ) ) ) {
+ FD_LOG_ERR(( "failed to decode txn status: %s", PB_GET_ERROR( &stream ) ));
+ }
+
+ EMIT_SIMPLE("\"meta\":{");
+ if (txn_status.has_compute_units_consumed)
+ fd_textstream_sprintf(ts, "\"computeUnitsConsumed\":%lu,", txn_status.compute_units_consumed);
+ EMIT_SIMPLE("\"err\":");
+ if (txn_status.has_err)
+ fd_error_to_json(ts, txn_status.err.err->bytes, txn_status.err.err->size);
+ else
+ EMIT_SIMPLE("null");
+ fd_textstream_sprintf(ts, ",\"fee\":%lu,\"innerInstructions\":[", txn_status.fee);
+ if (!txn_status.inner_instructions_none) {
+ for (pb_size_t i = 0; i < txn_status.inner_instructions_count; ++i) {
+ if ( i > 0 ) EMIT_SIMPLE(",");
+ fd_inner_instructions_to_json(ts, txn_status.inner_instructions + i);
+ }
+ }
+ EMIT_SIMPLE("],\"loadedAddresses\":{\"readonly\":[");
+ for (pb_size_t i = 0; i < txn_status.loaded_readonly_addresses_count; ++i) {
+ pb_bytes_array_t * ba = txn_status.loaded_readonly_addresses[i];
+ if (ba->size == 32) {
+ char buf32[FD_BASE58_ENCODED_32_SZ];
+ fd_base58_encode_32(ba->bytes, NULL, buf32);
+ fd_textstream_sprintf(ts, "%s\"%s\"", (i == 0 ? "" : ","), buf32);
+ } else
+ fd_textstream_sprintf(ts, "%s\"\"", (i == 0 ? "" : ","));
+ }
+ EMIT_SIMPLE("],\"writable\":[");
+ for (pb_size_t i = 0; i < txn_status.loaded_writable_addresses_count; ++i) {
+ pb_bytes_array_t * ba = txn_status.loaded_writable_addresses[i];
+ if (ba->size == 32) {
+ char buf32[FD_BASE58_ENCODED_32_SZ];
+ fd_base58_encode_32(ba->bytes, NULL, buf32);
+ fd_textstream_sprintf(ts, "%s\"%s\"", (i == 0 ? "" : ","), buf32);
+ } else
+ fd_textstream_sprintf(ts, "%s\"\"", (i == 0 ? "" : ","));
+ }
+ EMIT_SIMPLE("]},\"logMessages\":[");
+ for (pb_size_t i = 0; i < txn_status.log_messages_count; ++i)
+ fd_textstream_sprintf(ts, "%s\"%s\"", (i == 0 ? "" : ","), txn_status.log_messages[i]);
+ EMIT_SIMPLE("],\"postBalances\":[");
+ for (pb_size_t i = 0; i < txn_status.post_balances_count; ++i)
+ fd_textstream_sprintf(ts, "%s%lu", (i == 0 ? "" : ","), txn_status.post_balances[i]);
+ EMIT_SIMPLE("],\"postTokenBalances\":[");
+ for (pb_size_t i = 0; i < txn_status.post_token_balances_count; ++i) {
+ if (i > 0) EMIT_SIMPLE(",");
+ fd_tokenbalance_to_json(ts, txn_status.post_token_balances + i);
+ }
+ EMIT_SIMPLE("],\"preBalances\":[");
+ for (pb_size_t i = 0; i < txn_status.pre_balances_count; ++i)
+ fd_textstream_sprintf(ts, "%s%lu", (i == 0 ? "" : ","), txn_status.pre_balances[i]);
+ EMIT_SIMPLE("],\"preTokenBalances\":[");
+ for (pb_size_t i = 0; i < txn_status.pre_token_balances_count; ++i) {
+ if (i > 0) EMIT_SIMPLE(",");
+ fd_tokenbalance_to_json(ts, txn_status.pre_token_balances + i);
+ }
+ EMIT_SIMPLE("],\"rewards\":[");
+ EMIT_SIMPLE("],\"status\":{\"Ok\":null}},");
+
+ pb_release( fd_solblock_TransactionStatusMeta_fields, &txn_status );
+
+ return 0;
+}
+
+int fd_txn_to_json_full( fd_textstream_t * ts,
+ fd_txn_t* txn,
+ const uchar* raw,
+ fd_rpc_encoding_t encoding,
+ long maxvers,
+ int rewards ) {
+ (void)encoding;
+ (void)maxvers;
+ (void)rewards;
+
+ EMIT_SIMPLE("\"transaction\":{\"message\":{\"accountKeys\":[");
+
+ ushort acct_cnt = txn->acct_addr_cnt;
+ const fd_pubkey_t * accts = (const fd_pubkey_t *)(raw + txn->acct_addr_off);
+ char buf32[FD_BASE58_ENCODED_32_SZ];
+ for (ushort idx = 0; idx < acct_cnt; idx++) {
+ fd_base58_encode_32(accts[idx].uc, NULL, buf32);
+ fd_textstream_sprintf(ts, "%s\"%s\"", (idx == 0 ? "" : ","), buf32);
+ }
+
+ fd_textstream_sprintf(ts, "],\"header\":{\"numReadonlySignedAccounts\":%u,\"numReadonlyUnsignedAccounts\":%u,\"numRequiredSignatures\":%u},\"instructions\":[",
+ (uint)txn->readonly_signed_cnt, (uint)txn->readonly_unsigned_cnt, (uint)txn->signature_cnt);
+
+ ushort instr_cnt = txn->instr_cnt;
+ for (ushort idx = 0; idx < instr_cnt; idx++) {
+ fd_textstream_sprintf(ts, "%s{\"accounts\":[", (idx == 0 ? "" : ","));
+
+ fd_txn_instr_t * instr = &txn->instr[idx];
+ const uchar * instr_acc_idxs = raw + instr->acct_off;
+ for (ushort j = 0; j < instr->acct_cnt; j++)
+ fd_textstream_sprintf(ts, "%s%u", (j == 0 ? "" : ","), (uint)instr_acc_idxs[j]);
+
+ EMIT_SIMPLE("],\"data\":\"");
+ fd_textstream_encode_base58(ts, raw + instr->data_off, instr->data_sz);
+
+ fd_textstream_sprintf(ts, "\",\"programIdIndex\":%u,\"stackHeight\":null}", (uint)instr->program_id);
+ }
+
+ const fd_hash_t * recent = (const fd_hash_t *)(raw + txn->recent_blockhash_off);
+ fd_base58_encode_32(recent->uc, NULL, buf32);
+ fd_textstream_sprintf(ts, "],\"recentBlockhash\":\"%s\"},\"signatures\":[", buf32);
+
+ fd_ed25519_sig_t const * sigs = (fd_ed25519_sig_t const *)(raw + txn->signature_off);
+ for ( uchar j = 0; j < txn->signature_cnt; j++ ) {
+ char buf64[FD_BASE58_ENCODED_64_SZ];
+ fd_base58_encode_64((const uchar*)&sigs[j], NULL, buf64);
+ fd_textstream_sprintf(ts, "%s\"%s\"", (j == 0 ? "" : ","), buf64);
+ }
+
+ const char* vers;
+ switch (txn->transaction_version) {
+ case FD_TXN_VLEGACY: vers = "\"legacy\""; break;
+ case FD_TXN_V0: vers = "0"; break;
+ default: vers = "\"?\""; break;
+ }
+ fd_textstream_sprintf(ts, "]},\"version\":%s", vers);
+
+ return 0;
+}
+int fd_txn_to_json_accts( fd_textstream_t * ts,
+ fd_txn_t* txn,
+ const uchar* raw,
+ fd_rpc_encoding_t encoding,
+ long maxvers,
+ int rewards ) {
+ (void)encoding;
+ (void)maxvers;
+ (void)rewards;
+
+ EMIT_SIMPLE("\"transaction\":{\"accountKeys\":[");
+
+ ushort acct_cnt = txn->acct_addr_cnt;
+ const fd_pubkey_t * accts = (const fd_pubkey_t *)(raw + txn->acct_addr_off);
+ char buf32[FD_BASE58_ENCODED_32_SZ];
+ for (ushort idx = 0; idx < acct_cnt; idx++) {
+ fd_base58_encode_32(accts[idx].uc, NULL, buf32);
+ bool signer = (idx < txn->signature_cnt);
+ bool writable = ((idx < txn->signature_cnt - txn->readonly_signed_cnt) ||
+ ((idx >= txn->signature_cnt) && (idx < acct_cnt - txn->readonly_unsigned_cnt)));
+ fd_textstream_sprintf(ts, "%s{\"pubkey\":\"%s\",\"signer\":%s,\"source\":\"transaction\",\"writable\":%s}",
+ (idx == 0 ? "" : ","), buf32, (signer ? "true" : "false"), (writable ? "true" : "false"));
+ }
+
+ fd_textstream_sprintf(ts, "],\"signatures\":[");
+ fd_ed25519_sig_t const * sigs = (fd_ed25519_sig_t const *)(raw + txn->signature_off);
+ for ( uchar j = 0; j < txn->signature_cnt; j++ ) {
+ char buf64[FD_BASE58_ENCODED_64_SZ];
+ fd_base58_encode_64((const uchar*)&sigs[j], NULL, buf64);
+ fd_textstream_sprintf(ts, "%s\"%s\"", (j == 0 ? "" : ","), buf64);
+ }
+ EMIT_SIMPLE("]}");
+
+ return 0;
+}
+
+int fd_txn_to_json( fd_textstream_t * ts,
+ fd_txn_t* txn,
+ const uchar* raw,
+ fd_rpc_encoding_t encoding,
+ long maxvers,
+ enum fd_block_detail detail,
+ int rewards ) {
+ if( detail == FD_BLOCK_DETAIL_FULL )
+ return fd_txn_to_json_full( ts, txn, raw, encoding, maxvers, rewards );
+ else if( detail == FD_BLOCK_DETAIL_ACCTS )
+ return fd_txn_to_json_accts( ts, txn, raw, encoding, maxvers, rewards );
+ FD_LOG_ERR(("unsupported detail parameter"));
+ return -1;
+}
+
+int fd_block_to_json( fd_textstream_t * ts,
+ long call_id,
+ fd_block_t * blk,
+ const uchar * blk_data,
+ ulong blk_sz,
+ fd_slot_meta_t * meta,
+ fd_rpc_encoding_t encoding,
+ long maxvers,
+ enum fd_block_detail detail,
+ int rewards) {
+ EMIT_SIMPLE("{\"jsonrpc\":\"2.0\",\"result\":{");
+
+ if ( meta ) {
+ fd_textstream_sprintf(ts, "\"blockHeight\":%lu,\"blockTime\":%ld,\"parentSlot\":%lu",
+ blk->height, blk->ts/(long)1e9, meta->parent_slot);
+ }
+
+ if( detail == FD_BLOCK_DETAIL_NONE ) {
+ fd_textstream_sprintf(ts, "},\"id\":%lu}", call_id);
+ return 0;
+ }
+
+ EMIT_SIMPLE(",");
+
+ if( detail == FD_BLOCK_DETAIL_SIGS ) {
+ EMIT_SIMPLE("\"signatures\":[");
+
+ int first_sig = 1;
+ ulong blockoff = 0;
+ while (blockoff < blk_sz) {
+ if ( blockoff + sizeof(ulong) > blk_sz )
+ FD_LOG_ERR(("premature end of block"));
+ ulong mcount = *(const ulong *)(blk_data + blockoff);
+ blockoff += sizeof(ulong);
+
+ /* Loop across microblocks */
+ for (ulong mblk = 0; mblk < mcount; ++mblk) {
+ if ( blockoff + sizeof(fd_microblock_hdr_t) > blk_sz )
+ FD_LOG_ERR(("premature end of block"));
+ fd_microblock_hdr_t * hdr = (fd_microblock_hdr_t *)((const uchar *)blk_data + blockoff);
+ blockoff += sizeof(fd_microblock_hdr_t);
+
+ /* Loop across transactions */
+ for ( ulong txn_idx = 0; txn_idx < hdr->txn_cnt; txn_idx++ ) {
+ uchar txn_out[FD_TXN_MAX_SZ];
+ ulong pay_sz = 0;
+ const uchar* raw = (const uchar *)blk_data + blockoff;
+ ulong txn_sz = fd_txn_parse_core(raw, fd_ulong_min(blk_sz - blockoff, FD_TXN_MTU), txn_out, NULL, &pay_sz, 0);
+ if ( txn_sz == 0 || txn_sz > FD_TXN_MAX_SZ )
+ FD_LOG_ERR( ( "failed to parse transaction %lu in microblock %lu",
+ txn_idx,
+ mblk ) );
+ fd_txn_t * txn = (fd_txn_t *)txn_out;
+
+ /* Loop across signatures */
+ fd_ed25519_sig_t const * sigs = (fd_ed25519_sig_t const *)(raw + txn->signature_off);
+ for ( uchar j = 0; j < txn->signature_cnt; j++ ) {
+ char buf64[FD_BASE58_ENCODED_64_SZ];
+ fd_base58_encode_64((const uchar*)&sigs[j], NULL, buf64);
+ fd_textstream_sprintf(ts, "%s\"%s\"", (first_sig ? "" : ","), buf64);
+ first_sig = 0;
+ }
+
+ blockoff += pay_sz;
+ }
+ }
+ }
+ if ( blockoff != blk_sz )
+ FD_LOG_ERR(("garbage at end of block"));
+
+ fd_textstream_sprintf(ts, "]},\"id\":%lu}", call_id);
+ return 0;
+ }
+
+ EMIT_SIMPLE("\"transactions\":[");
+
+ int first_txn = 1;
+ ulong blockoff = 0;
+ while (blockoff < blk_sz) {
+ if ( blockoff + sizeof(ulong) > blk_sz )
+ FD_LOG_ERR(("premature end of block"));
+ ulong mcount = *(const ulong *)(blk_data + blockoff);
+ blockoff += sizeof(ulong);
+
+ /* Loop across microblocks */
+ for (ulong mblk = 0; mblk < mcount; ++mblk) {
+ if ( blockoff + sizeof(fd_microblock_hdr_t) > blk_sz )
+ FD_LOG_ERR(("premature end of block"));
+ fd_microblock_hdr_t * hdr = (fd_microblock_hdr_t *)((const uchar *)blk_data + blockoff);
+ blockoff += sizeof(fd_microblock_hdr_t);
+
+ /* Loop across transactions */
+ for ( ulong txn_idx = 0; txn_idx < hdr->txn_cnt; txn_idx++ ) {
+ uchar txn_out[FD_TXN_MAX_SZ];
+ ulong pay_sz = 0;
+ const uchar* raw = (const uchar *)blk_data + blockoff;
+ ulong txn_sz = fd_txn_parse_core(raw, fd_ulong_min(blk_sz - blockoff, FD_TXN_MTU), txn_out, NULL, &pay_sz, 0);
+ if ( txn_sz == 0 || txn_sz > FD_TXN_MAX_SZ )
+ FD_LOG_ERR( ( "failed to parse transaction %lu in microblock %lu",
+ txn_idx,
+ mblk ) );
+
+ if (first_txn) {
+ first_txn = 0;
+ EMIT_SIMPLE("{");
+ } else
+ EMIT_SIMPLE(",{");
+
+ int r = fd_txn_to_json( ts, (fd_txn_t *)txn_out, raw, encoding, maxvers, detail, rewards );
+ if ( r ) {
+ return r;
+ }
+
+ EMIT_SIMPLE("}");
+
+ blockoff += pay_sz;
+ }
+ }
+ }
+ if ( blockoff != blk_sz )
+ FD_LOG_ERR(("garbage at end of block"));
+
+ fd_textstream_sprintf(ts, "]},\"id\":%lu}", call_id);
+
+ return 0;
+}
+
+const char*
+fd_account_to_json( fd_textstream_t * ts,
+ fd_pubkey_t acct,
+ fd_rpc_encoding_t enc,
+ uchar const * val,
+ ulong val_sz,
+ long off,
+ long len ) {
+ fd_textstream_sprintf(ts, "{\"data\":[\"");
+
+ fd_account_meta_t * metadata = (fd_account_meta_t *)val;
+ if (val_sz < sizeof(fd_account_meta_t) && val_sz < metadata->hlen) {
+ return "failed to load account data";
+ }
+ val = (uchar*)val + metadata->hlen;
+ val_sz = val_sz - metadata->hlen;
+ if (val_sz > metadata->dlen)
+ val_sz = metadata->dlen;
+
+ if (len != FD_LONG_UNSET && off != FD_LONG_UNSET) {
+ if (enc == FD_ENC_JSON) {
+ return "cannot use jsonParsed encoding with slice";
+ }
+ if (off < 0 || (ulong)off >= val_sz) {
+ val = NULL;
+ val_sz = 0;
+ } else {
+ val = (uchar*)val + (ulong)off;
+ val_sz = val_sz - (ulong)off;
+ }
+ if (len < 0) {
+ val = NULL;
+ val_sz = 0;
+ } else if ((ulong)len < val_sz)
+ val_sz = (ulong)len;
+ }
+
+ const char* encstr;
+ switch (enc) {
+ case FD_ENC_BASE58:
+ if (fd_textstream_encode_base58(ts, val, val_sz)) {
+ return "failed to encode data in base58";
+ }
+ encstr = "base58";
+ break;
+ case FD_ENC_BASE64:
+ if (fd_textstream_encode_base64(ts, val, val_sz)) {
+ return "failed to encode data in base64";
+ }
+ encstr = "base64";
+ break;
+ default:
+ return "unsupported encoding";
+ }
+
+ char owner[50];
+ fd_base58_encode_32((uchar*)metadata->info.owner, 0, owner);
+ char addr[50];
+ fd_base58_encode_32(acct.uc, 0, addr);
+ fd_textstream_sprintf(ts, "\",\"%s\"],\"executable\":%s,\"lamports\":%lu,\"owner\":\"%s\",\"address\":\"%s\",\"rentEpoch\":%lu,\"space\":%lu}",
+ encstr,
+ (metadata->info.executable ? "true" : "false"),
+ metadata->info.lamports,
+ owner,
+ addr,
+ metadata->info.rent_epoch,
+ val_sz);
+
+ return NULL;
+}
diff --git a/src/app/rpcserver/fd_block_to_json.h b/src/app/rpcserver/fd_block_to_json.h
new file mode 100644
index 0000000000..0f716075b5
--- /dev/null
+++ b/src/app/rpcserver/fd_block_to_json.h
@@ -0,0 +1,38 @@
+typedef enum {
+ FD_ENC_BASE58, FD_ENC_BASE64, FD_ENC_BASE64_ZSTD, FD_ENC_JSON, FD_ENC_JSON_PARSED
+} fd_rpc_encoding_t;
+
+enum fd_block_detail { FD_BLOCK_DETAIL_FULL, FD_BLOCK_DETAIL_ACCTS, FD_BLOCK_DETAIL_SIGS, FD_BLOCK_DETAIL_NONE };
+
+int fd_txn_meta_to_json( fd_textstream_t * ts,
+ const void * meta_raw,
+ ulong meta_raw_sz );
+
+int fd_txn_to_json( fd_textstream_t * ts,
+ fd_txn_t* txn,
+ const uchar* raw,
+ fd_rpc_encoding_t encoding,
+ long maxvers,
+ enum fd_block_detail detail,
+ int rewards );
+
+int fd_block_to_json( fd_textstream_t * ts,
+ long call_id,
+ fd_block_t * blk,
+ const uchar * blk_data,
+ ulong blk_sz,
+ fd_slot_meta_t * meta,
+ fd_rpc_encoding_t encoding,
+ long maxvers,
+ enum fd_block_detail detail,
+ int rewards);
+
+#define FD_LONG_UNSET (1L << 63L)
+
+const char* fd_account_to_json( fd_textstream_t * ts,
+ fd_pubkey_t acct,
+ fd_rpc_encoding_t enc,
+ uchar const * val,
+ ulong val_sz,
+ long off,
+ long len );
diff --git a/src/app/rpcserver/fd_methods.c b/src/app/rpcserver/fd_methods.c
new file mode 100644
index 0000000000..d21fcd35e2
--- /dev/null
+++ b/src/app/rpcserver/fd_methods.c
@@ -0,0 +1,301 @@
+#include
+#include
+#include
+#include "fd_methods.h"
+#include "fd_webserver.h"
+#include "../../util/fd_util.h"
+
+// Read the next json lexical token and report any error to the client
+#define NEXT_TOKEN \
+ do { \
+ prevpos = lex->pos; \
+ prevtoken = lex->last_tok; \
+ token = json_lex_next_token(lex); \
+ if (token == JSON_TOKEN_ERROR) return 0; \
+ } while (0)
+
+#define UNNEXT_TOKEN \
+ lex->pos = prevpos; \
+ lex->last_tok = prevtoken;
+
+// Report a json parsing syntax error
+#define SYNTAX_ERROR(format, ...) \
+ do { \
+ json_lex_sprintf(lex, format, __VA_ARGS__); \
+ return 0; \
+ } while (0)
+
+// Parse a generic json value. The values argument is used for storing
+// leaf values for later access. path describes the path through the
+// json syntax tree to this value.
+int
+json_values_parse(json_lex_state_t* lex, struct json_values* values, struct json_path* path) {
+ ulong prevpos;
+ long token;
+ long prevtoken;
+
+ // Prepare to update the path to include a new element
+ if (path->len == JSON_MAX_PATH)
+ SYNTAX_ERROR("json value is too nested at position %lu", lex->pos);
+ uint* path_last = &path->elems[path->len ++];
+
+ NEXT_TOKEN;
+ switch (token) {
+ case JSON_TOKEN_LBRACE: // Start a new json object
+ do {
+ NEXT_TOKEN;
+ if (token == JSON_TOKEN_RBRACE)
+ break;
+ if (token != JSON_TOKEN_STRING)
+ SYNTAX_ERROR("expected string key at position %lu", prevpos);
+ // Translate the key string to a known keyword ID. We only allow
+ // a predetermined set of keys.
+ ulong key_sz;
+ const char* key = json_lex_get_text(lex, &key_sz);
+ long keyid = fd_webserver_json_keyword(key, key_sz);
+ if (keyid == KEYW_UNKNOWN)
+ SYNTAX_ERROR("unrecognized string key at position %lu", prevpos);
+ // Append to the path
+ *path_last = ((JSON_TOKEN_LBRACE<<16) | (uint)keyid);
+
+ NEXT_TOKEN;
+ if (token != JSON_TOKEN_COLON)
+ SYNTAX_ERROR("expected colon at position %lu", prevpos);
+
+ // Recursively parse the inner value
+ if (!json_values_parse(lex, values, path))
+ return 0;
+
+ NEXT_TOKEN;
+ if (token == JSON_TOKEN_RBRACE)
+ break;
+ if (token != JSON_TOKEN_COMMA)
+ SYNTAX_ERROR("expected comma at position %lu", prevpos);
+ } while(1);
+ break;
+
+ case JSON_TOKEN_LBRACKET: { // Start an array
+ uint i = 0;
+ do {
+ // Append to the path
+ *path_last = ((JSON_TOKEN_LBRACKET<<16) | i);
+ // Recursively parse the array element
+ if (!json_values_parse(lex, values, path))
+ return 0;
+
+ NEXT_TOKEN;
+ if (token == JSON_TOKEN_RBRACKET)
+ break;
+ if (token != JSON_TOKEN_COMMA)
+ SYNTAX_ERROR("expected comma at position %lu", prevpos);
+
+ ++i;
+ } while(1);
+ break;
+ }
+
+ case JSON_TOKEN_STRING: {
+ // Append to the path
+ *path_last = (JSON_TOKEN_STRING<<16);
+ // Store the leaf value in values, indexed by the current path
+ ulong str_sz;
+ const char* str = json_lex_get_text(lex, &str_sz);
+ json_add_value(values, path, str, str_sz);
+ break;
+ }
+
+ case JSON_TOKEN_INTEGER: {
+ // Append to the path
+ *path_last = (JSON_TOKEN_INTEGER<<16);
+ // Store the leaf value in values, indexed by the current path
+ long val = json_lex_as_int(lex);
+ json_add_value(values, path, &val, sizeof(val));
+ break;
+ }
+
+ case JSON_TOKEN_FLOAT: {
+ // Append to the path
+ *path_last = (JSON_TOKEN_FLOAT<<16);
+ // Store the leaf value in values, indexed by the current path
+ double val = json_lex_as_float(lex);
+ json_add_value(values, path, &val, sizeof(val));
+ break;
+ }
+
+ case JSON_TOKEN_BOOL:
+ // Append to the path
+ *path_last = (JSON_TOKEN_BOOL<<16);
+ // Store the leaf value in values, indexed by the current path
+ json_add_value(values, path, &lex->last_bool, sizeof(lex->last_bool));
+ break;
+
+ case JSON_TOKEN_NULL:
+ // Append to the path
+ *path_last = (JSON_TOKEN_NULL<<16);
+ // Store the leaf value in values, indexed by the current path
+ json_add_value(values, path, NULL, 0);
+ break;
+
+ case JSON_TOKEN_RBRACKET:
+ if (prevtoken == JSON_TOKEN_LBRACKET) {
+ /* Empty array */
+ UNNEXT_TOKEN;
+ break;
+ }
+ SYNTAX_ERROR("unexpected ']' at position %lu", prevpos);
+ break;
+
+ case JSON_TOKEN_RBRACE:
+ if (prevtoken == JSON_TOKEN_LBRACE) {
+ /* Empty object */
+ UNNEXT_TOKEN;
+ break;
+ }
+ SYNTAX_ERROR("unexpected '}' at position %lu", prevpos);
+ break;
+
+ default:
+ SYNTAX_ERROR("expected json value at position %lu", prevpos);
+ }
+
+ path->len --;
+ return 1;
+}
+
+// Initialize a json_values
+void json_values_new(struct json_values* values) {
+ values->num_values = 0;
+ values->buf = values->buf_init;
+ values->buf_sz = 0;
+ values->buf_alloc = sizeof(values->buf_init);
+}
+
+// Destroy a json_values
+void json_values_delete(struct json_values* values) {
+ if (values->buf != values->buf_init)
+ free(values->buf);
+}
+
+// Add a parsed value to a json_values
+void json_add_value(struct json_values* values, struct json_path* path, const void* data, ulong data_sz) {
+ if (values->num_values == JSON_MAX_PATHS) {
+ // Ignore when we have too many values. In the actual requests
+ // that we expect to handle, the number of values is modest.
+ return;
+ }
+
+ // Get the new buffer size after we add the new data (plus null terminator)
+ ulong new_buf_sz = values->buf_sz + data_sz + 1;
+ new_buf_sz = ((new_buf_sz + 7UL) & ~7UL); // 8-byte align
+ if (new_buf_sz > values->buf_alloc) {
+ // Grow the allocation
+ do {
+ values->buf_alloc <<= 1;
+ } while (new_buf_sz > values->buf_alloc);
+ char* newbuf = (char*)malloc(values->buf_alloc);
+ fd_memcpy(newbuf, values->buf, values->buf_sz);
+ if (values->buf != values->buf_init)
+ free(values->buf);
+ values->buf = newbuf;
+ }
+
+ // Add a new value to the table
+ uint i = values->num_values++;
+ struct json_path* path2 = &values->values[i].path;
+ uint len = path2->len = path->len;
+ for (uint j = 0; j < len; ++j)
+ path2->elems[j] = path->elems[j];
+ // Copy out the data
+ ulong off = values->values[i].data_offset = values->buf_sz;
+ values->values[i].data_sz = data_sz;
+ fd_memcpy(values->buf + off, data, data_sz);
+ values->buf[off + data_sz] = '\0';
+ values->buf_sz = new_buf_sz;
+}
+
+// Retrieve a value at a given path. A NULL is returned if the path
+// isn't found
+const void* json_get_value(struct json_values* values, const uint* path_elems, uint path_sz, ulong* data_sz) {
+ // Loop through the values
+ for (uint i = 0; i < values->num_values; ++i) {
+ // Compare paths
+ struct json_path* path = &values->values[i].path;
+ if (path->len == path_sz) {
+ for (uint j = 0; ; ++j) {
+ if (j == path_sz) {
+ *data_sz = values->values[i].data_sz;
+ return values->buf + values->values[i].data_offset;
+ }
+ if (path->elems[j] != path_elems[j])
+ break;
+ }
+ }
+ }
+ // Not found
+ *data_sz = 0;
+ return NULL;
+}
+
+const void* json_get_value_multi(struct json_values* values, const uint* path_elems, uint path_sz, ulong* data_sz, uint * pos) {
+ // Loop through the values
+ for (uint i = *pos; i < values->num_values; ++i) {
+ // Compare paths
+ struct json_path* path = &values->values[i].path;
+ if (path->len == path_sz) {
+ for (uint j = 0; ; ++j) {
+ if (j == path_sz) {
+ *data_sz = values->values[i].data_sz;
+ *pos = j+1;
+ return values->buf + values->values[i].data_offset;
+ }
+ if (path->elems[j] != path_elems[j])
+ break;
+ }
+ }
+ }
+ // Not found
+ *data_sz = 0;
+ *pos = values->num_values;
+ return NULL;
+}
+
+// Dump the values and paths to stdout
+void json_values_printout(struct json_values* values) {
+ for (uint i = 0; i < values->num_values; ++i) {
+ struct json_path* path = &values->values[i].path;
+ const char* data = values->buf + values->values[i].data_offset;
+ ulong data_sz = values->values[i].data_sz;
+ for (uint j = 0; j < path->len; ++j) {
+ uint e = path->elems[j];
+ switch (e >> 16U) {
+ case JSON_TOKEN_LBRACE:
+ printf(" (object|%s)", un_fd_webserver_json_keyword(e & 0xffffUL));
+ break;
+ case JSON_TOKEN_LBRACKET:
+ printf(" (array|%lu)", e & 0xffffUL);
+ break;
+ case JSON_TOKEN_STRING:
+ printf(" STRING \"");
+ fwrite(data, 1, data_sz, stdout);
+ printf("\"");
+ break;
+ case JSON_TOKEN_INTEGER:
+ assert(data_sz == sizeof(long));
+ printf(" INT %ld", *(long*)data);
+ break;
+ case JSON_TOKEN_FLOAT:
+ assert(data_sz == sizeof(double));
+ printf(" FLOAT %g", *(double*)data);
+ break;
+ case JSON_TOKEN_BOOL:
+ assert(data_sz == sizeof(int));
+ printf(" BOOL %d", *(int*)data);
+ break;
+ case JSON_TOKEN_NULL:
+ printf(" NULL");
+ break;
+ }
+ }
+ printf("\n");
+ }
+}
diff --git a/src/app/rpcserver/fd_methods.h b/src/app/rpcserver/fd_methods.h
new file mode 100644
index 0000000000..d61c10075a
--- /dev/null
+++ b/src/app/rpcserver/fd_methods.h
@@ -0,0 +1,68 @@
+#ifndef HEADER_fd_src_tango_webserver_fd_methods_h
+#define HEADER_fd_src_tango_webserver_fd_methods_h
+
+#include "json_lex.h"
+
+// Data structure describing a "path" to a value in json data. This is
+// basically a path through the syntax tree. A path element can be one
+// of the following:
+// object member: (JSON_TOKEN_LBRACE<<16) | keyword_id
+// array member: (JSON_TOKEN_LBRACKET<<16) | index
+// string value: (JSON_TOKEN_STRING<<16)
+// int value: (JSON_TOKEN_INTEGER<<16)
+// float value: (JSON_TOKEN_FLOAT<<16)
+// boolean value: (JSON_TOKEN_BOOL<<16)
+// null value: (JSON_TOKEN_NULL<<16)
+// keyword ids are generated by json_keyword(...) in keywords.h
+#define JSON_MAX_PATH 8
+struct json_path {
+ uint len;
+ uint elems[JSON_MAX_PATH];
+};
+
+// Represents the result of parsing a json data structure. Each leaf
+// value (string, number, boolean, etc) gets an entry in the values
+// list. The complete paths to those values are provided. This
+// structure is optimized for quickly finding values at predetermined
+// paths. It is compact and efficient.
+#define JSON_MAX_PATHS 32
+struct json_values {
+ // Number of leaf values
+ uint num_values;
+ struct {
+ // Path to data
+ struct json_path path;
+ // Offset and size of data value in buffer
+ ulong data_offset;
+ ulong data_sz;
+ } values[JSON_MAX_PATHS];
+ // Dynamic buffer containing all data
+ char* buf;
+ ulong buf_sz;
+ ulong buf_alloc;
+ char buf_init[2048];
+};
+
+// Initialize a json_values
+void json_values_new(struct json_values* values);
+
+// Destroy a json_values
+void json_values_delete(struct json_values* values);
+
+// Add a parsed value to a json_values
+void json_add_value(struct json_values* values, struct json_path* path, const void* data, ulong data_sz);
+
+// Retrieve a value at a given path. A NULL is returned if the path
+// isn't found
+const void* json_get_value(struct json_values* values, const uint* path, uint path_sz, ulong* data_sz);
+
+// Version that allows iterative retrieval. *pos should be initialized to zero.
+const void* json_get_value_multi(struct json_values* values, const uint* path, uint path_sz, ulong* data_sz, uint * pos);
+
+// Dump the values and paths to stdout
+void json_values_printout(struct json_values* values);
+
+// Parse a block of json. Returns 1 on success.
+int json_values_parse(json_lex_state_t* lex, struct json_values* values, struct json_path* path);
+
+#endif /* HEADER_fd_src_tango_webserver_fd_methods_h */
diff --git a/src/app/rpcserver/fd_rpc_service.c b/src/app/rpcserver/fd_rpc_service.c
new file mode 100644
index 0000000000..c6281190b8
--- /dev/null
+++ b/src/app/rpcserver/fd_rpc_service.c
@@ -0,0 +1,2038 @@
+#include "fd_rpc_service.h"
+#include
+#include "fd_methods.h"
+#include "fd_webserver.h"
+#include "../../flamenco/types/fd_types.h"
+#include "../../flamenco/types/fd_solana_block.pb.h"
+#include "../../flamenco/runtime/fd_runtime.h"
+#include "../../flamenco/runtime/fd_acc_mgr.h"
+#include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
+#include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
+#include "../../ballet/base58/fd_base58.h"
+#include "keywords.h"
+
+#define API_VERSION "1.17.6"
+
+#define CRLF "\r\n"
+#define MATCH_STRING(_text_,_text_sz_,_str_) (_text_sz_ == sizeof(_str_)-1 && memcmp(_text_, _str_, sizeof(_str_)-1) == 0)
+
+struct fd_ws_subscription {
+ fd_websocket_ctx_t * socket;
+ long meth_id;
+ long call_id;
+ ulong subsc_id;
+ union {
+ struct {
+ fd_pubkey_t acct;
+ fd_rpc_encoding_t enc;
+ long off;
+ long len;
+ } acct_subscribe;
+ };
+};
+
+#define FD_WS_MAX_SUBS 1024
+
+struct fd_rpc_global_ctx {
+ fd_readwrite_lock_t lock;
+ fd_webserver_t ws;
+ fd_funk_t * funk;
+ fd_blockstore_t * blockstore;
+ struct fd_ws_subscription sub_list[FD_WS_MAX_SUBS];
+ ulong sub_cnt;
+ ulong last_subsc_id;
+ fd_epoch_bank_t * epoch_bank;
+ ulong epoch_bank_epoch;
+};
+typedef struct fd_rpc_global_ctx fd_rpc_global_ctx_t;
+
+struct fd_rpc_ctx {
+ long call_id;
+ fd_rpc_global_ctx_t * global;
+};
+
+static void *
+read_account( fd_rpc_ctx_t * ctx, fd_pubkey_t * acct, fd_valloc_t valloc, ulong * result_len ) {
+ fd_funk_rec_key_t recid = fd_acc_funk_key(acct);
+ fd_funk_t * funk = ctx->global->funk;
+ return fd_funk_rec_query_safe(funk, &recid, valloc, result_len);
+}
+
+static void *
+read_account_with_xid( fd_rpc_ctx_t * ctx, fd_pubkey_t * acct, fd_funk_txn_xid_t * xid, fd_valloc_t valloc, ulong * result_len ) {
+ fd_funk_rec_key_t recid = fd_acc_funk_key(acct);
+ fd_funk_t * funk = ctx->global->funk;
+ return fd_funk_rec_query_xid_safe(funk, &recid, xid, valloc, result_len);
+}
+
+/* LEAVES THE LOCK IN READ MODE */
+fd_epoch_bank_t *
+read_epoch_bank( fd_rpc_ctx_t * ctx, fd_valloc_t valloc, ulong * smr ) {
+ fd_rpc_global_ctx_t * glob = ctx->global;
+
+ for(;;) {
+ fd_readwrite_start_read( &glob->lock );
+ *smr = glob->blockstore->smr;
+
+ if( glob->epoch_bank != NULL &&
+ glob->epoch_bank_epoch == fd_slot_to_epoch(&glob->epoch_bank->epoch_schedule, *smr, NULL) ) {
+ /* Leave lock held */
+ return glob->epoch_bank;
+ }
+
+ fd_readwrite_end_read( &glob->lock );
+ fd_readwrite_start_write( &glob->lock );
+
+ if( glob->epoch_bank != NULL ) {
+ fd_bincode_destroy_ctx_t binctx;
+ binctx.valloc = fd_libc_alloc_virtual();
+ fd_epoch_bank_destroy( glob->epoch_bank, &binctx );
+ free( glob->epoch_bank );
+ glob->epoch_bank = NULL;
+ }
+
+ fd_funk_rec_key_t recid = fd_runtime_epoch_bank_key();
+ ulong vallen;
+ fd_funk_t * funk = ctx->global->funk;
+ void * val = fd_funk_rec_query_safe(funk, &recid, valloc, &vallen);
+ if( val == NULL ) {
+ FD_LOG_WARNING(( "failed to decode epoch_bank" ));
+ fd_readwrite_end_write( &glob->lock );
+ return NULL;
+ }
+ fd_epoch_bank_t * epoch_bank = malloc( fd_epoch_bank_footprint() );
+ fd_epoch_bank_new( epoch_bank );
+ fd_bincode_decode_ctx_t binctx;
+ binctx.data = val;
+ binctx.dataend = (uchar*)val + vallen;
+ binctx.valloc = fd_libc_alloc_virtual();
+ if( fd_epoch_bank_decode( epoch_bank, &binctx )!=FD_BINCODE_SUCCESS ) {
+ FD_LOG_WARNING(( "failed to decode epoch_bank" ));
+ fd_valloc_free( valloc, val );
+ free( epoch_bank );
+ fd_readwrite_end_write( &glob->lock );
+ return NULL;
+ }
+ fd_valloc_free( valloc, val );
+
+ glob->epoch_bank = epoch_bank;
+ glob->epoch_bank_epoch = fd_slot_to_epoch(&epoch_bank->epoch_schedule, *smr, NULL);
+ fd_readwrite_end_write( &glob->lock );
+ }
+}
+
+fd_slot_bank_t *
+read_slot_bank( fd_rpc_ctx_t * ctx, fd_valloc_t valloc ) {
+ fd_funk_rec_key_t recid = fd_runtime_slot_bank_key();
+ ulong vallen;
+ fd_funk_t * funk = ctx->global->funk;
+ void * val = fd_funk_rec_query_safe(funk, &recid, valloc, &vallen);
+ if( val == NULL ) {
+ FD_LOG_WARNING(( "failed to decode slot_bank" ));
+ return NULL;
+ }
+ fd_slot_bank_t * slot_bank = fd_valloc_malloc( valloc, fd_slot_bank_align(), fd_slot_bank_footprint() );
+ fd_slot_bank_new( slot_bank );
+ fd_bincode_decode_ctx_t binctx;
+ binctx.data = val;
+ binctx.dataend = (uchar*)val + vallen;
+ binctx.valloc = valloc;
+ if( fd_slot_bank_decode( slot_bank, &binctx )!=FD_BINCODE_SUCCESS ) {
+ FD_LOG_WARNING(( "failed to decode slot_bank" ));
+ fd_valloc_free( valloc, val );
+ return NULL;
+ }
+ fd_valloc_free( valloc, val );
+ return slot_bank;
+}
+
+static void fd_method_cleanup( uchar ** smem ) {
+ fd_scratch_detach( NULL );
+ free( *smem );
+}
+
+/* Setup scratch space */
+#define FD_METHOD_SCRATCH_BEGIN( SMAX ) do { \
+ uchar * smem = aligned_alloc( FD_SCRATCH_SMEM_ALIGN, \
+ fd_ulong_align_up( fd_scratch_smem_footprint( SMAX ), FD_SCRATCH_SMEM_ALIGN ) ); \
+ ulong fmem[4U]; \
+ fd_scratch_attach( smem, fmem, SMAX, 4U ); \
+ fd_scratch_push(); \
+ uchar * __fd_scratch_guard_ ## __LINE__ \
+ __attribute__((cleanup(fd_method_cleanup))) = smem; \
+ do
+
+#define FD_METHOD_SCRATCH_END while(0); } while(0)
+
+// Implementation of the "getAccountInfo" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getAccountInfo", "params": [ "21bVZhkqPJRVYDG3YpYtzHLMvkc7sa4KB7fMwGekTquG", { "encoding": "base64" } ] }'
+
+static int
+method_getAccountInfo(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ FD_METHOD_SCRATCH_BEGIN( 11<<20 ) {
+ // Path to argument
+ static const uint PATH[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 0,
+ (JSON_TOKEN_STRING<<16)
+ };
+ ulong arg_sz = 0;
+ const void* arg = json_get_value(values, PATH, 3, &arg_sz);
+ if (arg == NULL) {
+ fd_web_replier_error(replier, "getAccountInfo requires a string as first parameter");
+ return 0;
+ }
+
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+
+ fd_pubkey_t acct;
+ fd_base58_decode_32((const char *)arg, acct.uc);
+ ulong val_sz;
+ void * val = read_account(ctx, &acct, fd_scratch_virtual(), &val_sz);
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ if (val == NULL) {
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"value\":null},\"id\":%lu}" CRLF,
+ blockstore->smr, ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+ }
+
+ static const uint PATH2[4] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_ENCODING,
+ (JSON_TOKEN_STRING<<16)
+ };
+ ulong enc_str_sz = 0;
+ const void* enc_str = json_get_value(values, PATH2, 4, &enc_str_sz);
+ fd_rpc_encoding_t enc;
+ if (enc_str == NULL || MATCH_STRING(enc_str, enc_str_sz, "base58"))
+ enc = FD_ENC_BASE58;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base64"))
+ enc = FD_ENC_BASE64;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base64+zstd"))
+ enc = FD_ENC_BASE64_ZSTD;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "jsonParsed"))
+ enc = FD_ENC_JSON;
+ else {
+ fd_web_replier_error(replier, "invalid data encoding %s", (const char*)enc_str);
+ return 0;
+ }
+
+ static const uint PATH3[5] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_DATASLICE,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_LENGTH,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ static const uint PATH4[5] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_DATASLICE,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_OFFSET,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ ulong len_sz = 0;
+ const void* len_ptr = json_get_value(values, PATH3, 5, &len_sz);
+ ulong off_sz = 0;
+ const void* off_ptr = json_get_value(values, PATH4, 5, &off_sz);
+ long off = (off_ptr ? *(long *)off_ptr : FD_LONG_UNSET);
+ long len = (len_ptr ? *(long *)len_ptr : FD_LONG_UNSET);
+
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"value\":",
+ blockstore->smr);
+ const char * err = fd_account_to_json( ts, acct, enc, val, val_sz, off, len );
+ if( err ) {
+ fd_web_replier_error(replier, "%s", err);
+ return 0;
+ }
+ fd_textstream_sprintf(ts, "},\"id\":%lu}" CRLF, ctx->call_id);
+
+ fd_web_replier_done(replier);
+
+ } FD_METHOD_SCRATCH_END;
+
+ return 0;
+}
+
+// Implementation of the "getBalance" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBalance", "params": [ "6s5gDyLyfNXP6WHUEn4YSMQJVcGETpKze7FCPeg9wxYT" ] }'
+
+static int
+method_getBalance(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ FD_METHOD_SCRATCH_BEGIN( 11<<20 ) {
+ // Path to argument
+ static const uint PATH[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 0,
+ (JSON_TOKEN_STRING<<16)
+ };
+ ulong arg_sz = 0;
+ const void* arg = json_get_value(values, PATH, 3, &arg_sz);
+ if (arg == NULL) {
+ fd_web_replier_error(replier, "getBalance requires a string as first parameter");
+ return 0;
+ }
+ fd_pubkey_t acct;
+ fd_base58_decode_32((const char *)arg, acct.uc);
+ ulong val_sz;
+ void * val = read_account(ctx, &acct, fd_scratch_virtual(), &val_sz);
+ if (val == NULL) {
+ fd_web_replier_error(replier, "failed to load account data for %s", (const char*)arg);
+ return 0;
+ }
+ fd_account_meta_t * metadata = (fd_account_meta_t *)val;
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"value\":%lu},\"id\":%lu}" CRLF,
+ blockstore->smr, metadata->info.lamports, ctx->call_id);
+ fd_web_replier_done(replier);
+ } FD_METHOD_SCRATCH_END;
+ return 0;
+}
+
+// Implementation of the "getBlock" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc": "2.0","id":1, "method":"getBlock", "params": [270562740, {"encoding": "json", "maxSupportedTransactionVersion":0, "transactionDetails":"full", "rewards":false}]} '
+
+static int
+method_getBlock(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ static const uint PATH_SLOT[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 0,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ static const uint PATH_ENCODING[4] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_ENCODING,
+ (JSON_TOKEN_STRING<<16)
+ };
+ static const uint PATH_MAXVERS[4] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_MAXSUPPORTEDTRANSACTIONVERSION,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ static const uint PATH_DETAIL[4] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_TRANSACTIONDETAILS,
+ (JSON_TOKEN_STRING<<16)
+ };
+ static const uint PATH_REWARDS[4] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_REWARDS,
+ (JSON_TOKEN_BOOL<<16)
+ };
+
+ ulong slot_sz = 0;
+ const void* slot = json_get_value(values, PATH_SLOT, 3, &slot_sz);
+ if (slot == NULL) {
+ fd_web_replier_error(replier, "getBlock requires a slot number as first parameter");
+ return 0;
+ }
+ ulong slotn = (ulong)(*(long*)slot);
+
+ ulong enc_str_sz = 0;
+ const void* enc_str = json_get_value(values, PATH_ENCODING, 4, &enc_str_sz);
+ fd_rpc_encoding_t enc;
+ if (enc_str == NULL || MATCH_STRING(enc_str, enc_str_sz, "json"))
+ enc = FD_ENC_JSON;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base58"))
+ enc = FD_ENC_BASE58;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base64"))
+ enc = FD_ENC_BASE64;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "jsonParsed"))
+ enc = FD_ENC_JSON_PARSED;
+ else {
+ fd_web_replier_error(replier, "invalid data encoding %s", (const char*)enc_str);
+ return 0;
+ }
+
+ ulong maxvers_sz = 0;
+ const void* maxvers = json_get_value(values, PATH_MAXVERS, 4, &maxvers_sz);
+
+ ulong det_str_sz = 0;
+ const void* det_str = json_get_value(values, PATH_DETAIL, 4, &det_str_sz);
+ enum fd_block_detail det;
+ if (det_str == NULL || MATCH_STRING(det_str, det_str_sz, "full"))
+ det = FD_BLOCK_DETAIL_FULL;
+ else if (MATCH_STRING(det_str, det_str_sz, "accounts"))
+ det = FD_BLOCK_DETAIL_ACCTS;
+ else if (MATCH_STRING(det_str, det_str_sz, "signatures"))
+ det = FD_BLOCK_DETAIL_SIGS;
+ else if (MATCH_STRING(det_str, det_str_sz, "none"))
+ det = FD_BLOCK_DETAIL_NONE;
+ else {
+ fd_web_replier_error(replier, "invalid block detail %s", (const char*)det_str);
+ return 0;
+ }
+
+ ulong rewards_sz = 0;
+ const void* rewards = json_get_value(values, PATH_REWARDS, 4, &rewards_sz);
+
+ fd_block_t blk[1];
+ fd_slot_meta_t slot_meta[1];
+ ulong blk_sz;
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ uchar * blk_data = fd_blockstore_block_query_volatile( blockstore, slotn, fd_libc_alloc_virtual(), blk, slot_meta, &blk_sz );
+ if( blk_data == NULL ) {
+ fd_web_replier_error(replier, "failed to display block for slot %lu", slotn);
+ return 0;
+ }
+
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ if (fd_block_to_json(ts,
+ ctx->call_id,
+ blk,
+ blk_data,
+ blk_sz,
+ slot_meta,
+ enc,
+ (maxvers == NULL ? 0 : *(const long*)maxvers),
+ det,
+ (rewards == NULL ? 1 : *(const int*)rewards))) {
+ free( blk_data );
+ fd_web_replier_error(replier, "failed to display block for slot %lu", slotn);
+ return 0;
+ }
+ free( blk_data );
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+// Implementation of the "getBlockCommitment" methods
+static int
+method_getBlockCommitment(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getBlockCommitment is not implemented");
+ return 0;
+}
+
+// Implementation of the "getBlockHeight" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d '{ "jsonrpc":"2.0","id":1, "method":"getBlockHeight" }'
+static int
+method_getBlockHeight(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void) values;
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_block_t blk[1];
+ fd_slot_meta_t slot_meta[1];
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ int ret = fd_blockstore_slot_meta_query_volatile(blockstore, blockstore->smr, blk, slot_meta);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}" CRLF,
+ (!ret ? blk->height : 0UL), ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+// Implementation of the "getBlockProduction" methods
+static int
+method_getBlockProduction(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getBlockProduction is not implemented");
+ return 0;
+}
+
+// Implementation of the "getBlocks" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc": "2.0", "id": 1, "method": "getBlocks", "params": [270562730, 270562740]} '
+
+static int
+method_getBlocks(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ static const uint PATH_STARTSLOT[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 0,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ ulong startslot_sz = 0;
+ const void* startslot = json_get_value(values, PATH_STARTSLOT, 3, &startslot_sz);
+ if (startslot == NULL) {
+ fd_web_replier_error(replier, "getBlocks requires a start slot number as first parameter");
+ return 0;
+ }
+ ulong startslotn = (ulong)(*(long*)startslot);
+ static const uint PATH_ENDSLOT[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ ulong endslot_sz = 0;
+ const void* endslot = json_get_value(values, PATH_ENDSLOT, 3, &endslot_sz);
+ ulong endslotn = (endslot == NULL ? ULONG_MAX : (ulong)(*(long*)endslot));
+
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ if (startslotn < blockstore->min)
+ startslotn = blockstore->min;
+ if (endslotn > blockstore->max)
+ endslotn = blockstore->max;
+
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":[");
+ uint cnt = 0;
+ for ( ulong i = startslotn; i <= endslotn && cnt < 500000U; ++i ) {
+ fd_block_t blk[1];
+ fd_slot_meta_t slot_meta[1];
+ int ret = fd_blockstore_slot_meta_query_volatile(blockstore, i, blk, slot_meta);
+ if (!ret) {
+ fd_textstream_sprintf(ts, "%s%lu", (cnt==0 ? "" : ","), i);
+ ++cnt;
+ }
+ }
+ fd_textstream_sprintf(ts, "],\"id\":%lu}" CRLF, ctx->call_id);
+
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+// Implementation of the "getBlocksWithLimit" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc": "2.0", "id":1, "method":"getBlocksWithLimit", "params":[270562730, 3]} '
+
+static int
+method_getBlocksWithLimit(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ static const uint PATH_SLOT[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 0,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ ulong startslot_sz = 0;
+ const void* startslot = json_get_value(values, PATH_SLOT, 3, &startslot_sz);
+ if (startslot == NULL) {
+ fd_web_replier_error(replier, "getBlocksWithLimit requires a start slot number as first parameter");
+ return 0;
+ }
+ ulong startslotn = (ulong)(*(long*)startslot);
+ static const uint PATH_LIMIT[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ ulong limit_sz = 0;
+ const void* limit = json_get_value(values, PATH_LIMIT, 3, &limit_sz);
+ if (limit == NULL) {
+ fd_web_replier_error(replier, "getBlocksWithLimit requires a limit as second parameter");
+ return 0;
+ }
+ ulong limitn = (ulong)(*(long*)limit);
+
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ if (startslotn < blockstore->min)
+ startslotn = blockstore->min;
+ if (limitn > 500000)
+ limitn = 500000;
+
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":[");
+ uint cnt = 0;
+ for ( ulong i = startslotn; i <= blockstore->max && cnt < limitn; ++i ) {
+ fd_block_t blk[1];
+ fd_slot_meta_t slot_meta[1];
+ int ret = fd_blockstore_slot_meta_query_volatile(blockstore, i, blk, slot_meta);
+ if (!ret) {
+ fd_textstream_sprintf(ts, "%s%lu", (cnt==0 ? "" : ","), i);
+ ++cnt;
+ }
+ }
+ fd_textstream_sprintf(ts, "],\"id\":%lu}" CRLF, ctx->call_id);
+
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+// Implementation of the "getBlockTime" methods
+static int
+method_getBlockTime(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getBlockTime is not implemented");
+ return 0;
+}
+
+// Implementation of the "getClusterNodes" methods
+static int
+method_getClusterNodes(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getClusterNodes is not implemented");
+ return 0;
+}
+
+// Implementation of the "getConfirmedBlock" methods
+static int
+method_getConfirmedBlock(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getConfirmedBlock is not implemented");
+ return 0;
+}
+
+// Implementation of the "getConfirmedBlocks" methods
+static int
+method_getConfirmedBlocks(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getConfirmedBlocks is not implemented");
+ return 0;
+}
+
+// Implementation of the "getConfirmedBlocksWithLimit" methods
+static int
+method_getConfirmedBlocksWithLimit(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getConfirmedBlocksWithLimit is not implemented");
+ return 0;
+}
+
+// Implementation of the "getConfirmedSignaturesForAddress2" methods
+static int
+method_getConfirmedSignaturesForAddress2(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getConfirmedSignaturesForAddress2 is not implemented");
+ return 0;
+}
+
+// Implementation of the "getConfirmedTransaction" methods
+static int
+method_getConfirmedTransaction(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getConfirmedTransaction is not implemented");
+ return 0;
+}
+
+// Implementation of the "getEpochInfo" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getEpochInfo"} '
+
+static int
+method_getEpochInfo(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ FD_METHOD_SCRATCH_BEGIN( 1<<28 ) { /* read_epoch consumes a ton of scratch space! */
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ ulong smr;
+ fd_epoch_bank_t * epoch_bank = read_epoch_bank(ctx, fd_scratch_virtual(), &smr);
+ fd_slot_bank_t * slot_bank = read_slot_bank(ctx, fd_scratch_virtual());
+ ulong slot_idx = 0;
+ ulong epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, smr, &slot_idx );
+ ulong slots_per_epoch = fd_epoch_slot_cnt( &epoch_bank->epoch_schedule, epoch );
+ fd_block_t blk[1];
+ fd_slot_meta_t slot_meta[1];
+ int ret = fd_blockstore_slot_meta_query_volatile(blockstore, smr, blk, slot_meta);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"absoluteSlot\":%lu,\"blockHeight\":%lu,\"epoch\":%lu,\"slotIndex\":%lu,\"slotsInEpoch\":%lu,\"transactionCount\":%lu},\"id\":%lu}" CRLF,
+ smr,
+ (!ret ? blk->height : 0UL),
+ epoch,
+ slot_idx,
+ slots_per_epoch,
+ slot_bank->transaction_count,
+ ctx->call_id);
+ fd_web_replier_done(replier);
+ fd_readwrite_end_read( &ctx->global->lock );
+ } FD_METHOD_SCRATCH_END;
+ return 0;
+}
+
+// Implementation of the "getEpochSchedule" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getEpochSchedule"} '
+
+static int
+method_getEpochSchedule(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ FD_METHOD_SCRATCH_BEGIN( 1<<28 ) { /* read_epoch consumes a ton of scratch space! */
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ ulong smr;
+ fd_epoch_bank_t * epoch_bank = read_epoch_bank(ctx, fd_scratch_virtual(), &smr);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"firstNormalEpoch\":%lu,\"firstNormalSlot\":%lu,\"leaderScheduleSlotOffset\":%lu,\"slotsPerEpoch\":%lu,\"warmup\":%s},\"id\":%lu}" CRLF,
+ epoch_bank->epoch_schedule.first_normal_epoch,
+ epoch_bank->epoch_schedule.first_normal_slot,
+ epoch_bank->epoch_schedule.leader_schedule_slot_offset,
+ epoch_bank->epoch_schedule.slots_per_epoch,
+ (epoch_bank->epoch_schedule.warmup ? "true" : "false"),
+ ctx->call_id);
+ fd_web_replier_done(replier);
+ fd_readwrite_end_read( &ctx->global->lock );
+ } FD_METHOD_SCRATCH_END;
+ return 0;
+}
+
+// Implementation of the "getFeeCalculatorForBlockhash" methods
+static int
+method_getFeeCalculatorForBlockhash(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getFeeCalculatorForBlockhash is not implemented");
+ return 0;
+}
+
+// Implementation of the "getFeeForMessage" methods
+static int
+method_getFeeForMessage(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getFeeForMessage is not implemented");
+ return 0;
+}
+
+// Implementation of the "getFeeRateGovernor" methods
+static int
+method_getFeeRateGovernor(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getFeeRateGovernor is not implemented");
+ return 0;
+}
+
+// Implementation of the "getFees" methods
+static int
+method_getFees(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getFees is not implemented");
+ return 0;
+}
+
+// Implementation of the "getFirstAvailableBlock" methods
+static int
+method_getFirstAvailableBlock(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getFirstAvailableBlock is not implemented");
+ return 0;
+}
+
+// Implementation of the "getGenesisHash" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getGenesisHash"} '
+
+static int
+method_getGenesisHash(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ FD_METHOD_SCRATCH_BEGIN( 1<<28 ) { /* read_epoch consumes a ton of scratch space! */
+ ulong smr;
+ fd_epoch_bank_t * epoch_bank = read_epoch_bank(ctx, fd_scratch_virtual(), &smr);
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":\"");
+ fd_textstream_encode_base58(ts, epoch_bank->genesis_hash.uc, sizeof(fd_pubkey_t));
+ fd_textstream_sprintf(ts, "\",\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ fd_readwrite_end_read( &ctx->global->lock );
+ } FD_METHOD_SCRATCH_END;
+ return 0;
+}
+
+// Implementation of the "getHealth" methods
+static int
+method_getHealth(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":\"ok\",\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+// Implementation of the "getHighestSnapshotSlot" methods
+static int
+method_getHighestSnapshotSlot(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getHighestSnapshotSlot is not implemented");
+ return 0;
+}
+
+// Implementation of the "getIdentity" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getIdentity"} '
+
+static int
+method_getIdentity(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void) values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getIdentity is not implemented");
+ return 0;
+ /* FIXME!
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"identity\":\"");
+ fd_textstream_encode_base58(ts, ctx->identity->uc, sizeof(fd_pubkey_t));
+ fd_textstream_sprintf(ts, "\"},\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+ */
+}
+// Implementation of the "getInflationGovernor" methods
+static int
+method_getInflationGovernor(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getInflationGovernor is not implemented");
+ return 0;
+}
+
+// Implementation of the "getInflationRate" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getInflationRate"} '
+
+static int
+method_getInflationRate(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void) values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getInflationRate is not implemented");
+ return 0;
+ /* FIXME!
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_inflation_rates_t rates;
+ calculate_inflation_rates( get_slot_ctx(ctx), &rates );
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"epoch\":%lu,\"foundation\":%.18f,\"total\":%.18f,\"validator\":%.18f},\"id\":%lu}" CRLF,
+ rates.epoch,
+ rates.foundation,
+ rates.total,
+ rates.validator,
+ ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+ */
+}
+
+// Implementation of the "getInflationReward" methods
+static int
+method_getInflationReward(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getInflationReward is not implemented");
+ return 0;
+}
+
+// Implementation of the "getLargestAccounts" methods
+static int
+method_getLargestAccounts(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getLargestAccounts is not implemented");
+ return 0;
+}
+
+// Implementation of the "getLatestBlockhash" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getLatestBlockhash"} '
+
+static int
+method_getLatestBlockhash(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ FD_METHOD_SCRATCH_BEGIN( 1UL<<26 ) {
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_slot_bank_t * slot_bank = read_slot_bank(ctx, fd_scratch_virtual());
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"value\":{\"blockhash\":\"",
+ slot_bank->slot);
+ fd_textstream_encode_base58(ts, slot_bank->poh.uc, sizeof(fd_pubkey_t));
+ fd_textstream_sprintf(ts, "\",\"lastValidBlockHeight\":%lu}},\"id\":%lu}" CRLF,
+ slot_bank->block_height, ctx->call_id);
+ fd_web_replier_done(replier);
+ } FD_METHOD_SCRATCH_END;
+ return 0;
+}
+
+// Implementation of the "getLeaderSchedule" methods
+// TODO
+static int
+method_getLeaderSchedule(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getLeaderSchedule is not implemented");
+ return 0;
+}
+
+// Implementation of the "getMaxRetransmitSlot" methods
+static int
+method_getMaxRetransmitSlot(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getMaxRetransmitSlot is not implemented");
+ return 0;
+}
+
+// Implementation of the "getMaxShredInsertSlot" methods
+static int
+method_getMaxShredInsertSlot(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getMaxShredInsertSlot is not implemented");
+ return 0;
+}
+
+// Implementation of the "getMinimumBalanceForRentExemption" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc": "2.0", "id": 1, "method": "getMinimumBalanceForRentExemption", "params": [50]} '
+
+static int
+method_getMinimumBalanceForRentExemption(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ FD_METHOD_SCRATCH_BEGIN( 1<<28 ) { /* read_epoch consumes a ton of scratch space! */
+ static const uint PATH_SIZE[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 0,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ ulong size_sz = 0;
+ const void* size = json_get_value(values, PATH_SIZE, 3, &size_sz);
+ ulong sizen = (size == NULL ? 0UL : (ulong)(*(long*)size));
+ ulong smr;
+ fd_epoch_bank_t * epoch_bank = read_epoch_bank(ctx, fd_scratch_virtual(), &smr);
+ ulong min_balance = fd_rent_exempt_minimum_balance2(&epoch_bank->rent, sizen);
+
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}" CRLF,
+ min_balance, ctx->call_id);
+ fd_web_replier_done(replier);
+ fd_readwrite_end_read( &ctx->global->lock );
+ } FD_METHOD_SCRATCH_END;
+ return 0;
+}
+
+// Implementation of the "getMultipleAccounts" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc": "2.0", "id": 1, "method": "getMultipleAccounts", "params": [["Cwg1f6m4m3DGwMEbmsbAfDtUToUf5jRdKrJSGD7GfZCB", "Cwg1f6m4m3DGwMEbmsbAfDtUToUf5jRdKrJSGD7GfZCB", "7935owQYeYk1H6HjzKRYnT1aZpf1uXcpZNYjgTZ8q7VR"], {"encoding": "base64"}]} '
+
+static int
+method_getMultipleAccounts(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ FD_METHOD_SCRATCH_BEGIN( 11<<20 ) {
+ static const uint ENC_PATH[4] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_ENCODING,
+ (JSON_TOKEN_STRING<<16)
+ };
+ ulong enc_str_sz = 0;
+ const void* enc_str = json_get_value(values, ENC_PATH, 4, &enc_str_sz);
+ fd_rpc_encoding_t enc;
+ if (enc_str == NULL || MATCH_STRING(enc_str, enc_str_sz, "base58"))
+ enc = FD_ENC_BASE58;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base64"))
+ enc = FD_ENC_BASE64;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base64+zstd"))
+ enc = FD_ENC_BASE64_ZSTD;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "jsonParsed"))
+ enc = FD_ENC_JSON;
+ else {
+ fd_web_replier_error(replier, "invalid data encoding %s", (const char*)enc_str);
+ return 0;
+ }
+
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"value\":[",
+ blockstore->smr);
+
+ // Iterate through account ids
+ for ( ulong i = 0; ; ++i ) {
+ // Path to argument
+ uint path[4];
+ path[0] = (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS;
+ path[1] = (JSON_TOKEN_LBRACKET<<16) | 0;
+ path[2] = (uint) ((JSON_TOKEN_LBRACKET<<16) | i);
+ path[3] = (JSON_TOKEN_STRING<<16);
+ ulong arg_sz = 0;
+ const void* arg = json_get_value(values, path, 4, &arg_sz);
+ if (arg == NULL)
+ // End of list
+ break;
+
+ if (i > 0)
+ fd_textstream_append(ts, ",", 1);
+
+ fd_pubkey_t acct;
+ fd_base58_decode_32((const char *)arg, acct.uc);
+ fd_scratch_push();
+ ulong val_sz;
+ void * val = read_account(ctx, &acct, fd_scratch_virtual(), &val_sz);
+ if (val == NULL) {
+ fd_textstream_sprintf(ts, "null");
+ continue;
+ }
+
+ fd_textstream_sprintf(ts, "{\"data\":[\"");
+
+ fd_account_meta_t * metadata = (fd_account_meta_t *)val;
+ if (val_sz < metadata->hlen) {
+ fd_web_replier_error(replier, "failed to load account data for %s", (const char*)arg);
+ return 0;
+ }
+ val = (char*)val + metadata->hlen;
+ val_sz = val_sz - metadata->hlen;
+ if (val_sz > metadata->dlen)
+ val_sz = metadata->dlen;
+
+ if (val_sz) {
+ switch (enc) {
+ case FD_ENC_BASE58:
+ if (fd_textstream_encode_base58(ts, val, val_sz)) {
+ fd_web_replier_error(replier, "failed to encode data in base58");
+ return 0;
+ }
+ break;
+ case FD_ENC_BASE64:
+ if (fd_textstream_encode_base64(ts, val, val_sz)) {
+ fd_web_replier_error(replier, "failed to encode data in base64");
+ return 0;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ char owner[50];
+ fd_base58_encode_32((uchar*)metadata->info.owner, 0, owner);
+ fd_textstream_sprintf(ts, "\",\"%s\"],\"executable\":%s,\"lamports\":%lu,\"owner\":\"%s\",\"rentEpoch\":%lu,\"space\":%lu}",
+ (const char*)enc_str,
+ (metadata->info.executable ? "true" : "false"),
+ metadata->info.lamports,
+ owner,
+ metadata->info.rent_epoch,
+ val_sz);
+
+ fd_scratch_pop();
+ }
+
+ fd_textstream_sprintf(ts, "]},\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ } FD_METHOD_SCRATCH_END;
+ return 0;
+}
+
+// Implementation of the "getProgramAccounts" methods
+static int
+method_getProgramAccounts(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getProgramAccounts is not implemented");
+ return 0;
+}
+
+// Implementation of the "getRecentBlockhash" methods
+static int
+method_getRecentBlockhash(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getRecentBlockhash is not implemented");
+ return 0;
+}
+
+// Implementation of the "getRecentPerformanceSamples" methods
+static int
+method_getRecentPerformanceSamples(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getRecentPerformanceSamples is not implemented");
+ return 0;
+}
+
+// Implementation of the "getRecentPrioritizationFees" methods
+static int
+method_getRecentPrioritizationFees(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getRecentPrioritizationFees is not implemented");
+ return 0;
+}
+
+// Implementation of the "getSignaturesForAddress" methods
+static int
+method_getSignaturesForAddress(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getSignaturesForAddress is not implemented");
+ return 0;
+}
+
+// Implementation of the "getSignatureStatuses" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc": "2.0", "id": 1, "method": "getSignatureStatuses", "params": [["4qj8WecUytFE96SFhdiTkc3v2AYLY7795sbSQTnYG7cPL9s6xKNHNyi3wraQc83PsNSgV8yedWbfGa4vRXfzBDzB"], {"searchTransactionHistory": true}]} '
+
+static int
+method_getSignatureStatuses(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"value\":[",
+ blockstore->smr);
+
+ // Iterate through account ids
+ for ( ulong i = 0; ; ++i ) {
+ // Path to argument
+ uint path[4];
+ path[0] = (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS;
+ path[1] = (JSON_TOKEN_LBRACKET<<16) | 0;
+ path[2] = (uint) ((JSON_TOKEN_LBRACKET<<16) | i);
+ path[3] = (JSON_TOKEN_STRING<<16);
+ ulong sig_sz = 0;
+ const void* sig = json_get_value(values, path, 4, &sig_sz);
+ if (sig == NULL)
+ // End of list
+ break;
+
+ if (i > 0)
+ fd_textstream_append(ts, ",", 1);
+
+ uchar key[FD_ED25519_SIG_SZ];
+ if ( fd_base58_decode_64( sig, key ) == NULL ) {
+ fd_textstream_sprintf(ts, "null");
+ continue;
+ }
+ fd_blockstore_txn_map_t elem;
+ if( fd_blockstore_txn_query_volatile( blockstore, key, &elem, NULL, NULL ) ) {
+ fd_textstream_sprintf(ts, "null");
+ continue;
+ }
+
+ // TODO other fields
+ fd_textstream_sprintf(ts, "{\"slot\":%lu,\"confirmations\":null,\"err\":null,\"confirmationStatus\":\"finalized\"}",
+ elem.slot);
+ }
+
+ fd_textstream_sprintf(ts, "]},\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+// Implementation of the "getSlot" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getSlot"} '
+
+static int
+method_getSlot(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void) values;
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}" CRLF,
+ blockstore->smr, ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+// Implementation of the "getSlotLeader" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getSlotLeader"} '
+
+static int
+method_getSlotLeader(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getSlotLeader is not implemented");
+ /* FIXME!
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":\"");
+ fd_pubkey_t const * leader = fd_epoch_leaders_get(fd_exec_epoch_ctx_leaders( ctx->replay->epoch_ctx ), get_slot_ctx(ctx)->slot_bank.slot);
+ fd_textstream_encode_base58(ts, leader->uc, sizeof(fd_pubkey_t));
+ fd_textstream_sprintf(ts, "\",\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ */
+ return 0;
+}
+
+// Implementation of the "getSlotLeaders" methods
+static int
+method_getSlotLeaders(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getSlotLeaders is not implemented");
+ return 0;
+}
+
+// Implementation of the "getSnapshotSlot" methods
+static int
+method_getSnapshotSlot(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getSnapshotSlot is not implemented");
+ return 0;
+}
+
+// Implementation of the "getStakeActivation" methods
+static int
+method_getStakeActivation(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getStakeActivation is not implemented");
+ return 0;
+}
+
+// Implementation of the "getStakeMinimumDelegation" methods
+static int
+method_getStakeMinimumDelegation(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getStakeMinimumDelegation is not implemented");
+ return 0;
+}
+
+// Implementation of the "getSupply" methods
+// TODO
+static int
+method_getSupply(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getSupply is not implemented");
+ return 0;
+}
+
+// Implementation of the "getTokenAccountBalance" methods
+static int
+method_getTokenAccountBalance(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getTokenAccountBalance is not implemented");
+ return 0;
+}
+
+// Implementation of the "getTokenAccountsByDelegate" methods
+static int
+method_getTokenAccountsByDelegate(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getTokenAccountsByDelegate is not implemented");
+ return 0;
+}
+
+// Implementation of the "getTokenAccountsByOwner" methods
+static int
+method_getTokenAccountsByOwner(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getTokenAccountsByOwner is not implemented");
+ return 0;
+}
+
+// Implementation of the "getTokenLargestAccounts" methods
+static int
+method_getTokenLargestAccounts(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getTokenLargestAccounts is not implemented");
+ return 0;
+}
+
+// Implementation of the "getTokenSupply" methods
+static int
+method_getTokenSupply(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getTokenSupply is not implemented");
+ return 0;
+}
+
+// Implementation of the "getTransaction" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": ["4qj8WecUytFE96SFhdiTkc3v2AYLY7795sbSQTnYG7cPL9s6xKNHNyi3wraQc83PsNSgV8yedWbfGa4vRXfzBDzB", "json"]} '
+
+static int
+method_getTransaction(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ static const uint PATH_SIG[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 0,
+ (JSON_TOKEN_STRING<<16)
+ };
+ static const uint PATH_ENCODING[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_STRING<<16)
+ };
+
+ ulong sig_sz = 0;
+ const void* sig = json_get_value(values, PATH_SIG, 3, &sig_sz);
+ if (sig == NULL) {
+ fd_web_replier_error(replier, "getTransaction requires a signature as first parameter");
+ return 0;
+ }
+
+ ulong enc_str_sz = 0;
+ const void* enc_str = json_get_value(values, PATH_ENCODING, 3, &enc_str_sz);
+ fd_rpc_encoding_t enc;
+ if (enc_str == NULL || MATCH_STRING(enc_str, enc_str_sz, "json"))
+ enc = FD_ENC_JSON;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base58"))
+ enc = FD_ENC_BASE58;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base64"))
+ enc = FD_ENC_BASE64;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "jsonParsed"))
+ enc = FD_ENC_JSON_PARSED;
+ else {
+ fd_web_replier_error(replier, "invalid data encoding %s", (const char*)enc_str);
+ return 0;
+ }
+
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+
+ uchar key[FD_ED25519_SIG_SZ];
+ if ( fd_base58_decode_64( sig, key) == NULL ) {
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+ }
+ fd_blockstore_txn_map_t elem;
+ long blk_ts;
+ uchar txn_data_raw[FD_TXN_MTU];
+ fd_blockstore_t * blockstore = ctx->global->blockstore;
+ if( fd_blockstore_txn_query_volatile( blockstore, key, &elem, &blk_ts, txn_data_raw ) ) {
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+ }
+
+ uchar txn_out[FD_TXN_MAX_SZ];
+ ulong pay_sz = 0;
+ ulong txn_sz = fd_txn_parse_core(txn_data_raw, elem.sz, txn_out, NULL, &pay_sz, 0);
+ if ( txn_sz == 0 || txn_sz > FD_TXN_MAX_SZ )
+ FD_LOG_ERR(("failed to parse transaction"));
+
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"blockTime\":%ld,\"slot\":%lu,",
+ blockstore->smr, blk_ts/(long)1e9, elem.slot);
+ fd_txn_to_json( ts, (fd_txn_t *)txn_out, txn_data_raw, enc, 0, FD_BLOCK_DETAIL_FULL, 0 );
+ fd_textstream_sprintf(ts, "},\"id\":%lu}" CRLF, ctx->call_id);
+
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+// Implementation of the "getTransactionCount" methods
+static int
+method_getTransactionCount(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "getTransactionCount is not implemented");
+ return 0;
+}
+
+// Implementation of the "getVersion" method
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getVersion"} '
+
+static int
+method_getVersion(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void) values;
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ /* TODO Where does feature-set come from? */
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"feature-set\":666,\"solana-core\":\"" API_VERSION "\"},\"id\":%lu}" CRLF,
+ ctx->call_id);
+ fd_web_replier_done(replier);
+ return 0;
+}
+
+static void
+vote_account_to_json(fd_textstream_t * ts, fd_vote_accounts_pair_t_mapnode_t * vote_node) {
+ fd_textstream_sprintf(ts, "{\"commission\":0,\"epochVoteAccount\":true,\"epochCredits\":[[1,64,0],[2,192,64]],\"nodePubkey\":\")");
+ fd_textstream_encode_base58(ts, vote_node->elem.value.owner.uc, sizeof(fd_pubkey_t));
+ fd_textstream_sprintf(ts, "\",\"lastVote\":147,\"activatedStake\":%lu,\"votePubkey\":\"",
+ vote_node->elem.value.lamports);
+ fd_textstream_encode_base58(ts, vote_node->elem.key.uc, sizeof(fd_pubkey_t));
+ fd_textstream_sprintf(ts, "\"}");
+}
+
+// Implementation of the "getVoteAccounts" methods
+// curl http://localhost:8123 -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "id": 1, "method": "getVoteAccounts", "params": [ { "votePubkey": "6j9YPqDdYWc9NWrmV6tSLygog9CrkG9BfYHb5zu9eidH" } ] }'
+
+static int
+method_getVoteAccounts(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ FD_METHOD_SCRATCH_BEGIN( 1<<28 ) { /* read_epoch consumes a ton of scratch space! */
+ ulong smr;
+ fd_epoch_bank_t * epoch_bank = read_epoch_bank(ctx, fd_scratch_virtual(), &smr);
+ fd_vote_accounts_t * accts = &epoch_bank->stakes.vote_accounts;
+ fd_vote_accounts_pair_t_mapnode_t * root = accts->vote_accounts_root;
+ fd_vote_accounts_pair_t_mapnode_t * pool = accts->vote_accounts_pool;
+
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"current\":[");
+
+ int needcomma = 0;
+ for ( ulong i = 0; ; ++i ) {
+ // Path to argument
+ uint path[4];
+ path[0] = (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS;
+ path[1] = (uint) ((JSON_TOKEN_LBRACKET<<16) | i);
+ path[2] = (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_VOTEPUBKEY;
+ path[3] = (JSON_TOKEN_STRING<<16);
+ ulong arg_sz = 0;
+ const void* arg = json_get_value(values, path, 4, &arg_sz);
+ if (arg == NULL)
+ // End of list
+ break;
+
+ fd_vote_accounts_pair_t_mapnode_t key = { 0 };
+ fd_base58_decode_32((const char *)arg, key.elem.key.uc);
+ fd_vote_accounts_pair_t_mapnode_t * vote_node = fd_vote_accounts_pair_t_map_find( pool, root, &key );
+ if( vote_node == NULL ) continue;
+
+ if( needcomma ) fd_textstream_sprintf(ts, ",");
+ vote_account_to_json(ts, vote_node);
+ needcomma = 1;
+ }
+
+ fd_textstream_sprintf(ts, "],\"delinquent\":[]},\"id\":%lu}" CRLF, ctx->call_id);
+ fd_web_replier_done(replier);
+ fd_readwrite_end_read( &ctx->global->lock );
+ } FD_METHOD_SCRATCH_END;
+ return 0;
+}
+
+// Implementation of the "isBlockhashValid" methods
+static int
+method_isBlockhashValid(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "isBlockhashValid is not implemented");
+ return 0;
+}
+
+// Implementation of the "minimumLedgerSlot" methods
+static int
+method_minimumLedgerSlot(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "minimumLedgerSlot is not implemented");
+ return 0;
+}
+
+// Implementation of the "requestAirdrop" methods
+static int
+method_requestAirdrop(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "requestAirdrop is not implemented");
+ return 0;
+}
+
+// Implementation of the "sendTransaction" methods
+static int
+method_sendTransaction(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "sendTransaction is not implemented");
+ return 0;
+}
+
+// Implementation of the "simulateTransaction" methods
+static int
+method_simulateTransaction(struct fd_web_replier* replier, struct json_values* values, fd_rpc_ctx_t * ctx) {
+ (void)values;
+ (void)ctx;
+ fd_web_replier_error(replier, "simulateTransaction is not implemented");
+ return 0;
+}
+
+// Top level method dispatch function
+void
+fd_webserver_method_generic(struct fd_web_replier* replier, struct json_values* values, void * cb_arg) {
+ fd_rpc_ctx_t ctx = *( fd_rpc_ctx_t *)cb_arg;
+
+ static const uint PATH[2] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_JSONRPC,
+ (JSON_TOKEN_STRING<<16)
+ };
+ ulong arg_sz = 0;
+ const void* arg = json_get_value(values, PATH, 2, &arg_sz);
+ if (arg == NULL) {
+ fd_web_replier_error(replier, "missing jsonrpc member");
+ return;
+ }
+ if (!MATCH_STRING(arg, arg_sz, "2.0")) {
+ fd_web_replier_error(replier, "jsonrpc value must be 2.0");
+ return;
+ }
+
+ static const uint PATH3[2] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_ID,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ arg_sz = 0;
+ arg = json_get_value(values, PATH3, 2, &arg_sz);
+ if (arg == NULL) {
+ fd_web_replier_error(replier, "missing id member");
+ return;
+ }
+ ctx.call_id = *(long*)arg;
+
+ static const uint PATH2[2] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_METHOD,
+ (JSON_TOKEN_STRING<<16)
+ };
+ arg_sz = 0;
+ arg = json_get_value(values, PATH2, 2, &arg_sz);
+ if (arg == NULL) {
+ fd_web_replier_error(replier, "missing method member");
+ return;
+ }
+ long meth_id = fd_webserver_json_keyword((const char*)arg, arg_sz);
+
+ switch (meth_id) {
+ case KEYW_RPCMETHOD_GETACCOUNTINFO:
+ if (!method_getAccountInfo(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETBALANCE:
+ if (!method_getBalance(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETBLOCK:
+ if (!method_getBlock(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETBLOCKCOMMITMENT:
+ if (!method_getBlockCommitment(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETBLOCKHEIGHT:
+ if (!method_getBlockHeight(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETBLOCKPRODUCTION:
+ if (!method_getBlockProduction(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETBLOCKS:
+ if (!method_getBlocks(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETBLOCKSWITHLIMIT:
+ if (!method_getBlocksWithLimit(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETBLOCKTIME:
+ if (!method_getBlockTime(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETCLUSTERNODES:
+ if (!method_getClusterNodes(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETCONFIRMEDBLOCK:
+ if (!method_getConfirmedBlock(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETCONFIRMEDBLOCKS:
+ if (!method_getConfirmedBlocks(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETCONFIRMEDBLOCKSWITHLIMIT:
+ if (!method_getConfirmedBlocksWithLimit(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETCONFIRMEDSIGNATURESFORADDRESS2:
+ if (!method_getConfirmedSignaturesForAddress2(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETCONFIRMEDTRANSACTION:
+ if (!method_getConfirmedTransaction(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETEPOCHINFO:
+ if (!method_getEpochInfo(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETEPOCHSCHEDULE:
+ if (!method_getEpochSchedule(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETFEECALCULATORFORBLOCKHASH:
+ if (!method_getFeeCalculatorForBlockhash(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETFEEFORMESSAGE:
+ if (!method_getFeeForMessage(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETFEERATEGOVERNOR:
+ if (!method_getFeeRateGovernor(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETFEES:
+ if (!method_getFees(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETFIRSTAVAILABLEBLOCK:
+ if (!method_getFirstAvailableBlock(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETGENESISHASH:
+ if (!method_getGenesisHash(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETHEALTH:
+ if (!method_getHealth(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETHIGHESTSNAPSHOTSLOT:
+ if (!method_getHighestSnapshotSlot(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETIDENTITY:
+ if (!method_getIdentity(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETINFLATIONGOVERNOR:
+ if (!method_getInflationGovernor(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETINFLATIONRATE:
+ if (!method_getInflationRate(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETINFLATIONREWARD:
+ if (!method_getInflationReward(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETLARGESTACCOUNTS:
+ if (!method_getLargestAccounts(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETLATESTBLOCKHASH:
+ if (!method_getLatestBlockhash(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETLEADERSCHEDULE:
+ if (!method_getLeaderSchedule(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETMAXRETRANSMITSLOT:
+ if (!method_getMaxRetransmitSlot(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETMAXSHREDINSERTSLOT:
+ if (!method_getMaxShredInsertSlot(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETMINIMUMBALANCEFORRENTEXEMPTION:
+ if (!method_getMinimumBalanceForRentExemption(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETMULTIPLEACCOUNTS:
+ if (!method_getMultipleAccounts(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETPROGRAMACCOUNTS:
+ if (!method_getProgramAccounts(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETRECENTBLOCKHASH:
+ if (!method_getRecentBlockhash(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETRECENTPERFORMANCESAMPLES:
+ if (!method_getRecentPerformanceSamples(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETRECENTPRIORITIZATIONFEES:
+ if (!method_getRecentPrioritizationFees(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSIGNATURESFORADDRESS:
+ if (!method_getSignaturesForAddress(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSIGNATURESTATUSES:
+ if (!method_getSignatureStatuses(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSLOT:
+ if (!method_getSlot(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSLOTLEADER:
+ if (!method_getSlotLeader(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSLOTLEADERS:
+ if (!method_getSlotLeaders(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSNAPSHOTSLOT:
+ if (!method_getSnapshotSlot(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSTAKEACTIVATION:
+ if (!method_getStakeActivation(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSTAKEMINIMUMDELEGATION:
+ if (!method_getStakeMinimumDelegation(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETSUPPLY:
+ if (!method_getSupply(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETTOKENACCOUNTBALANCE:
+ if (!method_getTokenAccountBalance(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETTOKENACCOUNTSBYDELEGATE:
+ if (!method_getTokenAccountsByDelegate(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETTOKENACCOUNTSBYOWNER:
+ if (!method_getTokenAccountsByOwner(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETTOKENLARGESTACCOUNTS:
+ if (!method_getTokenLargestAccounts(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETTOKENSUPPLY:
+ if (!method_getTokenSupply(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETTRANSACTION:
+ if (!method_getTransaction(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETTRANSACTIONCOUNT:
+ if (!method_getTransactionCount(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETVERSION:
+ if (!method_getVersion(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_GETVOTEACCOUNTS:
+ if (!method_getVoteAccounts(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_ISBLOCKHASHVALID:
+ if (!method_isBlockhashValid(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_MINIMUMLEDGERSLOT:
+ if (!method_minimumLedgerSlot(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_REQUESTAIRDROP:
+ if (!method_requestAirdrop(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_SENDTRANSACTION:
+ if (!method_sendTransaction(replier, values, &ctx))
+ return;
+ break;
+ case KEYW_RPCMETHOD_SIMULATETRANSACTION:
+ if (!method_simulateTransaction(replier, values, &ctx))
+ return;
+ break;
+ default:
+ fd_web_replier_error(replier, "unknown or unimplemented method %s", (const char*)arg);
+ return;
+ }
+
+ /* Probably should make an error here */
+ static const char* DOC=
+ "" CRLF
+ "" CRLF
+ "OK" CRLF
+ "" CRLF
+ "" CRLF
+ "" CRLF
+ "";
+ fd_textstream_t * ts = fd_web_replier_textstream(replier);
+ fd_textstream_append(ts, DOC, strlen(DOC));
+ fd_web_replier_done(replier);
+}
+
+static int
+ws_method_accountSubscribe(fd_websocket_ctx_t * wsctx, struct json_values * values, fd_rpc_ctx_t * ctx, fd_textstream_t * ts) {
+ FD_METHOD_SCRATCH_BEGIN( 11<<20 ) {
+ // Path to argument
+ static const uint PATH[3] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 0,
+ (JSON_TOKEN_STRING<<16)
+ };
+ ulong arg_sz = 0;
+ const void* arg = json_get_value(values, PATH, 3, &arg_sz);
+ if (arg == NULL) {
+ fd_web_ws_error(wsctx, "getAccountInfo requires a string as first parameter");
+ return 0;
+ }
+ fd_pubkey_t acct;
+ fd_base58_decode_32((const char *)arg, acct.uc);
+
+ static const uint PATH2[4] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_ENCODING,
+ (JSON_TOKEN_STRING<<16)
+ };
+ ulong enc_str_sz = 0;
+ const void* enc_str = json_get_value(values, PATH2, 4, &enc_str_sz);
+ fd_rpc_encoding_t enc;
+ if (enc_str == NULL || MATCH_STRING(enc_str, enc_str_sz, "base58"))
+ enc = FD_ENC_BASE58;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base64"))
+ enc = FD_ENC_BASE64;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "base64+zstd"))
+ enc = FD_ENC_BASE64_ZSTD;
+ else if (MATCH_STRING(enc_str, enc_str_sz, "jsonParsed"))
+ enc = FD_ENC_JSON;
+ else {
+ fd_web_ws_error(wsctx, "invalid data encoding %s", (const char*)enc_str);
+ return 0;
+ }
+
+ static const uint PATH3[5] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_DATASLICE,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_LENGTH,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ static const uint PATH4[5] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_PARAMS,
+ (JSON_TOKEN_LBRACKET<<16) | 1,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_DATASLICE,
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_OFFSET,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ ulong len_sz = 0;
+ const void* len_ptr = json_get_value(values, PATH3, 5, &len_sz);
+ ulong off_sz = 0;
+ const void* off_ptr = json_get_value(values, PATH4, 5, &off_sz);
+ if (len_ptr && off_ptr) {
+ if (enc == FD_ENC_JSON) {
+ fd_web_ws_error(wsctx, "cannot use jsonParsed encoding with slice");
+ return 0;
+ }
+ }
+
+ fd_rpc_global_ctx_t * subs = ctx->global;
+ fd_readwrite_start_write( &subs->lock );
+ if( subs->sub_cnt >= FD_WS_MAX_SUBS ) {
+ fd_readwrite_end_write( &subs->lock );
+ fd_web_ws_error(wsctx, "too many subscriptions");
+ return 0;
+ }
+ struct fd_ws_subscription * sub = &subs->sub_list[ subs->sub_cnt++ ];
+ sub->socket = wsctx;
+ sub->meth_id = KEYW_WS_METHOD_ACCOUNTSUBSCRIBE;
+ sub->call_id = ctx->call_id;
+ ulong subid = sub->subsc_id = ++(subs->last_subsc_id);
+ sub->acct_subscribe.acct = acct;
+ sub->acct_subscribe.enc = enc;
+ sub->acct_subscribe.off = (off_ptr ? *(long*)off_ptr : FD_LONG_UNSET);
+ sub->acct_subscribe.len = (len_ptr ? *(long*)len_ptr : FD_LONG_UNSET);
+ fd_readwrite_end_write( &subs->lock );
+
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}" CRLF,
+ subid, sub->call_id);
+
+ } FD_METHOD_SCRATCH_END;
+
+ return 1;
+}
+
+static int
+ws_method_accountSubscribe_update(fd_rpc_ctx_t * ctx, fd_replay_notif_msg_t * msg, struct fd_ws_subscription * sub, fd_textstream_t * ts) {
+ FD_METHOD_SCRATCH_BEGIN( 11<<20 ) {
+ fd_websocket_ctx_t * wsctx = sub->socket;
+
+ ulong val_sz;
+ void * val = read_account_with_xid(ctx, &sub->acct_subscribe.acct, &msg->acct_saved.funk_xid, fd_scratch_virtual(), &val_sz);
+ if (val == NULL) {
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"value\":null},\"subscription\":%lu}" CRLF,
+ msg->acct_saved.funk_xid.ul[0], sub->subsc_id);
+ return 1;
+ }
+
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"method\":\"accountNotification\",\"params\":{\"result\":{\"context\":{\"apiVersion\":\"" API_VERSION "\",\"slot\":%lu},\"value\":",
+ msg->acct_saved.funk_xid.ul[0]);
+ const char * err = fd_account_to_json( ts, sub->acct_subscribe.acct, sub->acct_subscribe.enc, val, val_sz, sub->acct_subscribe.off, sub->acct_subscribe.len );
+ if( err ) {
+ fd_web_ws_error(wsctx, "%s", err);
+ return 0;
+ }
+ fd_textstream_sprintf(ts, "},\"subscription\":%lu}}" CRLF, sub->subsc_id);
+ } FD_METHOD_SCRATCH_END;
+
+ return 1;
+}
+
+static int
+ws_method_slotSubscribe(fd_websocket_ctx_t * wsctx, struct json_values * values, fd_rpc_ctx_t * ctx, fd_textstream_t * ts) {
+ (void)values;
+ fd_rpc_global_ctx_t * subs = ctx->global;
+ fd_readwrite_start_write( &subs->lock );
+ if( subs->sub_cnt >= FD_WS_MAX_SUBS ) {
+ fd_readwrite_end_write( &subs->lock );
+ fd_web_ws_error(wsctx, "too many subscriptions");
+ return 0;
+ }
+ struct fd_ws_subscription * sub = &subs->sub_list[ subs->sub_cnt++ ];
+ sub->socket = wsctx;
+ sub->meth_id = KEYW_WS_METHOD_SLOTSUBSCRIBE;
+ sub->call_id = ctx->call_id;
+ ulong subid = sub->subsc_id = ++(subs->last_subsc_id);
+ fd_readwrite_end_write( &subs->lock );
+
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"result\":%lu,\"id\":%lu}" CRLF,
+ subid, sub->call_id);
+
+ return 1;
+}
+
+static int
+ws_method_slotSubscribe_update(fd_rpc_ctx_t * ctx, fd_replay_notif_msg_t * msg, struct fd_ws_subscription * sub, fd_textstream_t * ts) {
+ (void)ctx;
+ char bank_hash[50];
+ fd_base58_encode_32(msg->slot_exec.bank_hash.uc, 0, bank_hash);
+ fd_textstream_sprintf(ts, "{\"jsonrpc\":\"2.0\",\"method\":\"slotNotification\",\"params\":{\"result\":{\"parent\":%lu,\"root\":%lu,\"slot\":%lu,\"bank_hash\":\"%s\"},\"subscription\":%lu}}" CRLF,
+ msg->slot_exec.parent, msg->slot_exec.root, msg->slot_exec.slot,
+ bank_hash, sub->subsc_id);
+ return 1;
+}
+
+int
+fd_webserver_ws_subscribe(struct json_values* values, fd_websocket_ctx_t * wsctx, void * cb_arg) {
+ fd_rpc_ctx_t ctx = *( fd_rpc_ctx_t *)cb_arg;
+
+ static const uint PATH[2] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_JSONRPC,
+ (JSON_TOKEN_STRING<<16)
+ };
+ ulong arg_sz = 0;
+ const void* arg = json_get_value(values, PATH, 2, &arg_sz);
+ if (arg == NULL) {
+ fd_web_ws_error( wsctx, "missing jsonrpc member" );
+ return 0;
+ }
+ if (!MATCH_STRING(arg, arg_sz, "2.0")) {
+ fd_web_ws_error( wsctx, "jsonrpc value must be 2.0" );
+ return 0;
+ }
+
+ static const uint PATH3[2] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_ID,
+ (JSON_TOKEN_INTEGER<<16)
+ };
+ arg_sz = 0;
+ arg = json_get_value(values, PATH3, 2, &arg_sz);
+ if (arg == NULL) {
+ fd_web_ws_error( wsctx, "missing id member" );
+ return 0;
+ }
+ ctx.call_id = *(long*)arg;
+
+ static const uint PATH2[2] = {
+ (JSON_TOKEN_LBRACE<<16) | KEYW_JSON_METHOD,
+ (JSON_TOKEN_STRING<<16)
+ };
+ arg_sz = 0;
+ arg = json_get_value(values, PATH2, 2, &arg_sz);
+ if (arg == NULL) {
+ fd_web_ws_error( wsctx, "missing method member" );
+ return 0;
+ }
+ long meth_id = fd_webserver_json_keyword((const char*)arg, arg_sz);
+
+ fd_textstream_t ts;
+ fd_textstream_new(&ts, fd_libc_alloc_virtual(), 1UL<<15UL);
+
+ switch (meth_id) {
+ case KEYW_WS_METHOD_ACCOUNTSUBSCRIBE:
+ if (ws_method_accountSubscribe(wsctx, values, &ctx, &ts)) {
+ fd_web_ws_reply( wsctx, &ts );
+ fd_textstream_destroy(&ts);
+ return 1;
+ }
+ return 0;
+ case KEYW_WS_METHOD_SLOTSUBSCRIBE:
+ if (ws_method_slotSubscribe(wsctx, values, &ctx, &ts)) {
+ fd_web_ws_reply( wsctx, &ts );
+ fd_textstream_destroy(&ts);
+ return 1;
+ }
+ return 0;
+ }
+
+ fd_textstream_destroy(&ts);
+ fd_web_ws_error( wsctx, "unknown websocket method: %s", (const char*)arg );
+ return 0;
+}
+
+void
+fd_rpc_start_service(fd_rpcserver_args_t * args, fd_rpc_ctx_t ** ctx_p) {
+ fd_rpc_ctx_t * ctx = (fd_rpc_ctx_t *)malloc(sizeof(fd_rpc_ctx_t));
+ fd_rpc_global_ctx_t * gctx = (fd_rpc_global_ctx_t *)malloc(sizeof(fd_rpc_global_ctx_t));
+ fd_memset(ctx, 0, sizeof(fd_rpc_ctx_t));
+ fd_memset(gctx, 0, sizeof(fd_rpc_global_ctx_t));
+
+ fd_readwrite_new( &gctx->lock );
+ ctx->global = gctx;
+ gctx->funk = args->funk;
+ gctx->blockstore = args->blockstore;
+
+ FD_LOG_NOTICE(( "starting web server with %lu threads on port %u", args->num_threads, (uint)args->port ));
+ if (fd_webserver_start(args->num_threads, args->port, args->ws_port, &gctx->ws, ctx))
+ FD_LOG_ERR(("fd_webserver_start failed"));
+
+ *ctx_p = ctx;
+}
+
+void
+fd_rpc_stop_service(fd_rpc_ctx_t * ctx) {
+ FD_LOG_NOTICE(( "stopping web server" ));
+ if (fd_webserver_stop(&ctx->global->ws))
+ FD_LOG_ERR(("fd_webserver_stop failed"));
+ free(ctx->global);
+ free(ctx);
+}
+
+void
+fd_rpc_ws_poll(fd_rpc_ctx_t * ctx) {
+ fd_webserver_ws_poll(&ctx->global->ws);
+}
+
+void
+fd_webserver_ws_closed(fd_websocket_ctx_t * wsctx, void * cb_arg) {
+ fd_rpc_ctx_t * ctx = ( fd_rpc_ctx_t *)cb_arg;
+ fd_rpc_global_ctx_t * subs = ctx->global;
+ fd_readwrite_start_write( &subs->lock );
+ for( ulong i = 0; i < subs->sub_cnt; ++i ) {
+ if( subs->sub_list[i].socket == wsctx ) {
+ fd_memcpy( &subs->sub_list[i], &subs->sub_list[--(subs->sub_cnt)], sizeof(struct fd_ws_subscription) );
+ --i;
+ }
+ }
+ fd_readwrite_end_write( &subs->lock );
+}
+
+void
+fd_rpc_replay_notify(fd_rpc_ctx_t * ctx, fd_replay_notif_msg_t * msg) {
+ fd_rpc_global_ctx_t * subs = ctx->global;
+ fd_readwrite_start_read( &subs->lock );
+
+ if( subs->sub_cnt == 0 ) {
+ /* do nothing */
+
+ } else if( msg->type == FD_REPLAY_SAVED_TYPE ) {
+ fd_textstream_t ts;
+ fd_textstream_new(&ts, fd_libc_alloc_virtual(), 11UL<<21);
+
+ /* TODO: replace with a hash table lookup? */
+ for( uint i = 0; i < msg->acct_saved.acct_id_cnt; ++i ) {
+ fd_pubkey_t * id = &msg->acct_saved.acct_id[i];
+ for( ulong j = 0; j < subs->sub_cnt; ++j ) {
+ struct fd_ws_subscription * sub = &subs->sub_list[ j ];
+ if( sub->meth_id == KEYW_WS_METHOD_ACCOUNTSUBSCRIBE &&
+ fd_pubkey_eq( id, &sub->acct_subscribe.acct ) ) {
+ fd_textstream_clear( &ts );
+ if( ws_method_accountSubscribe_update( ctx, msg, sub, &ts ) )
+ fd_web_ws_reply( sub->socket, &ts );
+ }
+ }
+ }
+
+ fd_textstream_destroy(&ts);
+
+ } else if( msg->type == FD_REPLAY_SLOT_TYPE ) {
+ fd_textstream_t ts;
+ fd_textstream_new(&ts, fd_libc_alloc_virtual(), 1UL<<16);
+
+ for( ulong j = 0; j < subs->sub_cnt; ++j ) {
+ struct fd_ws_subscription * sub = &subs->sub_list[ j ];
+ if( sub->meth_id == KEYW_WS_METHOD_SLOTSUBSCRIBE ) {
+ fd_textstream_clear( &ts );
+ if( ws_method_slotSubscribe_update( ctx, msg, sub, &ts ) )
+ fd_web_ws_reply( sub->socket, &ts );
+ }
+ }
+
+ fd_textstream_destroy(&ts);
+ }
+
+ fd_readwrite_end_read( &subs->lock );
+}
diff --git a/src/app/rpcserver/fd_rpc_service.h b/src/app/rpcserver/fd_rpc_service.h
new file mode 100644
index 0000000000..8b6b915361
--- /dev/null
+++ b/src/app/rpcserver/fd_rpc_service.h
@@ -0,0 +1,33 @@
+#ifndef HEADER_fd_src_flamenco_rpc_fd_rpc_service_h
+#define HEADER_fd_src_flamenco_rpc_fd_rpc_service_h
+
+#include "../../util/fd_util.h"
+#include "../../funk/fd_funk.h"
+#include "../../flamenco/runtime/fd_blockstore.h"
+#include "../../tango/mcache/fd_mcache.h"
+#include "../../util/textstream/fd_textstream.h"
+#include "../fdctl/run/tiles/fd_replay_notif.h"
+#include "fd_block_to_json.h"
+
+struct fd_rpcserver_args {
+ fd_funk_t * funk;
+ fd_blockstore_t * blockstore;
+ fd_wksp_t * rep_notify_wksp;
+ fd_frag_meta_t * rep_notify;
+ ulong num_threads;
+ ushort port;
+ ushort ws_port;
+};
+typedef struct fd_rpcserver_args fd_rpcserver_args_t;
+
+typedef struct fd_rpc_ctx fd_rpc_ctx_t;
+
+void fd_rpc_start_service(fd_rpcserver_args_t * args, fd_rpc_ctx_t ** ctx);
+
+void fd_rpc_stop_service(fd_rpc_ctx_t * ctx);
+
+void fd_rpc_ws_poll(fd_rpc_ctx_t * ctx);
+
+void fd_rpc_replay_notify(fd_rpc_ctx_t * ctx, fd_replay_notif_msg_t * msg);
+
+#endif /* HEADER_fd_src_flamenco_rpc_fd_rpc_service_h */
diff --git a/src/app/rpcserver/fd_webserver.c b/src/app/rpcserver/fd_webserver.c
new file mode 100644
index 0000000000..3e44cd87e9
--- /dev/null
+++ b/src/app/rpcserver/fd_webserver.c
@@ -0,0 +1,364 @@
+#include "../../util/fd_util.h"
+#include "../../ballet/base64/fd_base64.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "fd_methods.h"
+#include "fd_webserver.h"
+
+struct fd_websocket_ctx {
+ struct MHD_UpgradeResponseHandle *urh;
+ MHD_socket sock;
+ fd_webserver_t * ws;
+};
+
+// Parse the top level json request object
+void json_parse_root(struct fd_web_replier* replier, json_lex_state_t* lex, void* cb_arg) {
+ struct json_values values;
+ json_values_new(&values);
+
+ struct json_path path;
+ path.len = 0;
+ if (json_values_parse(lex, &values, &path)) {
+ json_values_printout(&values);
+ fd_webserver_method_generic(replier, &values, cb_arg);
+ } else {
+ ulong sz;
+ const char* text = json_lex_get_text(lex, &sz);
+ FD_LOG_WARNING(( "json parsing error: %s", text ));
+ fd_web_replier_simple_error(replier, text, (uint)sz);
+ }
+
+ json_values_delete(&values);
+}
+
+struct fd_web_replier {
+ const char* upload_data;
+ size_t upload_data_size;
+ unsigned int status_code;
+ struct MHD_Response* response;
+ fd_textstream_t textstream;
+};
+
+struct fd_web_replier* fd_web_replier_new(void) {
+ struct fd_web_replier* r = (struct fd_web_replier*)malloc(sizeof(struct fd_web_replier));
+ r->upload_data = NULL;
+ r->upload_data_size = 0;
+ r->status_code = MHD_HTTP_OK;
+ r->response = NULL;
+ fd_textstream_new(&r->textstream, fd_libc_alloc_virtual(), 1UL<<18); // 256KB chunks
+ return r;
+}
+
+void fd_web_replier_delete(struct fd_web_replier* r) {
+ if (r->response != NULL)
+ MHD_destroy_response(r->response);
+ fd_textstream_destroy(&r->textstream);
+ free(r);
+}
+
+fd_textstream_t * fd_web_replier_textstream(struct fd_web_replier* r) {
+ return &r->textstream;
+}
+
+void fd_web_replier_done(struct fd_web_replier* r) {
+ struct fd_iovec iov[100];
+ ulong numiov = fd_textstream_get_iov_count(&r->textstream);
+ if (numiov > 100 || fd_textstream_get_iov(&r->textstream, iov)) {
+ fd_web_replier_error(r, "failure in reply generator");
+ return;
+ }
+ r->status_code = MHD_HTTP_OK;
+ if (r->response != NULL)
+ MHD_destroy_response(r->response);
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+ r->response = MHD_create_response_from_iovec((struct MHD_IoVec *)iov, (uint)numiov, NULL, NULL);
+}
+
+void fd_web_replier_error( struct fd_web_replier* r, const char* format, ... ) {
+ char text[4096];
+ va_list ap;
+ va_start(ap, format);
+ /* Would be nice to vsnprintf directly into the textstream, but that's messy */
+ int x = vsnprintf(text, sizeof(text), format, ap);
+ va_end(ap);
+ fd_web_replier_simple_error(r, text, (uint)x);
+}
+
+void fd_web_ws_error( fd_websocket_ctx_t * ctx, const char* format, ... ) {
+ char text[4096];
+ va_list ap;
+ va_start(ap, format);
+ /* Would be nice to vsnprintf directly into the textstream, but that's messy */
+ int x = vsnprintf(text, sizeof(text), format, ap);
+ va_end(ap);
+ fd_web_ws_simple_error(ctx, text, (uint)x);
+}
+
+void fd_web_replier_simple_error( struct fd_web_replier* r, const char* text, uint text_size) {
+#define CRLF "\r\n"
+ static const char* DOC1 =
+"" CRLF
+"" CRLF
+"ERROR" CRLF
+"" CRLF
+"" CRLF
+"";
+ static const char* DOC2 =
+"
" CRLF
+"Request:
";
+ static const char* DOC3 =
+"
" CRLF
+"" CRLF
+"" CRLF;
+
+ fd_textstream_t * ts = &r->textstream;
+ fd_textstream_clear(ts);
+ fd_textstream_append(ts, DOC1, strlen(DOC1));
+ fd_textstream_append(ts, text, text_size);
+ fd_textstream_append(ts, DOC2, strlen(DOC2));
+ fd_textstream_append(ts, r->upload_data, r->upload_data_size);
+ fd_textstream_append(ts, DOC3, strlen(DOC3));
+
+ struct fd_iovec iov[100];
+ ulong numiov = fd_textstream_get_iov_count(&r->textstream);
+ if (numiov > 100 || fd_textstream_get_iov(&r->textstream, iov)) {
+ FD_LOG_ERR(("failure in error reply generator"));
+ return;
+ }
+
+ r->status_code = MHD_HTTP_BAD_REQUEST;
+ if (r->response != NULL)
+ MHD_destroy_response(r->response);
+ r->response = MHD_create_response_from_iovec((struct MHD_IoVec *)iov, (uint)numiov, NULL, NULL);
+}
+
+/**
+ * Signature of the callback used by MHD to notify the
+ * application about completed requests.
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see MHD_OPTION_NOTIFY_COMPLETED
+ */
+static void completed_cb(void* cls,
+ struct MHD_Connection* connection,
+ void** con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ (void) cls; /* Unused. Silent compiler warning. */
+ (void) connection; /* Unused. Silent compiler warning. */
+ (void) toe; /* Unused. Silent compiler warning. */
+
+ if (*con_cls != NULL) {
+ fd_web_replier_delete((struct fd_web_replier*) (*con_cls));
+ *con_cls = NULL;
+ }
+}
+
+/**
+ * Main MHD callback for handling requests.
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param connection handle identifying the incoming connection
+ * @param url the requested url
+ * @param method the HTTP method used ("GET", "PUT", etc.)
+ * @param version the HTTP version string (i.e. "HTTP/1.1")
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of MHD_get_connection_values; very large POST
+ * data *will* be made available incrementally in
+ * upload_data)
+ * @param upload_data_size set initially to the size of the
+ * upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param ptr pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global "MHD_RequestCompleted" callback (which
+ * can be set with the MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, *con_cls will be NULL.
+ * @return MHS_YES if the connection was handled successfully,
+ * MHS_NO if the socket must be closed due to a serious
+ * error while handling the request
+ */
+static enum MHD_Result handler(void* cls,
+ struct MHD_Connection* connection,
+ const char* url,
+ const char* method,
+ const char* version,
+ const char* upload_data,
+ size_t* upload_data_size,
+ void** con_cls)
+{
+ (void) url; /* Unused. Silent compiler warning. */
+ (void) version; /* Unused. Silent compiler warning. */
+
+ fd_webserver_t * ws = (fd_webserver_t *)cls;
+
+ if (0 != strcmp (method, "POST"))
+ return MHD_NO; /* unexpected method */
+
+ struct fd_web_replier* replier;
+ if (*con_cls == NULL)
+ *con_cls = replier = fd_web_replier_new();
+ else
+ replier = (struct fd_web_replier*) (*con_cls);
+
+ size_t sz = *upload_data_size;
+ if (sz) {
+ replier->upload_data = upload_data;
+ replier->upload_data_size = sz;
+ json_lex_state_t lex;
+ json_lex_state_new(&lex, upload_data, sz);
+ json_parse_root(replier, &lex, ws->cb_arg);
+ json_lex_state_delete(&lex);
+ *upload_data_size = 0;
+ }
+
+ // Check if we are done with the request. This is clunky as hell,
+ // but I didn't design the API.
+ if (!sz && replier->upload_data_size) {
+ if (replier->response != NULL)
+ return MHD_queue_response (connection, replier->status_code, replier->response);
+ return MHD_NO;
+ }
+ return MHD_YES;
+}
+
+int
+fd_webserver_ws_request( fd_websocket_ctx_t * ctx, char const * msg, ulong msglen ) {
+ json_lex_state_t lex;
+ json_lex_state_new(&lex, msg, msglen);
+ struct json_values values;
+ json_values_new(&values);
+ struct json_path path;
+ path.len = 0;
+ int ret = json_values_parse(&lex, &values, &path);
+ if (ret) {
+ json_values_printout(&values);
+ ret = fd_webserver_ws_subscribe(&values, ctx, ctx->ws->cb_arg);
+ } else {
+ ulong sz;
+ const char* text = json_lex_get_text(&lex, &sz);
+ FD_LOG_WARNING(( "json parsing error: %s", text ));
+ fd_web_ws_simple_error( ctx, text, (uint)sz );
+ }
+ json_values_delete(&values);
+ json_lex_state_delete(&lex);
+ return ret;
+}
+
+#include "fd_websocket_support.h"
+
+void fd_web_ws_simple_error( fd_websocket_ctx_t * ctx, const char* text, uint text_size) {
+#define CRLF "\r\n"
+ static const char* DOC1 =
+"" CRLF
+"" CRLF
+"ERROR" CRLF
+"" CRLF
+"" CRLF
+"";
+ static const char* DOC2 =
+"
" CRLF
+"" CRLF
+"" CRLF;
+
+ fd_textstream_t ts;
+ fd_textstream_new(&ts, fd_libc_alloc_virtual(), 1UL<<12);
+ fd_textstream_append(&ts, DOC1, strlen(DOC1));
+ fd_textstream_append(&ts, text, text_size);
+ fd_textstream_append(&ts, DOC2, strlen(DOC2));
+
+ char buf[2048];
+ ulong sz = fd_textstream_total_size(&ts);
+ if ( sz <= sizeof(buf) ) {
+ fd_textstream_get_output( &ts, buf );
+ ws_send_frame( ctx->sock, WS_OPCODE_TEXT_FRAME, buf, sz );
+ }
+
+ fd_textstream_destroy(&ts);
+}
+
+void fd_web_ws_reply( fd_websocket_ctx_t * ctx, fd_textstream_t * ts) {
+ char buf[2048];
+ ulong sz = fd_textstream_total_size(ts);
+ if ( sz + WS_MAX_HDR_SIZE <= sizeof(buf) ) {
+ fd_textstream_get_output( ts, buf + WS_MAX_HDR_SIZE );
+ ws_send_frame_prepend_hdr( ctx->sock, WS_OPCODE_TEXT_FRAME, buf + WS_MAX_HDR_SIZE, sz );
+ } else {
+ char * buf2 = malloc(sz + WS_MAX_HDR_SIZE);
+ fd_textstream_get_output( ts, buf2 + WS_MAX_HDR_SIZE );
+ ws_send_frame_prepend_hdr( ctx->sock, WS_OPCODE_TEXT_FRAME, buf2 + WS_MAX_HDR_SIZE, sz );
+ free( buf2 );
+ }
+}
+
+int fd_webserver_start(ulong num_threads, ushort portno, ushort ws_portno, fd_webserver_t * ws, void * cb_arg) {
+ ws->cb_arg = cb_arg;
+
+ ws->daemon = MHD_start_daemon(
+ MHD_USE_INTERNAL_POLLING_THREAD
+ | MHD_USE_SUPPRESS_DATE_NO_CLOCK
+ | MHD_USE_AUTO | MHD_USE_TURBO,
+ portno,
+ NULL, NULL, &handler, ws,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
+ MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) num_threads,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, ws,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 1000,
+ MHD_OPTION_END);
+ if (ws->daemon == NULL)
+ return -1;
+
+ ws->ws_daemon = MHD_start_daemon(MHD_ALLOW_UPGRADE | MHD_USE_AUTO_INTERNAL_THREAD,
+ ws_portno, NULL, NULL, &ahc_cb, ws,
+ MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 1,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 1000,
+ MHD_OPTION_END);
+ if (ws->ws_daemon == NULL)
+ return -1;
+
+ ws->ws_epoll_fd = epoll_create1(0);
+ if( ws->ws_epoll_fd == -1 )
+ return -1;
+
+ return 0;
+}
+
+int fd_webserver_stop(fd_webserver_t * ws) {
+ MHD_stop_daemon(ws->daemon);
+ MHD_stop_daemon(ws->ws_daemon);
+ close( ws->ws_epoll_fd );
+ return 0;
+}
+
+void fd_webserver_ws_poll(fd_webserver_t * ws) {
+ struct epoll_event events[16];
+ int cnt = epoll_wait( ws->ws_epoll_fd, events, 16, 0 );
+ if( cnt < 0 ) {
+ FD_LOG_ERR(( "epoll_wait failed: %s", strerror(errno) ));
+ }
+ for(int i = 0; i < cnt; ++i) {
+ epoll_selected(events + i);
+ }
+}
diff --git a/src/app/rpcserver/fd_webserver.h b/src/app/rpcserver/fd_webserver.h
new file mode 100644
index 0000000000..7946ad79af
--- /dev/null
+++ b/src/app/rpcserver/fd_webserver.h
@@ -0,0 +1,50 @@
+#ifndef HEADER_fd_src_tango_webserver_fd_webserver_h
+#define HEADER_fd_src_tango_webserver_fd_webserver_h
+
+#include "fd_methods.h"
+#include "../../util/textstream/fd_textstream.h"
+
+struct fd_webserver {
+ void * cb_arg;
+ struct MHD_Daemon * daemon;
+ struct MHD_Daemon * ws_daemon;
+ int ws_epoll_fd;
+};
+typedef struct fd_webserver fd_webserver_t;
+
+typedef struct fd_websocket_ctx fd_websocket_ctx_t;
+
+int fd_webserver_start(ulong num_threads, ushort portno, ushort ws_portno, fd_webserver_t * ws, void * cb_arg);
+
+int fd_webserver_stop(fd_webserver_t * ws);
+
+void fd_webserver_ws_poll(fd_webserver_t * ws);
+
+void fd_webserver_ws_closed(fd_websocket_ctx_t * wsctx, void * cb_arg);
+
+#ifndef KEYW_UNKNOWN
+#define KEYW_UNKNOWN -1L
+#endif
+long fd_webserver_json_keyword(const char* keyw, size_t keyw_sz);
+const char* un_fd_webserver_json_keyword(long id);
+
+struct fd_web_replier;
+void fd_webserver_method_generic(struct fd_web_replier* replier, struct json_values* values, void * cb_arg);
+
+fd_textstream_t * fd_web_replier_textstream(struct fd_web_replier* replier);
+void fd_web_replier_done(struct fd_web_replier* replier);
+
+typedef struct fd_websocket_ctx fd_websocket_ctx_t;
+int fd_webserver_ws_subscribe(struct json_values* values, fd_websocket_ctx_t * ctx, void * cb_arg);
+
+void fd_web_ws_reply( fd_websocket_ctx_t * ctx, fd_textstream_t * ts);
+
+void fd_web_replier_error( struct fd_web_replier* replier, const char* format, ... )
+ __attribute__ ((format (printf, 2, 3)));
+void fd_web_replier_simple_error( struct fd_web_replier* replier, const char* text, uint text_size);
+
+void fd_web_ws_error( fd_websocket_ctx_t * ctx, const char* format, ... )
+ __attribute__ ((format (printf, 2, 3)));
+void fd_web_ws_simple_error( fd_websocket_ctx_t * ctx, const char* text, uint text_size);
+
+#endif /* HEADER_fd_src_tango_webserver_fd_webserver_h */
diff --git a/src/app/rpcserver/fd_websocket_support.h b/src/app/rpcserver/fd_websocket_support.h
new file mode 100644
index 0000000000..8832508536
--- /dev/null
+++ b/src/app/rpcserver/fd_websocket_support.h
@@ -0,0 +1,443 @@
+#include
+
+#pragma GCC diagnostic ignored "-Wstringop-overflow"
+#pragma GCC diagnostic ignored "-Wrestrict"
+
+#define BAD_REQUEST_PAGE \
+ "\n" \
+ "\n" \
+ "fd_rpcserver\n" \
+ "\n" \
+ "\n" \
+ "Bad Request\n" \
+ "\n" \
+ "\n"
+#define UPGRADE_REQUIRED_PAGE \
+ "\n" \
+ "\n" \
+ "fd_rpcserver\n" \
+ "\n" \
+ "\n" \
+ "Upgrade required\n" \
+ "\n" \
+ "\n"
+
+#define WS_SEC_WEBSOCKET_VERSION "13"
+#define WS_UPGRADE_VALUE "websocket"
+#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+#define WS_GUID_LEN 36
+#define WS_KEY_LEN 24
+#define WS_KEY_GUID_LEN ((WS_KEY_LEN) + (WS_GUID_LEN))
+#define WS_FIN 128
+#define WS_OPCODE_TEXT_FRAME 1
+#define WS_OPCODE_CLOSE_FRAME 8
+#define WS_OPCODE_PING_FRAME 9
+#define WS_OPCODE_PONG_FRAME 10
+#define SHA1HashSize 20
+
+static enum MHD_Result
+is_websocket_request (struct MHD_Connection *con, const char *upg_header,
+ const char *con_header)
+{
+
+ (void) con; /* Unused. Silent compiler warning. */
+
+ return ((upg_header != NULL) && (con_header != NULL)
+ && (0 == strcmp (upg_header, WS_UPGRADE_VALUE))
+ && (NULL != strstr (con_header, "Upgrade")))
+ ? MHD_YES
+ : MHD_NO;
+}
+
+static void do_nothing(void * arg) { (void)arg; }
+
+static enum MHD_Result
+send_bad_request (struct MHD_Connection *con)
+{
+ struct MHD_Response *res;
+ enum MHD_Result ret;
+
+ res = MHD_create_response_from_buffer_with_free_callback (strlen (BAD_REQUEST_PAGE), (void *) BAD_REQUEST_PAGE, do_nothing);
+ ret = MHD_queue_response (con, MHD_HTTP_BAD_REQUEST, res);
+ MHD_destroy_response (res);
+ return ret;
+}
+
+static enum MHD_Result
+send_upgrade_required (struct MHD_Connection *con)
+{
+ struct MHD_Response *res;
+ enum MHD_Result ret;
+
+ res = MHD_create_response_from_buffer_with_free_callback (strlen (UPGRADE_REQUIRED_PAGE), (void *) UPGRADE_REQUIRED_PAGE, do_nothing);
+ if (MHD_YES !=
+ MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION,
+ WS_SEC_WEBSOCKET_VERSION))
+ {
+ MHD_destroy_response (res);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (con, MHD_HTTP_UPGRADE_REQUIRED, res);
+ MHD_destroy_response (res);
+ return ret;
+}
+
+static enum MHD_Result
+ws_get_accept_value (const char *key, char * val)
+{
+ SHA_CTX ctx;
+ unsigned char hash[SHA1HashSize];
+
+ if ( (NULL == key) || (WS_KEY_LEN != strlen (key)))
+ {
+ return MHD_NO;
+ }
+ char str[WS_KEY_LEN + WS_GUID_LEN + 1];
+ strncpy (str, key, (WS_KEY_LEN + 1));
+ strncpy (str + WS_KEY_LEN, WS_GUID, WS_GUID_LEN + 1);
+ SHA1_Init (&ctx);
+ SHA1_Update (&ctx, (const unsigned char *) str, WS_KEY_GUID_LEN);
+ if (!SHA1_Final (hash, &ctx))
+ {
+ return MHD_NO;
+ }
+ ulong len = fd_base64_encode(val, hash, SHA1HashSize);
+ val[len] = '\0';
+ return MHD_YES;
+}
+
+static void
+make_blocking (MHD_socket fd)
+{
+#if defined(MHD_POSIX_SOCKETS)
+ int flags;
+
+ flags = fcntl (fd, F_GETFL);
+ if (-1 == flags)
+ abort ();
+ if ((flags & ~O_NONBLOCK) != flags)
+ if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
+ abort ();
+#elif defined(MHD_WINSOCK_SOCKETS)
+ unsigned long flags = 0;
+
+ if (0 != ioctlsocket (fd, (int) FIONBIO, &flags))
+ abort ();
+#endif /* MHD_WINSOCK_SOCKETS */
+}
+
+static size_t
+send_all (MHD_socket sock, const unsigned char *buf, size_t len)
+{
+ ssize_t ret;
+ size_t off;
+
+ for (off = 0; off < len; off += (size_t) ret)
+ {
+#if ! defined(_WIN32) || defined(__CYGWIN__)
+ ret = send (sock, (const void *) &buf[off], len - off, 0);
+#else /* Native W32 */
+ ret = send (sock, (const void *) &buf[off], (int) (len - off), 0);
+#endif /* Native W32 */
+ if (0 > ret)
+ {
+ if (EAGAIN == errno)
+ {
+ ret = 0;
+ continue;
+ }
+ break;
+ }
+ if (0 == ret)
+ {
+ break;
+ }
+ }
+ return off;
+}
+
+static ssize_t
+ws_send_frame (MHD_socket sock, const uchar type, const char *msg, size_t length)
+{
+ unsigned char *response;
+ unsigned char frame[10];
+ unsigned char idx_first_rdata;
+ unsigned char buf[1<<13];
+
+ frame[0] = (WS_FIN | type);
+ if (length <= 125)
+ {
+ frame[1] = length & 0x7F;
+ idx_first_rdata = 2;
+ }
+ else if (0xFFFF < length)
+ {
+ frame[1] = 127;
+ frame[2] = (unsigned char) ((length >> 56) & 0xFF);
+ frame[3] = (unsigned char) ((length >> 48) & 0xFF);
+ frame[4] = (unsigned char) ((length >> 40) & 0xFF);
+ frame[5] = (unsigned char) ((length >> 32) & 0xFF);
+ frame[6] = (unsigned char) ((length >> 24) & 0xFF);
+ frame[7] = (unsigned char) ((length >> 16) & 0xFF);
+ frame[8] = (unsigned char) ((length >> 8) & 0xFF);
+ frame[9] = (unsigned char) (length & 0xFF);
+ idx_first_rdata = 10;
+ }
+ else
+ {
+ frame[1] = 126;
+ frame[2] = (length >> 8) & 0xFF;
+ frame[3] = length & 0xFF;
+ idx_first_rdata = 4;
+ }
+ if( idx_first_rdata + length <= sizeof( buf ) )
+ response = buf;
+ else
+ response = malloc (idx_first_rdata + length);
+ if (NULL == response)
+ {
+ return -1;
+ }
+ memcpy(response, frame, idx_first_rdata);
+ memcpy(response + idx_first_rdata, msg, length);
+ size_t output = send_all (sock, response, idx_first_rdata + length);
+ if( response != buf ) free (response);
+ return (ssize_t) output;
+}
+
+#define WS_MAX_HDR_SIZE 10U
+
+static ssize_t
+ws_send_frame_prepend_hdr(MHD_socket sock, const uchar type, char * msg, size_t length)
+{
+ uchar * frame;
+ size_t tot_length;
+ if (length <= 125)
+ {
+ frame = (uchar*)msg - 2;
+ frame[0] = (WS_FIN | type);
+ frame[1] = length & 0x7F;
+ tot_length = length + 2;
+ }
+ else if (0xFFFF < length)
+ {
+ frame = (uchar*)msg - 10;
+ frame[0] = (WS_FIN | type);
+ frame[1] = 127;
+ frame[2] = (unsigned char) ((length >> 56) & 0xFF);
+ frame[3] = (unsigned char) ((length >> 48) & 0xFF);
+ frame[4] = (unsigned char) ((length >> 40) & 0xFF);
+ frame[5] = (unsigned char) ((length >> 32) & 0xFF);
+ frame[6] = (unsigned char) ((length >> 24) & 0xFF);
+ frame[7] = (unsigned char) ((length >> 16) & 0xFF);
+ frame[8] = (unsigned char) ((length >> 8) & 0xFF);
+ frame[9] = (unsigned char) (length & 0xFF);
+ tot_length = length + 10;
+ }
+ else
+ {
+ frame = (uchar*)msg - 4;
+ frame[0] = (WS_FIN | type);
+ frame[1] = 126;
+ frame[2] = (length >> 8) & 0xFF;
+ frame[3] = length & 0xFF;
+ tot_length = length + 4;
+ }
+ return (ssize_t) send_all (sock, frame, tot_length);
+}
+
+static unsigned char *
+ws_receive_frame (unsigned char *frame, ssize_t *length, int *type)
+{
+ unsigned char masks[4];
+ unsigned char mask;
+ unsigned char *msg;
+ unsigned char flength;
+ unsigned char idx_first_mask;
+ unsigned char idx_first_data;
+ size_t data_length;
+ int i;
+ int j;
+
+ msg = NULL;
+ *type = frame[0] & 0x0F;
+ if (frame[0] == (WS_FIN | WS_OPCODE_TEXT_FRAME) ||
+ frame[0] == (WS_FIN | WS_OPCODE_PING_FRAME))
+ {
+ idx_first_mask = 2;
+ mask = frame[1];
+ flength = mask & 0x7F;
+ if (flength == 126)
+ {
+ idx_first_mask = 4;
+ }
+ else if (flength == 127)
+ {
+ idx_first_mask = 10;
+ }
+ idx_first_data = (unsigned char) (idx_first_mask + 4);
+ data_length = (size_t) *length - idx_first_data;
+ masks[0] = frame[idx_first_mask + 0];
+ masks[1] = frame[idx_first_mask + 1];
+ masks[2] = frame[idx_first_mask + 2];
+ masks[3] = frame[idx_first_mask + 3];
+ msg = malloc (data_length + 1);
+ if (NULL != msg)
+ {
+ for (i = idx_first_data, j = 0; i < *length; i++, j++)
+ {
+ msg[j] = frame[i] ^ masks[j % 4];
+ }
+ *length = (ssize_t) data_length;
+ msg[j] = '\0';
+ }
+ }
+ return msg;
+}
+
+static void
+epoll_selected( struct epoll_event * event ) {
+ fd_websocket_ctx_t * ws = event->data.ptr;
+ struct MHD_UpgradeResponseHandle *urh = ws->urh;
+ unsigned char buf[2048];
+
+ do {
+ ssize_t got = recv (ws->sock, (void *) buf, sizeof (buf), 0);
+ if (0 >= got) {
+ break;
+ }
+ int type = -1;
+ char * msg = (char *)ws_receive_frame (buf, &got, &type);
+ if (type == WS_OPCODE_TEXT_FRAME) {
+ if (NULL == msg) {
+ break;
+ }
+ if( !fd_webserver_ws_request( ws, msg, (ulong)got ) ) {
+ free( msg );
+ break;
+ }
+ free( msg );
+ /* Happy path */
+ return;
+ } else if (type == WS_OPCODE_CLOSE_FRAME) {
+ break;
+ } else if (type == WS_OPCODE_PING_FRAME) {
+ ws_send_frame(ws->sock, WS_OPCODE_PONG_FRAME, msg, (ulong)got);
+ free( msg );
+ return;
+ }
+ /* Unknown type */
+ } while (0);
+
+ fd_webserver_ws_closed(ws, ws->ws->cb_arg);
+ epoll_ctl(ws->ws->ws_epoll_fd, EPOLL_CTL_DEL, ws->sock, NULL);
+ MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE);
+ free (ws);
+}
+
+static void
+uh_cb (void *cls, struct MHD_Connection *con, void *req_cls,
+ const char *extra_in, size_t extra_in_size, MHD_socket sock,
+ struct MHD_UpgradeResponseHandle *urh)
+{
+ fd_webserver_t * ws = (fd_webserver_t *)cls;
+
+ (void) con; /* Unused. Silent compiler warning. */
+ (void) req_cls; /* Unused. Silent compiler warning. */
+ (void) extra_in; /* Unused. Silent compiler warning. */
+ (void) extra_in_size; /* Unused. Silent compiler warning. */
+
+ fd_websocket_ctx_t * wsd = malloc (sizeof (fd_websocket_ctx_t));
+ memset (wsd, 0, sizeof (fd_websocket_ctx_t));
+ wsd->sock = sock;
+ wsd->urh = urh;
+ wsd->ws = ws;
+
+ make_blocking (sock);
+
+ struct epoll_event event;
+ event.events = EPOLLIN;
+ event.data.ptr = wsd;
+ if (epoll_ctl(ws->ws_epoll_fd, EPOLL_CTL_ADD, sock, &event)) {
+ FD_LOG_ERR(("epoll_ctl failed: %s", strerror(errno) ));
+ }
+}
+
+static enum MHD_Result
+ahc_cb (void *cls, struct MHD_Connection *con, const char *url,
+ const char *method, const char *version, const char *upload_data,
+ size_t *upload_data_size, void **req_cls)
+{
+ struct MHD_Response *res;
+ const char *upg_header;
+ const char *con_header;
+ const char *ws_version_header;
+ const char *ws_key_header;
+ enum MHD_Result ret;
+ size_t key_size;
+
+ fd_webserver_t * ws = (fd_webserver_t *)cls;
+
+ (void) url; /* Unused. Silent compiler warning. */
+ (void) upload_data; /* Unused. Silent compiler warning. */
+ (void) upload_data_size; /* Unused. Silent compiler warning. */
+
+ if (NULL == *req_cls)
+ {
+ *req_cls = (void *) 1;
+ return MHD_YES;
+ }
+ *req_cls = NULL;
+ upg_header = MHD_lookup_connection_value (con, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_UPGRADE);
+ con_header = MHD_lookup_connection_value (con, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONNECTION);
+ if (MHD_NO == is_websocket_request (con, upg_header, con_header))
+ {
+ return send_bad_request (con);
+ }
+ if ((0 != strcmp (method, MHD_HTTP_METHOD_GET))
+ || (0 != strcmp (version, MHD_HTTP_VERSION_1_1)))
+ {
+ return send_bad_request (con);
+ }
+ ws_version_header =
+ MHD_lookup_connection_value (con, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
+ if ((NULL == ws_version_header)
+ || (0 != strcmp (ws_version_header, WS_SEC_WEBSOCKET_VERSION)))
+ {
+ return send_upgrade_required (con);
+ }
+ ret = MHD_lookup_connection_value_n (con, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY,
+ strlen (MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY),
+ &ws_key_header, &key_size);
+ if ((MHD_NO == ret) || (key_size != WS_KEY_LEN))
+ {
+ return send_bad_request (con);
+ }
+ char ws_ac_value[2*SHA1HashSize+1];
+ ret = ws_get_accept_value (ws_key_header, ws_ac_value);
+ if (MHD_NO == ret)
+ {
+ return ret;
+ }
+ res = MHD_create_response_for_upgrade (&uh_cb, ws);
+ if (MHD_YES !=
+ MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
+ ws_ac_value))
+ {
+ MHD_destroy_response (res);
+ return MHD_NO;
+ }
+ if (MHD_YES !=
+ MHD_add_response_header (res, MHD_HTTP_HEADER_UPGRADE, WS_UPGRADE_VALUE))
+ {
+ MHD_destroy_response (res);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (con, MHD_HTTP_SWITCHING_PROTOCOLS, res);
+ MHD_destroy_response (res);
+ return ret;
+}
diff --git a/src/app/rpcserver/fuzz_json_lex.c b/src/app/rpcserver/fuzz_json_lex.c
new file mode 100644
index 0000000000..8cc17b1ebd
--- /dev/null
+++ b/src/app/rpcserver/fuzz_json_lex.c
@@ -0,0 +1,52 @@
+#include
+#include
+#include
+
+#include "../../util/fd_util.h"
+#include "json_lex.h"
+
+struct json_lex_state *lex_state = NULL;
+
+void free_lex_state(void) { free(lex_state); }
+
+int LLVMFuzzerInitialize(int *argc, char ***argv) {
+ /* Set up shell without signal handlers */
+ putenv("FD_LOG_BACKTRACE=0");
+ fd_boot(argc, argv);
+ atexit(fd_halt);
+
+ lex_state = malloc(sizeof(struct json_lex_state));
+ atexit(free_lex_state);
+
+ /* Disable parsing error logging */
+ fd_log_level_stderr_set(4);
+ return 0;
+}
+
+int __attribute__((optnone))
+LLVMFuzzerTestOneInput(uchar const *data, ulong size) {
+ json_lex_state_new(lex_state, (const char *)data, size);
+
+ for (;;) {
+ long token_type = json_lex_next_token(lex_state);
+
+ if (token_type == JSON_TOKEN_END || token_type == JSON_TOKEN_ERROR) {
+ json_lex_state_delete(lex_state);
+ return 0;
+ }
+
+ ulong sz_out;
+ const char *out = json_lex_get_text(lex_state, &sz_out);
+
+ if (sz_out) {
+ // Access the first and last byte of the state
+ const char a __attribute__((unused)) = out[0];
+
+ // A asan hit here would mean that json_lex_get_text claims that we can
+ // read further than we can.
+ const char b __attribute__((unused)) = out[sz_out - 1];
+ }
+ }
+
+ return 0;
+}
diff --git a/src/app/rpcserver/genkeywords.cxx b/src/app/rpcserver/genkeywords.cxx
new file mode 100644
index 0000000000..e4050efefa
--- /dev/null
+++ b/src/app/rpcserver/genkeywords.cxx
@@ -0,0 +1,415 @@
+#include
+#include
+#include
+#include
+#include
+#include