Skip to content

Commit

Permalink
feat(builder): add polygon bloxroute sender
Browse files Browse the repository at this point in the history
  • Loading branch information
dancoombs committed Sep 8, 2023
1 parent 52c0d6c commit 3f2de13
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 7 deletions.
180 changes: 180 additions & 0 deletions src/builder/sender/bloxroute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use std::{sync::Arc, time::Duration};

use anyhow::Context;
use ethers::{
middleware::SignerMiddleware,
providers::{JsonRpcClient, Middleware, Provider},
types::{
transaction::eip2718::TypedTransaction, Address, Bytes, TransactionReceipt, TxHash, H256,
},
utils::hex,
};
use ethers_signers::Signer;
use jsonrpsee::{
core::{client::ClientT, traits::ToRpcParams},
http_client::{transport::HttpBackend, HttpClient, HttpClientBuilder},
};
use reqwest::header::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use tokio::time;
use tonic::async_trait;

use crate::{
builder::sender::{fill_and_sign, SentTxInfo, TransactionSender, TxStatus},
common::types::ExpectedStorage,
};

pub struct PolygonBloxrouteTransactionSender<C, S>
where
C: JsonRpcClient + 'static,
S: Signer + 'static,
{
provider: SignerMiddleware<Arc<Provider<C>>, S>,
raw_provider: Arc<Provider<C>>,
client: PolygonBloxrouteClient,
poll_interval: Duration,
}

#[async_trait]
impl<C, S> TransactionSender for PolygonBloxrouteTransactionSender<C, S>
where
C: JsonRpcClient + 'static,
S: Signer + 'static,
{
async fn send_transaction(
&self,
tx: TypedTransaction,
_expected_storage: &ExpectedStorage,
) -> anyhow::Result<SentTxInfo> {
let (raw_tx, nonce) = fill_and_sign(&self.provider, tx).await?;
let tx_hash = self
.client
.send_transaction(raw_tx)
.await
.context("should send bloxroute polygon private tx")?;
Ok(SentTxInfo { nonce, tx_hash })
}

async fn get_transaction_status(&self, tx_hash: H256) -> anyhow::Result<TxStatus> {
let tx = self
.provider
.get_transaction(tx_hash)
.await
.context("provider should return transaction status")?;
Ok(match tx {
// BDN transactions will not always show up in the node's transaction pool
// so we can't rely on this to determine if the transaction was dropped
// Thus, always return pending.
None => TxStatus::Pending,
Some(tx) => match tx.block_number {
None => TxStatus::Pending,
Some(block_number) => TxStatus::Mined {
block_number: block_number.as_u64(),
},
},
})
}

async fn wait_until_mined(&self, tx_hash: H256) -> anyhow::Result<Option<TransactionReceipt>> {
Self::wait_until_mined_no_drop(tx_hash, Arc::clone(&self.raw_provider), self.poll_interval)
.await
}

fn address(&self) -> Address {
self.provider.address()
}
}

impl<C, S> PolygonBloxrouteTransactionSender<C, S>
where
C: JsonRpcClient + 'static,
S: Signer + 'static,
{
pub fn new(
provider: Arc<Provider<C>>,
signer: S,
poll_interval: Duration,
auth_header: &str,
) -> anyhow::Result<Self> {
Ok(Self {
provider: SignerMiddleware::new(Arc::clone(&provider), signer),
raw_provider: provider,
client: PolygonBloxrouteClient::new(auth_header)?,
poll_interval,
})
}

async fn wait_until_mined_no_drop(
tx_hash: H256,
provider: Arc<Provider<C>>,
poll_interval: Duration,
) -> anyhow::Result<Option<TransactionReceipt>> {
loop {
let tx = provider
.get_transaction(tx_hash)
.await
.context("provider should return transaction status")?;
match tx {
None => {}
Some(tx) => match tx.block_number {
None => {}
Some(_) => {
let receipt = provider
.get_transaction_receipt(tx_hash)
.await
.context("provider should return transaction receipt")?;
return Ok(receipt);
}
},
}

time::sleep(poll_interval).await;
}
}
}

struct PolygonBloxrouteClient {
client: HttpClient<HttpBackend>,
}

impl PolygonBloxrouteClient {
pub fn new(auth_header: &str) -> anyhow::Result<Self> {
let mut headers = HeaderMap::new();
headers.insert("Authorization", HeaderValue::from_str(auth_header)?);
let client = HttpClientBuilder::default()
.set_headers(headers)
.build("https://api.blxrbdn.com:443")?;
Ok(Self { client })
}

async fn send_transaction(&self, raw_tx: Bytes) -> anyhow::Result<TxHash> {
let request = BloxrouteRequest {
transaction: hex::encode(raw_tx),
};
let response: BloxrouteResponse =
self.client.request("polygon_private_tx", request).await?;
Ok(response.tx_hash)
}
}

#[derive(Serialize)]

struct BloxrouteRequest {
transaction: String,
}

