Skip to content

Commit

Permalink
Json rpc Proxy-server
Browse files Browse the repository at this point in the history
Initial commit to introduce the json rpc proxy-server
  • Loading branch information
ebin-mathews committed Dec 29, 2023
1 parent 06c46d4 commit 3f04d5a
Show file tree
Hide file tree
Showing 18 changed files with 3,567 additions and 0 deletions.
3,064 changes: 3,064 additions & 0 deletions proxy_server/Cargo.lock

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions proxy_server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "jito-block-engine-proxy-server"
version = "0.1.0"
authors = ["Jito Team <[email protected]>"]
edition = "2021"
description = "A sample proxy server to sign and forward json rpc requests"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = "0.5.17"
clap = { version = "4", features = ["cargo", "derive", "env"] }
futures-util = "0.3.29"
http = "0.2.11"
hyper = { version = "0.14", features = ["full"] }
reqwest = { version = "0.11", features = ["json", "stream"] }
serde = { version = "1.0.189", features = ["derive"] }
serde_json = "1.0.107"
solana-sdk = "=1.16"
tokio = { version = "1.14.1", features = ["full"] }

[workspace]

39 changes: 39 additions & 0 deletions proxy_server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# syntax=docker/dockerfile:1.4.0
FROM rust:1.73-slim-bullseye as builder

RUN set -x \
&& apt-get -qq update \
&& apt-get -qq -y install \
clang \
cmake \
libudev-dev \
unzip \
libssl-dev \
pkg-config \
zlib1g-dev \
curl

RUN rustup component add rustfmt && update-ca-certificates

ENV HOME=/home/root
WORKDIR $HOME/app
COPY . .

# cache these directories for reuse
# see: https://docs.docker.com/build/cache/#use-the-dedicated-run-cache
RUN --mount=type=cache,mode=0777,target=/home/root/app/target \
--mount=type=cache,mode=0777,target=/usr/local/cargo/registry \
--mount=type=cache,mode=0777,target=/usr/local/cargo/git \
RUSTFLAGS="-C target-cpu=native" cargo build --release && cp target/release/jito-* ./;

FROM debian:bullseye-slim as base_image
# read in build arg from ./b, default to false
ARG debug=false

