1use std::ops::Deref;
2
3use alloy_primitives::U256;
4pub use alloy_primitives::{Log, LogData};
5use alloy_rpc_types::Log as RPCLog;
6use alloy_sol_types::SolValue;
7use itertools::Itertools;
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 Certificate, Committee, Signed, Timestamp, Transaction,
12 consensus::{attestation::TimestampedHeadlessAttestation, committee::CommitteeError},
13 cryptography::{
14 Hash, MerkleMultiProof, Merkleizable,
15 hash::Hashable,
16 merkle_tree::{MerkleBuilder, MerkleProof, StandardMerkleTree, index_prefix},
17 },
18 metadata::{MetadataWrappedItem, PodLogMetadata},
19};
20
21use super::Receipt;
22
23pub fn to_rpc_format(inner_log: Log, tx_hash: Hash) -> RPCLog {
24 RPCLog {
25 inner: inner_log,
26 block_hash: Some(Hash::default()),
27 block_number: Some(1),
28 block_timestamp: None,
29 transaction_hash: Some(tx_hash),
30 transaction_index: Some(0),
31 log_index: Some(0),
32 removed: false,
33 }
34}
35
36#[derive(Debug, Serialize, Deserialize, Clone)]
37#[serde(rename_all = "camelCase")]
38#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
39pub struct Event {
40 pub log: Log,
41 pub log_index: u64,
42 pub tx: Signed<Transaction>,
43 pub attestations: Vec<TimestampedHeadlessAttestation>,
44 pub receipt: Receipt,
45}
46
47impl Merkleizable for LogData {
50 fn append_leaves(&self, builder: &mut MerkleBuilder) {
51 builder.add_slice("topics", self.topics());
52 builder.add_field("data", self.data.hash_custom());
53 }
54}
55
56impl Merkleizable for Log {
57 fn append_leaves(&self, builder: &mut MerkleBuilder) {
58 builder.add_field("address", self.address.hash_custom());
59 builder.add_merkleizable("data", &self.data);
60 }
61}
62
63impl Hashable for Log {
64 fn hash_custom(&self) -> Hash {
65 (self.address, self.data.topics(), &self.data.data)
66 .abi_encode()
67 .hash_custom()
68 }
69}
70
71pub type VerifiableLog = MetadataWrappedItem<RPCLog, PodLogMetadata>;
72
73impl VerifiableLog {
74 pub fn generate_multi_proof(&self) -> Option<(Vec<Hash>, MerkleMultiProof)> {
77 self.inner.log_index.and_then(|i| {
78 self.pod_metadata
79 .receipt
80 .generate_multi_proof_for_log(i.try_into().unwrap())
81 })
82 }
83 pub fn verify(&self, committee: &Committee) -> Result<(), CommitteeError> {
84 committee.verify_certificate(&Certificate {
85 signatures: self
86 .pod_metadata
87 .attestations
88 .iter()
89 .map(|att| att.signature)
90 .collect(),
91 certified: self.pod_metadata.receipt.clone(),
92 })
93 }
94 pub fn confirmation_time(&self) -> Timestamp {
95 let num_attestations = self.pod_metadata.attestations.len();
96 self.pod_metadata.attestations[num_attestations / 2].timestamp
97 }
98
99 pub fn generate_proof(&self) -> Option<MerkleProof> {
100 self.inner.log_index.and_then(|i| {
101 self.pod_metadata
102 .receipt
103 .generate_proof_for_log_hash(i.try_into().unwrap())
104 })
105 }
106
107 pub fn get_leaf(&self) -> Hash {
108 let log_index = self.inner.log_index.unwrap_or(0).try_into().unwrap();
109 StandardMerkleTree::hash_leaf(
110 index_prefix("log_hashes", log_index),
111 self.inner.inner.hash_custom(),
112 )
113 }
114
115 pub fn aggregate_signatures(&self) -> Vec<u8> {
116 self.pod_metadata
117 .attestations
118 .iter()
119 .map(|a| a.signature.as_bytes())
120 .fold(Vec::new(), |mut acc, sig| {
121 acc.extend_from_slice(&sig);
122 acc
123 })
124 }
125
126 fn sort_attestations_by_timestamp(&self) -> Vec<&TimestampedHeadlessAttestation> {
127 self.pod_metadata
128 .attestations
129 .iter()
130 .sorted_by_key(|a| a.timestamp)
131 .collect()
132 }
133
134 pub fn get_sorted_attestation_timestamps_in_seconds(&self) -> Vec<U256> {
135 self.sort_attestations_by_timestamp()
136 .iter()
137 .map(|a| a.timestamp.as_seconds().try_into().unwrap())
138 .collect()
139 }
140
141 pub fn get_sorted_attestation_signatures(&self) -> Vec<[u8; 65]> {
142 self.sort_attestations_by_timestamp()
143 .iter()
144 .map(|a| a.signature.as_bytes())
145 .collect()
146 }
147
148 pub fn verify_proof(&self, receipt_root: Hash, proof: MerkleProof) -> bool {
149 let leaf = self.get_leaf();
150 StandardMerkleTree::verify_proof(receipt_root, leaf, proof)
151 }
152}
153
154impl Deref for VerifiableLog {
155 type Target = alloy_rpc_types::Log;
156
157 fn deref(&self) -> &alloy_rpc_types::Log {
158 &self.inner
159 }
160}
161
162#[cfg(test)]
163mod test {
164 use super::*;
165 use alloy_primitives::{Address, Log, LogData, TxKind, U256};
166 use alloy_signer_local::PrivateKeySigner;
167
168 use crate::{AttestedTx, Hashable, Merkleizable, Transaction};
169
170 #[tokio::test]
171 async fn test_verifiable_log_hash_proof() {
172 let log = Log {
173 address: "0x217f5658c6ecc27d439922263ad9bb8e992e0373"
174 .parse()
175 .unwrap(),
176 data: LogData::new_unchecked(
177 vec![
178 "71a5674c44b823bc0df08201dfeb2e8bdf698cd684fd2bbaa79adcf2c99fc186"
179 .parse()
180 .unwrap(),
181 "0000000000000000000000000000000000000000000000000000000067dc55a9"
182 .parse()
183 .unwrap(),
184 "00000000000000000000000013791790bef192d14712d627f13a55c4abee52a4"
185 .parse()
186 .unwrap(),
187 "00000000000000000000000000000000000000000000000000000000cfb8ab4d"
188 .parse()
189 .unwrap(),
190 ],
191 "0000000000000000000000000000000000000000000000000de0b6b3a7640000"
192 .parse()
193 .unwrap(),
194 ),
195 };
196
197 let to: Address = "0x217f5658c6ecc27d439922263ad9bb8e992e0373"
198 .parse()
199 .unwrap();
200 let transaction = Transaction {
201 chain_id: 0x50d,
202 to: TxKind::Call(to.clone()),
203 nonce: 0,
204 gas_limit: 22048,
205 max_fee_per_gas: 1000000000,
206 max_priority_fee_per_gas: 1000000000,
207 access_list: Default::default(),
208 value: U256::ZERO,
209 input: vec![
210 133, 44, 166, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
211 0, 0, 0, 0, 0, 0, 103, 220, 85, 169, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
212 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 184, 171, 77, 0, 0, 0, 0, 0, 0, 0, 0,
213 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, 179, 167, 100, 0, 0,
214 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
215 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
216 0, 0, 0, 0, 0, 0, 0, 0, 2, 18, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
217 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
218 ]
219 .into(),
220 };
221 let signer = PrivateKeySigner::random();
222
223 let logs = vec![log.clone()];
224 let logs_tree = logs.to_merkle_tree();
225 let logs_root = logs_tree.root();
226
227 let rpc_log = RPCLog {
228 inner: log.clone(),
229 block_hash: Some(Hash::default()),
230 block_number: Some(0),
231 block_timestamp: Some(1742493092),
232 transaction_hash: Some(transaction.hash_custom()),
233 transaction_index: Some(0),
234 log_index: Some(0),
235 removed: false,
236 };
237
238 let verifiable_log = VerifiableLog {
239 inner: rpc_log,
240 pod_metadata: PodLogMetadata {
241 attestations: vec![],
242 receipt: Receipt {
243 status: true,
244 actual_gas_used: 21784,
245 max_fee_per_gas: transaction.max_fee_per_gas,
246 logs: logs.clone(),
247 logs_root,
248 attested_tx: AttestedTx::success(transaction.hash_custom(), 0),
249 signer: signer.address(),
250 to: Some(to),
251 contract_address: None,
252 },
253 },
254 };
255
256 let proof = verifiable_log.generate_proof().unwrap();
257 let receipt_root = verifiable_log
258 .pod_metadata
259 .receipt
260 .to_merkle_tree()
261 .hash_custom();
262
263 assert!(verifiable_log.verify_proof(receipt_root, proof));
264 assert_eq!(verifiable_log.inner.log_index, Some(0));
265 }
266}