pchain_runtime/
read_write_set.rs

1/*
2    Copyright © 2023, ParallelChain Lab
3    Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
4*/
5
6//! Defines a struct that serves as a cache layer on top of World State.
7//!
8//! There are two data caches:
9//! - `reads` (first-hand data obtained from world state)
10//! - `writes` (the data pended to commit to world state)
11//!
12//! The cache layer also measures gas consumption for the read-write operations.
13//!
14//! In Read Operation, `writes` is accessed first. If data is not found, search `reads`. If it fails in both Sets,
15//! then finally World State is accessed. The result will then be cached to `reads`.
16//!
17//! In Write Operation, it first performs a Read Operation, and then updates the `writes` with the newest data.
18//!
19//! At the end of state transition, if it succeeds, the data in `writes` will be committed to World State. Otherwise,
20//! `writes` is discarded without any changes to World State.
21
22use std::{cell::RefCell, collections::HashMap};
23
24use pchain_types::cryptography::PublicAddress;
25use pchain_world_state::{
26    keys::AppKey,
27    states::{AccountStorageState, WorldState},
28    storage::WorldStateStorage,
29};
30use wasmer::Store;
31
32use crate::{
33    contract::{self, Module, SmartContractContext},
34    cost::CostChange,
35    gas,
36};
37
38/// ReadWriteSet defines data cache for Read-Write opertaions during state transition.
39#[derive(Clone)]
40pub(crate) struct ReadWriteSet<S>
41where
42    S: WorldStateStorage + Send + Sync + Clone,
43{
44    /// World State services as the data source
45    pub ws: WorldState<S>,
46    /// writes stores key-value pairs for Write operations. It stores the data that is pending to store into world state
47    pub writes: HashMap<CacheKey, CacheValue>,
48    /// reads stores key-value pairs from Read operations. It is de facto the original data read from world state.
49    pub reads: RefCell<HashMap<CacheKey, Option<CacheValue>>>,
50    /// write_gas is protocol defined cost that to-be-charged write operation has been executed
51    pub write_gas: CostChange,
52    /// read_gas is protocol defined cost that to-be-charged read operation has been executed
53    pub read_gas: RefCell<CostChange>,
54}
55
56impl<S> ReadWriteSet<S>
57where
58    S: WorldStateStorage + Send + Sync + Clone,
59{
60    pub fn new(ws: WorldState<S>) -> Self {
61        Self {
62            ws,
63            writes: HashMap::new(),
64            reads: RefCell::new(HashMap::new()),
65            write_gas: CostChange::default(),
66            read_gas: RefCell::new(CostChange::default()),
67        }
68    }
69
70    /// get the balance from readwrite set. It key is not found, then get from world state and then cache it.
71    pub fn balance(&self, address: PublicAddress) -> (u64, CostChange) {
72        match self.get(CacheKey::Balance(address)) {
73            (Some(CacheValue::Balance(value)), cost) => (value, cost),
74            _ => panic!(),
75        }
76    }
77
78    /// set balance to write set. This operation does not write to world state immediately
79    pub fn set_balance(&mut self, address: PublicAddress, balance: u64) -> CostChange {
80        self.set(CacheKey::Balance(address), CacheValue::Balance(balance))
81    }
82
83    /// remove cached writes and return the value
84    pub fn purge_balance(&mut self, address: PublicAddress) -> u64 {
85        let (balance, _) = self.balance(address);
86        let key = CacheKey::Balance(address);
87        self.writes.remove(&key);
88        balance
89    }
90
91    /// get the contract code from readwrite set. It key is not found, then get from world state and then cache it.
92    pub fn code(&self, address: PublicAddress) -> (Option<Vec<u8>>, CostChange) {
93        match self.get(CacheKey::ContractCode(address)) {
94            (Some(CacheValue::ContractCode(value)), cost) => (Some(value), cost),
95            (None, cost) => (None, cost),
96            _ => panic!(),
97        }
98    }
99
100    /// get the contract code from smart contract cache. It it is not found, then get from read write set, i.e. code()
101    pub fn code_from_sc_cache(
102        &self,
103        address: PublicAddress,
104        sc_context: &SmartContractContext,
105    ) -> (Option<(Module, Store)>, CostChange) {
106        let wasmer_store = sc_context.instantiate_store();
107        let cached_module = match &sc_context.cache {
108            Some(sc_cache) => contract::Module::from_cache(address, sc_cache, &wasmer_store),
109            None => None,
110        };
111
112        // found from Smart Contract Cache
113        if let Some(module) = cached_module {
114            let cost_change = CostChange::deduct(gas::get_code_cost(module.bytes_length()));
115            *self.read_gas.borrow_mut() += cost_change;
116            return (Some((module, wasmer_store)), cost_change);
117        }
118
119        // found from read write set or world state
120        let (bytes, cost_change) = self.code(address);
121        let contract_code = match bytes {
122            Some(bs) => bs,
123            None => return (None, cost_change),
124        };
125
126        // build module
127        let module = match contract::Module::from_wasm_bytecode_unchecked(
128            contract::CBI_VERSION,
129            &contract_code,
130            &wasmer_store,
131        ) {
132            Ok(module) => {
133                // cache to sc_cache
134                if let Some(sc_cache) = &sc_context.cache {
135                    module.cache_to(address, &mut sc_cache.clone());
136                }
137                module
138            }
139            Err(_) => return (None, cost_change),
140        };
141
142        (Some((module, wasmer_store)), cost_change)
143    }
144
145    /// set contract bytecode. This operation does not write to world state immediately
146    pub fn set_code(&mut self, address: PublicAddress, code: Vec<u8>) -> CostChange {
147        self.set(
148            CacheKey::ContractCode(address),
149            CacheValue::ContractCode(code),
150        )
151    }
152
153    /// get the CBI version of the contract
154    pub fn cbi_version(&self, address: PublicAddress) -> (Option<u32>, CostChange) {
155        match self.get(CacheKey::CBIVersion(address)) {
156            (Some(CacheValue::CBIVersion(value)), cost) => (Some(value), cost),
157            (None, cost) => (None, cost),
158            _ => panic!(),
159        }
160    }
161
162    /// set cbi version. This operation does not write to world state immediately
163    pub fn set_cbi_version(&mut self, address: PublicAddress, cbi_version: u32) -> CostChange {
164        self.set(
165            CacheKey::CBIVersion(address),
166            CacheValue::CBIVersion(cbi_version),
167        )
168    }
169
170    /// get the contract storage from readwrite set. It key is not found, then get from world state and then cache it.
171    pub fn app_data(
172        &self,
173        address: PublicAddress,
174        app_key: AppKey,
175    ) -> (Option<Vec<u8>>, CostChange) {
176        match self.get(CacheKey::App(address, app_key)) {
177            (Some(CacheValue::App(value)), cost) => {
178                if value.is_empty() {
179                    (None, cost)
180                } else {
181                    (Some(value), cost)
182                }
183            }
184            (None, cost) => (None, cost),
185            _ => panic!(),
186        }
187    }
188
189    /// set value to contract storage. This operation does not write to world state immediately
190    pub fn set_app_data(
191        &mut self,
192        address: PublicAddress,
193        app_key: AppKey,
194        value: Vec<u8>,
195    ) -> CostChange {
196        self.set(CacheKey::App(address, app_key), CacheValue::App(value))
197    }
198
199    /// set value to contract storage. This operation does not write to world state immediately.
200    /// It is gas-free operation.
201    pub fn set_app_data_uncharged(
202        &mut self,
203        address: PublicAddress,
204        app_key: AppKey,
205        value: Vec<u8>,
206    ) {
207        self.writes
208            .insert(CacheKey::App(address, app_key), CacheValue::App(value));
209    }
210
211    /// check if App Key already exists
212    pub fn contains_app_data(&self, address: PublicAddress, app_key: AppKey) -> bool {
213        let cache_key = CacheKey::App(address, app_key.clone());
214
215        // charge gas for contains and charge gas
216        *self.read_gas.borrow_mut() += CostChange::deduct(gas::contains_cost(cache_key.len()));
217
218        // check from the value that was previously written/read
219        self.writes
220            .get(&cache_key)
221            .filter(|v| v.len() != 0)
222            .is_some()
223            || self
224                .reads
225                .borrow()
226                .get(&cache_key)
227                .filter(|v| v.is_some())
228                .is_some()
229            || self.ws.contains().storage_value(&address, &app_key)
230    }
231
232    /// check if App Key already exists. It is gas-free operation.
233    pub fn contains_app_data_from_account_storage_state(
234        &self,
235        account_storage_state: &AccountStorageState<S>,
236        app_key: AppKey,
237    ) -> bool {
238        let address = account_storage_state.address();
239        let cache_key = CacheKey::App(address, app_key.clone());
240
241        // check from the value that was previously written/read
242        self.writes
243            .get(&cache_key)
244            .filter(|v| v.len() != 0)
245            .is_some()
246            || self
247                .reads
248                .borrow()
249                .get(&cache_key)
250                .filter(|v| v.is_some())
251                .is_some()
252            || self
253                .ws
254                .contains()
255                .storage_value_from_account_storage_state(account_storage_state, &app_key)
256    }
257
258    /// Get app data given a account storage state. It is gas-free operation.
259    pub fn app_data_from_account_storage_state(
260        &self,
261        account_storage_state: &AccountStorageState<S>,
262        app_key: AppKey,
263    ) -> Option<Vec<u8>> {
264        let address = account_storage_state.address();
265        let cache_key = CacheKey::App(address, app_key.clone());
266
267        match self.writes.get(&cache_key) {
268            Some(CacheValue::App(value)) => return Some(value.clone()),
269            Some(_) => panic!(),
270            None => {}
271        }
272
273        match self.reads.borrow().get(&cache_key) {
274            Some(Some(CacheValue::App(value))) => return Some(value.clone()),
275            Some(None) => return None,
276            Some(_) => panic!(),
277            None => {}
278        }
279
280        self.ws
281            .cached_get()
282            .storage_value(account_storage_state.address(), &app_key)
283            .or_else(|| account_storage_state.get(&app_key))
284    }
285
286    /// Lowest level of get operation. It gets latest value from readwrite set. It key is not found, then get from world state and then cache it.
287    fn get(&self, key: CacheKey) -> (Option<CacheValue>, CostChange) {
288        // 1. Return the value that was written earlier in the transaction ('read-your-write' semantics).
289        if let Some(value) = self.writes.get(&key) {
290            let cost_change = self.charge_read_cost(&key, Some(value));
291            return (Some(value.clone()), cost_change);
292        }
293
294        // 2. Return the value that was read eariler in the transaction
295        if let Some(value) = self.reads.borrow().get(&key) {
296            let cost_change = self.charge_read_cost(&key, value.as_ref());
297            return (value.clone(), cost_change);
298        }
299
300        // 3. Get the value from world state
301        let value = key.get_from_world_state(&self.ws);
302        let cost_change = self.charge_read_cost(&key, value.as_ref());
303
304        // 4. Cache to reads
305        self.reads.borrow_mut().insert(key, value.clone());
306
307        (value, cost_change)
308    }
309
310    /// lowest level of set operation. It inserts to Write Set and returns the gas cost for this set operation.
311    fn set(&mut self, key: CacheKey, value: CacheValue) -> CostChange {
312        let key_len = key.len();
313        let new_val_len = value.len();
314
315        // 1. Get the length of original value and Charge for read cost
316        let old_val_len = self.get(key.clone()).0.map_or(0, |v| v.len());
317
318        // 2. Insert to write set
319        self.writes.insert(key, value);
320
321        // 3. Calculate gas cost
322        self.charge_write_cost(key_len, old_val_len, new_val_len)
323    }
324
325    fn charge_read_cost(&self, key: &CacheKey, value: Option<&CacheValue>) -> CostChange {
326        let cost_change = match key {
327            CacheKey::ContractCode(_) => {
328                CostChange::deduct(gas::get_code_cost(value.as_ref().map_or(0, |v| v.len())))
329            }
330            _ => CostChange::deduct(gas::get_cost(
331                key.len(),
332                value.as_ref().map_or(0, |v| v.len()),
333            )),
334        };
335        *self.read_gas.borrow_mut() += cost_change;
336        cost_change
337    }
338
339    fn charge_write_cost(
340        &mut self,
341        key_len: usize,
342        old_val_len: usize,
343        new_val_len: usize,
344    ) -> CostChange {
345        let new_cost_change =
346            // old_val_len is obtained from Get so the cost of reading the key is already charged
347            CostChange::reward(gas::set_cost_delete_old_value(key_len, old_val_len, new_val_len)) +
348            CostChange::deduct(gas::set_cost_write_new_value(new_val_len)) +
349            CostChange::deduct(gas::set_cost_rehash(key_len));
350        self.write_gas += new_cost_change;
351        new_cost_change
352    }
353
354    pub fn commit_to_world_state(self) -> WorldState<S> {
355        let mut ws = self.ws;
356        // apply changes to world state
357        self.writes.into_iter().for_each(|(cache_key, new_value)| {
358            new_value.set_to_world_state(cache_key, &mut ws);
359        });
360        ws.commit();
361        ws
362    }
363}
364
365/// CacheKey is the key for state changes cache in Runtime. It is different with world state Key or App Key for
366/// being useful in:
367/// - data read write cache
368/// - components in gas cost calculation
369#[derive(Clone, Debug, Hash, PartialEq, Eq)]
370pub(crate) enum CacheKey {
371    App(PublicAddress, AppKey),
372    Balance(PublicAddress),
373    ContractCode(PublicAddress),
374    CBIVersion(PublicAddress),
375}
376
377impl CacheKey {
378    /// length of the value as an input to gas calculation
379    pub fn len(&self) -> usize {
380        match self {
381            CacheKey::App(address, key) => {
382                gas::ACCOUNT_STATE_KEY_LENGTH + address.len() + key.len()
383            }
384            CacheKey::Balance(_) | CacheKey::ContractCode(_) | CacheKey::CBIVersion(_) => {
385                gas::ACCOUNT_STATE_KEY_LENGTH
386            }
387        }
388    }
389
390    /// get_from_world_state gets value from world state according to CacheKey
391    fn get_from_world_state<S>(&self, ws: &WorldState<S>) -> Option<CacheValue>
392    where
393        S: WorldStateStorage + Send + Sync + Clone,
394    {
395        match &self {
396            CacheKey::App(address, app_key) => {
397                ws.storage_value(address, app_key).map(CacheValue::App)
398            }
399            CacheKey::Balance(address) => Some(CacheValue::Balance(ws.balance(address.to_owned()))),
400            CacheKey::ContractCode(address) => {
401                ws.code(address.to_owned()).map(CacheValue::ContractCode)
402            }
403            CacheKey::CBIVersion(address) => ws
404                .cbi_version(address.to_owned())
405                .map(CacheValue::CBIVersion),
406        }
407    }
408}
409
410/// CacheValue is the cached write operations that are pending to be applied to world state.
411/// It is used as
412/// - intermediate data which could be dropped later.
413/// - write information for gas calculation
414#[derive(Clone, Debug)]
415pub(crate) enum CacheValue {
416    App(Vec<u8>),
417    Balance(u64),
418    ContractCode(Vec<u8>),
419    CBIVersion(u32),
420}
421
422impl CacheValue {
423    /// length of the value as an input to gas calculation
424    pub fn len(&self) -> usize {
425        match self {
426            CacheValue::App(value) => value.len(),
427            CacheValue::Balance(balance) => std::mem::size_of_val(balance),
428            CacheValue::ContractCode(code) => code.len(),
429            CacheValue::CBIVersion(cbi_version) => std::mem::size_of_val(cbi_version),
430        }
431    }
432
433    /// set_all_to_world_state performs setting cache values to world state according to CacheKey
434    fn set_to_world_state<S>(self, key: CacheKey, ws: &mut WorldState<S>)
435    where
436        S: WorldStateStorage + Send + Sync + Clone,
437    {
438        match self {
439            CacheValue::App(value) => {
440                if let CacheKey::App(address, app_key) = key {
441                    ws.cached().set_storage_value(address, app_key, value);
442                }
443            }
444            CacheValue::Balance(value) => {
445                if let CacheKey::Balance(address) = key {
446                    ws.cached().set_balance(address, value);
447                }
448            }
449            CacheValue::ContractCode(value) => {
450                if let CacheKey::ContractCode(address) = key {
451                    ws.cached().set_code(address, value);
452                }
453            }
454            CacheValue::CBIVersion(value) => {
455                if let CacheKey::CBIVersion(address) = key {
456                    ws.cached().set_cbi_version(address, value);
457                }
458            }
459        }
460    }
461}