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