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