stratus/eth/primitives/
block.rs

1use alloy_primitives::B256;
2use alloy_rpc_types_eth::BlockTransactions;
3use alloy_trie::root::ordered_trie_root;
4use display_json::DebugAsJson;
5use itertools::Itertools;
6
7use super::ExternalBlock;
8use super::Index;
9use super::LogMined;
10use super::PendingBlock;
11use super::Size;
12use super::TransactionExecution;
13use crate::alias::AlloyBlockAlloyTransaction;
14use crate::alias::AlloyBlockB256;
15use crate::alias::AlloyTransaction;
16use crate::alias::JsonValue;
17use crate::eth::primitives::BlockHeader;
18use crate::eth::primitives::BlockNumber;
19use crate::eth::primitives::Hash;
20use crate::eth::primitives::TransactionMined;
21use crate::eth::primitives::UnixTime;
22use crate::ext::to_json_value;
23
24#[derive(DebugAsJson, Clone, PartialEq, Eq, fake::Dummy, serde::Serialize, serde::Deserialize)]
25pub struct Block {
26    pub header: BlockHeader,
27    pub transactions: Vec<TransactionMined>,
28}
29
30impl Block {
31    /// Creates a new block with the given number and timestamp.
32    pub fn new(number: BlockNumber, timestamp: UnixTime) -> Self {
33        Self {
34            header: BlockHeader::new(number, timestamp),
35            transactions: Vec::new(),
36        }
37    }
38
39    /// Constructs an empty genesis block.
40    pub fn genesis() -> Block {
41        Block::new(BlockNumber::ZERO, UnixTime::from(1702568764))
42    }
43
44    /// Calculates block size label by the number of transactions.
45    pub fn label_size_by_transactions(&self) -> &'static str {
46        match self.transactions.len() {
47            0 => "0",
48            1..=5 => "1-5",
49            6..=10 => "6-10",
50            11..=15 => "11-15",
51            16..=20 => "16-20",
52            _ => "20+",
53        }
54    }
55
56    /// Calculates block size label by consumed gas.
57    pub fn label_size_by_gas(&self) -> &'static str {
58        match self.header.gas_used.as_u64() {
59            0 => "0",
60            1..=1_000_000 => "0-1M",
61            1_000_001..=2_000_000 => "1M-2M",
62            2_000_001..=3_000_000 => "2M-3M",
63            3_000_001..=4_000_000 => "3M-4M",
64            4_000_001..=5_000_000 => "4M-5M",
65            5_000_001..=6_000_000 => "5M-6M",
66            6_000_001..=7_000_000 => "6M-7M",
67            7_000_001..=8_000_000 => "7M-8M",
68            8_000_001..=9_000_000 => "8M-9M",
69            9_000_001..=10_000_000 => "9M-10M",
70            _ => "10M+",
71        }
72    }
73
74    /// Serializes itself to JSON-RPC block format with full transactions included.
75    pub fn to_json_rpc_with_full_transactions(self) -> JsonValue {
76        let alloy_block: AlloyBlockAlloyTransaction = self.into();
77        to_json_value(alloy_block)
78    }
79
80    /// Serializes itself to JSON-RPC block format with only transactions hashes included.
81    pub fn to_json_rpc_with_transactions_hashes(self) -> JsonValue {
82        let alloy_block: AlloyBlockB256 = self.into();
83        to_json_value(alloy_block)
84    }
85
86    /// Returns the block number.
87    pub fn number(&self) -> BlockNumber {
88        self.header.number
89    }
90
91    /// Returns the block hash.
92    pub fn hash(&self) -> Hash {
93        self.header.hash
94    }
95
96    fn mine_transaction(&mut self, tx: TransactionExecution, transaction_index: Index, log_index: &mut Index) -> TransactionMined {
97        let mined_logs = Self::mine_logs(self, &tx, transaction_index, log_index);
98
99        TransactionMined {
100            input: tx.input,
101            block_timestamp: tx.result.execution.block_timestamp,
102            result: tx.result.execution.result,
103            output: tx.result.execution.output,
104            gas: tx.result.execution.gas,
105            deployed_contract_address: tx.result.execution.deployed_contract_address,
106            transaction_index,
107            block_number: self.header.number,
108            block_hash: self.header.hash,
109            logs: mined_logs,
110        }
111    }
112
113    fn mine_logs(&mut self, tx: &TransactionExecution, transaction_index: Index, log_index: &mut Index) -> Vec<LogMined> {
114        tx.result
115            .execution
116            .logs
117            .iter()
118            .map(|mined_log| {
119                self.header.bloom.accrue_log(mined_log);
120                let log = LogMined::mine_log(mined_log.clone(), self.number(), self.hash(), tx, *log_index, transaction_index);
121                *log_index = *log_index + Index::ONE;
122                log
123            })
124            .collect()
125    }
126
127    fn calculate_transaction_root(&mut self) {
128        if !self.transactions.is_empty() {
129            let transactions_hashes: Vec<B256> = self.transactions.iter().map(|x| x.input.hash).map(B256::from).collect();
130            self.header.transactions_root = ordered_trie_root(&transactions_hashes).into();
131        }
132    }
133
134    fn update_block_hash(&mut self) {
135        for transaction in self.transactions.iter_mut() {
136            transaction.block_hash = self.header.hash;
137            for log in transaction.logs.iter_mut() {
138                log.block_hash = self.header.hash;
139            }
140        }
141    }
142
143    pub fn apply_external(&mut self, external_block: &ExternalBlock) {
144        self.header.hash = external_block.hash();
145        self.header.timestamp = external_block.timestamp();
146        for transaction in self.transactions.iter_mut() {
147            assert!(transaction.block_timestamp == self.header.timestamp);
148            transaction.block_hash = external_block.hash();
149            for log in transaction.logs.iter_mut() {
150                log.block_hash = external_block.hash();
151            }
152        }
153    }
154}
155
156impl From<PendingBlock> for Block {
157    fn from(value: PendingBlock) -> Self {
158        let mut block = Block::new(value.header.number, *value.header.timestamp);
159        let txs: Vec<TransactionExecution> = value.transactions.into_values().collect();
160        block.transactions.reserve(txs.len());
161        block.header.size = Size::from(txs.len() as u64);
162
163        let mut log_index = Index::ZERO;
164        for (tx_idx, tx) in txs.into_iter().enumerate() {
165            let transaction_index = Index::new(tx_idx as u64);
166            let mined_transaction = Self::mine_transaction(&mut block, tx, transaction_index, &mut log_index);
167            block.transactions.push(mined_transaction);
168        }
169
170        Self::calculate_transaction_root(&mut block);
171        Self::update_block_hash(&mut block);
172
173        block
174    }
175}
176
177// -----------------------------------------------------------------------------
178// Conversions: Self -> Other
179// -----------------------------------------------------------------------------
180impl From<Block> for AlloyBlockAlloyTransaction {
181    fn from(block: Block) -> Self {
182        let alloy_block: AlloyBlockAlloyTransaction = block.header.into();
183        let transactions: Vec<AlloyTransaction> = block.transactions.into_iter().map_into().collect();
184
185        Self {
186            transactions: BlockTransactions::Full(transactions),
187            ..alloy_block
188        }
189    }
190}
191
192impl From<Block> for AlloyBlockB256 {
193    fn from(block: Block) -> Self {
194        let alloy_block: AlloyBlockB256 = block.header.into();
195        let transaction_hashes: Vec<B256> = block.transactions.into_iter().map(|x| x.input.hash).map(B256::from).collect();
196
197        Self {
198            transactions: BlockTransactions::Hashes(transaction_hashes),
199            ..alloy_block
200        }
201    }
202}