-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathmod.rs
1340 lines (1184 loc) · 50.7 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2022 RBB S.r.l
// opensource@mintlayer.org
// SPDX-License-Identifier: MIT
// Licensed under the MIT License;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod epoch_seal;
mod in_memory_reorg;
mod tx_verifier_storage;
use std::{cmp::max, collections::BTreeSet};
use thiserror::Error;
use chainstate_storage::{
BlockchainStorageRead, BlockchainStorageWrite, TipStorageTag, TransactionRw,
};
use chainstate_types::{
block_index_ancestor_getter, get_skip_height, BlockIndex, BlockIndexHandle, BlockStatus,
BlockValidationStage, EpochData, EpochDataCache, GenBlockIndex, GetAncestorError,
PropertyQueryError,
};
use common::{
chain::{
block::{
signed_block_header::SignedBlockHeader, timestamp::BlockTimestamp, BlockReward,
ConsensusData,
},
config::EpochIndex,
tokens::TokenAuxiliaryData,
tokens::{get_tokens_issuance_count, TokenId},
AccountNonce, AccountType, Block, ChainConfig, GenBlock, GenBlockId, OutPointSourceId,
SignedTransaction, Transaction, TxMainChainIndex, TxOutput, UtxoOutPoint,
},
primitives::{id::WithId, time::Time, BlockDistance, BlockHeight, Id, Idable},
time_getter::TimeGetter,
Uint256,
};
use logging::log;
use pos_accounting::{PoSAccountingDB, PoSAccountingDelta, PoSAccountingView};
use tx_verifier::transaction_verifier::{config::TransactionVerifierConfig, TransactionVerifier};
use utils::{ensure, tap_error_log::LogError};
use utxo::{UtxosCache, UtxosDB, UtxosView};
use crate::{BlockError, ChainstateConfig};
use self::tx_verifier_storage::gen_block_index_getter;
use super::{
median_time::calculate_median_time_past,
tokens::check_tokens_data,
transaction_verifier::{error::TokensError, flush::flush_to_storage},
tx_verification_strategy::TransactionVerificationStrategy,
BlockSizeError, CheckBlockError, CheckBlockTransactionsError,
};
pub use epoch_seal::EpochSealError;
pub struct ChainstateRef<'a, S, V> {
chain_config: &'a ChainConfig,
chainstate_config: &'a ChainstateConfig,
tx_verification_strategy: &'a V,
db_tx: S,
time_getter: &'a TimeGetter,
}
impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> BlockIndexHandle
for ChainstateRef<'a, S, V>
{
fn get_block_index(
&self,
block_id: &Id<Block>,
) -> Result<Option<BlockIndex>, PropertyQueryError> {
self.get_block_index(block_id)
}
fn get_gen_block_index(
&self,
block_id: &Id<GenBlock>,
) -> Result<Option<GenBlockIndex>, PropertyQueryError> {
self.get_gen_block_index(block_id)
}
fn get_ancestor(
&self,
block_index: &BlockIndex,
ancestor_height: BlockHeight,
) -> Result<GenBlockIndex, PropertyQueryError> {
self.get_ancestor(&GenBlockIndex::Block(block_index.clone()), ancestor_height)
.map_err(PropertyQueryError::from)
}
fn get_block_reward(
&self,
block_index: &BlockIndex,
) -> Result<Option<BlockReward>, PropertyQueryError> {
self.get_block_reward(block_index)
}
}
impl<'a, S: TransactionRw, V> ChainstateRef<'a, S, V> {
pub fn commit_db_tx(self) -> chainstate_storage::Result<()> {
self.db_tx.commit()
}
}
impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> ChainstateRef<'a, S, V> {
pub fn new_rw(
chain_config: &'a ChainConfig,
chainstate_config: &'a ChainstateConfig,
tx_verification_strategy: &'a V,
db_tx: S,
time_getter: &'a TimeGetter,
) -> ChainstateRef<'a, S, V> {
ChainstateRef {
chain_config,
chainstate_config,
db_tx,
tx_verification_strategy,
time_getter,
}
}
pub fn new_ro(
chain_config: &'a ChainConfig,
chainstate_config: &'a ChainstateConfig,
tx_verification_strategy: &'a V,
db_tx: S,
time_getter: &'a TimeGetter,
) -> ChainstateRef<'a, S, V> {
ChainstateRef {
chain_config,
chainstate_config,
db_tx,
tx_verification_strategy,
time_getter,
}
}
pub fn make_utxo_view(&self) -> impl UtxosView<Error = S::Error> + '_ {
UtxosDB::new(&self.db_tx)
}
pub fn make_pos_accounting_view(
&self,
) -> impl PoSAccountingView<Error = pos_accounting::Error> + '_ {
PoSAccountingDB::<_, TipStorageTag>::new(&self.db_tx)
}
pub fn chain_config(&self) -> &ChainConfig {
self.chain_config
}
pub fn current_time(&self) -> Time {
self.time_getter.get_time()
}
pub fn get_is_transaction_index_enabled(&self) -> Result<bool, PropertyQueryError> {
Ok(self
.db_tx
.get_is_mainchain_tx_index_enabled()
.map_err(PropertyQueryError::from)?
.expect("Must be set on node initialization"))
}
pub fn get_best_block_id(&self) -> Result<Id<GenBlock>, PropertyQueryError> {
self.db_tx
.get_best_block_id()
.map_err(PropertyQueryError::from)
.map(|bid| bid.expect("Best block ID not initialized"))
}
pub fn get_block_index(
&self,
block_id: &Id<Block>,
) -> Result<Option<BlockIndex>, PropertyQueryError> {
log::trace!("Loading block index of id: {}", block_id);
self.db_tx.get_block_index(block_id).map_err(PropertyQueryError::from)
}
pub fn get_existing_block_index(
&self,
block_id: &Id<Block>,
) -> Result<BlockIndex, PropertyQueryError> {
self.get_block_index(block_id)?
.ok_or_else(|| PropertyQueryError::BlockIndexNotFound((*block_id).into()))
}
pub fn get_gen_block_index(
&self,
block_id: &Id<GenBlock>,
) -> Result<Option<GenBlockIndex>, PropertyQueryError> {
gen_block_index_getter(&self.db_tx, self.chain_config, block_id)
.map_err(PropertyQueryError::from)
}
pub fn get_best_block_index(&self) -> Result<GenBlockIndex, PropertyQueryError> {
self.get_gen_block_index(&self.get_best_block_id().log_err()?)
.log_err()?
.ok_or(PropertyQueryError::BestBlockIndexNotFound)
}
/// Return BlockIndex of the previous block.
fn get_previous_block_index(
&self,
block_index: &BlockIndex,
) -> Result<GenBlockIndex, PropertyQueryError> {
let prev_block_id = block_index.prev_block_id();
self.get_gen_block_index(prev_block_id)
.log_err()?
.ok_or(PropertyQueryError::PrevBlockIndexNotFound(*prev_block_id))
}
pub fn get_mainchain_tx_index(
&self,
tx_id: &OutPointSourceId,
) -> Result<Option<TxMainChainIndex>, PropertyQueryError> {
log::trace!("Loading transaction index of id: {:?}", tx_id);
self.db_tx.get_mainchain_tx_index(tx_id).map_err(PropertyQueryError::from)
}
pub fn get_transaction_in_block(
&self,
id: Id<Transaction>,
) -> Result<Option<SignedTransaction>, PropertyQueryError> {
log::trace!("Loading whether tx index is enabled: {}", id);
let is_tx_index_enabled = self.get_is_transaction_index_enabled()?;
if !is_tx_index_enabled {
return Err(PropertyQueryError::TransactionIndexDisabled);
}
log::trace!("Loading transaction index with id: {}", id);
let tx_index = self.db_tx.get_mainchain_tx_index(&OutPointSourceId::Transaction(id))?;
let tx_index = match tx_index {
Some(tx_index) => tx_index,
None => return Ok(None),
};
log::trace!("Loading transaction with id: {}", id);
let position = match tx_index.position() {
common::chain::SpendablePosition::Transaction(pos) => pos,
common::chain::SpendablePosition::BlockReward(_) => {
panic!("In get_transaction(), a tx id led to a block reward")
}
};
Ok(self.db_tx.get_mainchain_tx_by_position(position)?)
}
pub fn get_block_id_by_height(
&self,
height: &BlockHeight,
) -> Result<Option<Id<GenBlock>>, PropertyQueryError> {
self.db_tx
.get_block_id_by_height(height)
.map_err(PropertyQueryError::from)
.log_err()
}
pub fn get_existing_block_id_by_height(
&self,
height: &BlockHeight,
) -> Result<Id<GenBlock>, PropertyQueryError> {
self.get_block_id_by_height(height)?
.ok_or(PropertyQueryError::BlockForHeightNotFound(*height))
.log_err()
}
pub fn get_block(&self, block_id: Id<Block>) -> Result<Option<Block>, PropertyQueryError> {
self.db_tx.get_block(block_id).map_err(PropertyQueryError::from)
}
pub fn get_block_header(
&self,
block_id: Id<Block>,
) -> Result<Option<SignedBlockHeader>, PropertyQueryError> {
Ok(self.db_tx.get_block_header(block_id).log_err()?)
}
pub fn is_block_in_main_chain(
&self,
block_id: &Id<GenBlock>,
) -> Result<bool, PropertyQueryError> {
self.get_block_height_in_main_chain(block_id).log_err().map(|ht| ht.is_some())
}
pub fn get_min_height_with_allowed_reorg(&self) -> Result<BlockHeight, PropertyQueryError> {
Ok(self.db_tx.get_min_height_with_allowed_reorg().log_err()?.unwrap_or(0.into()))
}
pub fn get_ancestor(
&self,
block_index: &GenBlockIndex,
target_height: BlockHeight,
) -> Result<GenBlockIndex, GetAncestorError> {
block_index_ancestor_getter(
gen_block_index_getter,
&self.db_tx,
self.chain_config,
block_index.into(),
target_height,
)
}
/// Obtain the last common ancestor between the specified blocks.
pub fn last_common_ancestor(
&self,
first_block_index: &GenBlockIndex,
second_block_index: &GenBlockIndex,
) -> Result<GenBlockIndex, PropertyQueryError> {
let mut first_block_index = first_block_index.clone();
let mut second_block_index = second_block_index.clone();
match first_block_index.block_height().cmp(&second_block_index.block_height()) {
std::cmp::Ordering::Greater => {
first_block_index = self
.get_ancestor(&first_block_index, second_block_index.block_height())
.log_err()?;
}
std::cmp::Ordering::Less => {
second_block_index = self
.get_ancestor(&second_block_index, first_block_index.block_height())
.log_err()?;
}
std::cmp::Ordering::Equal => {}
}
loop {
match (&first_block_index, &second_block_index) {
_ if first_block_index.block_id() == second_block_index.block_id() => {
break Ok(first_block_index)
}
(GenBlockIndex::Block(first_blkidx), GenBlockIndex::Block(second_blkidx)) => {
first_block_index = self.get_previous_block_index(first_blkidx).log_err()?;
second_block_index = self.get_previous_block_index(second_blkidx).log_err()?;
}
_ => panic!("Chain iteration not in lockstep"),
}
}
}
/// Obtain the last common ancestor between the specified block and the tip of the main chain.
///
/// Note: if the block is itself on the main chain, its own index will be returned, not parent's.
// TODO: unlike its generic counterpart above, this function may be optimized by taking jumps
// via get_ancestor instead of the block-by-block iteration (because here the other chain is
// the main chain and we can always check whether we've reached it).
pub fn last_common_ancestor_in_main_chain(
&self,
block_index: &GenBlockIndex,
) -> Result<GenBlockIndex, PropertyQueryError> {
let best_block_index = self.get_best_block_index().log_err()?;
self.last_common_ancestor(block_index, &best_block_index)
}
pub fn get_token_aux_data(
&self,
token_id: &TokenId,
) -> Result<Option<TokenAuxiliaryData>, PropertyQueryError> {
self.db_tx.get_token_aux_data(token_id).map_err(PropertyQueryError::from)
}
pub fn get_token_id(
&self,
tx_id: &Id<Transaction>,
) -> Result<Option<TokenId>, PropertyQueryError> {
self.db_tx.get_token_id(tx_id).map_err(PropertyQueryError::from)
}
pub fn get_header_from_height(
&self,
height: &BlockHeight,
) -> Result<Option<SignedBlockHeader>, PropertyQueryError> {
let id = self.get_existing_block_id_by_height(height).log_err()?;
let id = id
.classify(self.chain_config)
.chain_block_id()
.ok_or(PropertyQueryError::GenesisHeaderRequested)
.log_err()?;
Ok(self
.get_block_index(&id)
.log_err()?
.map(|block_index| block_index.into_block_header()))
}
pub fn get_block_reward(
&self,
block_index: &BlockIndex,
) -> Result<Option<BlockReward>, PropertyQueryError> {
Ok(self.db_tx.get_block_reward(block_index).log_err()?)
}
pub fn get_epoch_data(
&self,
epoch_index: EpochIndex,
) -> Result<Option<EpochData>, PropertyQueryError> {
self.db_tx.get_epoch_data(epoch_index).map_err(PropertyQueryError::from)
}
pub fn get_account_nonce_count(
&self,
account: AccountType,
) -> Result<Option<AccountNonce>, PropertyQueryError> {
self.db_tx.get_account_nonce_count(account).map_err(PropertyQueryError::from)
}
pub fn get_block_height_in_main_chain(
&self,
id: &Id<GenBlock>,
) -> Result<Option<BlockHeight>, PropertyQueryError> {
let gen_id = id;
let id = match gen_id.classify(self.chain_config) {
GenBlockId::Block(id) => id,
GenBlockId::Genesis(_) => return Ok(Some(BlockHeight::zero())),
};
let block_index = self.get_block_index(&id).log_err()?;
let block_index = block_index.ok_or(PropertyQueryError::BlockNotFound(id)).log_err()?;
let mainchain_block_id = self.get_block_id_by_height(&block_index.block_height())?;
// Note: this function may be called when the chain is still empty, so we don't unwrap
// mainchain_block_id and wrap gen_id instead.
if mainchain_block_id.as_ref() == Some(gen_id) {
Ok(Some(block_index.block_height()))
} else {
Ok(None)
}
}
// Get indexes for a new longest chain
fn get_new_chain(
&self,
new_tip_block_index: &BlockIndex,
) -> Result<Vec<BlockIndex>, PropertyQueryError> {
let mut result = Vec::new();
let mut block_index = new_tip_block_index.clone();
while !self.is_block_in_main_chain(&(*block_index.block_id()).into()).log_err()? {
result.push(block_index.clone());
block_index = match self.get_previous_block_index(&block_index).log_err()? {
GenBlockIndex::Genesis(_) => break,
GenBlockIndex::Block(blkidx) => blkidx,
}
}
result.reverse();
Ok(result)
}
fn enforce_checkpoints(&self, header: &SignedBlockHeader) -> Result<(), CheckBlockError> {
let prev_block_index = self.get_previous_block_index_for_check_block(header)?;
let current_height = prev_block_index.block_height().next_height();
// If the block height is at the exact checkpoint height, we need to check that the block id matches the checkpoint id
if let Some(e) =
self.chain_config.height_checkpoints().checkpoint_at_height(¤t_height)
{
let expected_id = Id::<Block>::new(e.to_hash());
if expected_id != header.get_id() {
return Err(CheckBlockError::CheckpointMismatch(
expected_id,
header.get_id(),
));
}
}
// If the block height does not match a checkpoint height, we need to check that an ancestor block id matches the checkpoint id
let (expected_checkpoint_height, expected_checkpoint_id) = self
.chain_config
.height_checkpoints()
.parent_checkpoint_to_height(current_height);
let parent_checkpoint_block_index =
self.get_ancestor(&prev_block_index, expected_checkpoint_height)?;
let parent_checkpoint_id = parent_checkpoint_block_index.block_id();
if parent_checkpoint_id != expected_checkpoint_id {
return Err(CheckBlockError::ParentCheckpointMismatch(
expected_checkpoint_height,
expected_checkpoint_id,
parent_checkpoint_id,
));
}
Ok(())
}
fn check_block_height_vs_max_reorg_depth(
&self,
header: &SignedBlockHeader,
) -> Result<(), CheckBlockError> {
let prev_block_index = self.get_previous_block_index_for_check_block(header)?;
let common_ancestor_height =
self.last_common_ancestor_in_main_chain(&prev_block_index)?.block_height();
let min_allowed_height = self.get_min_height_with_allowed_reorg()?;
if common_ancestor_height < min_allowed_height {
let tip_block_height = self.get_best_block_index()?.block_height();
return Err(CheckBlockError::AttemptedToAddBlockBeforeReorgLimit(
common_ancestor_height,
tip_block_height,
min_allowed_height,
));
}
Ok(())
}
/// Return BlockIndex of the previous block.
fn get_previous_block_index_for_check_block(
&self,
block_header: &SignedBlockHeader,
) -> Result<GenBlockIndex, CheckBlockError> {
let prev_block_id = block_header.prev_block_id();
self.get_gen_block_index(prev_block_id)?
.ok_or(CheckBlockError::PrevBlockNotFound(
*prev_block_id,
block_header.get_id(),
))
}
/// Return Ok(()) if the specified block has a valid parent and an error otherwise.
pub fn check_block_parent(
&self,
block_header: &SignedBlockHeader,
) -> Result<(), CheckBlockError> {
let parent_block_index = self.get_previous_block_index_for_check_block(block_header)?;
ensure!(
parent_block_index.status().is_ok(),
CheckBlockError::InvalidParent(block_header.block_id())
);
Ok(())
}
pub fn check_block_header(&self, header: &SignedBlockHeader) -> Result<(), CheckBlockError> {
self.check_block_parent(header).log_err()?;
self.check_header_size(header).log_err()?;
self.enforce_checkpoints(header).log_err()?;
self.check_block_height_vs_max_reorg_depth(header)?;
let utxos_db = UtxosDB::new(&self.db_tx);
let pos_db = PoSAccountingDB::<_, TipStorageTag>::new(&self.db_tx);
let is_pos = match header.consensus_data() {
ConsensusData::None | ConsensusData::PoW(_) => false,
ConsensusData::PoS(_) => true,
};
let (utxos_cache, pos_delta, epoch_data_cache) = if is_pos {
// Validating PoS blocks in branches requires utxo set, accounting info and epoch data
// that should be updated by doing a reorg beforehand.
// It will be a no-op for attaching a block to the tip.
let best_block_id = self.get_best_block_id()?;
let (verifier_delta, consumed_epoch_data) =
self.reorganize_in_memory(header.header(), best_block_id)?;
let (consumed_utxos, consumed_deltas) = verifier_delta.consume();
let utxos_cache =
UtxosCache::from_data(&utxos_db, consumed_utxos).expect("should not fail");
let pos_delta = PoSAccountingDelta::from_data(&pos_db, consumed_deltas);
let epoch_data_cache = EpochDataCache::from_data(&self.db_tx, consumed_epoch_data);
(utxos_cache, pos_delta, epoch_data_cache)
} else {
let utxos_cache = UtxosCache::new(&utxos_db).expect("should not fail");
let pos_delta = PoSAccountingDelta::new(&pos_db);
let epoch_data_cache = EpochDataCache::new(&self.db_tx);
(utxos_cache, pos_delta, epoch_data_cache)
};
consensus::validate_consensus(
self.chain_config,
header,
self,
&epoch_data_cache,
&utxos_cache,
&pos_delta,
)
.map_err(CheckBlockError::ConsensusVerificationFailed)
.log_err()?;
// This enforces the minimum accepted timestamp for the block. Depending on the consensus algorithm,
// there might be extra checks. For example, PoS requires the timestamp to be greater the previous
// block's timestamp.
let median_time_past = calculate_median_time_past(self, header.prev_block_id());
ensure!(
header.timestamp() >= median_time_past,
CheckBlockError::BlockTimeOrderInvalid(header.timestamp(), median_time_past),
);
let max_future_offset = self.chain_config.max_future_block_time_offset();
let current_time = self.current_time().as_duration_since_epoch();
let block_timestamp = header.timestamp();
ensure!(
block_timestamp.as_duration_since_epoch() <= current_time + *max_future_offset,
CheckBlockError::BlockFromTheFuture,
);
Ok(())
}
fn check_block_reward_maturity_settings(&self, block: &Block) -> Result<(), CheckBlockError> {
block
.block_reward()
.outputs()
.iter()
.enumerate()
.try_for_each(|(index, output)| {
let required = match block.consensus_data() {
ConsensusData::None => {
self.chain_config.empty_consensus_reward_maturity_distance()
}
ConsensusData::PoW(_) => {
self.chain_config.get_proof_of_work_config().reward_maturity_distance()
}
ConsensusData::PoS(_) => BlockDistance::new(0),
};
match block.consensus_data() {
ConsensusData::None | ConsensusData::PoW(_) => match output {
TxOutput::LockThenTransfer(_, _, tl) => {
let outpoint = UtxoOutPoint::new(block.get_id().into(), index as u32);
tx_verifier::timelock_check::check_output_maturity_setting(
tl, required, outpoint,
)
.map_err(CheckBlockError::BlockRewardMaturityError)
}
TxOutput::Transfer(_, _)
| TxOutput::CreateStakePool(_, _)
| TxOutput::ProduceBlockFromStake(_, _)
| TxOutput::Burn(_)
| TxOutput::CreateDelegationId(_, _)
| TxOutput::DelegateStaking(_, _) => Err(
CheckBlockError::InvalidBlockRewardOutputType(block.get_id()),
),
},
ConsensusData::PoS(_) => {
match output {
// The output can be reused in block reward right away
TxOutput::ProduceBlockFromStake(_, _) => Ok(()),
TxOutput::Transfer(_, _)
| TxOutput::LockThenTransfer(_, _, _)
| TxOutput::CreateStakePool(_, _)
| TxOutput::Burn(_)
| TxOutput::CreateDelegationId(_, _)
| TxOutput::DelegateStaking(_, _) => Err(
CheckBlockError::InvalidBlockRewardOutputType(block.get_id()),
),
}
}
}
})
}
fn check_header_size(&self, header: &SignedBlockHeader) -> Result<(), BlockSizeError> {
let size = header.header_size();
ensure!(
size <= self.chain_config.max_block_header_size(),
BlockSizeError::Header(size, self.chain_config.max_block_header_size())
);
Ok(())
}
fn check_block_size(&self, block: &Block) -> Result<(), BlockSizeError> {
let block_size = block.block_size();
ensure!(
block_size.size_from_header() <= self.chain_config.max_block_header_size(),
BlockSizeError::Header(
block_size.size_from_header(),
self.chain_config.max_block_header_size()
)
);
ensure!(
block_size.size_from_txs() <= self.chain_config.max_block_size_from_std_scripts(),
BlockSizeError::SizeOfTxs(
block_size.size_from_txs(),
self.chain_config.max_block_size_from_std_scripts()
)
);
ensure!(
block_size.size_from_smart_contracts()
<= self.chain_config.max_block_size_from_smart_contracts(),
BlockSizeError::SizeOfSmartContracts(
block_size.size_from_smart_contracts(),
self.chain_config.max_block_size_from_smart_contracts()
)
);
Ok(())
}
fn check_witness_count(&self, block: &Block) -> Result<(), CheckBlockTransactionsError> {
for tx in block.transactions() {
ensure!(
tx.inputs().len() == tx.signatures().len(),
CheckBlockTransactionsError::InvalidWitnessCount
)
}
Ok(())
}
fn check_duplicate_inputs(&self, block: &Block) -> Result<(), CheckBlockTransactionsError> {
// check for duplicate inputs (see CVE-2018-17144)
let mut block_inputs = BTreeSet::new();
for tx in block.transactions() {
if tx.inputs().is_empty() || tx.outputs().is_empty() {
return Err(
CheckBlockTransactionsError::EmptyInputsOutputsInTransactionInBlock(
tx.transaction().get_id(),
block.get_id(),
),
);
}
let mut tx_inputs = BTreeSet::new();
for input in tx.inputs() {
ensure!(
tx_inputs.insert(input),
CheckBlockTransactionsError::DuplicateInputInTransaction(
tx.transaction().get_id(),
block.get_id()
)
);
ensure!(
block_inputs.insert(input),
CheckBlockTransactionsError::DuplicateInputInBlock(block.get_id())
);
}
}
Ok(())
}
fn check_tokens_txs(&self, block: &Block) -> Result<(), CheckBlockTransactionsError> {
for tx in block.transactions() {
// We can't issue multiple tokens in a single tx
let issuance_count = get_tokens_issuance_count(tx.outputs());
ensure!(
issuance_count <= 1,
CheckBlockTransactionsError::TokensError(
TokensError::MultipleTokenIssuanceInTransaction(
tx.transaction().get_id(),
block.get_id()
),
)
);
// Check tokens
tx.outputs()
.iter()
.filter_map(|output| match output {
TxOutput::Transfer(v, _)
| TxOutput::LockThenTransfer(v, _, _)
| TxOutput::Burn(v) => v.token_data(),
TxOutput::CreateStakePool(_, _)
| TxOutput::ProduceBlockFromStake(_, _)
| TxOutput::CreateDelegationId(_, _)
| TxOutput::DelegateStaking(_, _) => None,
})
.try_for_each(|token_data| {
check_tokens_data(
self.chain_config,
token_data,
tx.transaction(),
block.get_id(),
)
})
.map_err(CheckBlockTransactionsError::TokensError)
.log_err()?;
}
Ok(())
}
fn check_no_signature_size(&self, block: &Block) -> Result<(), CheckBlockTransactionsError> {
for tx in block.transactions() {
for signature in tx.signatures() {
match signature {
common::chain::signature::inputsig::InputWitness::NoSignature(data) => {
if let Some(inner_data) = data {
ensure!(
inner_data.len() <= self.chain_config.max_no_signature_data_size(),
CheckBlockTransactionsError::NoSignatureDataSizeTooLarge(
inner_data.len(),
self.chain_config.max_no_signature_data_size(),
)
)
}
}
common::chain::signature::inputsig::InputWitness::Standard(_) => (),
}
}
}
Ok(())
}
fn check_transactions(&self, block: &Block) -> Result<(), CheckBlockTransactionsError> {
// Note: duplicate txs are detected through duplicate inputs
self.check_witness_count(block).log_err()?;
self.check_duplicate_inputs(block).log_err()?;
self.check_tokens_txs(block).log_err()?;
self.check_no_signature_size(block).log_err()?;
Ok(())
}
fn get_block_from_index(
&self,
block_index: &BlockIndex,
) -> Result<Option<Block>, chainstate_storage::Error> {
self.db_tx.get_block(*block_index.block_id()).log_err()
}
pub fn check_block(&self, block: &WithId<Block>) -> Result<(), CheckBlockError> {
self.check_block_header(block.header()).log_err()?;
self.check_block_size(block)
.map_err(CheckBlockError::BlockSizeError)
.log_err()?;
self.check_block_reward_maturity_settings(block).log_err()?;
let merkle_proxy = block
.body()
.merkle_tree_proxy()
.map_err(|e| CheckBlockError::MerkleRootCalculationFailed(block.get_id(), e))
.log_err()?;
{
// Merkle root
let merkle_tree_root = block.merkle_root();
ensure!(
merkle_tree_root == merkle_proxy.merkle_tree().root(),
CheckBlockError::MerkleRootMismatch
);
}
{
// Witness merkle root
let witness_merkle_root = block.witness_merkle_root();
ensure!(
witness_merkle_root == merkle_proxy.witness_merkle_tree().root(),
CheckBlockError::MerkleRootMismatch
);
}
self.check_transactions(block)
.map_err(CheckBlockError::CheckTransactionFailed)
.log_err()?;
Ok(())
}
fn get_block_proof(
&self,
prev_block_timestamp: BlockTimestamp,
block: &Block,
) -> Result<Uint256, BlockError> {
block
.header()
.consensus_data()
.get_block_proof(prev_block_timestamp, block.timestamp())
.ok_or_else(|| BlockError::BlockProofCalculationError(block.get_id()))
}
pub fn get_mainchain_blocks_list(&self) -> Result<Vec<Id<Block>>, PropertyQueryError> {
let id_from_height = |block_height: u64| -> Result<Id<Block>, PropertyQueryError> {
let block_height: BlockHeight = block_height.into();
let block_id = self
.get_block_id_by_height(&block_height)
.log_err()?
.expect("Since block_height is >= best_height, this must exist");
let block_id = block_id
.classify(self.chain_config)
.chain_block_id()
.expect("Since the height is never zero, this cannot be genesis");
Ok(block_id)
};
let best_block_index = self.get_best_block_index().log_err()?;
let best_height = best_block_index.block_height();
let best_height_int: u64 = best_height.into();
let mut result = Vec::with_capacity(best_height_int as usize);
for block_height in 1..=best_height_int {
result.push(id_from_height(block_height).log_err()?);
}
Ok(result)
}
pub fn get_block_id_tree_as_list(&self) -> Result<Vec<Id<Block>>, PropertyQueryError> {
self.get_higher_block_ids_sorted_by_height(0.into())
}
/// Return ids of all blocks with height bigger than the specified one,
/// sorted by height (lower first).
// TODO: this function iterates over all block indices in the DB, which is too expensive
// for places where it's currently used (such as block invalidation or best chain selection).
// We need either to optimize it or replace it with some other solution.
// See https://github.com/mintlayer/mintlayer-core/issues/1033, item #5.
pub fn get_higher_block_ids_sorted_by_height(
&self,
start_from: BlockHeight,
) -> Result<Vec<Id<Block>>, PropertyQueryError> {
let block_tree_map = self.db_tx.get_block_tree_by_height(start_from).log_err()?;
let result = block_tree_map
.into_iter()
.flat_map(|(_height, ids_per_height)| ids_per_height)
.collect();
Ok(result)
}
/// Collect block indices corresponding to the branch starting at root_block_id.
/// The first block index in the result will correspond to root_block_id.
pub fn collect_block_indices_in_branch(
&self,
root_block_id: &Id<Block>,
) -> Result<Vec<BlockIndex>, PropertyQueryError> {
let root_block_index = self.get_existing_block_index(root_block_id).log_err()?;
let next_block_height = root_block_index.block_height().next_height();
let maybe_descendant_block_ids =
self.get_higher_block_ids_sorted_by_height(next_block_height)?;
let mut result = Vec::new();
let mut seen_block_ids = BTreeSet::new();
seen_block_ids.insert(*root_block_index.block_id());
result.push(root_block_index);
for cur_block_id in maybe_descendant_block_ids {
let cur_block_index = self.get_existing_block_index(&cur_block_id).log_err()?;
let prev_block_id = cur_block_index
.prev_block_id()
.classify(self.chain_config)
.chain_block_id()
.expect("Genesis at non-zero height");
if seen_block_ids.contains(&prev_block_id) {
result.push(cur_block_index);
seen_block_ids.insert(cur_block_id);
}
}
Ok(result)
}
pub fn create_block_index_for_new_block(
&self,
block: &WithId<Block>,
block_status: BlockStatus,
) -> Result<BlockIndex, BlockError> {
let prev_block_id = block.header().prev_block_id();
let prev_block_index = self
.get_gen_block_index(prev_block_id)
.map_err(|err| BlockError::BlockIndexQueryError(*prev_block_id, err))?
.ok_or(BlockError::PrevBlockNotFoundForNewBlock(block.get_id()))?;
// Set the block height
let height = prev_block_index.block_height().next_height();
let some_ancestor = {
let skip_ht = get_skip_height(height);
let err = |_| panic!("Ancestor retrieval failed for block: {}", block.get_id());
self.get_ancestor(&prev_block_index, skip_ht).unwrap_or_else(err).block_id()
};
// Set Time Max
let time_max = std::cmp::max(prev_block_index.chain_timestamps_max(), block.timestamp());
let current_block_proof =
self.get_block_proof(prev_block_index.block_timestamp(), block).log_err()?;
// Set Chain Trust
let prev_block_chaintrust: Uint256 = prev_block_index.chain_trust();
let chain_trust = (prev_block_chaintrust + current_block_proof)
.expect("Chain trust growth is locally controlled. This can't happen.");
let block_index = BlockIndex::new(
block,
chain_trust,
some_ancestor,
height,
time_max,
block_status,
);
Ok(block_index)
}
}
impl<'a, S: BlockchainStorageWrite, V: TransactionVerificationStrategy> ChainstateRef<'a, S, V> {
pub fn disconnect_until(
&mut self,
cur_tip_block_id: &Id<Block>,
last_to_remain_connected: &Id<GenBlock>,
) -> Result<(), BlockError> {
let mut block_id_to_disconnect: Id<GenBlock> = (*cur_tip_block_id).into();
while block_id_to_disconnect != *last_to_remain_connected {
let cur_block_id = match block_id_to_disconnect.classify(self.chain_config) {