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 gas = self.gas.ok_or("should have gas")?;
139        let nonce = self.nonce.ok_or("should have nonce")?;
140        let transaction_type = self.transaction_type();
141        let action = self.to.map_or(Action::Create, |rpc_addr| {
142            Action::Call(rpc_addr.hex_address)
143        });
144
145        let value = self.value.unwrap_or_default();
146        let storage_limit = self
147            .storage_limit
148            .map(|v| v.as_u64())
149            .ok_or("should have storage_limit")?;
150        let data = self.data.unwrap_or_default().into_vec();
151
152        let access_list = self.access_list.unwrap_or(vec![]);
153
154        let typed_native_tx = match transaction_type {
155            LEGACY_TX_TYPE => {
156                let gas_price =
157                    self.gas_price.ok_or("should have gas_price")?;
158                Cip155(NativeTransaction {
159                    nonce,
160                    action,
161                    gas,
162                    gas_price,
163                    value,
164                    storage_limit,
165                    epoch_height,
166                    chain_id,
167                    data,
168                })
169            }
170            CIP2930_TYPE => {
171                let gas_price =
172                    self.gas_price.ok_or("should have gas_price")?;
173                Cip2930(Cip2930Transaction {
174                    nonce,
175                    gas_price,
176                    gas,
177                    action,
178                    value,
179                    storage_limit,
180                    epoch_height,
181                    chain_id,
182                    data,
183                    access_list: to_primitive_access_list(access_list),
184                })
185            }
186            CIP1559_TYPE => {
187                let max_fee_per_gas = self
188                    .max_fee_per_gas
189                    .ok_or("should have max_fee_per_gas")?;
190                let max_priority_fee_per_gas = self
191                    .max_priority_fee_per_gas
192                    .ok_or("should have max_priority_fee_per_gas")?;
193                Cip1559(Cip1559Transaction {
194                    nonce,
195                    action,
196                    gas,
197                    value,
198                    max_fee_per_gas,
199                    max_priority_fee_per_gas,
200                    storage_limit,
201                    epoch_height,
202                    chain_id,
203                    data,
204                    access_list: to_primitive_access_list(access_list),
205                })
206            }
207            // TODO(7702): support transaction 7702
208            x => {
209                return Err(format!("Unrecognized transaction type: {:?}", x));
210            }
211        };
212
213        let tx = Transaction::Native(typed_native_tx);
214        let password = password.map(Password::from);
215        let sig = accounts
216            .sign(
217                self.from.unwrap().into(),
218                password,
219                tx.hash_for_compute_signature(),
220            )
221            // TODO: sign error into secret store error codes.
222            .map_err(|e| format!("failed to sign transaction: {:?}", e))?;
223
224        Ok(tx.with_signature(sig))
225    }
226
227    pub fn sign_call(
228        self, epoch_height: u64, chain_id: u32, max_gas: Option<U256>,
229    ) -> Result<SignedTransaction, ErrorObjectOwned> {
230        let max_gas = max_gas.unwrap_or(DEFAULT_CFX_GAS_CALL_REQUEST.into());
231        let gas = self.gas.unwrap_or(max_gas);
232        if gas > max_gas {
233            bail!(invalid_params(
234                "gas",
235                Some(format!(
236                    "specified gas is larger than max gas {:?}",
237                    max_gas
238                ))
239            ))
240        }
241        let transaction_type = self.transaction_type();
242        let nonce = self.nonce.unwrap_or_default();
243        let action = self.to.map_or(Action::Create, |rpc_addr| {
244            Action::Call(rpc_addr.hex_address)
245        });
246
247        let value = self.value.unwrap_or_default();
248        let storage_limit = self
249            .storage_limit
250            .map(|v| v.as_u64())
251            .unwrap_or(std::u64::MAX);
252        let data = self.data.unwrap_or_default().into_vec();
253
254        let gas_price = self.gas_price.unwrap_or(1.into());
255        let max_fee_per_gas = self
256            .max_fee_per_gas
257            .or(self.max_priority_fee_per_gas)
258            .unwrap_or(gas_price);
259        let max_priority_fee_per_gas =
260            self.max_priority_fee_per_gas.unwrap_or(U256::zero());
261        let access_list = self.access_list.unwrap_or(vec![]);
262
263        let transaction = match transaction_type {
264            LEGACY_TX_TYPE => Cip155(NativeTransaction {
265                nonce,
266                action,
267                gas,
268                gas_price,
269                value,
270                storage_limit,
271                epoch_height,
272                chain_id,
273                data,
274            }),
275            CIP2930_TYPE => Cip2930(Cip2930Transaction {
276                nonce,
277                gas_price,
278                gas,
279                action,
280                value,
281                storage_limit,
282                epoch_height,
283                chain_id,
284                data,
285                access_list: to_primitive_access_list(access_list),
286            }),
287            CIP1559_TYPE => Cip1559(Cip1559Transaction {
288                nonce,
289                action,
290                gas,
291                value,
292                max_fee_per_gas,
293                max_priority_fee_per_gas,
294                storage_limit,
295                epoch_height,
296                chain_id,
297                data,
298                access_list: to_primitive_access_list(access_list),
299            }),
300            // TODO(7702): support 7702 transaction
301            x => {
302                return Err(invalid_params_msg(&format!(
303                    "Unrecognized transaction type: {}",
304                    x
305                )));
306            }
307        };
308
309        let from = self
310            .from
311            .map_or_else(|| Address::zero(), |rpc_addr| rpc_addr.hex_address);
312
313        Ok(transaction.fake_sign_rpc(from.with_native_space()))
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::TransactionRequest;
320
321    use crate::RpcAddress;
322    use cfx_addr::Network;
323    use cfx_types::{H160, U256, U64};
324    use rustc_hex::FromHex;
325    use serde_json;
326    use std::str::FromStr;
327
328    #[test]
329    fn call_request_deserialize() {
330        let expected = TransactionRequest {
331            from: Some(
332                RpcAddress::try_from_h160(
333                    H160::from_low_u64_be(1),
334                    Network::Main,
335                )
336                .unwrap(),
337            ),
338            to: Some(
339                RpcAddress::try_from_h160(
340                    H160::from_low_u64_be(2),
341                    Network::Main,
342                )
343                .unwrap(),
344            ),
345            gas_price: Some(U256::from(1)),
346            gas: Some(U256::from(2)),
347            value: Some(U256::from(3)),
348            data: Some(vec![0x12, 0x34, 0x56].into()),
349            storage_limit: Some(U64::from_str("7b").unwrap()),
350            nonce: Some(U256::from(4)),
351            ..Default::default()
352        };
353
354        let s = r#"{
355            "from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6",
356            "to":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJD0WN6U9U",
357            "gasPrice":"0x1",
358            "gas":"0x2",
359            "value":"0x3",
360            "data":"0x123456",
361            "storageLimit":"0x7b",
362            "nonce":"0x4"
363        }"#;
364        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
365        assert!(
366            deserialized_result.is_ok(),
367            "serialized str should look like {}",
368            serde_json::to_string(&expected).unwrap()
369        );
370        assert_eq!(deserialized_result.unwrap(), expected);
371    }
372
373    #[test]
374    fn call_request_deserialize2() {
375        let expected = TransactionRequest {
376            from: Some(RpcAddress::try_from_h160(H160::from_str("160e8dd61c5d32be8058bb8eb970870f07233155").unwrap(),  Network::Main ).unwrap()),
377            to: Some(RpcAddress::try_from_h160(H160::from_str("846e8dd67c5d32be8058bb8eb970870f07244567").unwrap(), Network::Main).unwrap()),
378            gas_price: Some(U256::from_str("9184e72a000").unwrap()),
379            gas: Some(U256::from_str("76c0").unwrap()),
380            value: Some(U256::from_str("9184e72a").unwrap()),
381            storage_limit: Some(U64::from_str("3344adf").unwrap()),
382            data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex::<Vec<u8>>().unwrap().into()),
383            nonce: None,
384            ..Default::default()
385        };
386
387        let s = r#"{
388            "from": "CFX:TYPE.USER:AANA7DS0DVSXFTYANC727SNUU6HUSJ3VMYC3F1AY93",
389            "to": "CFX:TYPE.CONTRACT:ACCG7DS0TVSXFTYANC727SNUU6HUSKCFP6KB3NFJ02",
390            "gas": "0x76c0",
391            "gasPrice": "0x9184e72a000",
392            "value": "0x9184e72a",
393            "storageLimit":"0x3344adf",
394            "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
395        }"#;
396        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
397        assert!(
398            deserialized_result.is_ok(),
399            "serialized str should look like {}",
400            serde_json::to_string(&expected).unwrap()
401        );
402        assert_eq!(deserialized_result.unwrap(), expected);
403    }
404
405    #[test]
406    fn call_request_deserialize_empty() {
407        let expected = TransactionRequest {
408            from: Some(
409                RpcAddress::try_from_h160(
410                    H160::from_low_u64_be(1),
411                    Network::Main,
412                )
413                .unwrap(),
414            ),
415            to: None,
416            gas_price: None,
417            gas: None,
418            value: None,
419            data: None,
420            storage_limit: None,
421            nonce: None,
422            ..Default::default()
423        };
424
425        let s = r#"{"from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6"}"#;
426        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
427        assert!(
428            deserialized_result.is_ok(),
429            "serialized str should look like {}",
430            serde_json::to_string(&expected).unwrap()
431        );
432        assert_eq!(deserialized_result.unwrap(), expected);
433    }
434}