From 76e0da8eb9203df14c7ca6f38daf3aa1ceac7cd1 Mon Sep 17 00:00:00 2001 From: Leszek Wiesner Date: Fri, 17 Apr 2026 12:52:11 +0200 Subject: [PATCH 1/3] Tron archive: data model --- crates/archive/src/archive.rs | 4 +- crates/archive/src/cli.rs | 1 + crates/data/src/lib.rs | 1 + crates/data/src/tron/mod.rs | 2 + crates/data/src/tron/model.rs | 142 ++++++++++++++ crates/data/src/tron/tables/block.rs | 39 ++++ crates/data/src/tron/tables/common.rs | 8 + .../src/tron/tables/internal_transaction.rs | 57 ++++++ crates/data/src/tron/tables/log.rs | 53 ++++++ crates/data/src/tron/tables/mod.rs | 46 +++++ crates/data/src/tron/tables/transaction.rs | 179 ++++++++++++++++++ 11 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 crates/data/src/tron/mod.rs create mode 100644 crates/data/src/tron/model.rs create mode 100644 crates/data/src/tron/tables/block.rs create mode 100644 crates/data/src/tron/tables/common.rs create mode 100644 crates/data/src/tron/tables/internal_transaction.rs create mode 100644 crates/data/src/tron/tables/log.rs create mode 100644 crates/data/src/tron/tables/mod.rs create mode 100644 crates/data/src/tron/tables/transaction.rs diff --git a/crates/archive/src/archive.rs b/crates/archive/src/archive.rs index 2ead845b..524ae6a3 100644 --- a/crates/archive/src/archive.rs +++ b/crates/archive/src/archive.rs @@ -13,6 +13,7 @@ use sqd_data::evm::tables::EvmChunkBuilder; use sqd_data::hyperliquid_fills::tables::HyperliquidFillsChunkBuilder; use sqd_data::hyperliquid_replica_cmds::tables::HyperliquidReplicaCmdsChunkBuilder; use sqd_data::solana::tables::SolanaChunkBuilder; +use sqd_data::tron::tables::TronChunkBuilder; use sqd_primitives::BlockNumber; use std::time::Duration; @@ -70,7 +71,8 @@ pub async fn run(args: Cli) -> anyhow::Result<()> { NetworkKind::Solana => proc!(SolanaChunkBuilder::default()), NetworkKind::HyperliquidFills => proc!(HyperliquidFillsChunkBuilder::default()), NetworkKind::HyperliquidReplicaCmds => proc!(HyperliquidReplicaCmdsChunkBuilder::default()), - NetworkKind::Evm => proc!(EvmChunkBuilder::default()) + NetworkKind::Evm => proc!(EvmChunkBuilder::default()), + NetworkKind::Tron => proc!(TronChunkBuilder::default()) }; let attach_idx_field = args.attach_idx_field; diff --git a/crates/archive/src/cli.rs b/crates/archive/src/cli.rs index 9f81bcd7..b52d61a8 100644 --- a/crates/archive/src/cli.rs +++ b/crates/archive/src/cli.rs @@ -10,6 +10,7 @@ pub enum NetworkKind { HyperliquidFills, HyperliquidReplicaCmds, Evm, + Tron } diff --git a/crates/data/src/lib.rs b/crates/data/src/lib.rs index 434cf6d6..9454e6b2 100644 --- a/crates/data/src/lib.rs +++ b/crates/data/src/lib.rs @@ -3,4 +3,5 @@ pub mod evm; pub mod hyperliquid_fills; pub mod hyperliquid_replica_cmds; pub mod solana; +pub mod tron; mod types; diff --git a/crates/data/src/tron/mod.rs b/crates/data/src/tron/mod.rs new file mode 100644 index 00000000..a0c66c0a --- /dev/null +++ b/crates/data/src/tron/mod.rs @@ -0,0 +1,2 @@ +pub mod model; +pub mod tables; diff --git a/crates/data/src/tron/model.rs b/crates/data/src/tron/model.rs new file mode 100644 index 00000000..af69fdae --- /dev/null +++ b/crates/data/src/tron/model.rs @@ -0,0 +1,142 @@ +use crate::types::{HexBytes, JsonValue}; +use serde::Deserialize; +use sqd_primitives::{BlockNumber, ItemIndex}; + + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockHeader { + pub height: BlockNumber, + pub hash: HexBytes, + pub parent_hash: HexBytes, + pub tx_trie_root: HexBytes, + pub version: Option, + pub timestamp: i64, + pub witness_address: HexBytes, + pub witness_signature: Option, +} + + +#[derive(Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionResult { + pub contract_ret: Option, // eg. "SUCCESS", "REVERT", etc. +} + + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Transaction { + pub transaction_index: ItemIndex, + pub hash: HexBytes, + pub ret: Option>, + pub signature: Option>, + #[serde(rename = "type")] + pub r#type: String, + pub parameter: JsonValue, + pub permission_id: Option, + pub ref_block_bytes: Option, + pub ref_block_hash: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub fee_limit: Option, + pub expiration: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub timestamp: Option, + pub raw_data_hex: HexBytes, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub fee: Option, + pub contract_result: Option, + pub contract_address: Option, + pub res_message: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub withdraw_amount: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub unfreeze_amount: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub withdraw_expire_amount: Option, + pub cancel_unfreeze_v2_amount: Option, + pub result: Option, // Result from receipt, eg. "SUCCESS", "REVERT", etc. + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub energy_fee: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub energy_usage: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub energy_usage_total: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub net_usage: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub net_fee: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub origin_energy_usage: Option, + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub energy_penalty_total: Option, +} + + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Log { + pub transaction_index: ItemIndex, + pub log_index: ItemIndex, + pub address: HexBytes, + pub data: Option, + pub topics: Option>, +} + + +#[derive(Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CallValueInfo { + #[serde(deserialize_with="sqd_data_core::serde::decode_string_option", default)] + pub call_value: Option, + pub token_id: Option, +} + + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InternalTransaction { + pub transaction_index: ItemIndex, + pub internal_transaction_index: ItemIndex, + pub hash: HexBytes, + pub caller_address: HexBytes, + pub transfer_to_address: Option, + pub call_value_info: Vec, + pub note: HexBytes, + pub rejected: Option, + pub extra: Option, +} + + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Block { + pub header: BlockHeader, + pub transactions: Vec, + pub logs: Vec, + pub internal_transactions: Vec, +} + + +impl sqd_primitives::Block for Block { + fn number(&self) -> BlockNumber { + self.header.height + } + + fn hash(&self) -> &str { + &self.header.hash + } + + fn parent_number(&self) -> BlockNumber { + self.header.height.saturating_sub(1) + } + + fn parent_hash(&self) -> &str { + &self.header.parent_hash + } + + fn timestamp(&self) -> Option { + // Tron timestamps are already in milliseconds + Some(self.header.timestamp) + } +} diff --git a/crates/data/src/tron/tables/block.rs b/crates/data/src/tron/tables/block.rs new file mode 100644 index 00000000..d271b30d --- /dev/null +++ b/crates/data/src/tron/tables/block.rs @@ -0,0 +1,39 @@ +use crate::tron::model::BlockHeader; +use crate::tron::tables::common::*; +use sqd_array::builder::{Int32Builder, TimestampMillisecondBuilder, UInt64Builder}; +use sqd_data_core::table_builder; + + +table_builder! { + BlockBuilder { + number: UInt64Builder, + hash: HexBytesBuilder, + parent_hash: HexBytesBuilder, + tx_trie_root: HexBytesBuilder, + version: Int32Builder, + timestamp: TimestampMillisecondBuilder, + witness_address: HexBytesBuilder, + witness_signature: HexBytesBuilder, + } + + description(d) { + d.downcast.block_number = vec!["number"]; + d.sort_key = vec!["number"]; + d.options.add_stats("number"); + d.options.row_group_size = 5_000; + } +} + + +impl BlockBuilder { + pub fn push(&mut self, row: &BlockHeader) { + self.number.append(row.height); + self.hash.append(&row.hash); + self.parent_hash.append(&row.parent_hash); + self.tx_trie_root.append(&row.tx_trie_root); + self.version.append_option(row.version); + self.timestamp.append(row.timestamp); + self.witness_address.append(&row.witness_address); + self.witness_signature.append_option(row.witness_signature.as_deref()); + } +} diff --git a/crates/data/src/tron/tables/common.rs b/crates/data/src/tron/tables/common.rs new file mode 100644 index 00000000..5fa2644c --- /dev/null +++ b/crates/data/src/tron/tables/common.rs @@ -0,0 +1,8 @@ +use sqd_array::builder::{StringBuilder}; + +pub type HexBytesBuilder = StringBuilder; +pub type JsonBuilder = StringBuilder; + +pub fn sighash(bytes: &str) -> Option<&str> { + (bytes.len() >= 8).then(|| { &bytes[0..8] }) +} \ No newline at end of file diff --git a/crates/data/src/tron/tables/internal_transaction.rs b/crates/data/src/tron/tables/internal_transaction.rs new file mode 100644 index 00000000..d15fbd2b --- /dev/null +++ b/crates/data/src/tron/tables/internal_transaction.rs @@ -0,0 +1,57 @@ +use crate::tron::model::{Block, InternalTransaction}; +use crate::tron::tables::common::*; +use sqd_array::builder::{BooleanBuilder, UInt32Builder, UInt64Builder}; +use sqd_data_core::table_builder; + + +table_builder! { + InternalTransactionBuilder { + block_number: UInt64Builder, + transaction_index: UInt32Builder, + internal_transaction_index: UInt32Builder, + hash: HexBytesBuilder, + caller_address: HexBytesBuilder, + transfer_to_address: HexBytesBuilder, + call_value_info: JsonBuilder, + note: HexBytesBuilder, + rejected: BooleanBuilder, + extra: JsonBuilder, + } + + description(d) { + d.downcast.block_number = vec!["block_number"]; + d.downcast.item_index = vec!["transaction_index", "internal_transaction_index"]; + d.sort_key = vec![ + "transfer_to_address", + "caller_address", + "block_number", + "transaction_index", + "internal_transaction_index", + ]; + d.options.add_stats("block_number"); + d.options.add_stats("transaction_index"); + d.options.add_stats("internal_transaction_index"); + d.options.add_stats("transfer_to_address"); + d.options.add_stats("caller_address"); + d.options.row_group_size = 10_000; + } +} + + +impl InternalTransactionBuilder { + pub fn push(&mut self, block: &Block, row: &InternalTransaction) { + self.block_number.append(block.header.height); + self.transaction_index.append(row.transaction_index); + self.internal_transaction_index.append(row.internal_transaction_index); + self.hash.append(&row.hash); + self.caller_address.append(&row.caller_address); + self.transfer_to_address.append_option(row.transfer_to_address.as_deref()); + + let call_value_info = serde_json::to_string(&row.call_value_info).unwrap(); + self.call_value_info.append(&call_value_info); + + self.note.append(&row.note); + self.rejected.append_option(row.rejected); + self.extra.append_option(row.extra.as_deref()); + } +} diff --git a/crates/data/src/tron/tables/log.rs b/crates/data/src/tron/tables/log.rs new file mode 100644 index 00000000..ca4c1826 --- /dev/null +++ b/crates/data/src/tron/tables/log.rs @@ -0,0 +1,53 @@ +use crate::tron::model::{Block, Log}; +use crate::tron::tables::common::HexBytesBuilder; +use sqd_array::builder::{UInt32Builder, UInt64Builder}; +use sqd_data_core::table_builder; + + +table_builder! { + LogBuilder { + block_number: UInt64Builder, + log_index: UInt32Builder, + transaction_index: UInt32Builder, + address: HexBytesBuilder, + data: HexBytesBuilder, + topic0: HexBytesBuilder, + topic1: HexBytesBuilder, + topic2: HexBytesBuilder, + topic3: HexBytesBuilder, + data_size: UInt64Builder, + } + + description(d) { + d.downcast.block_number = vec!["block_number"]; + d.downcast.item_index = vec!["transaction_index", "log_index"]; + d.sort_key = vec!["topic0", "address", "block_number", "transaction_index", "log_index"]; + d.options.add_stats("block_number"); + d.options.add_stats("log_index"); + d.options.add_stats("transaction_index"); + d.options.add_stats("address"); + d.options.add_stats("topic0"); + d.options.use_dictionary("address"); + d.options.use_dictionary("topic0"); + d.options.row_group_size = 10_000; + } +} + + +impl LogBuilder { + pub fn push(&mut self, block: &Block, row: &Log) { + self.block_number.append(block.header.height); + self.log_index.append(row.log_index); + self.transaction_index.append(row.transaction_index); + self.address.append(&row.address); + self.data.append_option(row.data.as_deref()); + + let topics = row.topics.as_deref().unwrap_or(&[]); + self.topic0.append_option(topics.first().map(|x| x.as_str())); + self.topic1.append_option(topics.get(1).map(|x| x.as_str())); + self.topic2.append_option(topics.get(2).map(|x| x.as_str())); + self.topic3.append_option(topics.get(3).map(|x| x.as_str())); + + self.data_size.append(row.data.as_ref().map_or(0, |d| d.len() as u64)); + } +} diff --git a/crates/data/src/tron/tables/mod.rs b/crates/data/src/tron/tables/mod.rs new file mode 100644 index 00000000..e7b575d6 --- /dev/null +++ b/crates/data/src/tron/tables/mod.rs @@ -0,0 +1,46 @@ +mod common; +mod block; +mod transaction; +mod log; +mod internal_transaction; + +pub use block::*; +pub use transaction::*; +pub use log::*; +pub use internal_transaction::*; + +use super::model::Block; +use sqd_data_core::chunk_builder; + + +chunk_builder! { + TronChunkBuilder { + blocks: BlockBuilder, + transactions: TransactionBuilder, + logs: LogBuilder, + internal_transactions: InternalTransactionBuilder, + } +} + + +impl sqd_data_core::BlockChunkBuilder for TronChunkBuilder { + type Block = Block; + + fn push(&mut self, block: &Self::Block) -> anyhow::Result<()> { + self.blocks.push(&block.header); + + for row in block.transactions.iter() { + self.transactions.push(block, row); + } + + for row in block.logs.iter() { + self.logs.push(block, row); + } + + for row in block.internal_transactions.iter() { + self.internal_transactions.push(block, row); + } + + Ok(()) + } +} diff --git a/crates/data/src/tron/tables/transaction.rs b/crates/data/src/tron/tables/transaction.rs new file mode 100644 index 00000000..1a62bd7b --- /dev/null +++ b/crates/data/src/tron/tables/transaction.rs @@ -0,0 +1,179 @@ +use crate::tron::model::{Block, Transaction}; +use crate::tron::tables::common::*; +use sqd_array::builder::{Int32Builder, Int64Builder, ListBuilder, StringBuilder, TimestampMillisecondBuilder, UInt32Builder, UInt64Builder}; +use sqd_data_core::table_builder; + + +type SignatureListBuilder = ListBuilder; + + +table_builder! { + TransactionBuilder { + block_number: UInt64Builder, + transaction_index: UInt32Builder, + hash: HexBytesBuilder, + ret: JsonBuilder, + signature: SignatureListBuilder, + r#type: StringBuilder, + parameter: JsonBuilder, + permission_id: Int32Builder, + ref_block_bytes: HexBytesBuilder, + ref_block_hash: HexBytesBuilder, + fee_limit: Int64Builder, + expiration: TimestampMillisecondBuilder, + timestamp: Int64Builder, + raw_data_hex: HexBytesBuilder, + + // info + fee: Int64Builder, + contract_result: HexBytesBuilder, + contract_address: HexBytesBuilder, + res_message: HexBytesBuilder, + withdraw_amount: Int64Builder, + unfreeze_amount: Int64Builder, + withdraw_expire_amount: Int64Builder, + cancel_unfreeze_v2_amount: JsonBuilder, + + // receipt + result: StringBuilder, + energy_fee: Int64Builder, + energy_usage: Int64Builder, + energy_usage_total: Int64Builder, + net_usage: Int64Builder, + net_fee: Int64Builder, + origin_energy_usage: Int64Builder, + energy_penalty_total: Int64Builder, + + // TransferContract + _transfer_contract_owner: HexBytesBuilder, + _transfer_contract_to: HexBytesBuilder, + + // TransferAssetContract + _transfer_asset_contract_owner: HexBytesBuilder, + _transfer_asset_contract_to: HexBytesBuilder, + _transfer_asset_contract_asset: StringBuilder, + + // TriggerSmartContract + _trigger_smart_contract_owner: HexBytesBuilder, + _trigger_smart_contract_contract: HexBytesBuilder, + _trigger_smart_contract_sighash: HexBytesBuilder, + + raw_data_hex_size: UInt64Builder, + } + + description(d) { + d.downcast.block_number = vec!["block_number"]; + d.downcast.item_index = vec!["transaction_index"]; + d.sort_key = vec![ + "type", + "_trigger_smart_contract_sighash", + "_trigger_smart_contract_contract", + "_trigger_smart_contract_owner", + "_transfer_contract_owner", + "_transfer_contract_to", + "_transfer_asset_contract_owner", + "_transfer_asset_contract_to", + "_transfer_asset_contract_asset", + "block_number", + "transaction_index", + ]; + d.options.add_stats("block_number"); + d.options.add_stats("transaction_index"); + d.options.add_stats("type"); + d.options.add_stats("_transfer_contract_owner"); + d.options.add_stats("_transfer_contract_to"); + d.options.add_stats("_transfer_asset_contract_owner"); + d.options.add_stats("_transfer_asset_contract_to"); + d.options.add_stats("_transfer_asset_contract_asset"); + d.options.add_stats("_trigger_smart_contract_owner"); + d.options.add_stats("_trigger_smart_contract_contract"); + d.options.add_stats("_trigger_smart_contract_sighash"); + d.options.use_dictionary("type"); + d.options.use_dictionary("ret"); + d.options.row_group_size = 10_000; + } +} + + +impl TransactionBuilder { + pub fn push(&mut self, block: &Block, row: &Transaction) { + self.block_number.append(block.header.height); + self.transaction_index.append(row.transaction_index); + self.hash.append(&row.hash); + + let ret = row.ret.as_ref().map(|val| serde_json::to_string(val).unwrap()); + self.ret.append_option(ret.as_deref()); + + for sig in row.signature.iter().flatten() { + self.signature.values().append(sig); + } + self.signature.append(); + + self.r#type.append(&row.r#type); + + let parameter = serde_json::to_string(&row.parameter).unwrap(); + self.parameter.append(¶meter); + + self.permission_id.append_option(row.permission_id); + self.ref_block_bytes.append_option(row.ref_block_bytes.as_deref()); + self.ref_block_hash.append_option(row.ref_block_hash.as_deref()); + self.fee_limit.append_option(row.fee_limit.map(|v| v as i64)); + self.expiration.append_option(row.expiration); + self.timestamp.append_option(row.timestamp); + self.raw_data_hex.append(&row.raw_data_hex); + + self.fee.append_option(row.fee.map(|v| v as i64)); + self.contract_result.append_option(row.contract_result.as_deref()); + self.contract_address.append_option(row.contract_address.as_deref()); + self.res_message.append_option(row.res_message.as_deref()); + self.withdraw_amount.append_option(row.withdraw_amount.map(|v| v as i64)); + self.unfreeze_amount.append_option(row.unfreeze_amount.map(|v| v as i64)); + self.withdraw_expire_amount.append_option(row.withdraw_expire_amount.map(|v| v as i64)); + + let cancel = row.cancel_unfreeze_v2_amount.as_ref().map(|val| serde_json::to_string(val).unwrap()); + self.cancel_unfreeze_v2_amount.append_option(cancel.as_deref()); + + self.result.append_option(row.result.as_deref()); + self.energy_fee.append_option(row.energy_fee.map(|v| v as i64)); + self.energy_usage.append_option(row.energy_usage.map(|v| v as i64)); + self.energy_usage_total.append_option(row.energy_usage_total.map(|v| v as i64)); + self.net_usage.append_option(row.net_usage.map(|v| v as i64)); + self.net_fee.append_option(row.net_fee.map(|v| v as i64)); + self.origin_energy_usage.append_option(row.origin_energy_usage.map(|v| v as i64)); + self.energy_penalty_total.append_option(row.energy_penalty_total.map(|v| v as i64)); + + let value = &row.parameter["value"]; + + if row.r#type == "TransferContract" { + self._transfer_contract_owner.append_option(value["owner_address"].as_str()); + self._transfer_contract_to.append_option(value["to_address"].as_str()); + } else { + self._transfer_contract_owner.append_null(); + self._transfer_contract_to.append_null(); + } + + if row.r#type == "TransferAssetContract" { + self._transfer_asset_contract_owner.append_option(value["owner_address"].as_str()); + self._transfer_asset_contract_to.append_option(value["to_address"].as_str()); + self._transfer_asset_contract_asset.append_option(value["asset_name"].as_str()); + } else { + self._transfer_asset_contract_owner.append_null(); + self._transfer_asset_contract_to.append_null(); + self._transfer_asset_contract_asset.append_null(); + } + + if row.r#type == "TriggerSmartContract" { + self._trigger_smart_contract_owner.append_option(value["owner_address"].as_str()); + self._trigger_smart_contract_contract.append_option(value["contract_address"].as_str()); + self._trigger_smart_contract_sighash.append_option( + value["data"].as_str().and_then(sighash) + ); + } else { + self._trigger_smart_contract_owner.append_null(); + self._trigger_smart_contract_contract.append_null(); + self._trigger_smart_contract_sighash.append_null(); + } + + self.raw_data_hex_size.append(row.raw_data_hex.len() as u64); + } +} From f8650a196836475a057a6b662dc8c2413fb5b156 Mon Sep 17 00:00:00 2001 From: Leszek Wiesner Date: Fri, 17 Apr 2026 16:53:54 +0200 Subject: [PATCH 2/3] Tron queries --- crates/data/src/tron/tables/transaction.rs | 2 +- crates/hotblocks/src/types.rs | 5 + crates/query/src/query/mod.rs | 12 +- crates/query/src/query/tron.rs | 452 +++++++++++++++++++++ 4 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 crates/query/src/query/tron.rs diff --git a/crates/data/src/tron/tables/transaction.rs b/crates/data/src/tron/tables/transaction.rs index 1a62bd7b..77945816 100644 --- a/crates/data/src/tron/tables/transaction.rs +++ b/crates/data/src/tron/tables/transaction.rs @@ -21,7 +21,7 @@ table_builder! { ref_block_hash: HexBytesBuilder, fee_limit: Int64Builder, expiration: TimestampMillisecondBuilder, - timestamp: Int64Builder, + timestamp: TimestampMillisecondBuilder, raw_data_hex: HexBytesBuilder, // info diff --git a/crates/hotblocks/src/types.rs b/crates/hotblocks/src/types.rs index 29aa7671..bfbc4268 100644 --- a/crates/hotblocks/src/types.rs +++ b/crates/hotblocks/src/types.rs @@ -20,6 +20,8 @@ pub enum DatasetKind { HyperliquidFills, #[serde(rename = "hyperliquid-replica-cmds")] HyperliquidReplicaCmds, + #[serde(rename = "tron")] + Tron } @@ -35,6 +37,7 @@ impl DatasetKind { DatasetKind::Bitcoin => "bitcoin", DatasetKind::HyperliquidFills => "hl-fills", DatasetKind::HyperliquidReplicaCmds => "hl-replica-cmds", + DatasetKind::Tron => "tron", } } @@ -45,6 +48,7 @@ impl DatasetKind { DatasetKind::Bitcoin => sqd_data::bitcoin::tables::BitcoinChunkBuilder::dataset_description(), DatasetKind::HyperliquidFills => sqd_data::hyperliquid_fills::tables::HyperliquidFillsChunkBuilder::dataset_description(), DatasetKind::HyperliquidReplicaCmds => sqd_data::hyperliquid_replica_cmds::tables::HyperliquidReplicaCmdsChunkBuilder::dataset_description(), + DatasetKind::Tron => sqd_data::tron::tables::TronChunkBuilder::dataset_description(), } } @@ -55,6 +59,7 @@ impl DatasetKind { Query::Bitcoin(_) => Self::Bitcoin, Query::HyperliquidFills(_) => Self::HyperliquidFills, Query::HyperliquidReplicaCmds(_) => Self::HyperliquidReplicaCmds, + Query::Tron(_) => Self::Tron, _ => unimplemented!() } } diff --git a/crates/query/src/query/mod.rs b/crates/query/src/query/mod.rs index c48f2b00..2ea1e707 100644 --- a/crates/query/src/query/mod.rs +++ b/crates/query/src/query/mod.rs @@ -10,6 +10,7 @@ pub mod substrate; pub mod fuel; pub mod hyperliquid_fills; pub mod hyperliquid_replica_cmds; +pub mod tron; mod util; @@ -30,6 +31,8 @@ pub enum Query { HyperliquidFills(hyperliquid_fills::HyperliquidFillsQuery), #[serde(rename = "hyperliquidReplicaCmds")] HyperliquidReplicaCmds(hyperliquid_replica_cmds::HyperliquidReplicaCmdsQuery), + #[serde(rename = "tron")] + Tron(tron::TronQuery) } @@ -59,9 +62,10 @@ impl Query { Query::Fuel(q) => q.validate(), Query::HyperliquidFills(q) => q.validate(), Query::HyperliquidReplicaCmds(q) => q.validate(), + Query::Tron(q) => q.validate(), } } - + pub fn parent_block_hash(&self) -> Option<&str> { match self { Query::Bitcoin(q) => q.parent_block_hash.as_ref(), @@ -71,6 +75,7 @@ impl Query { Query::Fuel(q) => q.parent_block_hash.as_ref(), Query::HyperliquidFills(q) => q.parent_block_hash.as_ref(), Query::HyperliquidReplicaCmds(q) => q.parent_block_hash.as_ref(), + Query::Tron(q) => q.parent_block_hash.as_ref(), }.map(|s| s.as_str()) } @@ -83,6 +88,7 @@ impl Query { Query::Fuel(q) => q.from_block, Query::HyperliquidFills(q) => q.from_block, Query::HyperliquidReplicaCmds(q) => q.from_block, + Query::Tron(q) => q.from_block, } } @@ -95,6 +101,7 @@ impl Query { Query::Fuel(q) => q.from_block = block_number, Query::HyperliquidFills(q) => q.from_block = block_number, Query::HyperliquidReplicaCmds(q) => q.from_block = block_number, + Query::Tron(q) => q.from_block = block_number, } } @@ -107,6 +114,7 @@ impl Query { Query::Fuel(q) => q.to_block, Query::HyperliquidFills(q) => q.to_block, Query::HyperliquidReplicaCmds(q) => q.to_block, + Query::Tron(q) => q.to_block, } } @@ -120,6 +128,7 @@ impl Query { Query::Fuel(q) => q.to_block = block_number, Query::HyperliquidFills(q) => q.to_block = block_number, Query::HyperliquidReplicaCmds(q) => q.to_block = block_number, + Query::Tron(q) => q.to_block = block_number, } } @@ -132,6 +141,7 @@ impl Query { Query::Fuel(q) => q.compile(), Query::HyperliquidFills(q) => q.compile(), Query::HyperliquidReplicaCmds(q) => q.compile(), + Query::Tron(q) => q.compile(), } } } \ No newline at end of file diff --git a/crates/query/src/query/tron.rs b/crates/query/src/query/tron.rs new file mode 100644 index 00000000..006a5a0c --- /dev/null +++ b/crates/query/src/query/tron.rs @@ -0,0 +1,452 @@ +use crate::json::exp::Exp; +use crate::json::lang::*; +use crate::plan::{Plan, ScanBuilder, TableSet}; +use crate::primitives::BlockNumber; +use crate::query::util::{compile_plan, ensure_block_range, ensure_item_count, field_selection, item_field_selection, request, to_lowercase_list, PredicateBuilder}; +use serde::{Deserialize, Serialize}; +use std::sync::LazyLock; + + +static TABLES: LazyLock = LazyLock::new(|| { + let mut tables = TableSet::new(); + + tables.add_table("blocks", vec![ + "number" + ]); + + tables.add_table("transactions", vec![ + "block_number", + "transaction_index" + ]) + .add_child("logs", vec!["block_number", "transaction_index"]) + .add_child("internal_transactions", vec!["block_number", "transaction_index"]) + .set_weight_column("raw_data_hex", "raw_data_hex_size"); + + tables.add_table("logs", vec![ + "block_number", + "transaction_index", + "log_index" + ]) + .set_weight_column("data", "data_size"); + + tables.add_table("internal_transactions", vec![ + "block_number", + "transaction_index", + "internal_transaction_index" + ]) + .set_result_item_name("internalTransactions"); + + tables +}); + + +field_selection! { + block: BlockFieldSelection, + transaction: TransactionFieldSelection, + log: LogFieldSelection, + internal_transaction: InternalTransactionFieldSelection, +} + + +item_field_selection! { + BlockFieldSelection { + number, + hash, + parent_hash, + tx_trie_root, + version, + timestamp, + witness_address, + witness_signature, + } + + project(this) json_object! {{ + this.number, + this.hash, + this.parent_hash, + this.tx_trie_root, + this.version, + this.witness_address, + this.witness_signature, + [this.timestamp]: TimestampMillisecond, + }} +} + + +item_field_selection! { + TransactionFieldSelection { + transaction_index, + hash, + ret, + signature, + r#type, + parameter, + permission_id, + ref_block_bytes, + ref_block_hash, + fee_limit, + expiration, + timestamp, + raw_data_hex, + fee, + contract_result, + contract_address, + res_message, + withdraw_amount, + unfreeze_amount, + withdraw_expire_amount, + cancel_unfreeze_v2_amount, + result, + energy_fee, + energy_usage, + energy_usage_total, + net_usage, + net_fee, + origin_energy_usage, + energy_penalty_total, + } + + project(this) json_object! {{ + this.transaction_index, + this.hash, + this.signature, + this.r#type, + this.permission_id, + this.ref_block_bytes, + this.ref_block_hash, + this.raw_data_hex, + this.contract_result, + this.contract_address, + this.res_message, + this.result, + [this.ret]: Json, + [this.parameter]: Json, + [this.fee_limit]: BigNum, + [this.expiration]: TimestampMillisecond, + [this.timestamp]: TimestampMillisecond, + [this.fee]: BigNum, + [this.withdraw_amount]: BigNum, + [this.unfreeze_amount]: BigNum, + [this.withdraw_expire_amount]: BigNum, + [this.cancel_unfreeze_v2_amount]: Json, + [this.energy_fee]: BigNum, + [this.energy_usage]: BigNum, + [this.energy_usage_total]: BigNum, + [this.net_usage]: BigNum, + [this.net_fee]: BigNum, + [this.origin_energy_usage]: BigNum, + [this.energy_penalty_total]: BigNum, + }} +} + + +item_field_selection! { + LogFieldSelection { + transaction_index, + log_index, + address, + data, + topics, + } + + project(this) json_object! {{ + this.transaction_index, + this.log_index, + this.address, + this.data, + |obj| { + if this.topics { + obj.add("topics", roll(Exp::Value, vec![ + "topic0", + "topic1", + "topic2", + "topic3", + ])); + } + } + }} +} + + +item_field_selection! { + InternalTransactionFieldSelection { + transaction_index, + internal_transaction_index, + hash, + caller_address, + transfer_to_address, + call_value_info, + note, + rejected, + extra, + } + + project(this) json_object! {{ + this.transaction_index, + this.internal_transaction_index, + this.hash, + this.caller_address, + this.transfer_to_address, + this.note, + this.rejected, + this.extra, + [this.call_value_info]: Json, + }} +} + + +type Bytes = String; + + +request! { + pub struct TransactionRequest { + pub r#type: Option>, + pub logs: bool, + pub internal_transactions: bool, + } +} + + +impl TransactionRequest { + fn predicate(&self, p: &mut PredicateBuilder) { + p.col_in_list("type", self.r#type.as_deref()); + } + + fn relations(&self, scan: &mut ScanBuilder) { + if self.logs { + scan.join( + "logs", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + if self.internal_transactions { + scan.join( + "internal_transactions", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + } +} + + +request! { + pub struct TransferTransactionRequest { + pub owner: Option>, + pub to: Option>, + pub logs: bool, + pub internal_transactions: bool, + } +} + + +impl TransferTransactionRequest { + fn predicate(&self, p: &mut PredicateBuilder) { + p.col_eq("type", Some("TransferContract")); + p.col_in_list("_transfer_contract_owner", to_lowercase_list(&self.owner)); + p.col_in_list("_transfer_contract_to", to_lowercase_list(&self.to)); + } + + fn relations(&self, scan: &mut ScanBuilder) { + if self.logs { + scan.join( + "logs", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + if self.internal_transactions { + scan.join( + "internal_transactions", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + } +} + + +request! { + pub struct TransferAssetTransactionRequest { + pub owner: Option>, + pub to: Option>, + pub asset: Option>, + pub logs: bool, + pub internal_transactions: bool, + } +} + + +impl TransferAssetTransactionRequest { + fn predicate(&self, p: &mut PredicateBuilder) { + p.col_eq("type", Some("TransferAssetContract")); + p.col_in_list("_transfer_asset_contract_owner", to_lowercase_list(&self.owner)); + p.col_in_list("_transfer_asset_contract_to", to_lowercase_list(&self.to)); + p.col_in_list("_transfer_asset_contract_asset", self.asset.as_deref()); + } + + fn relations(&self, scan: &mut ScanBuilder) { + if self.logs { + scan.join( + "logs", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + if self.internal_transactions { + scan.join( + "internal_transactions", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + } +} + + +request! { + pub struct TriggerSmartContractTransactionRequest { + pub owner: Option>, + pub contract: Option>, + pub sighash: Option>, + pub logs: bool, + pub internal_transactions: bool, + } +} + + +impl TriggerSmartContractTransactionRequest { + fn predicate(&self, p: &mut PredicateBuilder) { + p.col_eq("type", Some("TriggerSmartContract")); + p.col_in_list("_trigger_smart_contract_owner", to_lowercase_list(&self.owner)); + p.col_in_list("_trigger_smart_contract_contract", to_lowercase_list(&self.contract)); + p.col_in_list("_trigger_smart_contract_sighash", to_lowercase_list(&self.sighash)); + } + + fn relations(&self, scan: &mut ScanBuilder) { + if self.logs { + scan.join( + "logs", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + if self.internal_transactions { + scan.join( + "internal_transactions", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + } +} + + +request! { + pub struct LogRequest { + pub address: Option>, + pub topic0: Option>, + pub topic1: Option>, + pub topic2: Option>, + pub topic3: Option>, + pub transaction: bool, + } +} + + +impl LogRequest { + fn predicate(&self, p: &mut PredicateBuilder) { + p.col_in_list("address", to_lowercase_list(&self.address)); + p.col_in_list("topic0", to_lowercase_list(&self.topic0)); + p.col_in_list("topic1", to_lowercase_list(&self.topic1)); + p.col_in_list("topic2", to_lowercase_list(&self.topic2)); + p.col_in_list("topic3", to_lowercase_list(&self.topic3)); + } + + fn relations(&self, scan: &mut ScanBuilder) { + if self.transaction { + scan.join( + "transactions", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + } +} + + +request! { + pub struct InternalTransactionRequest { + pub caller: Option>, + pub transfer_to: Option>, + pub transaction: bool, + } +} + + +impl InternalTransactionRequest { + fn predicate(&self, p: &mut PredicateBuilder) { + p.col_in_list("caller_address", to_lowercase_list(&self.caller)); + p.col_in_list("transfer_to_address", to_lowercase_list(&self.transfer_to)); + } + + fn relations(&self, scan: &mut ScanBuilder) { + if self.transaction { + scan.join( + "transactions", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"], + ); + } + } +} + + +request! { + pub struct TronQuery { + pub from_block: BlockNumber, + pub parent_block_hash: Option, + pub to_block: Option, + pub fields: FieldSelection, + pub include_all_blocks: bool, + pub transactions: Vec, + pub transfer_transactions: Vec, + pub transfer_asset_transactions: Vec, + pub trigger_smart_contract_transactions: Vec, + pub logs: Vec, + pub internal_transactions: Vec, + } +} + + +impl TronQuery { + pub fn validate(&self) -> anyhow::Result<()> { + ensure_block_range!(self); + ensure_item_count!( + self, + transactions, + transfer_transactions, + transfer_asset_transactions, + trigger_smart_contract_transactions, + logs, + internal_transactions + ); + Ok(()) + } + + pub fn compile(&self) -> Plan { + compile_plan!(self, &TABLES, + [blocks: self.fields.block.project()], + [transactions: self.fields.transaction.project()], + [logs: self.fields.log.project()], + [internal_transactions: self.fields.internal_transaction.project()], + transactions, + logs, + internal_transactions, + , + , + , + ) + } +} From f6342ce5519d16163286d90c5c0448eddd4f98ea Mon Sep 17 00:00:00 2001 From: Leszek Wiesner Date: Mon, 20 Apr 2026 11:36:35 +0200 Subject: [PATCH 3/3] Hotblocks ingest: Add Tron mapping --- crates/hotblocks/src/dataset_controller/ingest.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/hotblocks/src/dataset_controller/ingest.rs b/crates/hotblocks/src/dataset_controller/ingest.rs index 7f3bb2dc..8a1dd5c4 100644 --- a/crates/hotblocks/src/dataset_controller/ingest.rs +++ b/crates/hotblocks/src/dataset_controller/ingest.rs @@ -45,6 +45,9 @@ pub fn ingest<'a, 'b>( DatasetKind::HyperliquidReplicaCmds => { run!(sqd_data::hyperliquid_replica_cmds::tables::HyperliquidReplicaCmdsChunkBuilder::new()) } + DatasetKind::Tron => { + run!(sqd_data::tron::tables::TronChunkBuilder::new()) + } } }