1pub mod default_impls;
5pub mod dummy_impls;
6pub mod light_client_impls;
7pub mod offchain_impls;
8
9pub use default_impls::{
10 DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver,
11 DefaultTransactionDependencyProvider, SecpCkbRawKeySigner,
12};
13pub use light_client_impls::{
14 LightClientCellCollector, LightClientHeaderDepResolver,
15 LightClientTransactionDependencyProvider,
16};
17pub use offchain_impls::{
18 OffchainCellCollector, OffchainCellDepResolver, OffchainHeaderDepResolver,
19 OffchainTransactionDependencyProvider,
20};
21
22#[cfg(not(target_arch = "wasm32"))]
23use ckb_hash::blake2b_256;
24#[cfg(not(target_arch = "wasm32"))]
25use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider};
26#[cfg(not(target_arch = "wasm32"))]
27use ckb_types::core::{
28 cell::{CellMetaBuilder, CellProvider, CellStatus, HeaderChecker},
29 error::OutPointError,
30};
31use dyn_clone::DynClone;
32use thiserror::Error;
33
34use ckb_types::{
35 bytes::Bytes,
36 core::{HeaderView, TransactionView},
37 packed::{Byte32, CellDep, CellOutput, OutPoint, Script, Transaction},
38 prelude::*,
39};
40
41use crate::{rpc::ckb_indexer::SearchMode, util::is_mature};
42
43#[derive(Error, Debug)]
45pub enum SignerError {
46 #[error("the id is not found in the signer")]
47 IdNotFound,
48
49 #[error("invalid message, reason: `{0}`")]
50 InvalidMessage(String),
51
52 #[error("invalid transaction, reason: `{0}`")]
53 InvalidTransaction(String),
54
55 #[error(transparent)]
57 Other(#[from] anyhow::Error),
58}
59
60pub trait Signer: Send + Sync {
66 fn match_id(&self, id: &[u8]) -> bool;
68
69 fn sign(
74 &self,
75 id: &[u8],
76 message: &[u8],
77 recoverable: bool,
78 tx: &TransactionView,
79 ) -> Result<Bytes, SignerError>;
80}
81
82#[derive(Error, Debug)]
84pub enum TransactionDependencyError {
85 #[error("the resource is not found in the provider: `{0}`")]
86 NotFound(String),
87
88 #[error(transparent)]
89 Other(#[from] anyhow::Error),
90}
91
92#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
97#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
98pub trait TransactionDependencyProvider: Sync + Send {
99 async fn get_transaction_async(
100 &self,
101 tx_hash: &Byte32,
102 ) -> Result<TransactionView, TransactionDependencyError>;
103 async fn get_cell_async(
105 &self,
106 out_point: &OutPoint,
107 ) -> Result<CellOutput, TransactionDependencyError>;
108 async fn get_cell_data_async(
110 &self,
111 out_point: &OutPoint,
112 ) -> Result<Bytes, TransactionDependencyError>;
113 async fn get_header_async(
115 &self,
116 block_hash: &Byte32,
117 ) -> Result<HeaderView, TransactionDependencyError>;
118
119 async fn get_block_extension_async(
121 &self,
122 block_hash: &Byte32,
123 ) -> Result<Option<ckb_types::packed::Bytes>, TransactionDependencyError>;
124 #[cfg(not(target_arch = "wasm32"))]
126 fn get_transaction(
127 &self,
128 tx_hash: &Byte32,
129 ) -> Result<TransactionView, TransactionDependencyError> {
130 crate::rpc::block_on(self.get_transaction_async(tx_hash))
131 }
132 #[cfg(not(target_arch = "wasm32"))]
133 fn get_cell(&self, out_point: &OutPoint) -> Result<CellOutput, TransactionDependencyError> {
135 crate::rpc::block_on(self.get_cell_async(out_point))
136 }
137 #[cfg(not(target_arch = "wasm32"))]
138 fn get_cell_data(&self, out_point: &OutPoint) -> Result<Bytes, TransactionDependencyError> {
140 crate::rpc::block_on(self.get_cell_data_async(out_point))
141 }
142 #[cfg(not(target_arch = "wasm32"))]
143 fn get_header(&self, block_hash: &Byte32) -> Result<HeaderView, TransactionDependencyError> {
145 crate::rpc::block_on(self.get_header_async(block_hash))
146 }
147
148 #[cfg(not(target_arch = "wasm32"))]
150 fn get_block_extension(
151 &self,
152 block_hash: &Byte32,
153 ) -> Result<Option<ckb_types::packed::Bytes>, TransactionDependencyError> {
154 crate::rpc::block_on(self.get_block_extension_async(block_hash))
155 }
156}
157
158#[cfg(not(target_arch = "wasm32"))]
159impl CellDataProvider for &dyn TransactionDependencyProvider {
161 fn get_cell_data(&self, out_point: &OutPoint) -> Option<Bytes> {
162 TransactionDependencyProvider::get_cell_data(*self, out_point).ok()
163 }
164 fn get_cell_data_hash(&self, out_point: &OutPoint) -> Option<Byte32> {
165 TransactionDependencyProvider::get_cell_data(*self, out_point)
166 .ok()
167 .map(|data| blake2b_256(data.as_ref()).pack())
168 }
169}
170#[cfg(not(target_arch = "wasm32"))]
171impl HeaderProvider for &dyn TransactionDependencyProvider {
173 fn get_header(&self, hash: &Byte32) -> Option<HeaderView> {
174 TransactionDependencyProvider::get_header(*self, hash).ok()
175 }
176}
177#[cfg(not(target_arch = "wasm32"))]
178impl HeaderChecker for &dyn TransactionDependencyProvider {
179 fn check_valid(&self, block_hash: &Byte32) -> Result<(), OutPointError> {
180 TransactionDependencyProvider::get_header(*self, block_hash)
181 .map(|_| ())
182 .map_err(|_| OutPointError::InvalidHeader(block_hash.clone()))
183 }
184}
185#[cfg(not(target_arch = "wasm32"))]
186impl CellProvider for &dyn TransactionDependencyProvider {
187 fn cell(&self, out_point: &OutPoint, _eager_load: bool) -> CellStatus {
188 match self.get_transaction(&out_point.tx_hash()) {
189 Ok(tx) => tx
190 .outputs()
191 .get(out_point.index().unpack())
192 .map(|cell| {
193 let data = tx
194 .outputs_data()
195 .get(out_point.index().unpack())
196 .expect("output data");
197
198 let cell_meta = CellMetaBuilder::from_cell_output(cell, data.unpack())
199 .out_point(out_point.to_owned())
200 .build();
201
202 CellStatus::live_cell(cell_meta)
203 })
204 .unwrap_or(CellStatus::Unknown),
205 Err(_err) => CellStatus::Unknown,
206 }
207 }
208}
209#[cfg(not(target_arch = "wasm32"))]
210impl ExtensionProvider for &dyn TransactionDependencyProvider {
211 fn get_block_extension(&self, hash: &Byte32) -> Option<ckb_types::packed::Bytes> {
212 match TransactionDependencyProvider::get_block_extension(*self, hash).ok() {
213 Some(Some(bytes)) => Some(bytes),
214 _ => None,
215 }
216 }
217}
218
219#[derive(Error, Debug)]
221pub enum CellCollectorError {
222 #[error(transparent)]
223 Internal(anyhow::Error),
224
225 #[error(transparent)]
226 Other(anyhow::Error),
227}
228
229#[derive(Debug, Clone)]
230pub struct LiveCell {
231 pub output: CellOutput,
232 pub output_data: Bytes,
233 pub out_point: OutPoint,
234 pub block_number: u64,
235 pub tx_index: u32,
236}
237
238#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
240pub struct ValueRangeOption {
241 pub start: u64,
242 pub end: u64,
243}
244impl ValueRangeOption {
245 pub fn new(start: u64, end: u64) -> ValueRangeOption {
246 ValueRangeOption { start, end }
247 }
248 pub fn new_exact(value: u64) -> ValueRangeOption {
249 ValueRangeOption {
250 start: value,
251 end: value + 1,
252 }
253 }
254 pub fn new_min(start: u64) -> ValueRangeOption {
255 ValueRangeOption {
256 start,
257 end: u64::MAX,
258 }
259 }
260 pub fn match_value(&self, value: u64) -> bool {
261 self.start <= value && value < self.end
262 }
263}
264
265#[derive(Debug, Clone, Eq, PartialEq, Hash)]
269pub enum PrimaryScriptType {
270 Lock,
271 Type,
272}
273
274#[derive(Debug, Clone, Eq, PartialEq, Hash)]
275pub enum MaturityOption {
276 Mature,
277 Immature,
278 Both,
279}
280#[derive(Debug, Clone, Eq, PartialEq, Hash)]
281pub enum QueryOrder {
282 Desc,
283 Asc,
284}
285
286#[derive(Debug, Clone, Eq, PartialEq, Hash)]
287pub struct CellQueryOptions {
288 pub primary_script: Script,
289 pub primary_type: PrimaryScriptType,
290 pub with_data: Option<bool>,
291
292 pub secondary_script: Option<Script>,
294 pub secondary_script_len_range: Option<ValueRangeOption>,
295 pub data_len_range: Option<ValueRangeOption>,
296 pub capacity_range: Option<ValueRangeOption>,
297 pub block_range: Option<ValueRangeOption>,
298
299 pub order: QueryOrder,
300 pub limit: Option<u32>,
301 pub maturity: MaturityOption,
303 pub min_total_capacity: u64,
307 pub script_search_mode: Option<SearchMode>,
308}
309impl CellQueryOptions {
310 pub fn new(primary_script: Script, primary_type: PrimaryScriptType) -> CellQueryOptions {
311 CellQueryOptions {
312 primary_script,
313 primary_type,
314 secondary_script: None,
315 secondary_script_len_range: None,
316 data_len_range: None,
317 capacity_range: None,
318 block_range: None,
319 with_data: None,
320 order: QueryOrder::Asc,
321 limit: None,
322 maturity: MaturityOption::Mature,
323 min_total_capacity: 1,
324 script_search_mode: None,
325 }
326 }
327 pub fn new_lock(primary_script: Script) -> CellQueryOptions {
328 CellQueryOptions::new(primary_script, PrimaryScriptType::Lock)
329 }
330 pub fn new_type(primary_script: Script) -> CellQueryOptions {
331 CellQueryOptions::new(primary_script, PrimaryScriptType::Type)
332 }
333 pub fn match_cell(&self, cell: &LiveCell, max_mature_number: u64) -> bool {
334 fn extract_raw_data(script: &Script) -> Vec<u8> {
335 [
336 script.code_hash().as_slice(),
337 script.hash_type().as_slice(),
338 &script.args().raw_data(),
339 ]
340 .concat()
341 }
342 let filter_prefix = self.secondary_script.as_ref().map(|script| {
343 if script != &Script::default() {
344 extract_raw_data(script)
345 } else {
346 Vec::new()
347 }
348 });
349 match self.primary_type {
350 PrimaryScriptType::Lock => {
351 if cell.output.lock() != self.primary_script {
353 return false;
354 }
355
356 if let Some(prefix) = filter_prefix {
358 if prefix.is_empty() {
359 if cell.output.type_().is_some() {
360 return false;
361 }
362 } else if cell
363 .output
364 .type_()
365 .to_opt()
366 .as_ref()
367 .map(extract_raw_data)
368 .filter(|data| data.starts_with(&prefix))
369 .is_none()
370 {
371 return false;
372 }
373 }
374 }
375 PrimaryScriptType::Type => {
376 if cell.output.type_().to_opt().as_ref() != Some(&self.primary_script) {
378 return false;
379 }
380
381 if let Some(prefix) = filter_prefix {
383 if !extract_raw_data(&cell.output.lock()).starts_with(&prefix) {
384 return false;
385 }
386 }
387 }
388 }
389 if let Some(range) = self.secondary_script_len_range {
390 match self.primary_type {
391 PrimaryScriptType::Lock => {
392 let script_len = cell
393 .output
394 .type_()
395 .to_opt()
396 .map(|script| extract_raw_data(&script).len())
397 .unwrap_or_default();
398 if !range.match_value(script_len as u64) {
399 return false;
400 }
401 }
402 PrimaryScriptType::Type => {
403 let script_len = extract_raw_data(&cell.output.lock()).len();
404 if !range.match_value(script_len as u64) {
405 return false;
406 }
407 }
408 }
409 }
410
411 if let Some(range) = self.data_len_range {
412 if !range.match_value(cell.output_data.len() as u64) {
413 return false;
414 }
415 }
416 if let Some(range) = self.capacity_range {
417 let capacity: u64 = cell.output.capacity().unpack();
418 if !range.match_value(capacity) {
419 return false;
420 }
421 }
422 if let Some(range) = self.block_range {
423 if !range.match_value(cell.block_number) {
424 return false;
425 }
426 }
427 let cell_is_mature = is_mature(cell, max_mature_number);
428 match self.maturity {
429 MaturityOption::Mature => cell_is_mature,
430 MaturityOption::Immature => !cell_is_mature,
431 MaturityOption::Both => true,
432 }
433 }
434}
435
436#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
437#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
438pub trait CellCollector: DynClone + Send + Sync {
439 async fn collect_live_cells_async(
442 &mut self,
443 query: &CellQueryOptions,
444 apply_changes: bool,
445 ) -> Result<(Vec<LiveCell>, u64), CellCollectorError>;
446 #[cfg(not(target_arch = "wasm32"))]
449 fn collect_live_cells(
450 &mut self,
451 query: &CellQueryOptions,
452 apply_changes: bool,
453 ) -> Result<(Vec<LiveCell>, u64), CellCollectorError> {
454 crate::rpc::block_on(self.collect_live_cells_async(query, apply_changes))
455 }
456
457 fn lock_cell(
459 &mut self,
460 out_point: OutPoint,
461 tip_block_number: u64,
462 ) -> Result<(), CellCollectorError>;
463 fn apply_tx(
465 &mut self,
466 tx: Transaction,
467 tip_block_number: u64,
468 ) -> Result<(), CellCollectorError>;
469
470 fn reset(&mut self);
472}
473
474pub trait CellDepResolver: Send + Sync {
475 fn resolve(&self, script: &Script) -> Option<CellDep>;
479}
480
481#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
482#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
483pub trait HeaderDepResolver: Send + Sync {
484 async fn resolve_by_tx_async(
486 &self,
487 tx_hash: &Byte32,
488 ) -> Result<Option<HeaderView>, anyhow::Error>;
489
490 async fn resolve_by_number_async(
492 &self,
493 number: u64,
494 ) -> Result<Option<HeaderView>, anyhow::Error>;
495 #[cfg(not(target_arch = "wasm32"))]
496 fn resolve_by_tx(&self, tx_hash: &Byte32) -> Result<Option<HeaderView>, anyhow::Error> {
498 crate::rpc::block_on(self.resolve_by_tx_async(tx_hash))
499 }
500 #[cfg(not(target_arch = "wasm32"))]
501 fn resolve_by_number(&self, number: u64) -> Result<Option<HeaderView>, anyhow::Error> {
503 crate::rpc::block_on(self.resolve_by_number_async(number))
504 }
505}
506
507#[cfg(test)]
509mod anyhow_tests {
510 use anyhow::anyhow;
511 #[test]
512 fn test_signer_error() {
513 use super::SignerError;
514 let error = anyhow!(SignerError::IdNotFound);
515 assert_eq!("the id is not found in the signer", error.to_string());
516 let error = anyhow!(SignerError::InvalidMessage("InvalidMessage".to_string()));
517 assert_eq!(
518 "invalid message, reason: `InvalidMessage`",
519 error.to_string()
520 );
521 let error = anyhow!(SignerError::InvalidTransaction(
522 "InvalidTransaction".to_string()
523 ));
524 assert_eq!(
525 "invalid transaction, reason: `InvalidTransaction`",
526 error.to_string()
527 );
528 let error = anyhow!(SignerError::Other(anyhow::anyhow!("Other")));
529 assert_eq!("Other", error.to_string());
530 }
531
532 #[test]
533 fn test_transaction_dependency_error() {
534 use super::TransactionDependencyError;
535 let error = TransactionDependencyError::NotFound("NotFound".to_string());
536 let error = anyhow!(error);
537
538 assert_eq!(
539 "the resource is not found in the provider: `NotFound`",
540 error.to_string()
541 );
542 }
543
544 #[test]
545 fn test_cell_collector_error() {
546 use super::CellCollectorError;
547 let error = CellCollectorError::Internal(anyhow!("Internel"));
548 let error = anyhow!(error);
549 assert_eq!("Internel", error.to_string());
550
551 let error = CellCollectorError::Other(anyhow!("Other"));
552 let error = anyhow!(error);
553 assert_eq!("Other", error.to_string());
554 }
555}