cfx_rpc_cfx_types/
transaction_request.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::{to_primitive_access_list, CfxAccessList},
7    address::{check_rpc_address_network, check_two_rpc_address_network_match},
8    RpcAddress,
9};
10use cfx_addr::Network;
11use cfx_parameters::{
12    block::{
13        CIP1559_CORE_TRANSACTION_GAS_RATIO, DEFAULT_TARGET_BLOCK_GAS_LIMIT,
14    },
15    RATIO_BASE_TEN,
16};
17use cfx_rpc_primitives::Bytes;
18use cfx_rpc_utils::error::jsonrpsee_error_helpers::{
19    invalid_params, invalid_params_check, invalid_params_msg,
20};
21use cfx_types::{Address, AddressSpaceUtil, U256, U64};
22use cfx_util_macros::bail;
23use cfxcore_accounts::AccountProvider;
24use cfxkey::Password;
25use jsonrpsee::types::ErrorObjectOwned;
26use primitives::{
27    transaction::{
28        Action, Cip1559Transaction, Cip2930Transaction, NativeTransaction,
29        TypedNativeTransaction::*, CIP1559_TYPE, CIP2930_TYPE, LEGACY_TX_TYPE,
30    },
31    SignedTransaction, Transaction, TransactionWithSignature,
32};
33use serde::{Deserialize, Serialize};
34use std::{convert::Into, sync::Arc};
35
36/// The maximum gas limit accepted by most tx pools.
37pub const DEFAULT_CFX_GAS_CALL_REQUEST: u64 = DEFAULT_TARGET_BLOCK_GAS_LIMIT
38    * CIP1559_CORE_TRANSACTION_GAS_RATIO
39    / RATIO_BASE_TEN;
40
41#[derive(Debug, Default, Deserialize, PartialEq, Serialize, Clone)]
42#[serde(rename_all = "camelCase")]
43pub struct TransactionRequest {
44    /// From
45    pub from: Option<RpcAddress>,
46    /// To
47    pub to: Option<RpcAddress>,
48    /// Gas Price
49    pub gas_price: Option<U256>,
50    /// Gas
51    pub gas: Option<U256>,
52    /// Value
53    pub value: Option<U256>,
54    /// Data
55    pub data: Option<Bytes>,
56    /// Nonce
57    pub nonce: Option<U256>,
58    /// StorageLimit
59    pub storage_limit: Option<U64>,
60    /// Access list in EIP-2930
61    pub access_list: Option<CfxAccessList>,
62    pub max_fee_per_gas: Option<U256>,
63    pub max_priority_fee_per_gas: Option<U256>,
64    #[serde(rename = "type")]
65    pub transaction_type: Option<U64>,
66    ///
67    pub chain_id: Option<U256>,
68    ///
69    pub epoch_height: Option<U256>,
70}
71
72#[derive(Debug, Default, PartialEq, Deserialize, Serialize, Clone)]
73#[serde(rename_all = "camelCase")]
74pub struct EstimateGasAndCollateralResponse {
75    /// The recommended gas_limit.
76    pub gas_limit: U256,
77    /// The amount of gas used in the execution.
78    pub gas_used: U256,
79    /// The number of bytes collateralized in the execution.
80    pub storage_collateralized: U64,
81}
82
83#[derive(Debug, Default, PartialEq, Deserialize, Serialize, Clone)]
84#[serde(rename_all = "camelCase")]
85pub struct CheckBalanceAgainstTransactionResponse {
86    /// Whether the account should pay transaction fee by self.
87    pub will_pay_tx_fee: bool,
88    /// Whether the account should pay collateral by self.
89    pub will_pay_collateral: bool,
90    /// Whether the account balance is enough for this transaction.
91    pub is_balance_enough: bool,
92}
93
94impl TransactionRequest {
95    pub fn check_rpc_address_network(
96        &self, param_name: &str, expected: &Network,
97    ) -> Result<(), ErrorObjectOwned> {
98        let rpc_request_network = invalid_params_check(
99            param_name,
100            check_two_rpc_address_network_match(
101                self.from.as_ref(),
102                self.to.as_ref(),
103            ),
104        )?;
105        invalid_params_check(
106            param_name,
107            check_rpc_address_network(rpc_request_network, expected),
108        )
109        .map_err(|e| e.into())
110    }
111
112    pub fn transaction_type(&self) -> u8 {
113        if let Some(tx_type) = self.transaction_type {
114            tx_type.as_usize() as u8
115        } else {
116            if self.max_fee_per_gas.is_some()
117                || self.max_priority_fee_per_gas.is_some()
118            {
119                CIP1559_TYPE
120            } else if self.access_list.is_some() {
121                CIP2930_TYPE
122            } else {
123                LEGACY_TX_TYPE
124            }
125        }
126    }
127
128    pub fn has_gas_price(&self) -> bool {
129        self.gas_price.is_some()
130            || self.max_fee_per_gas.is_some()
131            || self.max_priority_fee_per_gas.is_some()
132    }
133
134    pub fn sign_with(
135        self, epoch_height: u64, chain_id: u32, password: Option<String>,
136        accounts: Arc<AccountProvider>,
137    ) -> Result<TransactionWithSignature, String> {
138        let from = self.from.clone().ok_or("should have from")?;
139        let gas = self.gas.ok_or("should have gas")?;
140        let nonce = self.nonce.ok_or("should have nonce")?;
141        let transaction_type = self.transaction_type();
142        let action = self.to.map_or(Action::Create, |rpc_addr| {
143            Action::Call(rpc_addr.hex_address)
144        });
145
146        let value = self.value.unwrap_or_default();
147        let storage_limit = self
148            .storage_limit
149            .map(|v| v.as_u64())
150            .ok_or("should have storage_limit")?;
151        let data = self.data.unwrap_or_default().into_vec();
152
153        let access_list = self.access_list.unwrap_or(vec![]);
154
155        let typed_native_tx = match transaction_type {
156            LEGACY_TX_TYPE => {
157                let gas_price =
158                    self.gas_price.ok_or("should have gas_price")?;
159                Cip155(NativeTransaction {
160                    nonce,
161                    action,
162                    gas,
163                    gas_price,
164                    value,
165                    storage_limit,
166                    epoch_height,
167                    chain_id,
168                    data,
169                })
170            }
171            CIP2930_TYPE => {
172                let gas_price =
173                    self.gas_price.ok_or("should have gas_price")?;
174                Cip2930(Cip2930Transaction {
175                    nonce,
176                    gas_price,
177                    gas,
178                    action,
179                    value,
180                    storage_limit,
181                    epoch_height,
182                    chain_id,
183                    data,
184                    access_list: to_primitive_access_list(access_list),
185                })
186            }
187            CIP1559_TYPE => {
188                let max_fee_per_gas = self
189                    .max_fee_per_gas
190                    .ok_or("should have max_fee_per_gas")?;
191                let max_priority_fee_per_gas = self
192                    .max_priority_fee_per_gas
193                    .ok_or("should have max_priority_fee_per_gas")?;
194                Cip1559(Cip1559Transaction {
195                    nonce,
196                    action,
197                    gas,
198                    value,
199                    max_fee_per_gas,
200                    max_priority_fee_per_gas,
201                    storage_limit,
202                    epoch_height,
203                    chain_id,
204                    data,
205                    access_list: to_primitive_access_list(access_list),
206                })
207            }
208            // TODO(7702): support transaction 7702
209            x => {
210                return Err(format!("Unrecognized transaction type: {:?}", x));
211            }
212        };
213
214        let tx = Transaction::Native(typed_native_tx);
215        let password = password.map(Password::from);
216        let sig = accounts
217            .sign(from.into(), password, tx.hash_for_compute_signature())
218            // TODO: sign error into secret store error codes.
219            .map_err(|e| format!("failed to sign transaction: {:?}", e))?;
220
221        Ok(tx.with_signature(sig))
222    }
223
224    pub fn sign_call(
225        self, epoch_height: u64, chain_id: u32, max_gas: Option<U256>,
226    ) -> Result<SignedTransaction, ErrorObjectOwned> {
227        let max_gas = max_gas.unwrap_or(DEFAULT_CFX_GAS_CALL_REQUEST.into());
228        let gas = self.gas.unwrap_or(max_gas);
229        if gas > max_gas {
230            bail!(invalid_params(
231                "gas",
232                Some(format!(
233                    "specified gas is larger than max gas {:?}",
234                    max_gas
235                ))
236            ))
237        }
238        let transaction_type = self.transaction_type();
239        let nonce = self.nonce.unwrap_or_default();
240        let action = self.to.map_or(Action::Create, |rpc_addr| {
241            Action::Call(rpc_addr.hex_address)
242        });
243
244        let value = self.value.unwrap_or_default();
245        let storage_limit = self
246            .storage_limit
247            .map(|v| v.as_u64())
248            .unwrap_or(std::u64::MAX);
249        let data = self.data.unwrap_or_default().into_vec();
250
251        let gas_price = self.gas_price.unwrap_or(1.into());
252        let max_fee_per_gas = self
253            .max_fee_per_gas
254            .or(self.max_priority_fee_per_gas)
255            .unwrap_or(gas_price);
256        let max_priority_fee_per_gas =
257            self.max_priority_fee_per_gas.unwrap_or(U256::zero());
258        let access_list = self.access_list.unwrap_or(vec![]);
259
260        let transaction = match transaction_type {
261            LEGACY_TX_TYPE => Cip155(NativeTransaction {
262                nonce,
263                action,
264                gas,
265                gas_price,
266                value,
267                storage_limit,
268                epoch_height,
269                chain_id,
270                data,
271            }),
272            CIP2930_TYPE => Cip2930(Cip2930Transaction {
273                nonce,
274                gas_price,
275                gas,
276                action,
277                value,
278                storage_limit,
279                epoch_height,
280                chain_id,
281                data,
282                access_list: to_primitive_access_list(access_list),
283            }),
284            CIP1559_TYPE => Cip1559(Cip1559Transaction {
285                nonce,
286                action,
287                gas,
288                value,
289                max_fee_per_gas,
290                max_priority_fee_per_gas,
291                storage_limit,
292                epoch_height,
293                chain_id,
294                data,
295                access_list: to_primitive_access_list(access_list),
296            }),
297            // TODO(7702): support 7702 transaction
298            x => {
299                return Err(invalid_params_msg(&format!(
300                    "Unrecognized transaction type: {}",
301                    x
302                )));
303            }
304        };
305
306        let from = self
307            .from
308            .map_or_else(|| Address::zero(), |rpc_addr| rpc_addr.hex_address);
309
310        Ok(transaction.fake_sign_rpc(from.with_native_space()))
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::TransactionRequest;
317
318    use crate::RpcAddress;
319    use cfx_addr::Network;
320    use cfx_types::{H160, U256, U64};
321    use rustc_hex::FromHex;
322    use serde_json;
323    use std::str::FromStr;
324
325    #[test]
326    fn call_request_deserialize() {
327        let expected = TransactionRequest {
328            from: Some(
329                RpcAddress::try_from_h160(
330                    H160::from_low_u64_be(1),
331                    Network::Main,
332                )
333                .unwrap(),
334            ),
335            to: Some(
336                RpcAddress::try_from_h160(
337                    H160::from_low_u64_be(2),
338                    Network::Main,
339                )
340                .unwrap(),
341            ),
342            gas_price: Some(U256::from(1)),
343            gas: Some(U256::from(2)),
344            value: Some(U256::from(3)),
345            data: Some(vec![0x12, 0x34, 0x56].into()),
346            storage_limit: Some(U64::from_str("7b").unwrap()),
347            nonce: Some(U256::from(4)),
348            ..Default::default()
349        };
350
351        let s = r#"{
352            "from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6",
353            "to":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJD0WN6U9U",
354            "gasPrice":"0x1",
355            "gas":"0x2",
356            "value":"0x3",
357            "data":"0x123456",
358            "storageLimit":"0x7b",
359            "nonce":"0x4"
360        }"#;
361        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
362        assert!(
363            deserialized_result.is_ok(),
364            "serialized str should look like {}",
365            serde_json::to_string(&expected).unwrap()
366        );
367        assert_eq!(deserialized_result.unwrap(), expected);
368    }
369
370    #[test]
371    fn call_request_deserialize2() {
372        let expected = TransactionRequest {
373            from: Some(RpcAddress::try_from_h160(H160::from_str("160e8dd61c5d32be8058bb8eb970870f07233155").unwrap(),  Network::Main ).unwrap()),
374            to: Some(RpcAddress::try_from_h160(H160::from_str("846e8dd67c5d32be8058bb8eb970870f07244567").unwrap(), Network::Main).unwrap()),
375            gas_price: Some(U256::from_str("9184e72a000").unwrap()),
376            gas: Some(U256::from_str("76c0").unwrap()),
377            value: Some(U256::from_str("9184e72a").unwrap()),
378            storage_limit: Some(U64::from_str("3344adf").unwrap()),
379            data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex::<Vec<u8>>().unwrap().into()),
380            nonce: None,
381            ..Default::default()
382        };
383
384        let s = r#"{
385            "from": "CFX:TYPE.USER:AANA7DS0DVSXFTYANC727SNUU6HUSJ3VMYC3F1AY93",
386            "to": "CFX:TYPE.CONTRACT:ACCG7DS0TVSXFTYANC727SNUU6HUSKCFP6KB3NFJ02",
387            "gas": "0x76c0",
388            "gasPrice": "0x9184e72a000",
389            "value": "0x9184e72a",
390            "storageLimit":"0x3344adf",
391            "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
392        }"#;
393        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
394        assert!(
395            deserialized_result.is_ok(),
396            "serialized str should look like {}",
397            serde_json::to_string(&expected).unwrap()
398        );
399        assert_eq!(deserialized_result.unwrap(), expected);
400    }
401
402    #[test]
403    fn call_request_deserialize_empty() {
404        let expected = TransactionRequest {
405            from: Some(
406                RpcAddress::try_from_h160(
407                    H160::from_low_u64_be(1),
408                    Network::Main,
409                )
410                .unwrap(),
411            ),
412            to: None,
413            gas_price: None,
414            gas: None,
415            value: None,
416            data: None,
417            storage_limit: None,
418            nonce: None,
419            ..Default::default()
420        };
421
422        let s = r#"{"from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6"}"#;
423        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
424        assert!(
425            deserialized_result.is_ok(),
426            "serialized str should look like {}",
427            serde_json::to_string(&expected).unwrap()
428        );
429        assert_eq!(deserialized_result.unwrap(), expected);
430    }
431}