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