Skip to content

Commit

Permalink
add TTL to request parameter
Browse files Browse the repository at this point in the history
Update readme
  • Loading branch information
jordy25519 committed Nov 11, 2024
1 parent 322d467 commit 9e5317e
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 18 deletions.
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Self hosted API gateway to easily interact with Drift V2 Protocol
- [Delegated Signing Mode](#delegated-signing-mode)
- [Sub-account Switching](#sub-account-switching)
- [Emulation Mode](#emulation-mode)
- [Transaction Confirmation](#transaction-confirmtaion-and-ttl)
- [CU price/limits](#cu-price--limits)
3. [API Examples](#api-examples)
- [HTTP API](#http-api)
Expand Down Expand Up @@ -49,7 +50,9 @@ docker run \
-e DRIFT_GATEWAY_KEY=<BASE58_SEED> \
-p 8080:8080 \
--platform linux/x86_64 \
ghcr.io/drift-labs/gateway https://rpc-provider.example.com --host 0.0.0.0 --markets wbtc,drift
ghcr.io/drift-labs/gateway https://rpc-provider.example.com --host 0.0.0.0 \
--markets wbtc,drift
--extra-rpcs https://api.mainnet-beta.solana.com
```

Build the Docker image:
Expand Down Expand Up @@ -145,6 +148,7 @@ Options:
default sub_account_id to use (default: 0)
--skip-tx-preflight
skip tx preflight checks
--extra-rpc extra solana RPC urls for improved Tx broadcast
--verbose enable debug logging
--help display usage information
```
Expand All @@ -168,7 +172,7 @@ e.g `http://<gateway>/v1/orders?subAccountId=3` will return orders for the walle

## Emulation Mode

Passing the `--emulate <EMULATED_PUBBKEY>` flag will instruct the gateway to run in read-only mode.
Passing the `--emulate <EMULATED_PUBKEY>` flag will instruct the gateway to run in read-only mode.

The gateway will receive all events, positions, etc. as normal but be unable to send transactions.

Expand All @@ -194,6 +198,33 @@ $ curl 'localhost:8080/v2/orders?computeUnitLimit=300000&computeUnitPrice=1000'
-d # { order data ...}
```

## Transaction Confirmation and TTLs

Gateway endpoints that place network transactions will return the signature as a base64 string.
User's can poll `transactionEvent` to confirm success by signature or watch Ws events for e.g. confirmation by order Ids instead.

Gateway will resubmit txs until they are either confirmed by the network or timeout.
This allows gateway txs to have a higher chance of confirmation during busy network periods.
setting `?ttl=<TIMEOUT_IN_SECS>` on a request determines how long gateway will resubmit txs for, (default: 4s/~10 slots).
e.g. `ttl?=2` means that the tx will be rebroadcast over the next 5 slots (5 * 400ms).

⚠️ users should take care to set either `max_order` ts or use atomic place/cancel/modify requests to prevent
double orders or orders being accepted later than intended.

improving tx confirmation rates will require trial and error, try adjusting tx TTL and following parameters until
results are meet requirements:
- set `--extra-rpcs=<RPC_1>,<RPC_2>` to broadcast tx to multiple nodes
- set `--skip-tx-preflight` to disable preflight RPC checks
- set statically higher CU prices per request (see previous section) when no ack rates increase

**example request**

```bash
$ curl 'localhost:8080/v2/orders?ttl=2' -X POST \
-H 'content-type: application/json' \
-d # { order data ...}
```

## API Examples

Please refer to https://drift-labs.github.io/v2-teacher/ for further examples and reference documentation on various types, fields, and operations available on drift.
Expand Down Expand Up @@ -819,7 +850,7 @@ Use the UI or Ts/Python sdk to initialize the sub-account first.

#### `429`s / gateway hitting RPC rate limits

this can occur during gateway startup as drift market data is pulled from the network and subscriptions are intialized.
this can occur during gateway startup as drift market data is pulled from the network and subscriptions are initialized.
try setting `INIT_RPC_THROTTLE=2` for e.g. 2s or longer, this allows some time between request bursts on start up.

The free \_api.mainnet-beta.solana.com_ RPC support is limited due to rate-limits
Expand Down
22 changes: 13 additions & 9 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ use crate::{
Context, LOG_TARGET,
};

/// Default TTL in seconds of gateway tx retry
/// afterwhich gateway will no longer resubmit or monitor the tx
// ~10 slots
const DEFAULT_TX_TTL: u16 = 4;

pub type GatewayResult<T> = Result<T, ControllerError>;

#[derive(Error, Debug)]
Expand Down Expand Up @@ -247,7 +252,7 @@ impl AppState {
)
.with_priority_fee(priority_fee, ctx.cu_limit);
let tx = build_cancel_ix(builder, req)?.build();
self.send_tx(tx, "cancel_orders", None).await
self.send_tx(tx, "cancel_orders", ctx.ttl).await
}

/// Return position for market if given, otherwise return all positions
Expand Down Expand Up @@ -476,7 +481,7 @@ impl AppState {
.place_orders(orders)
.build();

self.send_tx(tx, "cancel_and_place", None).await
self.send_tx(tx, "cancel_and_place", ctx.ttl).await
}

pub async fn place_orders(
Expand Down Expand Up @@ -508,7 +513,7 @@ impl AppState {
.place_orders(orders)
.build();

self.send_tx(tx, "place_orders", None).await
self.send_tx(tx, "place_orders", ctx.ttl).await
}

pub async fn modify_orders(
Expand All @@ -527,7 +532,7 @@ impl AppState {
)
.with_priority_fee(ctx.cu_price.unwrap_or(pf), ctx.cu_limit);
let tx = build_modify_ix(builder, req, self.client.program_data())?.build();
self.send_tx(tx, "modify_orders", None).await
self.send_tx(tx, "modify_orders", ctx.ttl).await
}

pub async fn get_tx_events_for_subaccount_id(
Expand Down Expand Up @@ -604,7 +609,7 @@ impl AppState {
&self,
tx: VersionedMessage,
reason: &'static str,
ttl: Option<u64>,
ttl: Option<u16>,
) -> GatewayResult<TxResponse> {
let recent_block_hash = self.client.get_latest_blockhash().await?;
let tx = self.wallet.sign_tx(tx, recent_block_hash)?;
Expand All @@ -621,9 +626,8 @@ impl AppState {
.inner()
.send_transaction_with_config(&tx, tx_config)
.await
.map(|s| {
.inspect(|s| {
debug!(target: LOG_TARGET, "sent tx ({reason}): {s}");
s
})
.map_err(|err| {
warn!(target: LOG_TARGET, "sending tx ({reason}) failed: {err:?}");
Expand All @@ -637,11 +641,11 @@ impl AppState {
// - retried upto some given deadline
// client should poll for the tx to confirm success
let primary_rpc = Arc::clone(&self.client);
let tx_signature = sig.clone();
let tx_signature = sig;
let extra_rpcs = self.extra_rpcs.clone();
tokio::spawn(async move {
let start = SystemTime::now();
let ttl = Duration::from_secs(ttl.unwrap_or(16));
let ttl = Duration::from_secs(ttl.unwrap_or(DEFAULT_TX_TTL) as u64);
let mut confirmed = false;
while SystemTime::now()
.duration_since(start)
Expand Down
15 changes: 9 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ struct Context {
pub cu_limit: Option<u32>,
#[serde(default, rename = "computeUnitPrice")]
pub cu_price: Option<u64>,
/// Tx retry TTL
#[serde(default, rename = "ttl")]
pub ttl: Option<u16>,
}

#[get("/markets")]
Expand Down Expand Up @@ -240,7 +243,7 @@ async fn main() -> std::io::Result<()> {
Some((state_commitment, tx_commitment)),
Some(config.default_sub_account_id),
config.skip_tx_preflight,
config.extra_rpc.split(",").into_iter().collect(),
config.extra_rpc.split(",").collect(),
)
.await;

Expand Down Expand Up @@ -410,12 +413,12 @@ struct GatewayConfig {
/// skip tx preflight checks
#[argh(switch)]
skip_tx_preflight: bool,
/// extra solana RPC urls for improved Tx broadcast
#[argh(option)]
extra_rpc: String,
/// enable debug logging
#[argh(switch)]
verbose: bool,
/// extra solana RPC urls
#[argh(option)]
extra_rpc: String,
}

/// Parse raw markets list from user command
Expand Down Expand Up @@ -455,7 +458,7 @@ mod tests {
};
let rpc_endpoint = std::env::var("TEST_RPC_ENDPOINT")
.unwrap_or_else(|_| "https://api.devnet.solana.com".to_string());
AppState::new(&rpc_endpoint, true, wallet, None, None, false).await
AppState::new(&rpc_endpoint, true, wallet, None, None, false, vec![]).await
}

// likely safe to ignore during development, mainy regression tests for CI
Expand All @@ -476,7 +479,7 @@ mod tests {

let rpc_endpoint = std::env::var("TEST_RPC_ENDPOINT")
.unwrap_or_else(|_| "https://api.devnet.solana.com".to_string());
let state = AppState::new(&rpc_endpoint, true, wallet, None, None, false).await;
let state = AppState::new(&rpc_endpoint, true, wallet, None, None, false, vec![]).await;

let app = test::init_service(
App::new()
Expand Down

0 comments on commit 9e5317e

Please sign in to comment.