Skip to content

Commit

Permalink
Merge pull request #339 from chainbound/jonas/feat/gross-revenue-metric
Browse files Browse the repository at this point in the history
feat(sidecar): gross tip revenue metric
  • Loading branch information
thedevbirb authored Oct 29, 2024
2 parents bac4a76 + dde39ae commit 7f76718
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 4 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/bolt_sidecar_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Bolt Sidecar CI

on:
push:
branches:
- unstable
- main
paths:
- "bolt-sidecar/**"
pull_request:
Expand All @@ -18,7 +21,7 @@ concurrency:
jobs:
cargo-tests:
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 20
env:
RUST_BACKTRACE: 1

Expand Down
11 changes: 10 additions & 1 deletion bolt-sidecar/src/builder/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use ethereum_consensus::{
crypto::{KzgCommitment, KzgProof},
deneb::mainnet::{Blob, BlobsBundle},
};
use reth_primitives::TransactionSigned;
use reth_primitives::{TransactionSigned, TxHash};
use tracing::warn;

use crate::{
Expand Down Expand Up @@ -57,6 +57,15 @@ impl BlockTemplate {
.collect()
}

/// Get all the transaction hashes in the signed constraints list.
#[inline]
pub fn transaction_hashes(&self) -> Vec<TxHash> {
self.signed_constraints_list
.iter()
.flat_map(|sc| sc.message.transactions.iter().map(|c| *c.hash()))
.collect()
}

/// Converts the list of signed constraints into a list of all blobs in all transactions
/// in the constraints. Use this when building a local execution payload.
#[inline]
Expand Down
58 changes: 57 additions & 1 deletion bolt-sidecar/src/client/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ use alloy::{
primitives::{Address, Bytes, B256, U256, U64},
rpc::{
client::{self as alloyClient, ClientBuilder},
types::{Block, FeeHistory},
types::{Block, FeeHistory, TransactionReceipt},
},
transports::{http::Http, TransportErrorKind, TransportResult},
};

use futures::{stream::FuturesUnordered, StreamExt};
use reqwest::{Client, Url};
use reth_primitives::TxHash;

use crate::primitives::AccountState;

Expand Down Expand Up @@ -115,6 +117,33 @@ impl RpcClient {
pub async fn send_raw_transaction(&self, raw: Bytes) -> TransportResult<B256> {
self.0.request("eth_sendRawTransaction", [raw]).await
}

/// Get the receipts for a list of transaction hashes.
pub async fn get_receipts(
&self,
hashes: &[TxHash],
) -> TransportResult<Vec<Option<TransactionReceipt>>> {
let mut batch = self.0.new_batch();

let futs = FuturesUnordered::new();

for hash in hashes {
futs.push(
batch
.add_call("eth_getTransactionReceipt", &(&[hash]))
.expect("Correct parameters"),
);
}

batch.send().await?;

Ok(futs
.collect::<Vec<TransportResult<TransactionReceipt>>>()
.await
.into_iter()
.map(|r| r.ok())
.collect())
}
}

impl Deref for RpcClient {
Expand Down Expand Up @@ -161,6 +190,33 @@ mod tests {
assert_eq!(account_state.transaction_count, 0);
}

#[tokio::test]
#[ignore]
async fn test_get_receipts() {
let _ = tracing_subscriber::fmt().try_init();
let client = RpcClient::new(Url::from_str("http://localhost:8545").unwrap());

let _receipts = client
.get_receipts(&[
TxHash::from_str(
"0x518d9497868b7380ddfa3d245bead7b418248a0776896f6152590da1bf92c3fe",
)
.unwrap(),
TxHash::from_str(
"0x6825cfb19d21cc4e69070f4aa506e3de65e09249d38d79b4112f81688bf43379",
)
.unwrap(),
TxHash::from_str(
"0x5825cfb19d21cc4e69070f4aa506e3de65e09249d38d79b4112f81688bf43379",
)
.unwrap(),
])
.await
.unwrap();

println!("{_receipts:?}");
}

#[tokio::test]
#[ignore]
async fn test_smart_contract_code() -> eyre::Result<()> {
Expand Down
17 changes: 16 additions & 1 deletion bolt-sidecar/src/state/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
common::{calculate_max_basefee, max_transaction_cost, validate_transaction},
config::limits::LimitsOpts,
primitives::{AccountState, CommitmentRequest, SignedConstraints, Slot},
telemetry::ApiMetrics,
};

use super::fetcher::StateFetcher;
Expand Down Expand Up @@ -460,7 +461,21 @@ impl<C: StateFetcher> ExecutionState<C> {
self.apply_state_update(update);

// Remove any block templates that are no longer valid
self.remove_block_template(slot);
if let Some(template) = self.remove_block_template(slot) {
debug!(%slot, "Removed block template for slot");
let hashes = template.transaction_hashes();
let receipts = self.client.get_receipts(&hashes).await?;

for receipt in receipts.into_iter().flatten() {
// Calculate the total tip revenue for this transaction: (effective_gas_price - basefee) * gas_used
let tip_per_gas = receipt.effective_gas_price - self.basefee;
let total_tip = tip_per_gas * receipt.gas_used;

trace!(hash = %receipt.transaction_hash, total_tip, "Receipt found");

ApiMetrics::increment_gross_tip_revenue(total_tip);
}
}

Ok(())
}
Expand Down
15 changes: 15 additions & 0 deletions bolt-sidecar/src/state/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ use std::{collections::HashMap, time::Duration};
use alloy::{
eips::BlockNumberOrTag,
primitives::{Address, Bytes, U256, U64},
rpc::types::TransactionReceipt,
transports::TransportError,
};
use futures::{stream::FuturesOrdered, StreamExt};
use reqwest::Url;
use reth_primitives::TxHash;
use tracing::error;

use crate::{client::rpc::RpcClient, primitives::AccountState};
Expand Down Expand Up @@ -45,6 +47,12 @@ pub trait StateFetcher {
) -> Result<AccountState, TransportError>;

async fn get_chain_id(&self) -> Result<u64, TransportError>;

/// Gets the receipts for the said list of transaction hashes. IMPORTANT: order is not maintained!
async fn get_receipts(
&self,
hashes: &[TxHash],
) -> Result<Vec<Option<TransactionReceipt>>, TransportError>;
}

/// A basic state fetcher that uses an RPC client to fetch state updates.
Expand Down Expand Up @@ -208,6 +216,13 @@ impl StateFetcher for StateClient {
async fn get_chain_id(&self) -> Result<u64, TransportError> {
self.client.get_chain_id().await
}

async fn get_receipts(
&self,
hashes: &[TxHash],
) -> Result<Vec<Option<TransactionReceipt>>, TransportError> {
self.client.get_receipts(hashes).await
}
}

#[cfg(test)]
Expand Down
24 changes: 24 additions & 0 deletions bolt-sidecar/src/telemetry/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const INCLUSION_COMMITMENTS_ACCEPTED: &str = "bolt_sidecar_inclusion_commitments
const TRANSACTIONS_PRECONFIRMED: &str = "bolt_sidecar_transactions_preconfirmed";
/// Counter for the number of validation errors; to spot most the most common ones
const VALIDATION_ERRORS: &str = "bolt_sidecar_validation_errors";
/// Counter that tracks the gross tip revenue. Effective tip per gas * gas used.
/// We call it "gross" because in the case of PBS, it doesn't mean the proposer will
/// get all of this as revenue.
const GROSS_TIP_REVENUE: &str = "bolt_sidecar_gross_tip_revenue";

// Gauges ------------------------------------------------------------------
/// Gauge for the latest slot number
Expand All @@ -44,6 +48,7 @@ impl ApiMetrics {
describe_counter!(INCLUSION_COMMITMENTS_ACCEPTED, "Inclusion commitments accepted");
describe_counter!(TRANSACTIONS_PRECONFIRMED, "Transactions preconfirmed");
describe_counter!(VALIDATION_ERRORS, "Validation errors");
describe_counter!(GROSS_TIP_REVENUE, "Gross tip revenue");

// Gauges
describe_gauge!(LATEST_HEAD, "Latest slot number");
Expand Down Expand Up @@ -81,6 +86,25 @@ impl ApiMetrics {
counter!(INCLUSION_COMMITMENTS_ACCEPTED).increment(1);
}

pub fn increment_gross_tip_revenue(mut tip: u128) {
// If the tip is too large, we need to split it into multiple u64 parts
if tip > u64::MAX as u128 {
let mut parts = Vec::new();
while tip > u64::MAX as u128 {
parts.push(u64::MAX);
tip -= u64::MAX as u128;
}

parts.push(tip as u64);

for part in parts {
counter!(GROSS_TIP_REVENUE).increment(part);
}
} else {
counter!(GROSS_TIP_REVENUE).increment(tip as u64);
}
}

pub fn increment_transactions_preconfirmed(tx_type: TxType) {
counter!(TRANSACTIONS_PRECONFIRMED, &[("type", tx_type_str(tx_type))]).increment(1);
}
Expand Down

0 comments on commit 7f76718

Please sign in to comment.