cfx_rpc_cfx_types/
transaction.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use crate::{
6    access_list::{from_primitive_access_list, CfxAccessList},
7    receipt::Receipt,
8    RpcAddress,
9};
10use cfx_addr::Network;
11use cfx_rpc_primitives::Bytes;
12use cfx_types::{Space, H256, U256, U64};
13use cfxkey::Error;
14use primitives::{
15    transaction::{
16        eth_transaction::Eip155Transaction,
17        native_transaction::NativeTransaction, Action,
18    },
19    SignedTransaction, Transaction as PrimitiveTransaction, TransactionIndex,
20    TransactionWithSignature, TransactionWithSignatureSerializePart,
21};
22use serde::{Deserialize, Serialize};
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct Transaction {
27    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
28    pub transaction_type: Option<U64>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub space: Option<Space>,
31    pub hash: H256,
32    pub nonce: U256,
33    pub block_hash: Option<H256>,
34    pub transaction_index: Option<U64>,
35    pub from: RpcAddress,
36    pub to: Option<RpcAddress>,
37    pub value: U256,
38    pub gas_price: U256,
39    pub gas: U256,
40    pub contract_created: Option<RpcAddress>,
41    pub data: Bytes,
42    pub storage_limit: U256,
43    pub epoch_height: U256,
44    pub chain_id: Option<U256>,
45    pub status: Option<U64>,
46    /// Optional access list
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub access_list: Option<CfxAccessList>,
49    /// miner bribe
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub max_priority_fee_per_gas: Option<U256>,
52    /// Max fee per gas
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub max_fee_per_gas: Option<U256>,
55    /// The standardised V field of the signature.
56    pub v: U256,
57    /// The R field of the signature.
58    pub r: U256,
59    /// The S field of the signature.
60    pub s: U256,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub y_parity: Option<U64>,
63}
64
65pub enum PackedOrExecuted {
66    Packed(TransactionIndex),
67    Executed(Receipt),
68}
69
70impl Transaction {
71    pub fn default(network: Network) -> Result<Transaction, String> {
72        Ok(Transaction {
73            space: None,
74            hash: Default::default(),
75            nonce: Default::default(),
76            block_hash: Default::default(),
77            transaction_index: Default::default(),
78            from: RpcAddress::null(network)?,
79            to: Default::default(),
80            value: Default::default(),
81            gas_price: Default::default(),
82            gas: Default::default(),
83            contract_created: Default::default(),
84            data: Default::default(),
85            storage_limit: Default::default(),
86            epoch_height: Default::default(),
87            chain_id: Some(U256::one()),
88            status: Default::default(),
89            v: Default::default(),
90            r: Default::default(),
91            s: Default::default(),
92            access_list: Default::default(),
93            max_priority_fee_per_gas: Default::default(),
94            max_fee_per_gas: Default::default(),
95            y_parity: Default::default(),
96            transaction_type: Default::default(),
97        })
98    }
99
100    pub fn from_signed(
101        t: &SignedTransaction,
102        maybe_packed_or_executed: Option<PackedOrExecuted>, network: Network,
103    ) -> Result<Transaction, String> {
104        let mut contract_created = None;
105        let mut status: Option<U64> = None;
106        let mut block_hash = None;
107        let mut transaction_index = None;
108        match maybe_packed_or_executed {
109            None => {}
110            Some(PackedOrExecuted::Packed(tx_index)) => {
111                block_hash = Some(tx_index.block_hash);
112                transaction_index = Some(
113                    tx_index.rpc_index.unwrap_or(tx_index.real_index).into(),
114                );
115            }
116            Some(PackedOrExecuted::Executed(receipt)) => {
117                block_hash = Some(receipt.block_hash);
118                transaction_index = Some(receipt.index.into());
119                if let Some(ref address) = receipt.contract_created {
120                    contract_created = Some(address.clone());
121                }
122                status = Some(receipt.outcome_status);
123            }
124        }
125        let (storage_limit, epoch_height) =
126            if let PrimitiveTransaction::Native(ref tx) = t.unsigned {
127                (*tx.storage_limit(), *tx.epoch_height())
128            } else {
129                (0, 0)
130            };
131        let space = match t.space() {
132            Space::Native => None,
133            Space::Ethereum => Some(Space::Ethereum),
134        };
135        Ok(Transaction {
136            space,
137            hash: t.transaction.hash().into(),
138            nonce: t.nonce().into(),
139            block_hash,
140            transaction_index,
141            status,
142            contract_created,
143            from: RpcAddress::try_from_h160(t.sender().address, network)?,
144            to: match t.action() {
145                Action::Create => None,
146                Action::Call(ref address) => {
147                    Some(RpcAddress::try_from_h160(address.clone(), network)?)
148                }
149            },
150            value: t.value().into(),
151            gas_price: t.gas_price().into(),
152            gas: t.gas().into(),
153            data: t.data().clone().into(),
154            storage_limit: storage_limit.into(),
155            epoch_height: epoch_height.into(),
156            chain_id: t.chain_id().map(|x| U256::from(x as u64)),
157            access_list: t
158                .access_list()
159                .cloned()
160                .map(|list| from_primitive_access_list(list, network)),
161            max_fee_per_gas: t.after_1559().then_some(*t.gas_price()),
162            max_priority_fee_per_gas: t
163                .after_1559()
164                .then_some(*t.max_priority_gas_price()),
165            y_parity: t.is_2718().then_some(t.transaction.v.into()),
166            transaction_type: Some(U64::from(t.type_id())),
167            v: t.transaction.v.into(),
168            r: t.transaction.r.into(),
169            s: t.transaction.s.into(),
170        })
171    }
172
173    pub fn into_signed(self) -> Result<SignedTransaction, Error> {
174        let tx_with_sig = TransactionWithSignature {
175            transaction: TransactionWithSignatureSerializePart {
176                unsigned: if self.space == Some(Space::Ethereum) {
177                    Eip155Transaction {
178                        nonce: self.nonce.into(),
179                        gas_price: self.gas_price.into(),
180                        gas: self.gas.into(),
181                        action: match self.to {
182                            None => Action::Create,
183                            Some(address) => Action::Call(address.into()),
184                        },
185                        value: self.value.into(),
186                        chain_id: self.chain_id.map(|x| x.as_u32()),
187                        data: self.data.into(),
188                    }
189                    .into()
190                } else {
191                    NativeTransaction {
192                        nonce: self.nonce.into(),
193                        gas_price: self.gas_price.into(),
194                        gas: self.gas.into(),
195                        action: match self.to {
196                            None => Action::Create,
197                            Some(address) => Action::Call(address.into()),
198                        },
199                        value: self.value.into(),
200                        storage_limit: self.storage_limit.as_u64(),
201                        epoch_height: self.epoch_height.as_u64(),
202                        chain_id: self
203                            .chain_id
204                            .ok_or(Error::Custom(
205                                "Native transaction must have chain_id".into(),
206                            ))?
207                            .as_u32(),
208                        data: self.data.into(),
209                    }
210                    .into()
211                },
212                v: self.v.as_usize() as u8,
213                r: self.r.into(),
214                s: self.s.into(),
215            },
216            hash: self.hash.into(),
217            rlp_size: None,
218        };
219        let public = tx_with_sig.recover_public()?;
220        Ok(SignedTransaction::new(public, tx_with_sig))
221    }
222}