hypersync_client/
simple_types.rs

1//! Base object types for the Hypersync client.
2use std::{collections::HashMap, sync::Arc};
3
4use arrayvec::ArrayVec;
5use hypersync_format::{
6    AccessList, Address, Authorization, BlockNumber, BloomFilter, Data, Hash, LogArgument,
7    LogIndex, Nonce, Quantity, TransactionIndex, TransactionStatus, TransactionType, Withdrawal,
8};
9use hypersync_net_types::{
10    block::BlockField, log::LogField, transaction::TransactionField, FieldSelection,
11};
12use nohash_hasher::IntMap;
13use serde::{Deserialize, Serialize};
14use xxhash_rust::xxh3::Xxh3Builder;
15
16use crate::types::ResponseData;
17
18/// An Ethereum event object.
19#[derive(Debug, Default, Clone, PartialEq)]
20pub struct Event {
21    /// An Ethereum event transaction object.
22    pub transaction: Option<Arc<Transaction>>,
23    /// An Ethereum event block object.
24    pub block: Option<Arc<Block>>,
25    /// An Ethereum event log object.
26    pub log: Log,
27}
28
29// Field lists for implementing event based API, these fields are used for joining
30// so they should always be added to the field selection.
31const BLOCK_JOIN_FIELD: BlockField = BlockField::Number;
32const TX_JOIN_FIELD: TransactionField = TransactionField::Hash;
33const LOG_JOIN_FIELD_WITH_TX: LogField = LogField::TransactionHash;
34const LOG_JOIN_FIELD_WITH_BLOCK: LogField = LogField::BlockNumber;
35
36enum InternalJoinStrategy {
37    NotSelected,
38    OnlyLogJoinField,
39    FullJoin,
40}
41
42/// Internal event join strategy for determining how to join blocks and transactions with logs
43pub(crate) struct InternalEventJoinStrategy {
44    block: InternalJoinStrategy,
45    transaction: InternalJoinStrategy,
46}
47
48impl From<&FieldSelection> for InternalEventJoinStrategy {
49    fn from(field_selection: &FieldSelection) -> Self {
50        let block_fields_num = field_selection.block.len();
51        let transaction_fields_num = field_selection.transaction.len();
52
53        Self {
54            block: if block_fields_num == 0 {
55                InternalJoinStrategy::NotSelected
56            } else if block_fields_num == 1 && field_selection.block.contains(&BLOCK_JOIN_FIELD) {
57                InternalJoinStrategy::OnlyLogJoinField
58            } else {
59                InternalJoinStrategy::FullJoin
60            },
61            transaction: if transaction_fields_num == 0 {
62                InternalJoinStrategy::NotSelected
63            } else if transaction_fields_num == 1
64                && field_selection.transaction.contains(&TX_JOIN_FIELD)
65            {
66                InternalJoinStrategy::OnlyLogJoinField
67            } else {
68                InternalJoinStrategy::FullJoin
69            },
70        }
71    }
72}
73
74impl InternalEventJoinStrategy {
75    /// Add join fields to field selection based on the event join strategy
76    pub(crate) fn add_join_fields_to_selection(&self, field_selection: &mut FieldSelection) {
77        match self.block {
78            InternalJoinStrategy::NotSelected => (),
79            InternalJoinStrategy::OnlyLogJoinField => {
80                field_selection.log.insert(LOG_JOIN_FIELD_WITH_BLOCK);
81                field_selection.block.remove(&BLOCK_JOIN_FIELD);
82            }
83            InternalJoinStrategy::FullJoin => {
84                field_selection.log.insert(LOG_JOIN_FIELD_WITH_BLOCK);
85                field_selection.block.insert(BLOCK_JOIN_FIELD);
86            }
87        }
88
89        match self.transaction {
90            InternalJoinStrategy::NotSelected => (),
91            InternalJoinStrategy::OnlyLogJoinField => {
92                field_selection.log.insert(LOG_JOIN_FIELD_WITH_TX);
93                field_selection.transaction.remove(&TX_JOIN_FIELD);
94            }
95            InternalJoinStrategy::FullJoin => {
96                field_selection.log.insert(LOG_JOIN_FIELD_WITH_TX);
97                field_selection.transaction.insert(TX_JOIN_FIELD);
98            }
99        }
100    }
101
102    /// Join response data into events based on the event join strategy
103    pub(crate) fn join_from_response_data(&self, data: ResponseData) -> Vec<Event> {
104        let blocks = data
105            .blocks
106            .into_iter()
107            .flat_map(|blocks| {
108                blocks
109                    .into_iter()
110                    .map(|block| (block.number.unwrap(), Arc::new(block)))
111            })
112            .collect::<IntMap<u64, _>>();
113
114        let transactions = data
115            .transactions
116            .into_iter()
117            .flat_map(|txs| {
118                txs.into_iter()
119                    .map(|tx| (tx.hash.clone().unwrap(), Arc::new(tx)))
120            })
121            .collect::<HashMap<_, _, Xxh3Builder>>();
122
123        data.logs
124            .into_iter()
125            .flat_map(|logs| {
126                logs.into_iter().map(|log| {
127                    let block = match self.block {
128                        InternalJoinStrategy::NotSelected => None,
129                        InternalJoinStrategy::OnlyLogJoinField => Some(Arc::new(Block {
130                            number: Some(log.block_number.unwrap().into()),
131                            ..Block::default()
132                        })),
133                        InternalJoinStrategy::FullJoin => {
134                            blocks.get(&log.block_number.unwrap().into()).cloned()
135                        }
136                    };
137                    let transaction = match self.transaction {
138                        InternalJoinStrategy::NotSelected => None,
139                        InternalJoinStrategy::OnlyLogJoinField => Some(Arc::new(Transaction {
140                            hash: log.transaction_hash.clone(),
141                            ..Transaction::default()
142                        })),
143                        InternalJoinStrategy::FullJoin => transactions
144                            .get(log.transaction_hash.as_ref().unwrap())
145                            .cloned(),
146                    };
147
148                    Event {
149                        transaction,
150                        block,
151                        log,
152                    }
153                })
154            })
155            .collect()
156    }
157}
158
159/// Block object
160#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct Block {
162    /// A scalar value equal to the number of ancestor blocks. The genesis block has a number of
163    /// zero; formally Hi.
164    pub number: Option<u64>,
165    /// The Keccak 256-bit hash of the block
166    pub hash: Option<Hash>,
167    /// The Keccak 256-bit hash of the parent
168    /// block’s header, in its entirety; formally Hp.
169    pub parent_hash: Option<Hash>,
170    /// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of
171    /// computation has been carried out on this block; formally Hn.
172    pub nonce: Option<Nonce>,
173    /// The Keccak 256-bit hash of the ommers/uncles list portion of this block; formally Ho.
174    pub sha3_uncles: Option<Hash>,
175    /// The Bloom filter composed from indexable information (logger address and log topics)
176    /// contained in each log entry from the receipt of each transaction in the transactions list;
177    /// formally Hb.
178    pub logs_bloom: Option<BloomFilter>,
179    /// The Keccak 256-bit hash of the root node of the trie structure populated with each
180    /// transaction in the transactions list portion of the block; formally Ht.
181    pub transactions_root: Option<Hash>,
182    /// The Keccak 256-bit hash of the root node of the state trie, after all transactions are
183    /// executed and finalisations applied; formally Hr.
184    pub state_root: Option<Hash>,
185    /// The Keccak 256-bit hash of the root node of the trie structure populated with each
186    /// transaction in the transactions list portion of the block; formally Ht.
187    pub receipts_root: Option<Hash>,
188    /// The 160-bit address to which all fees collected from the successful mining of this block
189    /// be transferred; formally Hc.
190    pub miner: Option<Address>,
191    /// A scalar value corresponding to the difficulty level of this block. This can be calculated
192    /// from the previous block’s difficulty level and the timestamp; formally Hd.
193    pub difficulty: Option<Quantity>,
194    /// The cumulative sum of the difficulty of all blocks that have been mined in the Ethereum
195    /// network since the inception of the network.
196    /// It measures the overall security and integrity of the Ethereum network.
197    pub total_difficulty: Option<Quantity>,
198    /// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or
199    /// fewer; formally Hx.
200    pub extra_data: Option<Data>,
201    /// The size of this block in bytes as an integer value, encoded as hexadecimal.
202    pub size: Option<Quantity>,
203    /// A scalar value equal to the current limit of gas expenditure per block; formally Hl.
204    pub gas_limit: Option<Quantity>,
205    /// A scalar value equal to the total gas used in transactions in this block; formally Hg.
206    pub gas_used: Option<Quantity>,
207    /// A scalar value equal to the reasonable output of Unix’s time() at this block’s inception;
208    /// formally Hs.
209    pub timestamp: Option<Quantity>,
210    /// Ommers/uncles header.
211    pub uncles: Option<Vec<Hash>>,
212    /// A scalar representing EIP1559 base fee which can move up or down each block according
213    /// to a formula which is a function of gas used in parent block and gas target
214    /// (block gas limit divided by elasticity multiplier) of parent block.
215    /// The algorithm results in the base fee per gas increasing when blocks are
216    /// above the gas target, and decreasing when blocks are below the gas target. The base fee per
217    /// gas is burned.
218    pub base_fee_per_gas: Option<Quantity>,
219    /// The total amount of blob gas consumed by the transactions within the block, added in
220    /// EIP-4844.
221    pub blob_gas_used: Option<Quantity>,
222    /// A running total of blob gas consumed in excess of the target, prior to the block. Blocks
223    /// with above-target blob gas consumption increase this value, blocks with below-target blob
224    /// gas consumption decrease it (bounded at 0). This was added in EIP-4844.
225    pub excess_blob_gas: Option<Quantity>,
226    /// The hash of the parent beacon block's root is included in execution blocks, as proposed by
227    /// EIP-4788.
228    ///
229    /// This enables trust-minimized access to consensus state, supporting staking pools, bridges,
230    /// and more.
231    ///
232    /// The beacon roots contract handles root storage, enhancing Ethereum's functionalities.
233    pub parent_beacon_block_root: Option<Hash>,
234    /// The Keccak 256-bit hash of the withdrawals list portion of this block.
235    ///
236    /// See [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895).
237    pub withdrawals_root: Option<Hash>,
238    /// Withdrawal represents a validator withdrawal from the consensus layer.
239    pub withdrawals: Option<Vec<Withdrawal>>,
240    /// The L1 block number that would be used for block.number calls.
241    pub l1_block_number: Option<BlockNumber>,
242    /// The number of L2 to L1 messages since Nitro genesis.
243    pub send_count: Option<Quantity>,
244    /// The Merkle root of the outbox tree state.
245    pub send_root: Option<Hash>,
246    /// A 256-bit hash which, combined with the
247    /// nonce, proves that a sufficient amount of computation has been carried out on this block;
248    /// formally Hm.
249    pub mix_hash: Option<Hash>,
250}
251
252/// Transaction object
253#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
254pub struct Transaction {
255    /// The Keccak 256-bit hash of the block
256    pub block_hash: Option<Hash>,
257    /// A scalar value equal to the number of ancestor blocks. The genesis block has a number of
258    /// zero; formally Hi.
259    pub block_number: Option<BlockNumber>,
260    /// The 160-bit address of the message call’s sender
261    pub from: Option<Address>,
262    /// A scalar value equal to the maximum
263    /// amount of gas that should be used in executing
264    /// this transaction. This is paid up-front, before any
265    /// computation is done and may not be increased
266    /// later; formally Tg.
267    pub gas: Option<Quantity>,
268    /// A scalar value equal to the number of
269    /// Wei to be paid per unit of gas for all computation
270    /// costs incurred as a result of the execution of this transaction; formally Tp.
271    pub gas_price: Option<Quantity>,
272    /// A transaction hash is a keccak hash of an RLP encoded signed transaction.
273    pub hash: Option<Hash>,
274    /// Input has two uses depending if transaction is Create or Call (if `to` field is None or
275    /// Some). pub init: An unlimited size byte array specifying the
276    /// EVM-code for the account initialisation procedure CREATE,
277    /// data: An unlimited size byte array specifying the
278    /// input data of the message call, formally Td.
279    pub input: Option<Data>,
280    /// A scalar value equal to the number of transactions sent by the sender; formally Tn.
281    pub nonce: Option<Quantity>,
282    /// The 160-bit address of the message call’s recipient or, for a contract creation
283    /// transaction, ∅, used here to denote the only member of B0 ; formally Tt.
284    pub to: Option<Address>,
285    /// Index of the transaction in the block
286    pub transaction_index: Option<TransactionIndex>,
287    /// A scalar value equal to the number of Wei to
288    /// be transferred to the message call’s recipient or,
289    /// in the case of contract creation, as an endowment
290    /// to the newly created account; formally Tv.
291    pub value: Option<Quantity>,
292    /// Replay protection value based on chain_id. See EIP-155 for more info.
293    pub v: Option<Quantity>,
294    /// The R field of the signature; the point on the curve.
295    pub r: Option<Quantity>,
296    /// The S field of the signature; the point on the curve.
297    pub s: Option<Quantity>,
298    /// yParity: Signature Y parity; formally Ty
299    pub y_parity: Option<Quantity>,
300    /// Max Priority fee that transaction is paying
301    ///
302    /// As ethereum circulation is around 120mil eth as of 2022 that is around
303    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
304    /// 340282366920938463463374607431768211455
305    ///
306    /// This is also known as `GasTipCap`
307    pub max_priority_fee_per_gas: Option<Quantity>,
308    /// A scalar value equal to the maximum
309    /// amount of gas that should be used in executing
310    /// this transaction. This is paid up-front, before any
311    /// computation is done and may not be increased
312    /// later; formally Tg.
313    ///
314    /// As ethereum circulation is around 120mil eth as of 2022 that is around
315    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
316    /// 340282366920938463463374607431768211455
317    ///
318    /// This is also known as `GasFeeCap`
319    pub max_fee_per_gas: Option<Quantity>,
320    /// Added as EIP-pub 155: Simple replay attack protection
321    pub chain_id: Option<Quantity>,
322    /// The accessList specifies a list of addresses and storage keys;
323    /// these addresses and storage keys are added into the `accessed_addresses`
324    /// and `accessed_storage_keys` global sets (introduced in EIP-2929).
325    /// A gas cost is charged, though at a discount relative to the cost of
326    /// accessing outside the list.
327    pub access_list: Option<Vec<AccessList>>,
328    /// The authorization_list specifies a list of authorizations for the transaction
329    /// (introduced in EIP-7702)
330    pub authorization_list: Option<Vec<Authorization>>,
331    /// Max fee per data gas
332    ///
333    /// aka BlobFeeCap or blobGasFeeCap
334    pub max_fee_per_blob_gas: Option<Quantity>,
335    /// It contains a vector of fixed size hash(32 bytes)
336    pub blob_versioned_hashes: Option<Vec<Hash>>,
337    /// The total amount of gas used in the block until this transaction was executed.
338    pub cumulative_gas_used: Option<Quantity>,
339    /// The sum of the base fee and tip paid per unit of gas.
340    pub effective_gas_price: Option<Quantity>,
341    /// Gas used by transaction
342    pub gas_used: Option<Quantity>,
343    /// Address of created contract if transaction was a contract creation
344    pub contract_address: Option<Address>,
345    /// Bloom filter for logs produced by this transaction
346    pub logs_bloom: Option<BloomFilter>,
347    /// Transaction type. For ethereum: Legacy, Eip2930, Eip1559, Eip4844
348    #[serde(rename = "type")]
349    pub kind: Option<TransactionType>,
350    /// The Keccak 256-bit hash of the root node of the trie structure populated with each
351    /// transaction in the transactions list portion of the block; formally Ht.
352    pub root: Option<Hash>,
353    /// If transaction is executed successfully.
354    ///
355    /// This is the `statusCode`
356    pub status: Option<TransactionStatus>,
357    /// The fee associated with a transaction on the Layer 1,
358    /// it is calculated as l1GasPrice multiplied by l1GasUsed
359    pub l1_fee: Option<Quantity>,
360    /// The gas price for transactions on the Layer 1
361    pub l1_gas_price: Option<Quantity>,
362    /// The amount of gas consumed by a transaction on the Layer 1
363    pub l1_gas_used: Option<Quantity>,
364    /// A multiplier applied to the actual gas usage on Layer 1 to calculate the dynamic costs.
365    /// If set to 1, it has no impact on the L1 gas usage
366    pub l1_fee_scalar: Option<f64>,
367    /// Amount of gas spent on L1 calldata in units of L2 gas.
368    pub gas_used_for_l1: Option<Quantity>,
369}
370
371/// Log object
372#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
373pub struct Log {
374    /// The boolean value indicating if the event was removed from the blockchain due
375    /// to a chain reorganization. True if the log was removed. False if it is a valid log.
376    pub removed: Option<bool>,
377    /// The integer identifying the index of the event within the block's list of events.
378    pub log_index: Option<LogIndex>,
379    /// The integer index of the transaction within the block's list of transactions.
380    pub transaction_index: Option<TransactionIndex>,
381    /// The hash of the transaction that triggered the event.
382    pub transaction_hash: Option<Hash>,
383    /// The hash of the block in which the event was included.
384    pub block_hash: Option<Hash>,
385    /// The block number in which the event was included.
386    pub block_number: Option<BlockNumber>,
387    /// The contract address from which the event originated.
388    pub address: Option<Address>,
389    /// The non-indexed data that was emitted along with the event.
390    pub data: Option<Data>,
391    /// An array of 32-byte data fields containing indexed event parameters.
392    pub topics: ArrayVec<Option<LogArgument>, 4>,
393}
394
395/// Trace object
396#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
397pub struct Trace {
398    /// The address of the sender who initiated the transaction.
399    pub from: Option<Address>,
400    /// The address of the recipient of the transaction if it was a transaction to an address.
401    /// For contract creation transactions, this field is None.
402    pub to: Option<Address>,
403    /// The type of trace, `call` or `delegatecall`, two ways to invoke a function in a smart contract.
404    ///
405    /// `call` creates a new environment for the function to work in, so changes made in that
406    /// function won't affect the environment where the function was called.
407    ///
408    /// `delegatecall` doesn't create a new environment. Instead, it runs the function within the
409    /// environment of the caller, so changes made in that function will affect the caller's environment.
410    pub call_type: Option<String>,
411    /// The units of gas included in the transaction by the sender.
412    pub gas: Option<Quantity>,
413    /// The optional input data sent with the transaction, usually used to interact with smart contracts.
414    pub input: Option<Data>,
415    /// The init code.
416    pub init: Option<Data>,
417    /// The value of the native token transferred along with the transaction, in Wei.
418    pub value: Option<Quantity>,
419    /// The address of the receiver for reward transaction.
420    pub author: Option<Address>,
421    /// Kind of reward. `Block` reward or `Uncle` reward.
422    pub reward_type: Option<String>,
423    /// The hash of the block in which the transaction was included.
424    pub block_hash: Option<Hash>,
425    /// The number of the block in which the transaction was included.
426    pub block_number: Option<u64>,
427    /// Destroyed address.
428    pub address: Option<Address>,
429    /// Contract code.
430    pub code: Option<Data>,
431    /// The total used gas by the call, encoded as hexadecimal.
432    pub gas_used: Option<Quantity>,
433    /// The return value of the call, encoded as a hexadecimal string.
434    pub output: Option<Data>,
435    /// The number of sub-traces created during execution. When a transaction is executed on the EVM,
436    /// it may trigger additional sub-executions, such as when a smart contract calls another smart
437    /// contract or when an external account is accessed.
438    pub subtraces: Option<u64>,
439    /// An array that indicates the position of the transaction in the trace.
440    pub trace_address: Option<Vec<u64>>,
441    /// The hash of the transaction.
442    pub transaction_hash: Option<Hash>,
443    /// The index of the transaction in the block.
444    pub transaction_position: Option<u64>,
445    /// The type of action taken by the transaction, `call`, `create`, `reward` and `suicide`.
446    ///
447    /// `call` is the most common type of trace and occurs when a smart contract invokes another contract's function.
448    ///
449    /// `create` represents the creation of a new smart contract. This type of trace occurs when a smart contract is deployed to the blockchain.
450    #[serde(rename = "type")]
451    pub kind: Option<String>,
452    /// A string that indicates whether the transaction was successful or not.
453    ///
454    /// None if successful, Reverted if not.
455    pub error: Option<String>,
456}