pod_sdk/network/
mod.rs

1use alloy_consensus::{TxEnvelope, TxType, TypedTransaction};
2use alloy_eips::eip2930::AccessList;
3use alloy_network::{
4    BuildResult, Network, NetworkWallet, ReceiptResponse, TransactionBuilder,
5    TransactionBuilderError,
6};
7use alloy_primitives::{
8    Address, BlockHash, Bytes, ChainId, Log, Signature, TxHash, TxKind, B256, U256,
9};
10use alloy_provider::fillers::{
11    ChainIdFiller, GasFiller, JoinFill, NonceFiller, RecommendedFillers,
12};
13
14use pod_types::{
15    consensus::{attestation::AttestedTx, committee::CommitteeError},
16    ledger::Transaction,
17    metadata::DetailedReceiptMetadata,
18};
19
20use alloy_rpc_types::{TransactionReceipt, TransactionRequest};
21use pod_types::{Committee, Hashable, Merkleizable, Receipt, Timestamp};
22use serde::{Deserialize, Serialize};
23use std::ops::{Deref, DerefMut};
24
25#[derive(Debug, Clone, Copy)]
26pub struct PodNetwork;
27
28#[derive(Debug, Serialize, Deserialize, Clone)]
29pub struct PodTransactionRequest {
30    #[serde(flatten)]
31    pub inner: TransactionRequest,
32}
33
34impl Default for PodTransactionRequest {
35    fn default() -> Self {
36        let mut inner = TransactionRequest::default();
37        inner.set_max_fee_per_gas(1_000_000_000);
38        inner.set_max_priority_fee_per_gas(1_000_000_000);
39        Self { inner }
40    }
41}
42
43impl Deref for PodTransactionRequest {
44    type Target = TransactionRequest;
45
46    fn deref(&self) -> &Self::Target {
47        &self.inner
48    }
49}
50
51impl DerefMut for PodTransactionRequest {
52    fn deref_mut(&mut self) -> &mut Self::Target {
53        &mut self.inner
54    }
55}
56
57impl From<TypedTransaction> for PodTransactionRequest {
58    fn from(value: TypedTransaction) -> Self {
59        Self {
60            inner: value.into(),
61        }
62    }
63}
64
65impl From<TxEnvelope> for PodTransactionRequest {
66    fn from(value: TxEnvelope) -> Self {
67        Self {
68            inner: value.into(),
69        }
70    }
71}
72
73impl TransactionBuilder<PodNetwork> for PodTransactionRequest {
74    fn chain_id(&self) -> Option<ChainId> {
75        self.chain_id
76    }
77
78    fn set_chain_id(&mut self, chain_id: ChainId) {
79        self.chain_id = Some(chain_id);
80    }
81
82    fn nonce(&self) -> Option<u64> {
83        self.nonce
84    }
85
86    fn set_nonce(&mut self, nonce: u64) {
87        self.nonce = Some(nonce);
88    }
89
90    fn take_nonce(&mut self) -> Option<u64> {
91        self.nonce.take()
92    }
93
94    fn input(&self) -> Option<&Bytes> {
95        self.input.input()
96    }
97
98    fn set_input<T: Into<Bytes>>(&mut self, input: T) {
99        self.input.input = Some(input.into());
100    }
101
102    fn from(&self) -> Option<Address> {
103        self.from
104    }
105
106    fn set_from(&mut self, from: Address) {
107        self.from = Some(from);
108    }
109
110    fn kind(&self) -> Option<TxKind> {
111        self.to
112    }
113
114    fn clear_kind(&mut self) {
115        self.to = None;
116    }
117
118    fn set_kind(&mut self, kind: TxKind) {
119        self.to = Some(kind);
120    }
121
122    fn value(&self) -> Option<U256> {
123        self.value
124    }
125
126    fn set_value(&mut self, value: U256) {
127        self.value = Some(value)
128    }
129
130    fn gas_price(&self) -> Option<u128> {
131        self.gas_price
132    }
133
134    fn set_gas_price(&mut self, gas_price: u128) {
135        self.gas_price = Some(gas_price);
136    }
137
138    fn max_fee_per_gas(&self) -> Option<u128> {
139        self.max_fee_per_gas
140    }
141
142    fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
143        self.max_fee_per_gas = Some(max_fee_per_gas);
144    }
145
146    fn max_priority_fee_per_gas(&self) -> Option<u128> {
147        self.max_priority_fee_per_gas
148    }
149
150    fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
151        self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
152    }
153
154    fn gas_limit(&self) -> Option<u64> {
155        self.gas
156    }
157
158    fn set_gas_limit(&mut self, gas_limit: u64) {
159        self.gas = Some(gas_limit);
160    }
161
162    fn access_list(&self) -> Option<&AccessList> {
163        self.access_list.as_ref()
164    }
165
166    fn set_access_list(&mut self, access_list: AccessList) {
167        self.access_list = Some(access_list);
168    }
169
170    fn complete_type(&self, ty: TxType) -> Result<(), Vec<&'static str>> {
171        match ty {
172            TxType::Eip1559 => self.complete_1559(),
173            _ => unimplemented!(), // Preventing usage of any other except EIP-1559 Tx
174        }
175    }
176
177    fn can_submit(&self) -> bool {
178        // value and data may be None. If they are, they will be set to default.
179        // gas fields and nonce may be None, if they are, they will be populated
180        // with default values by the RPC server
181        self.from.is_some()
182    }
183
184    fn can_build(&self) -> bool {
185        // Only supporting EIP-1559 Transactions
186        self.max_fee_per_gas.is_some() && self.max_priority_fee_per_gas.is_some()
187    }
188
189    #[doc(alias = "output_transaction_type")]
190    fn output_tx_type(&self) -> TxType {
191        TxType::Eip1559
192    }
193
194    #[doc(alias = "output_transaction_type_checked")]
195    fn output_tx_type_checked(&self) -> Option<TxType> {
196        self.buildable_type()
197    }
198
199    fn prep_for_submission(&mut self) {
200        self.transaction_type = Some(self.preferred_type() as u8);
201        self.trim_conflicting_keys();
202        self.populate_blob_hashes();
203    }
204
205    fn build_unsigned(self) -> BuildResult<TypedTransaction, PodNetwork> {
206        if let Err((tx_type, missing)) = self.missing_keys() {
207            return Err(
208                TransactionBuilderError::InvalidTransactionRequest(tx_type, missing)
209                    .into_unbuilt(self),
210            );
211        }
212        Ok(self
213            .inner
214            .build_typed_tx()
215            .expect("checked by missing_keys"))
216    }
217
218    async fn build<W: NetworkWallet<PodNetwork>>(
219        self,
220        wallet: &W,
221    ) -> Result<<PodNetwork as Network>::TxEnvelope, TransactionBuilderError<PodNetwork>> {
222        Ok(wallet.sign_request(self).await?)
223    }
224}
225
226impl ReceiptResponse for PodReceiptResponse {
227    fn contract_address(&self) -> Option<Address> {
228        // For now not allowing deployments
229        None
230    }
231
232    fn status(&self) -> bool {
233        self.receipt.status()
234    }
235
236    fn block_hash(&self) -> Option<BlockHash> {
237        // todo
238        Some(BlockHash::default())
239    }
240
241    fn block_number(&self) -> Option<u64> {
242        // todo
243        None
244    }
245
246    fn transaction_hash(&self) -> TxHash {
247        self.receipt.transaction_hash()
248    }
249
250    fn transaction_index(&self) -> Option<u64> {
251        // todo
252        None
253    }
254
255    fn gas_used(&self) -> u64 {
256        self.receipt.gas_used()
257    }
258
259    fn effective_gas_price(&self) -> u128 {
260        self.receipt.effective_gas_price()
261    }
262
263    fn blob_gas_used(&self) -> Option<u64> {
264        // todo
265        None
266    }
267
268    fn blob_gas_price(&self) -> Option<u128> {
269        // todo
270        None
271    }
272
273    fn from(&self) -> Address {
274        self.receipt.from()
275    }
276
277    fn to(&self) -> Option<Address> {
278        self.receipt.to()
279    }
280
281    fn cumulative_gas_used(&self) -> u64 {
282        // todo
283        self.receipt.cumulative_gas_used()
284    }
285
286    fn state_root(&self) -> Option<B256> {
287        // todo
288        None
289    }
290}
291
292#[derive(Debug, Serialize, Deserialize, Clone)]
293pub struct AttestationData {
294    pub public_key: Address,
295    pub signature: Signature,
296    pub timestamp: Timestamp,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct PodReceiptResponse {
301    #[serde(flatten)]
302    pub receipt: TransactionReceipt,
303    pub pod_metadata: DetailedReceiptMetadata,
304}
305
306impl PodReceiptResponse {
307    pub fn verify_receipt(&self, committee: &Committee) -> Result<(), CommitteeError> {
308        let logs = self
309            .receipt
310            .inner
311            .logs()
312            .iter()
313            .map(|l| l.inner.clone())
314            .collect::<Vec<Log>>();
315
316        let logs_root = logs.to_merkle_tree().hash_custom();
317        let tx_hash = self.pod_metadata.transaction.hash_custom();
318        let to = match self.pod_metadata.transaction.to {
319            TxKind::Create => None,
320            TxKind::Call(address) => Some(address),
321        };
322
323        let receipt = Receipt {
324            status: self.status(),
325            actual_gas_used: self.receipt.gas_used,
326            logs,
327            logs_root,
328            attested_tx: AttestedTx {
329                tx_hash,
330                success: self.pod_metadata.tx_attestation_status,
331                committee_epoch: self.pod_metadata.committee_epoch,
332            },
333            max_fee_per_gas: self.pod_metadata.transaction.max_fee_per_gas,
334            signer: self.pod_metadata.transaction.signer,
335            to,
336            contract_address: self.receipt.contract_address,
337        };
338
339        committee.verify_aggregate_attestation(
340            receipt.attested_tx.hash_custom(),
341            &self
342                .pod_metadata
343                .attestations
344                .iter()
345                .map(|a| a.signature)
346                .collect(),
347        )?;
348
349        committee.verify_aggregate_attestation(
350            receipt.hash_custom(),
351            &self
352                .pod_metadata
353                .receipt_attestations
354                .iter()
355                .map(|a| a.signature)
356                .collect(),
357        )?;
358
359        Ok(())
360    }
361
362    pub fn transaction(&self) -> &pod_types::Signed<Transaction> {
363        &self.pod_metadata.transaction
364    }
365}
366
367impl Deref for PodReceiptResponse {
368    type Target = TransactionReceipt;
369    fn deref(&self) -> &TransactionReceipt {
370        &self.receipt
371    }
372}
373
374impl Network for PodNetwork {
375    type TxType = TxType;
376    type TxEnvelope = alloy_consensus::TxEnvelope;
377    type UnsignedTx = TypedTransaction;
378    type ReceiptEnvelope = alloy_consensus::ReceiptEnvelope;
379    type Header = alloy_consensus::Header;
380    type TransactionRequest = PodTransactionRequest;
381    type TransactionResponse = alloy_rpc_types::Transaction;
382    type ReceiptResponse = PodReceiptResponse;
383    type HeaderResponse = alloy_rpc_types::Header;
384    type BlockResponse = alloy_rpc_types::Block;
385}
386
387impl RecommendedFillers for PodNetwork {
388    type RecommendedFillers = JoinFill<GasFiller, JoinFill<NonceFiller, ChainIdFiller>>;
389
390    fn recommended_fillers() -> Self::RecommendedFillers {
391        JoinFill::new(
392            GasFiller,
393            JoinFill::new(NonceFiller::default(), ChainIdFiller::default()),
394        )
395    }
396}