impl ToRpcParams for BloxrouteRequest {
fn to_rpc_params(self) -> Result<Option<Box<RawValue>>, jsonrpsee::core::Error> {
let s = String::from_utf8(serde_json::to_vec(&self)?).expect("Valid UTF8 format");
RawValue::from_string(s)
.map(Some)
.map_err(jsonrpsee::core::Error::ParseError)
}
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct BloxrouteResponse {
tx_hash: TxHash,
}
24 changes: 19 additions & 5 deletions src/builder/sender/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
mod bloxroute;
mod conditional;
mod flashbots;
mod raw;

use std::sync::Arc;
use std::{sync::Arc, time::Duration};

use anyhow::Context;
pub use conditional::ConditionalTransactionSender;
Expand All @@ -11,7 +12,8 @@ use ethers::{
prelude::SignerMiddleware,
providers::{JsonRpcClient, Middleware, Provider},
types::{
transaction::eip2718::TypedTransaction, Address, Bytes, TransactionReceipt, H256, U256,
transaction::eip2718::TypedTransaction, Address, Bytes, Chain, TransactionReceipt, H256,
U256,
},
};
use ethers_signers::Signer;
Expand All @@ -21,6 +23,7 @@ use mockall::automock;
pub use raw::RawTransactionSender;
use tonic::async_trait;

use self::bloxroute::PolygonBloxrouteTransactionSender;
use crate::common::types::ExpectedStorage;

#[derive(Debug)]
Expand Down Expand Up @@ -62,6 +65,7 @@ where
Raw(RawTransactionSender<C, S>),
Conditional(ConditionalTransactionSender<C, S>),
Flashbots(FlashbotsTransactionSender<C, S>),
PolygonBloxroute(PolygonBloxrouteTransactionSender<C, S>),
}

async fn fill_and_sign<C, S>(
Expand Down Expand Up @@ -92,16 +96,26 @@ pub fn get_sender<C, S>(
signer: S,
is_conditional: bool,
url: &str,
) -> TransactionSenderEnum<C, S>
chain_id: u64,
poll_interval: Duration,
bloxroute_auth_header: &Option<String>,
) -> anyhow::Result<TransactionSenderEnum<C, S>>
where
C: JsonRpcClient + 'static,
S: Signer + 'static,
{
if is_conditional {
let sender = if is_conditional {
ConditionalTransactionSender::new(provider, signer).into()
} else if url.contains("flashbots") {
FlashbotsTransactionSender::new(provider, signer).into()
} else if let Some(auth_header) = bloxroute_auth_header {
if chain_id != Chain::Polygon as u64 {
panic!("Bloxroute sender is only supported on Polygon mainnet");
}
PolygonBloxrouteTransactionSender::new(provider, signer, poll_interval, auth_header)?.into()
} else {
RawTransactionSender::new(provider, signer).into()
}
};

Ok(sender)
}
6 changes: 5 additions & 1 deletion src/builder/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub struct Args {
pub max_blocks_to_wait_for_mine: u64,
pub replacement_fee_percent_increase: u64,
pub max_fee_increases: u64,
pub bloxroute_auth_header: Option<String>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -146,7 +147,10 @@ impl Task for BuilderTask {
signer,
self.args.use_conditional_send_transaction,
&self.args.submit_url,
);
self.args.chain_id,
self.args.eth_poll_interval,
&self.args.bloxroute_auth_header,
)?;
let tracker_settings = transaction_tracker::Settings {
poll_interval: self.args.eth_poll_interval,
max_blocks_to_wait_for_mine: self.args.max_blocks_to_wait_for_mine,
Expand Down
13 changes: 12 additions & 1 deletion src/cli/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ pub struct BuilderArgs {
)]
replacement_fee_percent_increase: u64,

///
/// Maximum number of times to increase gas fees when retrying a transaction
/// before giving up.
#[arg(
long = "builder.max_fee_increases",
name = "builder.max_fee_increases",
Expand All @@ -144,6 +145,15 @@ pub struct BuilderArgs {
default_value = "7"
)]
max_fee_increases: u64,

/// If using Polygon Mainnet, the auth header to use
/// for Bloxroute polygon_private_tx sender
#[arg(
long = "builder.bloxroute_auth_header",
name = "builder.bloxroute_auth_header",
env = "BUILDER_BLOXROUTE_AUTH_HEADER"
)]
bloxroute_auth_header: Option<String>,
}

impl BuilderArgs {
Expand Down Expand Up @@ -205,6 +215,7 @@ impl BuilderArgs {
max_blocks_to_wait_for_mine: self.max_blocks_to_wait_for_mine,
replacement_fee_percent_increase: self.replacement_fee_percent_increase,
max_fee_increases: self.max_fee_increases,
bloxroute_auth_header: self.bloxroute_auth_header.clone(),
})
}

Expand Down

0 comments on commit 3f2de13

Please sign in to comment.