exonum/blockchain/
schema.rs

1// Copyright 2020 The Exonum Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY owner, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use anyhow::format_err;
16use exonum_derive::{BinaryValue, ObjectHash};
17use exonum_merkledb::{
18    access::{Access, AccessExt, RawAccessMut},
19    impl_binary_key_for_binary_value,
20    indexes::{Entries, Values},
21    Entry, KeySetIndex, ListIndex, MapIndex, ObjectHash, ProofEntry, ProofListIndex, ProofMapIndex,
22};
23use exonum_proto::ProtobufConvert;
24
25use std::fmt;
26
27use super::{Block, BlockProof, CallProof, ConsensusConfig};
28use crate::{
29    crypto::{Hash, PublicKey},
30    helpers::{Height, ValidatorId},
31    messages::{AnyTx, Precommit, Verified},
32    proto::schema::blockchain as pb_blockchain,
33    runtime::{ExecutionError, ExecutionErrorAux, InstanceId},
34};
35
36/// Defines `&str` constants with given name and value.
37macro_rules! define_names {
38    (
39        $(
40            $name:ident => $value:expr;
41        )+
42    ) => (
43        $(const $name: &str = concat!("core.", $value);)*
44    )
45}
46
47define_names!(
48    TRANSACTIONS => "transactions";
49    CALL_ERRORS => "call_errors";
50    CALL_ERRORS_AUX => "call_errors_aux";
51    TRANSACTIONS_LEN => "transactions_len";
52    TRANSACTIONS_POOL => "transactions_pool";
53    TRANSACTIONS_POOL_LEN => "transactions_pool_len";
54    TRANSACTIONS_LOCATIONS => "transactions_locations";
55    BLOCKS => "blocks";
56    BLOCK_HASHES_BY_HEIGHT => "block_hashes_by_height";
57    BLOCK_TRANSACTIONS => "block_transactions";
58    BLOCK_SKIP => "block_skip";
59    PRECOMMITS => "precommits";
60    CONSENSUS_CONFIG => "consensus_config";
61);
62
63/// Transaction location in a block. Defines the block where the transaction was
64/// included and the position of this transaction in the block.
65#[derive(Debug, Clone, Copy, PartialEq)]
66#[derive(Serialize, Deserialize)]
67#[derive(ProtobufConvert, BinaryValue, ObjectHash)]
68#[protobuf_convert(source = "pb_blockchain::TxLocation")]
69pub struct TxLocation {
70    /// Height of the block where the transaction was included.
71    block_height: Height,
72    /// Zero-based position of this transaction in the block.
73    position_in_block: u32,
74}
75
76impl TxLocation {
77    /// Creates a new transaction location.
78    pub fn new(block_height: Height, position_in_block: u32) -> Self {
79        Self {
80            block_height,
81            position_in_block,
82        }
83    }
84
85    /// Height of the block where the transaction was included.
86    pub fn block_height(&self) -> Height {
87        self.block_height
88    }
89
90    /// Zero-based position of this transaction in the block.
91    pub fn position_in_block(&self) -> u32 {
92        self.position_in_block
93    }
94}
95
96/// Information schema for indexes maintained by the Exonum core logic.
97///
98/// Indexes defined by this schema are present in the blockchain regardless of
99/// the deployed services and store general-purpose information, such as
100/// committed transactions.
101#[derive(Debug, Clone, Copy)]
102pub struct Schema<T> {
103    // For performance reasons, we don't use the field-per-index schema pattern.
104    // Indeed, the core schema has many indexes, most of which are never accessed
105    // for any particular `Schema` instance.
106    access: T,
107}
108
109impl<T: Access> Schema<T> {
110    /// Constructs information schema based on the given `access`.
111    #[doc(hidden)]
112    pub fn new(access: T) -> Self {
113        Self { access }
114    }
115
116    /// Returns a table that represents a map with a key-value pair of a
117    /// transaction hash and raw transaction message.
118    pub fn transactions(&self) -> MapIndex<T::Base, Hash, Verified<AnyTx>> {
119        self.access.get_map(TRANSACTIONS)
120    }
121
122    pub(crate) fn call_errors_map(
123        &self,
124        block_height: Height,
125    ) -> ProofMapIndex<T::Base, CallInBlock, ExecutionError> {
126        self.access.get_proof_map((CALL_ERRORS, &block_height.0))
127    }
128
129    /// Returns auxiliary information about an error that does not influence blockchain state hash.
130    fn call_errors_aux(
131        &self,
132        block_height: Height,
133    ) -> MapIndex<T::Base, CallInBlock, ExecutionErrorAux> {
134        self.access.get_map((CALL_ERRORS_AUX, &block_height.0))
135    }
136
137    /// Returns a record of errors that occurred during execution of a particular block.
138    /// If the block is not committed, returns `None`.
139    pub fn call_records(&self, block_height: Height) -> Option<CallRecords<T>> {
140        self.block_hash_by_height(block_height)?;
141        Some(CallRecords {
142            height: block_height,
143            errors: self.call_errors_map(block_height),
144            errors_aux: self.call_errors_aux(block_height),
145            access: self.access.clone(),
146        })
147    }
148
149    /// Returns the result of the execution for a transaction with the specified location.
150    /// If the location does not correspond to a transaction, returns `None`.
151    pub fn transaction_result(&self, location: TxLocation) -> Option<Result<(), ExecutionError>> {
152        let records = self.call_records(location.block_height)?;
153        let txs_in_block = self.block_transactions(location.block_height).len();
154        if txs_in_block <= u64::from(location.position_in_block) {
155            return None;
156        }
157        let status = records.get(CallInBlock::transaction(location.position_in_block));
158        Some(status)
159    }
160
161    /// Returns an entry that represents a count of committed transactions in the blockchain.
162    fn transactions_len_index(&self) -> Entry<T::Base, u64> {
163        self.access.get_entry(TRANSACTIONS_LEN)
164    }
165
166    /// Returns the number of committed transactions in the blockchain.
167    pub fn transactions_len(&self) -> u64 {
168        // TODO: Change a count of tx logic after replacement storage to MerkleDB. ECR-3087
169        let pool = self.transactions_len_index();
170        pool.get().unwrap_or(0)
171    }
172
173    /// Returns a table that represents a set of uncommitted transactions hashes.
174    ///
175    /// # Stability
176    ///
177    /// Since a signature of this method could be changed in the future due to performance reasons,
178    /// this method is considered unstable.
179    pub fn transactions_pool(&self) -> KeySetIndex<T::Base, Hash> {
180        self.access.get_key_set(TRANSACTIONS_POOL)
181    }
182
183    /// Returns an entry that represents count of uncommitted transactions.
184    #[doc(hidden)]
185    pub fn transactions_pool_len_index(&self) -> Entry<T::Base, u64> {
186        self.access.get_entry(TRANSACTIONS_POOL_LEN)
187    }
188
189    /// Returns the number of transactions in the pool.
190    pub fn transactions_pool_len(&self) -> u64 {
191        let pool = self.transactions_pool_len_index();
192        pool.get().unwrap_or(0)
193    }
194
195    /// Returns a table that keeps the block height and transaction position inside the block for every
196    /// transaction hash.
197    pub fn transactions_locations(&self) -> MapIndex<T::Base, Hash, TxLocation> {
198        self.access.get_map(TRANSACTIONS_LOCATIONS)
199    }
200
201    /// Returns a table that stores a block object for every block height.
202    pub fn blocks(&self) -> MapIndex<T::Base, Hash, Block> {
203        self.access.get_map(BLOCKS)
204    }
205
206    /// Returns a table that keeps block hashes for corresponding block heights.
207    pub fn block_hashes_by_height(&self) -> ListIndex<T::Base, Hash> {
208        self.access.get_list(BLOCK_HASHES_BY_HEIGHT)
209    }
210
211    /// Returns a table that keeps a list of transactions for each block.
212    pub fn block_transactions(&self, height: Height) -> ProofListIndex<T::Base, Hash> {
213        let height: u64 = height.into();
214        self.access.get_proof_list((BLOCK_TRANSACTIONS, &height))
215    }
216
217    /// Returns an entry storing the latest skip block for the node.
218    fn block_skip_entry(&self) -> Entry<T::Base, Block> {
219        self.access.get_entry(BLOCK_SKIP)
220    }
221
222    /// Returns the recorded [block skip], if any.
223    ///
224    /// [block skip]: enum.BlockContents.html#variant.Skip
225    pub fn block_skip(&self) -> Option<Block> {
226        self.block_skip_entry().get()
227    }
228
229    /// Returns the recorded [block skip] together with authenticating information.
230    ///
231    /// [block skip]: enum.BlockContents.html#variant.Skip
232    pub fn block_skip_and_precommits(&self) -> Option<BlockProof> {
233        let block = self.block_skip_entry().get()?;
234        let precommits = self.precommits(&block.object_hash()).iter().collect();
235        Some(BlockProof::new(block, precommits))
236    }
237
238    /// Returns a table that keeps a list of precommits for the block with the given hash.
239    pub fn precommits(&self, hash: &Hash) -> ListIndex<T::Base, Verified<Precommit>> {
240        self.access.get_list((PRECOMMITS, hash))
241    }
242
243    /// Returns an actual consensus configuration entry.
244    #[doc(hidden)]
245    pub fn consensus_config_entry(&self) -> ProofEntry<T::Base, ConsensusConfig> {
246        self.access.get_proof_entry(CONSENSUS_CONFIG)
247    }
248
249    /// Returns the block hash for the given height.
250    pub fn block_hash_by_height(&self, height: Height) -> Option<Hash> {
251        self.block_hashes_by_height().get(height.into())
252    }
253
254    /// Returns the block for the given height with the proof of its inclusion.
255    pub fn block_and_precommits(&self, height: Height) -> Option<BlockProof> {
256        let block_hash = self.block_hash_by_height(height)?;
257        let block = self.blocks().get(&block_hash).unwrap();
258        let precommits = self.precommits(&block_hash).iter().collect();
259        Some(BlockProof::new(block, precommits))
260    }
261
262    /// Returns the latest committed block.
263    ///
264    /// # Panics
265    ///
266    /// Panics if the genesis block was not created.
267    pub fn last_block(&self) -> Block {
268        let hash = self
269            .block_hashes_by_height()
270            .last()
271            .expect("An attempt to get the `last_block` during creating the genesis block.");
272        self.blocks().get(&hash).unwrap()
273    }
274
275    /// Returns the height of the latest committed block.
276    ///
277    /// # Panics
278    ///
279    /// Panics if invoked before the genesis block was created, e.g. within
280    /// `after_transactions` hook for genesis block.
281    pub fn height(&self) -> Height {
282        let len = self.block_hashes_by_height().len();
283        assert!(
284            len > 0,
285            "An attempt to get the actual `height` during creating the genesis block."
286        );
287        Height(len - 1)
288    }
289
290    /// Returns the height of the block to be committed.
291    ///
292    /// Unlike `height`, this method never panics.
293    pub fn next_height(&self) -> Height {
294        let len = self.block_hashes_by_height().len();
295        Height(len)
296    }
297
298    /// Returns an actual consensus configuration of the blockchain.
299    ///
300    /// # Panics
301    ///
302    /// Panics if the genesis block was not created.
303    pub fn consensus_config(&self) -> ConsensusConfig {
304        self.consensus_config_entry()
305            .get()
306            .expect("Consensus configuration is absent")
307    }
308
309    /// Attempts to find a `ValidatorId` by the provided service public key.
310    pub fn validator_id(&self, service_public_key: PublicKey) -> Option<ValidatorId> {
311        self.consensus_config()
312            .find_validator(|validator_keys| service_public_key == validator_keys.service_key)
313    }
314}
315
316impl<T: Access> Schema<T>
317where
318    T::Base: RawAccessMut,
319{
320    /// Adds a transaction into the persistent pool. The caller must ensure that the transaction
321    /// is not already in the pool.
322    ///
323    /// This method increments the number of transactions in the pool,
324    /// be sure to decrement it when the transaction committed.
325    #[doc(hidden)]
326    pub fn add_transaction_into_pool(&mut self, tx: Verified<AnyTx>) {
327        self.transactions_pool().insert(&tx.object_hash());
328        let x = self.transactions_pool_len_index().get().unwrap_or(0);
329        self.transactions_pool_len_index().set(x + 1);
330        self.transactions().put(&tx.object_hash(), tx);
331    }
332
333    /// Changes the transaction status from `in_pool`, to `committed`.
334    ///
335    /// **NB.** This method does not remove transactions from the `transactions_pool`.
336    /// The pool is updated during block commit in `update_transaction_count` in order to avoid
337    /// data race between commit and adding transactions into the pool.
338    pub(crate) fn commit_transaction(&mut self, hash: &Hash, height: Height, tx: Verified<AnyTx>) {
339        if !self.transactions().contains(hash) {
340            self.transactions().put(hash, tx)
341        }
342
343        self.block_transactions(height).push(*hash);
344    }
345
346    /// Updates transaction count of the blockchain.
347    pub(crate) fn update_transaction_count(&mut self) {
348        let block_transactions = self.block_transactions(self.height());
349        let count = block_transactions.len();
350
351        let mut len_index = self.transactions_len_index();
352        let new_len = len_index.get().unwrap_or(0) + count;
353        len_index.set(new_len);
354
355        // Determine the number of committed transactions present in the pool (besides the pool,
356        // transactions can be taken from the non-persistent cache). Remove the committed transactions
357        // from the pool.
358        let mut pool = self.transactions_pool();
359        let pool_count = block_transactions
360            .iter()
361            .filter(|tx_hash| {
362                if pool.contains(tx_hash) {
363                    pool.remove(tx_hash);
364                    true
365                } else {
366                    false
367                }
368            })
369            .count();
370
371        let mut pool_len_index = self.transactions_pool_len_index();
372        let new_pool_len = pool_len_index.get().unwrap_or(0) - pool_count as u64;
373        pool_len_index.set(new_pool_len);
374    }
375
376    /// Saves an error to the blockchain.
377    pub(crate) fn save_error(
378        &mut self,
379        height: Height,
380        call: CallInBlock,
381        mut err: ExecutionError,
382    ) {
383        let aux = err.split_aux();
384        self.call_errors_map(height).put(&call, err);
385        self.call_errors_aux(height).put(&call, aux);
386    }
387
388    pub(super) fn clear_block_skip(&mut self) {
389        if let Some(block_skip) = self.block_skip_entry().take() {
390            let block_hash = block_skip.object_hash();
391            self.precommits(&block_hash).clear();
392        }
393    }
394
395    pub(super) fn store_block_skip(&mut self, block_skip: Block) {
396        // TODO: maybe it makes sense to use a circular buffer here.
397        self.clear_block_skip();
398        self.block_skip_entry().set(block_skip);
399    }
400}
401
402/// Information about call errors within a specific block.
403///
404/// This data type can be used to get information or build proofs that execution
405/// of a certain call ended up with a particular status.
406#[derive(Debug)]
407pub struct CallRecords<T: Access> {
408    height: Height,
409    errors: ProofMapIndex<T::Base, CallInBlock, ExecutionError>,
410    errors_aux: MapIndex<T::Base, CallInBlock, ExecutionErrorAux>,
411    access: T,
412}
413
414impl<T: Access> CallRecords<T> {
415    /// Iterates over errors in a block.
416    pub fn errors(&self) -> CallErrorsIter<'_> {
417        CallErrorsIter {
418            errors_iter: self.errors.iter(),
419            aux_iter: self.errors_aux.values(),
420        }
421    }
422
423    /// Returns a result of a call execution.
424    ///
425    /// # Return value
426    ///
427    /// This method will return `Ok(())` both if the call completed successfully, or if
428    /// was not performed at all. The caller is responsible to distinguish these two outcomes.
429    pub fn get(&self, call: CallInBlock) -> Result<(), ExecutionError> {
430        match self.errors.get(&call) {
431            Some(mut err) => {
432                let aux = self
433                    .errors_aux
434                    .get(&call)
435                    .expect("BUG: Aux info is not saved for an error");
436                err.recombine_with_aux(aux);
437                Err(err)
438            }
439            None => Ok(()),
440        }
441    }
442
443    /// Returns a cryptographic proof of authenticity for a top-level call within a block.
444    pub fn get_proof(&self, call: CallInBlock) -> CallProof {
445        let block_proof = Schema::new(self.access.clone())
446            .block_and_precommits(self.height)
447            .unwrap();
448        let error_description = self.errors_aux.get(&call).map(|aux| aux.description);
449        let call_proof = self.errors.get_proof(call);
450        CallProof::new(block_proof, call_proof, error_description)
451    }
452}
453
454/// Iterator over errors in a block returned by `CallRecords::errors()`.
455#[derive(Debug)]
456pub struct CallErrorsIter<'a> {
457    errors_iter: Entries<'a, CallInBlock, ExecutionError>,
458    aux_iter: Values<'a, ExecutionErrorAux>,
459}
460
461impl Iterator for CallErrorsIter<'_> {
462    type Item = (CallInBlock, ExecutionError);
463
464    fn next(&mut self) -> Option<Self::Item> {
465        let (call, mut error) = self.errors_iter.next()?;
466        let aux = self
467            .aux_iter
468            .next()
469            .expect("BUG: Aux info is not saved for an error");
470        error.recombine_with_aux(aux);
471        Some((call, error))
472    }
473}
474
475/// Location of an isolated call within a block.
476///
477/// Exonum isolates execution of the transactions included into the the block,
478/// and `before_transactions` / `after_transactions` hooks that are executed for each active service.
479/// If an isolated call ends with an error, all changes to the blockchain state made within a call
480/// are rolled back.
481///
482/// `CallInBlock` objects are ordered in the same way the corresponding calls would be performed
483/// within a block:
484///
485/// ```rust
486/// # use exonum::blockchain::CallInBlock;
487/// assert!(CallInBlock::before_transactions(3) < CallInBlock::transaction(0));
488/// assert!(CallInBlock::transaction(0) < CallInBlock::transaction(1));
489/// assert!(CallInBlock::transaction(1) < CallInBlock::after_transactions(0));
490/// assert!(CallInBlock::after_transactions(0) < CallInBlock::after_transactions(1));
491/// ```
492///
493/// # See also
494///
495/// Not to be confused with [`CallSite`], which provides information about a call in which
496/// an error may occur. Since Exonum services may call each other's methods, `CallSite` is
497/// richer than `CallInBlock`.
498///
499/// One example of difference between these types is [`CallType::Constructor`]: since services
500/// are constructed outside of the block processing routine, this kind of errors cannot be
501/// represented as `CallInBlock`.
502///
503/// [`CallSite`]: ../runtime/error/struct.CallSite.html
504/// [`CallType::Constructor`]: ../runtime/error/enum.CallType.html
505#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] // builtin traits
506#[derive(Serialize, Deserialize, BinaryValue, ObjectHash)]
507#[serde(tag = "type", rename_all = "snake_case")]
508#[non_exhaustive]
509pub enum CallInBlock {
510    /// Call of `before_transactions` hook in a service.
511    BeforeTransactions {
512        /// Numerical service identifier.
513        id: InstanceId,
514    },
515    /// Call of a transaction within the block.
516    Transaction {
517        /// Zero-based transaction index.
518        index: u32,
519    },
520    /// Call of `after_transactions` hook in a service.
521    AfterTransactions {
522        /// Numerical service identifier.
523        id: InstanceId,
524    },
525}
526
527impl ProtobufConvert for CallInBlock {
528    type ProtoStruct = pb_blockchain::CallInBlock;
529
530    fn to_pb(&self) -> Self::ProtoStruct {
531        let mut pb = Self::ProtoStruct::new();
532        match self {
533            Self::BeforeTransactions { id } => pb.set_before_transactions(*id),
534            Self::Transaction { index } => pb.set_transaction(*index),
535            Self::AfterTransactions { id } => pb.set_after_transactions(*id),
536        }
537        pb
538    }
539
540    fn from_pb(pb: Self::ProtoStruct) -> anyhow::Result<Self> {
541        if pb.has_before_transactions() {
542            Ok(Self::BeforeTransactions {
543                id: pb.get_before_transactions(),
544            })
545        } else if pb.has_transaction() {
546            Ok(Self::Transaction {
547                index: pb.get_transaction(),
548            })
549        } else if pb.has_after_transactions() {
550            Ok(Self::AfterTransactions {
551                id: pb.get_after_transactions(),
552            })
553        } else {
554            Err(format_err!("Invalid location format"))
555        }
556    }
557}
558
559impl CallInBlock {
560    /// Creates a location corresponding to a `before_transactions` call.
561    pub fn before_transactions(id: InstanceId) -> Self {
562        Self::BeforeTransactions { id }
563    }
564
565    /// Creates a location corresponding to a transaction.
566    pub fn transaction(index: u32) -> Self {
567        Self::Transaction { index }
568    }
569
570    /// Creates a location corresponding to a `after_transactions` call.
571    pub fn after_transactions(id: InstanceId) -> Self {
572        Self::AfterTransactions { id }
573    }
574}
575
576impl_binary_key_for_binary_value!(CallInBlock);
577
578impl fmt::Display for CallInBlock {
579    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
580        match self {
581            Self::BeforeTransactions { id } => write!(
582                formatter,
583                "`before_transactions` for service with ID {}",
584                id
585            ),
586            Self::Transaction { index } => write!(formatter, "transaction #{}", index + 1),
587            Self::AfterTransactions { id } => {
588                write!(formatter, "`after_transactions` for service with ID {}", id)
589            }
590        }
591    }
592}
593
594#[test]
595fn location_json_serialization() {
596    use pretty_assertions::assert_eq;
597    use serde_json::json;
598
599    let location = CallInBlock::transaction(1);
600    assert_eq!(
601        serde_json::to_value(location).unwrap(),
602        json!({ "type": "transaction", "index": 1 })
603    );
604
605    let location = CallInBlock::after_transactions(1_000);
606    assert_eq!(
607        serde_json::to_value(location).unwrap(),
608        json!({ "type": "after_transactions", "id": 1_000 })
609    );
610}