RUN apt-get -qq update && apt-get -qq -y install ca-certificates libssl1.1 && rm -rf /var/lib/apt/lists/*;

FROM base_image as proxy_server
ENV APP="jito-block-engine-proxy-server"
WORKDIR /app
COPY --from=builder /home/root/app/${APP} ./
ENTRYPOINT ["/app/jito-block-engine-proxy-server"]
49 changes: 49 additions & 0 deletions proxy_server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Jito proxy server for json rpc

The sample proxy server can be used to sign and send the payload to the Jito json rpc endpoint.

Expected environment variables are set in the config/.env file

The binary can be run by itself by providing the parameters
--proxy-json-rpc-port <PROXY_JSON_RPC_PORT>
--json-rpc-url <JSON_RPC_URL>
--key-pair-path <KEY_PAIR_PATH>

## Running as a container

The server can be run as a container using the provided script, run_proxy_server.sh.

```shell
# starts the proxy server.
# The script reads env values from config/.env. Modify as necessary
./run_proxy_server
```

## Stopping the container

The container can be stopped and resources removed using, stop_proxy_server.sh.

```shell
# starts the proxy server.
# The script reads env values from config/.env. Modify as necessary
./stop_proxy_server
```

## Running the binary

The binary can be run by itself by providing the parameters
--proxy-json-rpc-port <PROXY_JSON_RPC_PORT>
--json-rpc-url <JSON_RPC_URL>
--key-pair-path <KEY_PAIR_PATH>

```shell
# starts the proxy server.
# You can also source the env variables and pass them as parameters as well
cargo run --proxy-json-rpc-port 8080 --json-rpc-url url --key-pair-path
```

## Use curl or similar to send request

```shell
curl 'http://127.0.0.1:8080/api/v1/bundles' -POST -d '{"jsonrpc": "2.0", "method": "getTipAccounts", "params": [], "id": 1}' -H 'Content-Type: application/json
```
15 changes: 15 additions & 0 deletions proxy_server/config/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

# Port at which the proxy server should be listening on
export PROXY_JSON_RPC_PORT=8080

# JSON RPC url for the Jito bundle service
export JSON_RPC_URL="http://searcher_service:1008/api/v1/bundles"

# The keypair you want to use to sign the request. Please note, the pubkey should
# be shared in advance with the Jito team. If you are a searcher and already have
# added your pubkey with Jito, no need to share it again.
export KEY_PAIR_PATH="./config/tppvXqEfck4Mchr3aZiceiGiRjMYzBSbYYZTtSRb8en.json"

# This is any external network you want your container to connect to
export DOCKER_NETWORK="block-engine_default"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[60,14,105,58,167,30,16,112,37,125,39,0,112,16,13,160,159,149,41,192,120,133,121,74,51,221,90,60,253,193,113,249,13,70,178,214,120,17,107,70,76,187,71,17,102,61,110,231,58,78,191,13,90,173,27,193,229,109,159,122,79,82,165,187]
31 changes: 31 additions & 0 deletions proxy_server/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# See ./b for running instructions
version: "3.8"

services:
# Proxy server
proxy-server:
image: $ORG/block-engine-proxy-server:$TAG
container_name: proxy_server
build:
context: .
dockerfile: Dockerfile
target: proxy_server
environment:
- RUST_BACKTRACE=1
- PROXY_JSON_RPC_PORT=$PROXY_JSON_RPC_PORT
- JSON_RPC_URL=$JSON_RPC_URL
- KEY_PAIR_PATH=/etc/$KEY_PAIR_PATH
expose:
- "$PROXY_JSON_RPC_PORT"
ports:
- "$PROXY_JSON_RPC_PORT:$PROXY_JSON_RPC_PORT"
restart: on-failure
volumes:
- "$KEY_PAIR_PATH:/etc/$KEY_PAIR_PATH"
networks:
proxy_server_network: {}

networks:
proxy_server_network:
external: true
name: $DOCKER_NETWORK
52 changes: 52 additions & 0 deletions proxy_server/run_proxy_server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euxo pipefail

ENV_FILE=./config/.env
if [[ -f $ENV_FILE ]]; then
# A little hacky, but .env files can't execute so this is the best we have for now
# shellcheck disable=SC2046
export $(grep -v '#' "$ENV_FILE" | awk '/=/ {print $1}')
else
echo ".env file not found at ${ENV_FILE}"
exit 1
fi

# Some container vars
TAG=$(git describe --match=NeVeRmAtCh --always --abbrev=0 --dirty)
ORG="jitolabs"

export DOCKER_LOCALHOST=172.17.0.1
if [[ $(uname) == "Darwin" ]]; then
DOCKER_LOCALHOST=docker.for.mac.localhost
fi

# Build and run
DOCKER_BUILDKIT=1 \
BUILDKIT_PROGRESS=plain \
TAG=$TAG \
ORG=$ORG \
docker compose --env-file="${ENV_FILE}" up --build --remove-orphans --renew-anon-volumes -d

# Set a timeout (in seconds)
timeout=300
start_time=$(date +%s)

# Wait for the container to be up with timeout
while [ "$(TAG=$TAG ORG=$ORG docker compose --env-file="${ENV_FILE}" ps -q | xargs docker inspect --format '{{.State.Status}}' | grep -c "running")" -eq 0 ]; do
# Check if the timeout has been reached
current_time=$(date +%s)
elapsed_time=$((current_time - start_time))
if [ $elapsed_time -ge $timeout ]; then
echo "Timeout reached. Container did not start within $timeout seconds."
exit 1
fi

# Sleep for a while before checking again
sleep 5
done

# Tag the image as latest
IMAGE_NAME="$(TAG=$TAG ORG=$ORG docker compose --env-file="${ENV_FILE}" ps -q | xargs docker inspect --format '{{.Config.Image}}')"
docker image tag "$IMAGE_NAME" "${IMAGE_NAME%%:*}":latest

echo -e "\e[1;32mProxy server is up and running.\e[0m"
5 changes: 5 additions & 0 deletions proxy_server/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[toolchain]
# Needs to be compiled with the same rust version that the RPC loading this plugin was compiled with.
# https://github.com/solana-labs/solana/blob/v1.16.14/rust-toolchain.toml
channel = "1.73.0"
components = [ "rustfmt", "rustc-dev", "clippy", "cargo" ]
5 changes: 5 additions & 0 deletions proxy_server/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
edition = "2021" # required by rust-analyzer
imports_granularity="Crate"
format_code_in_doc_comments = true
error_on_unformatted = true
group_imports = "StdExternalCrate"
7 changes: 7 additions & 0 deletions proxy_server/src/json_rpc/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub struct JsonRpcConsts;

impl JsonRpcConsts {
// Some const definitions for json rpc http headers
pub const AUTHORIZATION_HEADER: &'static str = "Authorization";
pub const SENDER_PUBKEY_HEADER: &'static str = "X-Sender-Pubkey";
}
4 changes: 4 additions & 0 deletions proxy_server/src/json_rpc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod consts;
pub mod request_parser;
pub mod server;
pub mod signer;
28 changes: 28 additions & 0 deletions proxy_server/src/json_rpc/request_parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::fmt::Display;

use axum::{
body::{Bytes, HttpBody},
http::StatusCode,
};

#[derive(Clone)]
pub struct JsonrpcRequestParser;

impl JsonrpcRequestParser {
pub async fn get_req_body<B>(body: B) -> Result<Bytes, (StatusCode, String)>
where
B: HttpBody<Data = Bytes>,
B::Error: Display,
{
let bytes = match hyper::body::to_bytes(body).await {
Ok(bytes) => bytes,
Err(err) => {
return Err((
StatusCode::BAD_REQUEST,
format!("failed to read request body: {}", err),
));
}
};
Ok(bytes)
}
}
100 changes: 100 additions & 0 deletions proxy_server/src/json_rpc/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};

use axum::{body::StreamBody, http::HeaderMap, response::IntoResponse, routing::post, Router};
use tokio::task::JoinHandle;

use crate::json_rpc::signer::{SignerContext, SignerMiddleware};

pub async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};

#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};

#[cfg(not(unix))]
let terminate = std::future::pending::<()>();

tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}

println!("signal received, starting graceful shutdown");
}

#[derive(Debug, Clone)]
pub struct ProxyServerImpl {
block_engine_url: String,
client: reqwest::Client,
}

impl ProxyServerImpl {
fn new(block_engine_url: String) -> Self {
Self {
block_engine_url,
client: reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap(),
}
}

/// Method to handle the incoming json rpc requests
async fn handle_jsonrpc(
&self,
headers: HeaderMap,
payload: String,
) -> Result<impl IntoResponse, (http::StatusCode, String)> {
println!("Sending request {:?} to {}", payload, self.block_engine_url);
let response = self
.client
.post(self.block_engine_url.clone())
.headers(headers)
.body(payload)
.send()
.await
.map_err(|err| (http::StatusCode::BAD_GATEWAY, err.to_string()))?;
if response.status().is_success() {
Ok(StreamBody::new(response.bytes_stream()))
} else {
Err((response.status(), response.status().to_string()))
}
}

/// Creates and runs a json rpc server. The server binds to the given port of localhost
pub async fn run(block_engine_url: String, keypair_path: String, port: u16) -> JoinHandle<()> {
println!(
"Starting proxy server at port: {}, with json_rpc_url: {} and keypair path: {}",
port, block_engine_url, keypair_path
);
let handler = ProxyServerImpl::new(block_engine_url);
let auth_context = SignerContext::new(keypair_path);

let app = Router::new()
.route(
"/api/v1/bundles",
post(|headers: HeaderMap, payload: String| async move {
handler.handle_jsonrpc(headers, payload).await
}),
)
.layer(axum::middleware::from_fn(SignerMiddleware::sign))
.layer(axum::Extension(auth_context));

tokio::spawn(async move {
axum::Server::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port))
.serve(app.into_make_service())
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap()
})
}
}
Loading

0 comments on commit 3f04d5a

Please sign in to comment.