1use 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
36macro_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#[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 block_height: Height,
72 position_in_block: u32,
74}
75
76impl TxLocation {
77 pub fn new(block_height: Height, position_in_block: u32) -> Self {
79 Self {
80 block_height,
81 position_in_block,
82 }
83 }
84
85 pub fn block_height(&self) -> Height {
87 self.block_height
88 }
89
90 pub fn position_in_block(&self) -> u32 {
92 self.position_in_block
93 }
94}
95
96#[derive(Debug, Clone, Copy)]
102pub struct Schema<T> {
103 access: T,
107}
108
109impl<T: Access> Schema<T> {
110 #[doc(hidden)]
112 pub fn new(access: T) -> Self {
113 Self { access }
114 }
115
116 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 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 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 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 fn transactions_len_index(&self) -> Entry<T::Base, u64> {
163 self.access.get_entry(TRANSACTIONS_LEN)
164 }
165
166 pub fn transactions_len(&self) -> u64 {
168 let pool = self.transactions_len_index();
170 pool.get().unwrap_or(0)
171 }
172
173 pub fn transactions_pool(&self) -> KeySetIndex<T::Base, Hash> {
180 self.access.get_key_set(TRANSACTIONS_POOL)
181 }
182
183 #[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 pub fn transactions_pool_len(&self) -> u64 {
191 let pool = self.transactions_pool_len_index();
192 pool.get().unwrap_or(0)
193 }
194
195 pub fn transactions_locations(&self) -> MapIndex<T::Base, Hash, TxLocation> {
198 self.access.get_map(TRANSACTIONS_LOCATIONS)
199 }
200
201 pub fn blocks(&self) -> MapIndex<T::Base, Hash, Block> {
203 self.access.get_map(BLOCKS)
204 }
205
206 pub fn block_hashes_by_height(&self) -> ListIndex<T::Base, Hash> {
208 self.access.get_list(BLOCK_HASHES_BY_HEIGHT)
209 }
210
211 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 fn block_skip_entry(&self) -> Entry<T::Base, Block> {
219 self.access.get_entry(BLOCK_SKIP)
220 }
221
222 pub fn block_skip(&self) -> Option<Block> {
226 self.block_skip_entry().get()
227 }
228
229 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 pub fn precommits(&self, hash: &Hash) -> ListIndex<T::Base, Verified<Precommit>> {
240 self.access.get_list((PRECOMMITS, hash))
241 }
242
243 #[doc(hidden)]
245 pub fn consensus_config_entry(&self) -> ProofEntry<T::Base, ConsensusConfig> {
246 self.access.get_proof_entry(CONSENSUS_CONFIG)
247 }
248
249 pub fn block_hash_by_height(&self, height: Height) -> Option<Hash> {
251 self.block_hashes_by_height().get(height.into())
252 }
253
254 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 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 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 pub fn next_height(&self) -> Height {
294 let len = self.block_hashes_by_height().len();
295 Height(len)
296 }
297
298 pub fn consensus_config(&self) -> ConsensusConfig {
304 self.consensus_config_entry()
305 .get()
306 .expect("Consensus configuration is absent")
307 }
308
309 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 #[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 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 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 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 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 self.clear_block_skip();
398 self.block_skip_entry().set(block_skip);
399 }
400}
401
402#[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 pub fn errors(&self) -> CallErrorsIter<'_> {
417 CallErrorsIter {
418 errors_iter: self.errors.iter(),
419 aux_iter: self.errors_aux.values(),
420 }
421 }
422
423 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 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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Serialize, Deserialize, BinaryValue, ObjectHash)]
507#[serde(tag = "type", rename_all = "snake_case")]
508#[non_exhaustive]
509pub enum CallInBlock {
510 BeforeTransactions {
512 id: InstanceId,
514 },
515 Transaction {
517 index: u32,
519 },
520 AfterTransactions {
522 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 pub fn before_transactions(id: InstanceId) -> Self {
562 Self::BeforeTransactions { id }
563 }
564
565 pub fn transaction(index: u32) -> Self {
567 Self::Transaction { index }
568 }
569
570 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}