Skip to main content

ckb_verification/
cache.rs

1//! TX verification cache
2
3use ckb_script::TransactionState;
4use ckb_types::{
5    core::{Capacity, Cycle, EntryCompleted, TransactionView},
6    packed::Byte32,
7};
8use std::collections::HashMap;
9use std::sync::Arc;
10
11/// An opaque transaction verification cache key derived from a witness transaction hash.
12///
13/// The private field and the absence of a `Byte32` conversion ensure callers can only create a
14/// key from a [`TransactionView`].
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
16pub struct VerifyCacheKey(Byte32);
17
18impl From<&TransactionView> for VerifyCacheKey {
19    fn from(tx: &TransactionView) -> Self {
20        Self(tx.witness_hash())
21    }
22}
23
24/// TX verification lru cache
25pub type TxVerificationCache = lru::LruCache<VerifyCacheKey, CachedScriptCycles>;
26
27/// Verification cache entries fetched for a batch of transactions.
28pub type FetchedTxVerificationCache = HashMap<VerifyCacheKey, CachedScriptCycles>;
29
30/// Lookup entries in a transaction verification cache by witness transaction hash.
31pub trait TxVerificationCacheLookup {
32    /// Returns the cached verification result for `key`.
33    fn get_by_wtx_hash(&self, key: &VerifyCacheKey) -> Option<&CachedScriptCycles>;
34}
35
36impl TxVerificationCacheLookup for TxVerificationCache {
37    fn get_by_wtx_hash(&self, key: &VerifyCacheKey) -> Option<&CachedScriptCycles> {
38        self.peek(key)
39    }
40}
41
42impl TxVerificationCacheLookup for FetchedTxVerificationCache {
43    fn get_by_wtx_hash(&self, key: &VerifyCacheKey) -> Option<&CachedScriptCycles> {
44        self.get(key)
45    }
46}
47
48const CACHE_SIZE: usize = 1000 * 30;
49
50/// Initialize cache
51pub fn init_cache() -> TxVerificationCache {
52    lru::LruCache::new(CACHE_SIZE)
53}
54
55/// Cached result of successful transaction script verification.
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct CachedScriptCycles {
58    /// Cached transaction script cycles.
59    pub cycles: Cycle,
60}
61
62impl CachedScriptCycles {
63    /// Creates cached script cycles.
64    pub fn new(cycles: Cycle) -> CachedScriptCycles {
65        Self { cycles }
66    }
67}
68
69/// Suspended state
70#[derive(Clone, Debug)]
71pub struct Suspended {
72    /// Cached tx fee
73    pub fee: Capacity,
74    /// State
75    pub state: Arc<TransactionState>,
76}
77
78/// Completed contextual transaction verification.
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub struct Completed {
81    /// Verified transaction script cycles.
82    pub cycles: Cycle,
83    /// Calculated transaction fee.
84    pub fee: Capacity,
85}
86
87impl From<Completed> for EntryCompleted {
88    fn from(value: Completed) -> Self {
89        EntryCompleted {
90            cycles: value.cycles,
91            fee: value.fee,
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use ckb_types::{bytes::Bytes, core::TransactionBuilder};
100
101    #[test]
102    fn cache_key_distinguishes_transactions_with_different_witnesses() {
103        let tx = TransactionBuilder::default().build();
104        let cousin = tx.as_advanced_builder().witness(Bytes::new()).build();
105
106        assert_eq!(tx.hash(), cousin.hash());
107        assert_ne!(tx.witness_hash(), cousin.witness_hash());
108
109        let cached_cycles = CachedScriptCycles::new(42);
110        let tx_key = VerifyCacheKey::from(&tx);
111        let cousin_key = VerifyCacheKey::from(&cousin);
112
113        let mut cache = init_cache();
114        cache.put(tx_key.clone(), cached_cycles);
115        assert_eq!(cache.get_by_wtx_hash(&tx_key), Some(&cached_cycles));
116        assert_eq!(cache.get_by_wtx_hash(&cousin_key), None);
117
118        let fetched_cache = FetchedTxVerificationCache::from([(tx_key.clone(), cached_cycles)]);
119        assert_eq!(fetched_cache.get_by_wtx_hash(&tx_key), Some(&cached_cycles));
120        assert_eq!(fetched_cache.get_by_wtx_hash(&cousin_key), None);
121    }
122}