-
Notifications
You must be signed in to change notification settings - Fork 3
Index private transactions #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2dab63a
1564d08
303168a
95dd0c2
41bdef9
e9b5b2c
86a9a1a
07a2eda
aafa052
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| CREATE TABLE transactions_from_send_raw_tx | ||
| ( | ||
| `received_at` DateTime64(3, | ||
| 'UTC'), | ||
| `hash` String, | ||
| `chain_id` String, | ||
| `tx_type` Int64, | ||
| `from` String, | ||
| `to` String, | ||
| `value` String, | ||
| `nonce` String, | ||
| `gas` String, | ||
| `gas_price` String, | ||
| `gas_tip_cap` String, | ||
| `gas_fee_cap` String, | ||
| `data_size` Int64, | ||
| `data_4bytes` String, | ||
| `raw_tx` String, | ||
| `ver` Int64 MATERIALIZED -toUnixTimestamp(received_at) | ||
| ) | ||
| ENGINE = ReplacingMergeTree(ver) | ||
| PARTITION BY toYYYYMM(received_at) | ||
| PRIMARY KEY hash | ||
| ORDER BY hash | ||
| SETTINGS index_granularity = 8192 | ||
| COMMENT 'Transaction details, | ||
| deduplicated by hash, | ||
| will keep the transaction with earliest received_at.'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,20 @@ | ||
| //! Contains the model used for storing data inside Clickhouse. | ||
|
|
||
| use crate::{ | ||
| indexer::ser::{address, addresses, hash, hashes, u256es}, | ||
| indexer::ser::{address, addresses, hash, hashes, raw_bytes, u256es}, | ||
| primitives::BundleReceipt, | ||
| }; | ||
| use alloy_consensus::Transaction; | ||
| use alloy_eips::Typed2718; | ||
| use alloy_primitives::{Address, Keccak256, B256, U256}; | ||
| use alloy_primitives::{hex, Address, Keccak256, B256, U256}; | ||
| use alloy_rlp::Encodable; | ||
| use clickhouse::Row; | ||
| use rbuilder_primitives::BundleVersion; | ||
| use rbuilder_utils::clickhouse::backup::primitives::{ClickhouseIndexableData, ClickhouseRowExt}; | ||
| use time::{OffsetDateTime, UtcDateTime}; | ||
| use uuid::Uuid; | ||
|
|
||
| use crate::primitives::{DecodedBundle, SystemBundle}; | ||
| use crate::primitives::{DecodedBundle, SystemBundle, SystemTransaction}; | ||
|
|
||
| /// Model representing Clickhouse bundle row. | ||
| /// | ||
|
|
@@ -424,6 +424,102 @@ impl ClickhouseIndexableData for BundleReceipt { | |
| } | ||
| } | ||
|
|
||
| /// Model representing a Clickhouse transaction row for individual transactions received via | ||
| /// `eth_sendRawTransaction`. | ||
| /// | ||
| /// NOTE: Make sure the fields are in the same order as the columns in the Clickhouse table. | ||
| #[derive(Clone, clickhouse::Row, Debug, serde::Serialize, serde::Deserialize)] | ||
| #[cfg_attr(test, derive(PartialEq, Eq))] | ||
| pub struct TransactionRow { | ||
| #[serde(with = "clickhouse::serde::time::datetime64::millis")] | ||
| pub received_at: OffsetDateTime, | ||
| pub hash: String, | ||
| pub chain_id: String, | ||
| pub tx_type: i64, | ||
| #[serde(rename = "from")] | ||
| pub tx_from: String, | ||
| pub to: String, | ||
| pub value: String, | ||
| pub nonce: String, | ||
| pub gas: String, | ||
| pub gas_price: String, | ||
| pub gas_tip_cap: String, | ||
| pub gas_fee_cap: String, | ||
| pub data_size: i64, | ||
| pub data_4bytes: String, | ||
| #[serde(serialize_with = "raw_bytes::serialize")] | ||
| pub raw_tx: Vec<u8>, | ||
| } | ||
|
|
||
| impl ClickhouseRowExt for TransactionRow { | ||
| type TraceId = String; | ||
| const TABLE_NAME: &'static str = "transaction"; | ||
|
|
||
| fn trace_id(&self) -> Self::TraceId { | ||
| self.hash.clone() | ||
| } | ||
|
|
||
| fn to_row_ref(row: &Self) -> &<Self as Row>::Value<'_> { | ||
| row | ||
| } | ||
| } | ||
|
|
||
| impl From<(SystemTransaction, String)> for TransactionRow { | ||
| fn from((system_tx, _builder_name): (SystemTransaction, String)) -> Self { | ||
| let tx = &system_tx.transaction; | ||
| let millis = system_tx.received_at.utc.millisecond(); | ||
| let received_at: OffsetDateTime = system_tx | ||
| .received_at | ||
| .utc | ||
| .replace_millisecond(millis) | ||
| .expect("to replace milliseconds") | ||
| .into(); | ||
|
|
||
| let input = tx.decoded.input(); | ||
| let data_4bytes = if input.len() >= 4 { | ||
| format!("0x{}", hex::encode(&input[..4])) | ||
| } else { | ||
| String::new() | ||
| }; | ||
|
|
||
| TransactionRow { | ||
| received_at, | ||
| hash: format!("{:#x}", tx.decoded.tx_hash()), | ||
| chain_id: tx.decoded.chain_id().map(|c| c.to_string()).unwrap_or_default(), | ||
| tx_type: tx.decoded.tx_type() as i64, | ||
| tx_from: format!("{:#x}", system_tx.tx_sender), | ||
| to: tx.decoded.to().map(|a| format!("{:#x}", a)).unwrap_or_default(), | ||
| value: tx.decoded.value().to_string(), | ||
| nonce: tx.decoded.nonce().to_string(), | ||
| gas: tx.decoded.gas_limit().to_string(), | ||
| gas_price: tx.decoded.gas_price().map(|p| p.to_string()).unwrap_or_default(), | ||
| gas_tip_cap: tx | ||
| .decoded | ||
| .max_priority_fee_per_gas() | ||
| .map(|p| p.to_string()) | ||
| .unwrap_or_default(), | ||
| gas_fee_cap: tx.decoded.max_fee_per_gas().to_string(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude says "For legacy txs, max_fee_per_gas() returns the Can you double check this?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This field is not really used, on backtesting we decode the tx from raw_tx. |
||
| data_size: input.len() as i64, | ||
| data_4bytes, | ||
| raw_tx: tx.raw.to_vec(), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl ClickhouseIndexableData for SystemTransaction { | ||
| type ClickhouseRowType = TransactionRow; | ||
|
|
||
| const DATA_NAME: &'static str = <TransactionRow as ClickhouseRowExt>::TABLE_NAME; | ||
|
|
||
| fn trace_id(&self) -> String { | ||
| format!("{:#x}", self.tx_hash()) | ||
| } | ||
|
|
||
| fn to_row(self, builder_name: String) -> Self::ClickhouseRowType { | ||
| (self, builder_name).into() | ||
| } | ||
| } | ||
|
|
||
| /// Tests to make sure round-trip conversion between raw bundle and clickhouse bundle types is | ||
| /// feasible. | ||
| #[cfg(test)] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly is this doing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This truncates the precision to millisecs which is what we store on the DB.
Copied this "pattern" from BundleRow.