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#[async_trait::async_trait]
97pub trait TransactionDependencyProvider: Sync + Send {
98 async fn get_transaction_async(
99 &self,
100 tx_hash: &Byte32,
101 ) -> Result<TransactionView, TransactionDependencyError>;
102 async fn get_cell_async(
104 &self,
105 out_point: &OutPoint,
106 ) -> Result<CellOutput, TransactionDependencyError>;
107 async fn get_cell_data_async(
109 &self,
110 out_point: &OutPoint,
111 ) -> Result<Bytes, TransactionDependencyError>;
112 async fn get_header_async(
114 &self,
115 block_hash: &Byte32,
116 ) -> Result<HeaderView, TransactionDependencyError>;
117
118 async fn get_block_extension_async(
120 &self,
121 block_hash: &Byte32,
122 ) -> Result<Option<ckb_types::packed::Bytes>, TransactionDependencyError>;
123 #[cfg(not(target_arch = "wasm32"))]
125 fn get_transaction(
126 &self,
127 tx_hash: &Byte32,
128 ) -> Result<TransactionView, TransactionDependencyError> {
129 crate::rpc::block_on(self.get_transaction_async(tx_hash))
130 }
131 #[cfg(not(target_arch = "wasm32"))]
132 fn get_cell(&self, out_point: &OutPoint) -> Result<CellOutput, TransactionDependencyError> {
134 crate::rpc::block_on(self.get_cell_async(out_point))
135 }
136 #[cfg(not(target_arch = "wasm32"))]
137 fn get_cell_data(&self, out_point: &OutPoint) -> Result<Bytes, TransactionDependencyError> {
139 crate::rpc::block_on(self.get_cell_data_async(out_point))
140 }
141 #[cfg(not(target_arch = "wasm32"))]
142 fn get_header(&self, block_hash: &Byte32) -> Result<HeaderView, TransactionDependencyError> {
144 crate::rpc::block_on(self.get_header_async(block_hash))
145 }
146
147 #[cfg(not(target_arch = "wasm32"))]
149 fn get_block_extension(
150 &self,
151 block_hash: &Byte32,
152 ) -> Result<Option<ckb_types::packed::Bytes>, TransactionDependencyError> {
153 crate::rpc::block_on(self.get_block_extension_async(block_hash))
154 }
155}
156
157#[cfg(not(target_arch = "wasm32"))]
158impl CellDataProvider for &dyn TransactionDependencyProvider {
160 fn get_cell_data(&self, out_point: &OutPoint) -> Option<Bytes> {
161 TransactionDependencyProvider::get_cell_data(*self, out_point).ok()
162 }
163 fn get_cell_data_hash(&self, out_point: &OutPoint) -> Option<Byte32> {
164 TransactionDependencyProvider::get_cell_data(*self, out_point)
165 .ok()
166 .map(|data| blake2b_256(data.as_ref()).pack())
167 }
168}
169#[cfg(not(target_arch = "wasm32"))]
170impl HeaderProvider for &dyn TransactionDependencyProvider {
172 fn get_header(&self, hash: &Byte32) -> Option<HeaderView> {
173 TransactionDependencyProvider::get_header(*self, hash).ok()
174 }
175}
176#[cfg(not(target_arch = "wasm32"))]
177impl HeaderChecker for &dyn TransactionDependencyProvider {
178 fn check_valid(&self, block_hash: &Byte32) -> Result<(), OutPointError> {
179 TransactionDependencyProvider::get_header(*self, block_hash)
180 .map(|_| ())
181 .map_err(|_| OutPointError::InvalidHeader(block_hash.clone()))
182 }
183}
184#[cfg(not(target_arch = "wasm32"))]
185impl CellProvider for &dyn TransactionDependencyProvider {
186 fn cell(&self, out_point: &OutPoint, _eager_load: bool) -> CellStatus {
187 match self.get_transaction(&out_point.tx_hash()) {
188 Ok(tx) => tx
189 .outputs()
190 .get(out_point.index().unpack())
191 .map(|cell| {
192 let data = tx
193 .outputs_data()
194 .get(out_point.index().unpack())
195 .expect("output data");
196
197 let cell_meta = CellMetaBuilder::from_cell_output(cell, data.unpack())
198 .out_point(out_point.to_owned())
199 .build();
200
201 CellStatus::live_cell(cell_meta)
202 })
203 .unwrap_or(CellStatus::Unknown),
204 Err(_err) => CellStatus::Unknown,
205 }
206 }
207}
208#[cfg(not(target_arch = "wasm32"))]
209impl ExtensionProvider for &dyn TransactionDependencyProvider {
210 fn get_block_extension(&self, hash: &Byte32) -> Option<ckb_types::packed::Bytes> {
211 match TransactionDependencyProvider::get_block_extension(*self, hash).ok() {
212 Some(Some(bytes)) => Some(bytes),
213 _ => None,
214 }
215 }
216}
217
218#[derive(Error, Debug)]
220pub enum CellCollectorError {
221 #[error(transparent)]
222 Internal(anyhow::Error),
223
224 #[error(transparent)]
225 Other(anyhow::Error),
226}
227
228#[derive(Debug, Clone)]
229pub struct LiveCell {
230 pub output: CellOutput,
231 pub output_data: Bytes,
232 pub out_point: OutPoint,
233 pub block_number: u64,
234 pub tx_index: u32,
235}
236
237#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
239pub struct ValueRangeOption {
240 pub start: u64,
241 pub end: u64,
242}
243impl ValueRangeOption {
244 pub fn new(start: u64, end: u64) -> ValueRangeOption {
245 ValueRangeOption { start, end }
246 }
247 pub fn new_exact(value: u64) -> ValueRangeOption {
248 ValueRangeOption {
249 start: value,
250 end: value + 1,
251 }
252 }
253 pub fn new_min(start: u64) -> ValueRangeOption {
254 ValueRangeOption {
255 start,
256 end: u64::MAX,
257 }
258 }
259 pub fn match_value(&self, value: u64) -> bool {
260 self.start <= value && value < self.end
261 }
262}
263
264#[derive(Debug, Clone, Eq, PartialEq, Hash)]
268pub enum PrimaryScriptType {
269 Lock,
270 Type,
271}
272
273#[derive(Debug, Clone, Eq, PartialEq, Hash)]
274pub enum MaturityOption {
275 Mature,
276 Immature,
277 Both,
278}
279#[derive(Debug, Clone, Eq, PartialEq, Hash)]
280pub enum QueryOrder {
281 Desc,
282 Asc,
283}
284
285#[derive(Debug, Clone, Eq, PartialEq, Hash)]
286pub struct CellQueryOptions {
287 pub primary_script: Script,
288 pub primary_type: PrimaryScriptType,
289 pub with_data: Option<bool>,
290
291 pub secondary_script: Option<Script>,
293 pub secondary_script_len_range: Option<ValueRangeOption>,
294 pub data_len_range: Option<ValueRangeOption>,
295 pub capacity_range: Option<ValueRangeOption>,
296 pub block_range: Option<ValueRangeOption>,
297
298 pub order: QueryOrder,
299 pub limit: Option<u32>,
300 pub maturity: MaturityOption,
302 pub min_total_capacity: u64,
306 pub script_search_mode: Option<SearchMode>,
307}
308impl CellQueryOptions {
309 pub fn new(primary_script: Script, primary_type: PrimaryScriptType) -> CellQueryOptions {
310 CellQueryOptions {
311 primary_script,
312 primary_type,
313 secondary_script: None,
314 secondary_script_len_range: None,
315 data_len_range: None,
316 capacity_range: None,
317 block_range: None,
318 with_data: None,
319 order: QueryOrder::Asc,
320 limit: None,
321 maturity: MaturityOption::Mature,
322 min_total_capacity: 1,
323 script_search_mode: None,
324 }
325 }
326 pub fn new_lock(primary_script: Script) -> CellQueryOptions {
327 CellQueryOptions::new(primary_script, PrimaryScriptType::Lock)
328 }
329 pub fn new_type(primary_script: Script) -> CellQueryOptions {
330 CellQueryOptions::new(primary_script, PrimaryScriptType::Type)
331 }
332 pub fn match_cell(&self, cell: &LiveCell, max_mature_number: u64) -> bool {
333 fn extract_raw_data(script: &Script) -> Vec<u8> {
334 [
335 script.code_hash().as_slice(),
336 script.hash_type().as_slice(),
337 &script.args().raw_data(),
338 ]
339 .concat()
340 }
341 let filter_prefix = self.secondary_script.as_ref().map(|script| {
342 if script != &Script::default() {
343 extract_raw_data(script)
344 } else {
345 Vec::new()
346 }
347 });
348 match self.primary_type {
349 PrimaryScriptType::Lock => {
350 if cell.output.lock() != self.primary_script {
352 return false;
353 }
354
355 if let Some(prefix) = filter_prefix {
357 if prefix.is_empty() {
358 if cell.output.type_().is_some() {
359 return false;
360 }
361 } else if cell
362 .output
363 .type_()
364 .to_opt()
365 .as_ref()
366 .map(extract_raw_data)
367 .filter(|data| data.starts_with(&prefix))
368 .is_none()
369 {
370 return false;
371 }
372 }
373 }
374 PrimaryScriptType::Type => {
375 if cell.output.type_().to_opt().as_ref() != Some(&self.primary_script) {
377 return false;
378 }
379
380 if let Some(prefix) = filter_prefix {
382 if !extract_raw_data(&cell.output.lock()).starts_with(&prefix) {
383 return false;
384 }
385 }
386 }
387 }
388 if let Some(range) = self.secondary_script_len_range {
389 match self.primary_type {
390 PrimaryScriptType::Lock => {
391 let script_len = cell
392 .output
393 .type_()
394 .to_opt()
395 .map(|script| extract_raw_data(&script).len())
396 .unwrap_or_default();
397 if !range.match_value(script_len as u64) {
398 return false;
399 }
400 }
401 PrimaryScriptType::Type => {
402 let script_len = extract_raw_data(&cell.output.lock()).len();
403 if !range.match_value(script_len as u64) {
404 return false;
405 }
406 }
407 }
408 }
409
410 if let Some(range) = self.data_len_range {
411 if !range.match_value(cell.output_data.len() as u64) {
412 return false;
413 }
414 }
415 if let Some(range) = self.capacity_range {
416 let capacity: u64 = cell.output.capacity().unpack();
417 if !range.match_value(capacity) {
418 return false;
419 }
420 }
421 if let Some(range) = self.block_range {
422 if !range.match_value(cell.block_number) {
423 return false;
424 }
425 }
426 let cell_is_mature = is_mature(cell, max_mature_number);
427 match self.maturity {
428 MaturityOption::Mature => cell_is_mature,
429 MaturityOption::Immature => !cell_is_mature,
430 MaturityOption::Both => true,
431 }
432 }
433}
434
435#[async_trait::async_trait]
436pub trait CellCollector: DynClone + Send + Sync {
437 async fn collect_live_cells_async(
440 &mut self,
441 query: &CellQueryOptions,
442 apply_changes: bool,
443 ) -> Result<(Vec<LiveCell>, u64), CellCollectorError>;
444 #[cfg(not(target_arch = "wasm32"))]
447 fn collect_live_cells(
448 &mut self,
449 query: &CellQueryOptions,
450 apply_changes: bool,
451 ) -> Result<(Vec<LiveCell>, u64), CellCollectorError> {
452 crate::rpc::block_on(self.collect_live_cells_async(query, apply_changes))
453 }
454
455 fn lock_cell(
457 &mut self,
458 out_point: OutPoint,
459 tip_block_number: u64,
460 ) -> Result<(), CellCollectorError>;
461 fn apply_tx(
463 &mut self,
464 tx: Transaction,
465 tip_block_number: u64,
466 ) -> Result<(), CellCollectorError>;
467
468 fn reset(&mut self);
470}
471
472pub trait CellDepResolver: Send + Sync {
473 fn resolve(&self, script: &Script) -> Option<CellDep>;
477}
478
479#[async_trait::async_trait]
480pub trait HeaderDepResolver: Send + Sync {
481 async fn resolve_by_tx_async(
483 &self,
484 tx_hash: &Byte32,
485 ) -> Result<Option<HeaderView>, anyhow::Error>;
486
487 async fn resolve_by_number_async(
489 &self,
490 number: u64,
491 ) -> Result<Option<HeaderView>, anyhow::Error>;
492 #[cfg(not(target_arch = "wasm32"))]
493 fn resolve_by_tx(&self, tx_hash: &Byte32) -> Result<Option<HeaderView>, anyhow::Error> {
495 crate::rpc::block_on(self.resolve_by_tx_async(tx_hash))
496 }
497 #[cfg(not(target_arch = "wasm32"))]
498 fn resolve_by_number(&self, number: u64) -> Result<Option<HeaderView>, anyhow::Error> {
500 crate::rpc::block_on(self.resolve_by_number_async(number))
501 }
502}
503
504#[cfg(test)]
506mod anyhow_tests {
507 use anyhow::anyhow;
508 #[test]
509 fn test_signer_error() {
510 use super::SignerError;
511 let error = anyhow!(SignerError::IdNotFound);
512 assert_eq!("the id is not found in the signer", error.to_string());
513 let error = anyhow!(SignerError::InvalidMessage("InvalidMessage".to_string()));
514 assert_eq!(
515 "invalid message, reason: `InvalidMessage`",
516 error.to_string()
517 );
518 let error = anyhow!(SignerError::InvalidTransaction(
519 "InvalidTransaction".to_string()
520 ));
521 assert_eq!(
522 "invalid transaction, reason: `InvalidTransaction`",
523 error.to_string()
524 );
525 let error = anyhow!(SignerError::Other(anyhow::anyhow!("Other")));
526 assert_eq!("Other", error.to_string());
527 }
528
529 #[test]
530 fn test_transaction_dependency_error() {
531 use super::TransactionDependencyError;
532 let error = TransactionDependencyError::NotFound("NotFound".to_string());
533 let error = anyhow!(error);
534
535 assert_eq!(
536 "the resource is not found in the provider: `NotFound`",
537 error.to_string()
538 );
539 }
540
541 #[test]
542 fn test_cell_collector_error() {
543 use super::CellCollectorError;
544 let error = CellCollectorError::Internal(anyhow!("Internel"));
545 let error = anyhow!(error);
546 assert_eq!("Internel", error.to_string());
547
548 let error = CellCollectorError::Other(anyhow!("Other"));
549 let error = anyhow!(error);
550 assert_eq!("Other", error.to_string());
551 }
552}