hdp_primitives/task/datalake/transactions/
rlp_fields.rs

1use std::{fmt::Display, str::FromStr};
2
3use alloy::{consensus::Eip658Value, primitives::U256};
4use anyhow::{bail, Result};
5use eth_trie_proofs::{tx::ConsensusTx, tx_receipt::ConsensusTxReceipt};
6use serde::{Deserialize, Serialize};
7
8use crate::task::datalake::DatalakeField;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub enum TransactionField {
12    // ===== Transaction fields =====
13    Nonce,
14    GasPrice,
15    GasLimit,
16    To,
17    Value,
18    Input,
19    V,
20    R,
21    S,
22    ChainId,
23    // Not for legacy transactions
24    AccessList,
25
26    // EIP-1559 transactions and EIP-4844 transactions
27    MaxFeePerGas,
28    // EIP-1559 transactions and EIP-4844 transactions
29    MaxPriorityFeePerGas,
30
31    // Only for EIP-4844 transactions
32    BlobVersionedHashes,
33    // Only for EIP-4844 transactions
34    MaxFeePerBlobGas,
35}
36
37impl TransactionField {
38    pub fn variants() -> Vec<String> {
39        vec![
40            "NONCE".to_string(),
41            "GAS_PRICE".to_string(),
42            "GAS_LIMIT".to_string(),
43            "TO".to_string(),
44            "VALUE".to_string(),
45            "INPUT".to_string(),
46            "V".to_string(),
47            "R".to_string(),
48            "S".to_string(),
49            "CHAIN_ID".to_string(),
50            "ACCESS_LIST".to_string(),
51            "MAX_FEE_PER_GAS".to_string(),
52            "MAX_PRIORITY_FEE_PER_GAS".to_string(),
53            "BLOB_VERSIONED_HASHES".to_string(),
54            "MAX_FEE_PER_BLOB_GAS".to_string(),
55        ]
56    }
57
58    /// This function is for generating random TransactionField for testing purposes.
59    pub fn integer_variants_index(index: u8) -> Self {
60        match index {
61            0 => TransactionField::Nonce,
62            1 => TransactionField::GasPrice,
63            2 => TransactionField::GasLimit,
64            3 => TransactionField::ChainId,
65            4 => TransactionField::MaxFeePerGas,
66            5 => TransactionField::MaxPriorityFeePerGas,
67            6 => TransactionField::MaxFeePerBlobGas,
68            _ => unreachable!(),
69        }
70    }
71}
72
73// Note: This index is use to parse the transaction datalake field from the datalake's sampled property.
74// It is not used to index the transaction datalake field itself.
75impl DatalakeField for TransactionField {
76    fn from_index(index: u8) -> Result<Self> {
77        match index {
78            0 => Ok(TransactionField::Nonce),
79            1 => Ok(TransactionField::GasPrice),
80            2 => Ok(TransactionField::GasLimit),
81            3 => Ok(TransactionField::To),
82            4 => Ok(TransactionField::Value),
83            5 => Ok(TransactionField::Input),
84            6 => Ok(TransactionField::V),
85            7 => Ok(TransactionField::R),
86            8 => Ok(TransactionField::S),
87            9 => Ok(TransactionField::ChainId),
88            10 => Ok(TransactionField::AccessList),
89            11 => Ok(TransactionField::MaxFeePerGas),
90            12 => Ok(TransactionField::MaxPriorityFeePerGas),
91            13 => Ok(TransactionField::BlobVersionedHashes),
92            14 => Ok(TransactionField::MaxFeePerBlobGas),
93            _ => bail!("Invalid transaction field index"),
94        }
95    }
96
97    fn to_index(&self) -> u8 {
98        match self {
99            TransactionField::Nonce => 0,
100            TransactionField::GasPrice => 1,
101            TransactionField::GasLimit => 2,
102            TransactionField::To => 3,
103            TransactionField::Value => 4,
104            TransactionField::Input => 5,
105            TransactionField::V => 6,
106            TransactionField::R => 7,
107            TransactionField::S => 8,
108            TransactionField::ChainId => 9,
109            TransactionField::AccessList => 10,
110            TransactionField::MaxFeePerGas => 11,
111            TransactionField::MaxPriorityFeePerGas => 12,
112            TransactionField::BlobVersionedHashes => 13,
113            TransactionField::MaxFeePerBlobGas => 14,
114        }
115    }
116
117    fn decode_field_from_rlp(&self, rlp: &[u8]) -> U256 {
118        let raw_tx = ConsensusTx::rlp_decode(rlp).unwrap();
119        match self {
120            TransactionField::Nonce => U256::from(raw_tx.nonce()),
121            TransactionField::GasPrice => {
122                U256::from(raw_tx.gas_price().expect("gas price does not exist"))
123            }
124            TransactionField::GasLimit => U256::from(raw_tx.gas_limit()),
125            TransactionField::To => U256::from_str_radix(
126                &raw_tx.to().to().expect("to does not exist").to_string(),
127                16,
128            )
129            .unwrap(),
130            TransactionField::Value => U256::from(raw_tx.value()),
131            TransactionField::Input => U256::from_be_slice(raw_tx.input()),
132            TransactionField::V => U256::from(raw_tx.v()),
133            TransactionField::R => U256::from(raw_tx.r()),
134            TransactionField::S => U256::from(raw_tx.s()),
135            TransactionField::ChainId => {
136                U256::from(raw_tx.chain_id().expect("chain id does not exist"))
137            }
138            // TODO:  string should be properly rlp encoded
139            TransactionField::AccessList => todo!("access list cannot parse into u256"),
140            TransactionField::MaxFeePerGas => U256::from(
141                raw_tx
142                    .max_fee_per_gas()
143                    .expect("max fee per gas does not exist"),
144            ),
145            TransactionField::MaxPriorityFeePerGas => U256::from(
146                raw_tx
147                    .max_priority_fee_per_gas()
148                    .expect("max priority fee per gas does not exist"),
149            ),
150            TransactionField::BlobVersionedHashes => raw_tx
151                .blob_versioned_hashes()
152                .expect("blob versioned hashes does not exist")[0]
153                .into(),
154            TransactionField::MaxFeePerBlobGas => U256::from(
155                raw_tx
156                    .max_fee_per_blob_gas()
157                    .expect("max fee per blob gas does not exist"),
158            ),
159        }
160    }
161}
162
163impl FromStr for TransactionField {
164    type Err = anyhow::Error;
165
166    fn from_str(s: &str) -> Result<Self> {
167        match s {
168            "NONCE" => Ok(TransactionField::Nonce),
169            "GAS_PRICE" => Ok(TransactionField::GasPrice),
170            "GAS_LIMIT" => Ok(TransactionField::GasLimit),
171            "TO" => Ok(TransactionField::To),
172            "VALUE" => Ok(TransactionField::Value),
173            "INPUT" => Ok(TransactionField::Input),
174            "V" => Ok(TransactionField::V),
175            "R" => Ok(TransactionField::R),
176            "S" => Ok(TransactionField::S),
177            "CHAIN_ID" => Ok(TransactionField::ChainId),
178            "ACCESS_LIST" => Ok(TransactionField::AccessList),
179            "MAX_FEE_PER_GAS" => Ok(TransactionField::MaxFeePerGas),
180            "MAX_PRIORITY_FEE_PER_GAS" => Ok(TransactionField::MaxPriorityFeePerGas),
181            "BLOB_VERSIONED_HASHES" => Ok(TransactionField::BlobVersionedHashes),
182            "MAX_FEE_PER_BLOB_GAS" => Ok(TransactionField::MaxFeePerBlobGas),
183            _ => bail!("Unknown transaction datalake field"),
184        }
185    }
186}
187
188impl Display for TransactionField {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        match self {
191            TransactionField::Nonce => write!(f, "NONCE"),
192            TransactionField::GasPrice => write!(f, "GAS_PRICE"),
193            TransactionField::GasLimit => write!(f, "GAS_LIMIT"),
194            TransactionField::To => write!(f, "TO"),
195            TransactionField::Value => write!(f, "VALUE"),
196            TransactionField::Input => write!(f, "INPUT"),
197            TransactionField::V => write!(f, "V"),
198            TransactionField::R => write!(f, "R"),
199            TransactionField::S => write!(f, "S"),
200            TransactionField::ChainId => write!(f, "CHAIN_ID"),
201            TransactionField::AccessList => write!(f, "ACCESS_LIST"),
202            TransactionField::MaxFeePerGas => write!(f, "MAX_FEE_PER_GAS"),
203            TransactionField::MaxPriorityFeePerGas => write!(f, "MAX_PRIORITY_FEE_PER_GAS"),
204            TransactionField::BlobVersionedHashes => write!(f, "BLOB_VERSIONED_HASHES"),
205            TransactionField::MaxFeePerBlobGas => write!(f, "MAX_FEE_PER_BLOB_GAS"),
206        }
207    }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211pub enum TransactionReceiptField {
212    Success,
213    CumulativeGasUsed,
214    Logs,
215    Bloom,
216}
217
218impl TransactionReceiptField {
219    pub fn variants() -> Vec<String> {
220        vec![
221            "SUCCESS".to_string(),
222            "CUMULATIVE_GAS_USED".to_string(),
223            "LOGS".to_string(),
224            "BLOOM".to_string(),
225        ]
226    }
227}
228
229impl FromStr for TransactionReceiptField {
230    type Err = anyhow::Error;
231
232    fn from_str(s: &str) -> Result<Self> {
233        match s {
234            "SUCCESS" => Ok(TransactionReceiptField::Success),
235            "CUMULATIVE_GAS_USED" => Ok(TransactionReceiptField::CumulativeGasUsed),
236            "LOGS" => Ok(TransactionReceiptField::Logs),
237            "BLOOM" => Ok(TransactionReceiptField::Bloom),
238            _ => bail!("Unknown transaction receipt field"),
239        }
240    }
241}
242
243impl Display for TransactionReceiptField {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        match self {
246            TransactionReceiptField::Success => write!(f, "SUCCESS"),
247            TransactionReceiptField::CumulativeGasUsed => write!(f, "CUMULATIVE_GAS_USED"),
248            TransactionReceiptField::Logs => write!(f, "LOGS"),
249            TransactionReceiptField::Bloom => write!(f, "BLOOM"),
250        }
251    }
252}
253
254impl DatalakeField for TransactionReceiptField {
255    fn to_index(&self) -> u8 {
256        match self {
257            TransactionReceiptField::Success => 0,
258            TransactionReceiptField::CumulativeGasUsed => 1,
259            TransactionReceiptField::Logs => 2,
260            TransactionReceiptField::Bloom => 3,
261        }
262    }
263
264    fn from_index(index: u8) -> Result<Self> {
265        match index {
266            0 => Ok(TransactionReceiptField::Success),
267            1 => Ok(TransactionReceiptField::CumulativeGasUsed),
268            2 => Ok(TransactionReceiptField::Logs),
269            3 => Ok(TransactionReceiptField::Bloom),
270            _ => bail!("Invalid transaction receipt field index"),
271        }
272    }
273
274    fn decode_field_from_rlp(&self, rlp: &[u8]) -> U256 {
275        let raw_tx_receipt = ConsensusTxReceipt::rlp_decode(rlp).unwrap();
276
277        match self {
278            TransactionReceiptField::Success => match raw_tx_receipt.status() {
279                Eip658Value::Eip658(bool) => U256::from(*bool as u8),
280                Eip658Value::PostState(state) => (*state).into(),
281            },
282            TransactionReceiptField::CumulativeGasUsed => {
283                U256::from(raw_tx_receipt.cumulative_gas_used())
284            }
285            // TODO: string should be properly rlp encoded
286            TransactionReceiptField::Logs => U256::from(raw_tx_receipt.logs().len()),
287            TransactionReceiptField::Bloom => U256::from(raw_tx_receipt.bloom().len()),
288        }
289    }
290}