QUIC Transaction Submission
Example
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Context, Result};
use quinn::{ClientConfig, Connection, Endpoint, TransportConfig};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
const ALPN_NB_TX: &[u8] = b"nb-tx/1";
const AUTH_OK: u8 = 0x00;
const MAX_TX_SIZE: usize = 1232;
pub struct NextblockQuicClient {
// Keep the endpoint alive for as long as the connection is in use.
endpoint: Endpoint,
connection: Connection,
}
impl NextblockQuicClient {
pub async fn connect(server_addr: &str, api_key: &str) -> Result<Self> {
let endpoint = create_endpoint()?;
let addr: SocketAddr = tokio::net::lookup_host(server_addr)
.await
.context("dns lookup failed")?
.next()
.ok_or_else(|| anyhow!("no addresses found for {server_addr}"))?;
let server_name = server_addr
.split(':')
.next()
.ok_or_else(|| anyhow!("invalid server address"))?;
let connection = endpoint
.connect(addr, server_name)?
.await
.context("quic handshake failed")?;
let (mut send, mut recv) = connection
.open_bi()
.await
.context("failed to open auth stream")?;
send.write_all(api_key.as_bytes())
.await
.context("failed to send api key")?;
send.finish().context("failed to close auth stream")?;
let mut response = [0u8; 1];
recv.read_exact(&mut response)
.await
.context("failed to read auth response")?;
if response[0] != AUTH_OK {
return Err(anyhow!("authentication rejected"));
}
Ok(Self { endpoint, connection })
}
pub async fn send_transaction(&self, raw_tx: &[u8]) -> Result<()> {
if raw_tx.len() > MAX_TX_SIZE {
return Err(anyhow!("transaction too large: {}", raw_tx.len()));
}
let mut send = self
.connection
.open_uni()
.await
.context("failed to open tx stream")?;
send.write_all(raw_tx)
.await
.context("failed to write transaction")?;
send.finish().context("failed to finish tx stream")?;
Ok(())
}
}
impl Drop for NextblockQuicClient {
fn drop(&mut self) {
self.connection.close(quinn::VarInt::from_u32(0), b"client closing");
}
}
fn create_endpoint() -> Result<Endpoint> {
let mut roots = rustls::RootCertStore::empty();
let native_certs = rustls_native_certs::load_native_certs();
for cert in native_certs.certs {
roots.add(cert).ok();
}
let mut crypto = rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_no_client_auth();
crypto.alpn_protocols = vec![ALPN_NB_TX.to_vec()];
let mut transport = TransportConfig::default();
transport.max_idle_timeout(Some(quinn::IdleTimeout::try_from(Duration::from_secs(60))?));
transport.keep_alive_interval(Some(Duration::from_secs(15)));
let mut client_config = ClientConfig::new(Arc::new(
quinn::crypto::rustls::QuicClientConfig::try_from(crypto)?,
));
client_config.transport_config(Arc::new(transport));
let mut endpoint = Endpoint::client("0.0.0.0:0".parse()?)?;
endpoint.set_default_client_config(client_config);
Ok(endpoint)
}
#[tokio::main]
async fn main() -> Result<()> {
let api_key = std::env::var("NEXTBLOCK_API_KEY")
.context("set NEXTBLOCK_API_KEY before running")?;
let client = NextblockQuicClient::connect("london.nextblock.io:11100", &api_key).await?;
// Replace this with the signed bytes from your existing Solana flow.
// For example: let raw_tx = bincode::serialize(&signed_transaction)?;
let raw_tx: Vec<u8> = vec![0; 32];
client.send_transaction(&raw_tx).await?;
println!("transaction queued");
Ok(())
}What To Replace
Notes
Last updated