Skip to main content

ckb_rpc/module/
experiment.rs

1use crate::error::RPCError;
2use crate::module::chain::CyclesEstimator;
3use async_trait::async_trait;
4use ckb_dao::DaoCalculator;
5use ckb_jsonrpc_types::{
6    Capacity, DaoWithdrawingCalculationKind, EstimateCycles, EstimateMode, OutPoint, Transaction,
7    Uint64,
8};
9use ckb_shared::{Snapshot, shared::Shared};
10use ckb_store::ChainStore;
11use ckb_types::{core, packed};
12use jsonrpc_core::Result;
13use jsonrpc_utils::rpc;
14
15/// RPC Module Experiment for experimenting methods.
16///
17/// **EXPERIMENTAL warning**
18///
19/// The methods here may be removed or changed in future releases without prior notifications.
20#[rpc(openrpc)]
21#[async_trait]
22pub trait ExperimentRpc {
23    /// Dry run a transaction and return the execution cycles.
24    ///
25    /// This method will not check the transaction validity, but only run the lock script
26    /// and type script and then return the execution cycles.
27    ///
28    /// It is used to debug transaction scripts and query how many cycles the scripts consume.
29    ///
30    /// ## Errors
31    ///
32    /// * [`TransactionFailedToResolve (-301)`](../enum.RPCError.html#variant.TransactionFailedToResolve) - Failed to resolve the referenced cells and headers used in the transaction, as inputs or dependencies.
33    /// * [`TransactionFailedToVerify (-302)`](../enum.RPCError.html#variant.TransactionFailedToVerify) - There is a script returns with an error.
34    ///
35    /// ## Examples
36    ///
37    /// Request
38    ///
39    /// ```json
40    /// {
41    ///   "id": 42,
42    ///   "jsonrpc": "2.0",
43    ///   "method": "dry_run_transaction",
44    ///   "params": [
45    ///     {
46    ///       "cell_deps": [
47    ///         {
48    ///           "dep_type": "code",
49    ///           "out_point": {
50    ///             "index": "0x0",
51    ///             "tx_hash": "0xa4037a893eb48e18ed4ef61034ce26eba9c585f15c9cee102ae58505565eccc3"
52    ///           }
53    ///         }
54    ///       ],
55    ///       "header_deps": [
56    ///         "0x7978ec7ce5b507cfb52e149e36b1a23f6062ed150503c85bbf825da3599095ed"
57    ///       ],
58    ///       "inputs": [
59    ///         {
60    ///           "previous_output": {
61    ///             "index": "0x0",
62    ///             "tx_hash": "0x365698b50ca0da75dca2c87f9e7b563811d3b5813736b8cc62cc3b106faceb17"
63    ///           },
64    ///           "since": "0x0"
65    ///         }
66    ///       ],
67    ///       "outputs": [
68    ///         {
69    ///           "capacity": "0x2540be400",
70    ///           "lock": {
71    ///             "code_hash": "0x28e83a1277d48add8e72fadaa9248559e1b632bab2bd60b27955ebc4c03800a5",
72    ///             "hash_type": "data",
73    ///             "args": "0x"
74    ///           },
75    ///           "type": null
76    ///         }
77    ///       ],
78    ///       "outputs_data": [
79    ///         "0x"
80    ///       ],
81    ///       "version": "0x0",
82    ///       "witnesses": []
83    ///     }
84    ///   ]
85    /// }
86    /// ```
87    ///
88    /// Response
89    ///
90    /// ```json
91    /// {
92    ///   "id": 42,
93    ///   "jsonrpc": "2.0",
94    ///   "result": {
95    ///     "cycles": "0x219"
96    ///   }
97    /// }
98    /// ```
99    #[deprecated(
100        since = "0.105.1",
101        note = "Please use the RPC method [`estimate_cycles`](#chain-estimate_cycles) instead"
102    )]
103    #[rpc(name = "dry_run_transaction")]
104    fn dry_run_transaction(&self, tx: Transaction) -> Result<EstimateCycles>;
105
106    /// Calculates the maximum withdrawal one can get, given a referenced DAO cell, and
107    /// a withdrawing block hash.
108    ///
109    /// ## Params
110    ///
111    /// * `out_point` - Reference to the DAO cell, the depositing transaction's output.
112    /// * `kind` - Two kinds of dao withdrawal amount calculation option.
113    ///
114    /// option 1, the assumed reference block hash for withdrawing phase 1 transaction, this block must be in the
115    /// [canonical chain](trait.ChainRpc.html#canonical-chain), the calculation of occupied capacity will be based on the depositing transaction's output, assuming the output of phase 1 transaction is the same as the depositing transaction's output.
116    ///
117    /// option 2, the out point of the withdrawing phase 1 transaction, the calculation of occupied capacity will be based on corresponding phase 1 transaction's output.
118    ///
119    /// ## Returns
120    ///
121    /// The RPC returns the final capacity when the cell `out_point` is withdrawn using the block hash or withdrawing phase 1 transaction out point as the reference.
122    ///
123    /// In CKB, scripts cannot get the information about in which block the transaction is
124    /// committed. A workaround is letting the transaction reference a block hash so the script
125    /// knows that the transaction is committed at least after the reference block.
126    ///
127    /// ## Errors
128    ///
129    /// * [`DaoError (-5)`](../enum.RPCError.html#variant.DaoError) - The given out point is not a valid cell for DAO computation.
130    /// * [`CKBInternalError (-1)`](../enum.RPCError.html#variant.CKBInternalError) - Mathematics overflow.
131    ///
132    /// ## Examples
133    ///
134    /// Request
135    ///
136    /// ```json
137    /// {
138    ///   "id": 42,
139    ///   "jsonrpc": "2.0",
140    ///   "method": "calculate_dao_maximum_withdraw",
141    ///   "params": [
142    ///     {
143    ///       "index": "0x0",
144    ///       "tx_hash": "0xa4037a893eb48e18ed4ef61034ce26eba9c585f15c9cee102ae58505565eccc3"
145    ///     },
146    ///     "0xa5f5c85987a15de25661e5a214f2c1449cd803f071acc7999820f25246471f40"
147    ///   ]
148    /// }
149    /// ```
150    ///
151    /// Response
152    ///
153    /// ```json
154    /// {
155    ///   "id": 42,
156    ///   "jsonrpc": "2.0",
157    ///   "result": "0x4a8b4e8a4"
158    /// }
159    /// ```
160    #[rpc(name = "calculate_dao_maximum_withdraw")]
161    fn calculate_dao_maximum_withdraw(
162        &self,
163        out_point: OutPoint,
164        kind: DaoWithdrawingCalculationKind,
165    ) -> Result<Capacity>;
166
167    /// Get fee estimates.
168    ///
169    /// ## Params
170    ///
171    /// * `estimate_mode` - The fee estimate mode.
172    ///
173    ///   Default: `no_priority`.
174    ///
175    /// * `enable_fallback` - True to enable a simple fallback algorithm, when lack of historical empirical data to estimate fee rates with configured algorithm.
176    ///
177    ///   Default: `true`.
178    ///
179    /// ### The fallback algorithm
180    ///
181    /// Since CKB transaction confirmation involves a two-step process—1) propose and 2) commit, it is complex to
182    /// predict the transaction fee accurately with the expectation that it will be included within a certain block height.
183    ///
184    /// This algorithm relies on two assumptions and uses a simple strategy to estimate the transaction fee: 1) all transactions
185    /// in the pool are waiting to be proposed, and 2) no new transactions will be added to the pool.
186    ///
187    /// In practice, this simple algorithm should achieve good accuracy fee rate and running performance.
188    ///
189    /// ## Returns
190    ///
191    /// The estimated fee rate in shannons per kilobyte.
192    ///
193    /// ## Examples
194    ///
195    /// Request
196    ///
197    /// ```json
198    /// {
199    ///   "id": 42,
200    ///   "jsonrpc": "2.0",
201    ///   "method": "estimate_fee_rate",
202    ///   "params": []
203    /// }
204    /// ```
205    ///
206    /// Response
207    ///
208    /// ```json
209    /// {
210    ///   "id": 42,
211    ///   "jsonrpc": "2.0",
212    ///   "result": "0x3e8"
213    /// }
214    /// ```
215    #[rpc(name = "estimate_fee_rate")]
216    fn estimate_fee_rate(
217        &self,
218        estimate_mode: Option<EstimateMode>,
219        enable_fallback: Option<bool>,
220    ) -> Result<Uint64>;
221}
222
223#[derive(Clone)]
224pub(crate) struct ExperimentRpcImpl {
225    pub shared: Shared,
226}
227
228#[async_trait]
229impl ExperimentRpc for ExperimentRpcImpl {
230    fn dry_run_transaction(&self, tx: Transaction) -> Result<EstimateCycles> {
231        let tx: packed::Transaction = tx.into();
232        CyclesEstimator::new(&self.shared).run(tx)
233    }
234
235    fn calculate_dao_maximum_withdraw(
236        &self,
237        out_point: OutPoint,
238        kind: DaoWithdrawingCalculationKind,
239    ) -> Result<Capacity> {
240        let snapshot: &Snapshot = &self.shared.snapshot();
241        let consensus = snapshot.consensus();
242        let out_point: packed::OutPoint = out_point.into();
243        let data_loader = snapshot.borrow_as_data_loader();
244        let calculator = DaoCalculator::new(consensus, &data_loader);
245        match kind {
246            DaoWithdrawingCalculationKind::WithdrawingHeaderHash(withdrawing_header_hash) => {
247                let (tx, deposit_header_hash) = snapshot
248                    .get_transaction(&out_point.tx_hash())
249                    .ok_or_else(|| RPCError::invalid_params("invalid out_point"))?;
250                let output = tx
251                    .outputs()
252                    .get(out_point.index().into())
253                    .ok_or_else(|| RPCError::invalid_params("invalid out_point"))?;
254                let output_data = tx
255                    .outputs_data()
256                    .get(out_point.index().into())
257                    .ok_or_else(|| RPCError::invalid_params("invalid out_point"))?;
258
259                match calculator.calculate_maximum_withdraw(
260                    &output,
261                    core::Capacity::bytes(output_data.len()).expect("should not overflow"),
262                    &deposit_header_hash,
263                    &withdrawing_header_hash.into(),
264                ) {
265                    Ok(capacity) => Ok(capacity.into()),
266                    Err(err) => Err(RPCError::custom_with_error(RPCError::DaoError, err)),
267                }
268            }
269            DaoWithdrawingCalculationKind::WithdrawingOutPoint(withdrawing_out_point) => {
270                let (_tx, deposit_header_hash) = snapshot
271                    .get_transaction(&out_point.tx_hash())
272                    .ok_or_else(|| RPCError::invalid_params("invalid out_point"))?;
273
274                let withdrawing_out_point: packed::OutPoint = withdrawing_out_point.into();
275                let (withdrawing_tx, withdrawing_header_hash) = snapshot
276                    .get_transaction(&withdrawing_out_point.tx_hash())
277                    .ok_or_else(|| RPCError::invalid_params("invalid withdrawing_out_point"))?;
278
279                let output = withdrawing_tx
280                    .outputs()
281                    .get(withdrawing_out_point.index().into())
282                    .ok_or_else(|| RPCError::invalid_params("invalid withdrawing_out_point"))?;
283                let output_data = withdrawing_tx
284                    .outputs_data()
285                    .get(withdrawing_out_point.index().into())
286                    .ok_or_else(|| RPCError::invalid_params("invalid withdrawing_out_point"))?;
287
288                match calculator.calculate_maximum_withdraw(
289                    &output,
290                    core::Capacity::bytes(output_data.len()).expect("should not overflow"),
291                    &deposit_header_hash,
292                    &withdrawing_header_hash,
293                ) {
294                    Ok(capacity) => Ok(capacity.into()),
295                    Err(err) => Err(RPCError::custom_with_error(RPCError::DaoError, err)),
296                }
297            }
298        }
299    }
300
301    fn estimate_fee_rate(
302        &self,
303        estimate_mode: Option<EstimateMode>,
304        enable_fallback: Option<bool>,
305    ) -> Result<Uint64> {
306        let estimate_mode = estimate_mode.unwrap_or_default();
307        let enable_fallback = enable_fallback.unwrap_or(true);
308        self.shared
309            .tx_pool_controller()
310            .estimate_fee_rate(estimate_mode.into(), enable_fallback)
311            .map_err(|err| RPCError::custom(RPCError::CKBInternalError, err.to_string()))?
312            .map_err(RPCError::from_any_error)
313            .map(core::FeeRate::as_u64)
314            .map(Into::into)
315    }
316}