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}