Skip to content

Commit 74f7624

Browse files
authored
Merge pull request #1972 from mintlayer/fork_detection_script_deps
Various changes related to the fork detection script
2 parents 526f6c1 + 92743c3 commit 74f7624

File tree

38 files changed

+1122
-277
lines changed

38 files changed

+1122
-277
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ cfg-if = "1.0"
149149
chacha20poly1305 = "0.10"
150150
chrono = "0.4"
151151
clap = "4.5"
152+
csv = "1.3"
152153
ctor = "0.2"
153154
criterion = "0.5"
154155
crossterm = "0.28"

blockprod/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ mod tests {
274274
max_db_commit_attempts: Default::default(),
275275
max_orphan_blocks: Default::default(),
276276
min_max_bootstrap_import_buffer_sizes: Default::default(),
277+
allow_checkpoints_mismatch: Default::default(),
277278
};
278279

279280
let mempool_config = MempoolConfig::new();

build-tools/docker/build.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import toml
1+
import argparse
22
import os
3+
import pathlib
34
import subprocess
4-
import argparse
5+
import toml
6+
7+
8+
ROOT_DIR = pathlib.Path(__file__).resolve().parent.parent.parent
9+
ROOT_CARGO_TOML = ROOT_DIR.joinpath("Cargo.toml")
510

611

712
def get_cargo_version(cargo_toml_path):
@@ -44,7 +49,7 @@ def build_docker_image(dockerfile_path, image_name, tags, num_jobs=None):
4449

4550
try:
4651
# Run the command
47-
subprocess.check_call(command, shell=True)
52+
subprocess.check_call(command, shell=True, cwd=ROOT_DIR)
4853
print(f"Built {image_name} successfully (the tags are: {full_tags}).")
4954
except subprocess.CalledProcessError as error:
5055
print(f"Failed to build {image_name}: {error}")
@@ -121,7 +126,7 @@ def main():
121126
parser.add_argument('--local_tags', nargs='*', help='Additional tags to apply (these won\'t be pushed)', default=[])
122127
args = parser.parse_args()
123128

124-
version = args.version if args.version else get_cargo_version("Cargo.toml")
129+
version = args.version if args.version else get_cargo_version(ROOT_CARGO_TOML)
125130
# Note: the CI currently takes the version from the release tag, so it always starts with "v",
126131
# but the version from Cargo.toml doesn't have this prefix.
127132
version = version.removeprefix("v")

build-tools/docker/example-mainnet/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ services:
6565
- node-daemon
6666
environment:
6767
<<: *ml-common-env
68+
ML_API_SCANNER_DAEMON_NETWORK: mainnet
6869
ML_API_SCANNER_DAEMON_POSTGRES_HOST: api-postgres-db
6970
ML_API_SCANNER_DAEMON_POSTGRES_USER: $API_SERVER_POSTGRES_USER
7071
ML_API_SCANNER_DAEMON_POSTGRES_PASSWORD: $API_SERVER_POSTGRES_PASSWORD
@@ -83,6 +84,7 @@ services:
8384
- node-daemon
8485
environment:
8586
<<: *ml-common-env
87+
ML_API_WEB_SRV_NETWORK: mainnet
8688
ML_API_WEB_SRV_BIND_ADDRESS: 0.0.0.0:3000
8789
ML_API_WEB_SRV_POSTGRES_HOST: api-postgres-db
8890
ML_API_WEB_SRV_POSTGRES_USER: $API_SERVER_POSTGRES_USER

chainstate/src/config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,24 @@ make_config_setting!(MaxTipAge, Duration, Duration::from_secs(60 * 60 * 24));
3838
pub struct ChainstateConfig {
3939
/// The number of maximum attempts to process a block.
4040
pub max_db_commit_attempts: MaxDbCommitAttempts,
41+
4142
/// The maximum capacity of the orphan blocks pool.
4243
pub max_orphan_blocks: MaxOrphanBlocks,
44+
4345
/// When importing bootstrap file, this controls the buffer sizes (min, max)
4446
/// (see bootstrap import function for more information)
4547
pub min_max_bootstrap_import_buffer_sizes: MinMaxBootstrapImportBufferSizes,
48+
4649
/// The initial block download is finished if the difference between the current time and the
4750
/// tip time is less than this value.
4851
pub max_tip_age: MaxTipAge,
52+
4953
/// If true, additional computationally-expensive consistency checks will be performed by
5054
/// the chainstate. The default value depends on the chain type.
5155
pub enable_heavy_checks: Option<bool>,
56+
57+
/// If true, blocks and block headers will not be rejected if checkpoints mismatch is detected.
58+
pub allow_checkpoints_mismatch: Option<bool>,
5259
}
5360

5461
impl ChainstateConfig {
@@ -90,4 +97,8 @@ impl ChainstateConfig {
9097
ChainType::Regtest => true,
9198
}
9299
}
100+
101+
pub fn checkpoints_mismatch_allowed(&self) -> bool {
102+
self.allow_checkpoints_mismatch.unwrap_or(false)
103+
}
93104
}

chainstate/src/detail/ban_score.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,13 @@ impl BanScore for CheckBlockError {
317317
CheckBlockError::MerkleRootCalculationFailed(_, _) => 100,
318318
CheckBlockError::BlockRewardMaturityError(err) => err.ban_score(),
319319
CheckBlockError::PropertyQueryError(_) => 100,
320-
CheckBlockError::CheckpointMismatch(_, _) => 100,
321-
CheckBlockError::ParentCheckpointMismatch(_, _, _) => 100,
320+
CheckBlockError::CheckpointMismatch { .. } => 100,
322321
CheckBlockError::GetAncestorError(_) => 100,
323-
CheckBlockError::AttemptedToAddBlockBeforeReorgLimit(_, _, _) => 100,
322+
CheckBlockError::AttemptedToAddBlockBeforeReorgLimit { .. } => 100,
324323
CheckBlockError::EpochSealError(err) => err.ban_score(),
325324
CheckBlockError::InvalidParent { .. } => 100,
326325
CheckBlockError::InMemoryReorgFailed(err) => err.ban_score(),
326+
CheckBlockError::InvalidBlockAlreadyProcessed(_) => 100,
327327
}
328328
}
329329
}

chainstate/src/detail/chainstateref/mod.rs

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub use in_memory_reorg::InMemoryReorgError;
7676

7777
pub struct ChainstateRef<'a, S, V> {
7878
chain_config: &'a ChainConfig,
79-
_chainstate_config: &'a ChainstateConfig,
79+
chainstate_config: &'a ChainstateConfig,
8080
tx_verification_strategy: &'a V,
8181
db_tx: S,
8282
time_getter: &'a TimeGetter,
@@ -141,7 +141,7 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
141141
) -> Self {
142142
ChainstateRef {
143143
chain_config,
144-
_chainstate_config: chainstate_config,
144+
chainstate_config,
145145
db_tx,
146146
tx_verification_strategy,
147147
time_getter,
@@ -157,7 +157,7 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
157157
) -> Self {
158158
ChainstateRef {
159159
chain_config,
160-
_chainstate_config: chainstate_config,
160+
chainstate_config,
161161
db_tx,
162162
tx_verification_strategy,
163163
time_getter,
@@ -457,22 +457,45 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
457457
Ok(result)
458458
}
459459

460+
fn enforce_checkpoint_impl(
461+
&self,
462+
height: BlockHeight,
463+
expected: &Id<GenBlock>,
464+
given: &Id<GenBlock>,
465+
) -> Result<(), CheckBlockError> {
466+
if given != expected {
467+
// Note: we only log the mismatch if we're going to ignore it (because if it's
468+
// not ignored, we'll log the error anyway).
469+
if self.chainstate_config.checkpoints_mismatch_allowed() {
470+
log::warn!(
471+
"Checkpoint mismatch at height {}, expected: {:x}, actual: {:x}",
472+
height,
473+
expected,
474+
given,
475+
);
476+
} else {
477+
return Err(CheckBlockError::CheckpointMismatch {
478+
height,
479+
expected: *expected,
480+
given: *given,
481+
});
482+
}
483+
}
484+
485+
Ok(())
486+
}
487+
460488
// If the header height is at an exact checkpoint height, check that the block id matches the checkpoint id.
461489
// Return true if the header height is at an exact checkpoint height.
462490
fn enforce_exact_checkpoint_assuming_height(
463491
&self,
464492
header: &SignedBlockHeader,
465493
header_height: BlockHeight,
466494
) -> Result<bool, CheckBlockError> {
467-
if let Some(e) = self.chain_config.height_checkpoints().checkpoint_at_height(&header_height)
495+
if let Some(expected_id) =
496+
self.chain_config.height_checkpoints().checkpoint_at_height(&header_height)
468497
{
469-
let expected_id = Id::<Block>::new(e.to_hash());
470-
if expected_id != header.get_id() {
471-
return Err(CheckBlockError::CheckpointMismatch(
472-
expected_id,
473-
header.get_id(),
474-
));
475-
}
498+
self.enforce_checkpoint_impl(header_height, expected_id, &header.get_id().into())?;
476499
Ok(true)
477500
} else {
478501
Ok(false)
@@ -499,17 +522,13 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
499522

500523
let parent_checkpoint_block_index =
501524
self.get_ancestor(&prev_block_index, expected_checkpoint_height)?;
502-
503525
let parent_checkpoint_id = parent_checkpoint_block_index.block_id();
504526

505-
if parent_checkpoint_id != expected_checkpoint_id {
506-
return Err(CheckBlockError::ParentCheckpointMismatch(
507-
expected_checkpoint_height,
508-
expected_checkpoint_id,
509-
parent_checkpoint_id,
510-
));
511-
}
512-
527+
self.enforce_checkpoint_impl(
528+
expected_checkpoint_height,
529+
&expected_checkpoint_id,
530+
&parent_checkpoint_id,
531+
)?;
513532
Ok(())
514533
}
515534

@@ -564,11 +583,11 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
564583
if common_ancestor_height < min_allowed_height {
565584
let tip_block_height = self.get_best_block_index()?.block_height();
566585

567-
return Err(CheckBlockError::AttemptedToAddBlockBeforeReorgLimit(
586+
return Err(CheckBlockError::AttemptedToAddBlockBeforeReorgLimit {
568587
common_ancestor_height,
569588
tip_block_height,
570589
min_allowed_height,
571-
));
590+
});
572591
}
573592

574593
Ok(())
@@ -599,8 +618,45 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
599618
Ok(parent_block_index)
600619
}
601620

621+
/// This function is intended to be used in check_block and check_block_header.
622+
///
623+
/// Return true if the block already exists in the chainstate and has an "ok" status
624+
/// with the validation stage CheckBlockOk or later.
625+
/// If it has a non-"ok" status, return an error.
626+
/// If the block is new, or if its validation stage is below CheckBlockOk (i.e. it's Unchecked),
627+
/// return false.
628+
fn skip_check_block_because_block_exists_and_is_checked(
629+
&self,
630+
block_id: &Id<Block>,
631+
) -> Result<bool, CheckBlockError> {
632+
if let Some(block_index) = self.get_block_index(block_id)? {
633+
let status = block_index.status();
634+
635+
if status.is_ok() {
636+
let checked = status.last_valid_stage() >= BlockValidationStage::CheckBlockOk;
637+
Ok(checked)
638+
} else {
639+
Err(CheckBlockError::InvalidBlockAlreadyProcessed(*block_id))
640+
}
641+
} else {
642+
Ok(false)
643+
}
644+
}
645+
602646
#[log_error]
603647
pub fn check_block_header(&self, header: &SignedBlockHeader) -> Result<(), CheckBlockError> {
648+
let header = WithId::new(header);
649+
if self.skip_check_block_because_block_exists_and_is_checked(&WithId::id(&header))? {
650+
return Ok(());
651+
}
652+
653+
self.check_block_header_impl(&header)
654+
}
655+
656+
fn check_block_header_impl(
657+
&self,
658+
header: &WithId<&SignedBlockHeader>,
659+
) -> Result<(), CheckBlockError> {
604660
let parent_block_index = self.check_block_parent(header)?;
605661
self.check_header_size(header)?;
606662
self.enforce_checkpoints(header)?;
@@ -662,7 +718,7 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
662718
ensure!(
663719
block_timestamp.as_duration_since_epoch() <= current_time_as_secs + max_future_offset,
664720
CheckBlockError::BlockFromTheFuture {
665-
block_id: header.block_id(),
721+
block_id: WithId::id(header),
666722
block_timestamp,
667723
current_time
668724
},
@@ -833,7 +889,14 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
833889

834890
#[log_error]
835891
pub fn check_block(&self, block: &WithId<Block>) -> Result<(), CheckBlockError> {
836-
self.check_block_header(block.header())?;
892+
let header_with_id = WithId::as_sub_obj(block);
893+
if self
894+
.skip_check_block_because_block_exists_and_is_checked(&WithId::id(&header_with_id))?
895+
{
896+
return Ok(());
897+
}
898+
899+
self.check_block_header_impl(&header_with_id)?;
837900

838901
self.check_block_size(block).map_err(CheckBlockError::BlockSizeError)?;
839902

chainstate/src/detail/error.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,25 @@ pub enum CheckBlockError {
159159
InvalidBlockRewardOutputType(Id<Block>),
160160
#[error("Block reward maturity error: {0}")]
161161
BlockRewardMaturityError(#[from] tx_verifier::timelock_check::OutputMaturityError),
162-
#[error("Checkpoint mismatch: expected {0} vs given {1}")]
163-
CheckpointMismatch(Id<Block>, Id<Block>),
164-
#[error("Parent checkpoint mismatch at height {0}: expected {1} vs given {2}")]
165-
ParentCheckpointMismatch(BlockHeight, Id<GenBlock>, Id<GenBlock>),
162+
#[error("Checkpoint mismatch at height {height}: expected {expected:x}, given {given:x}")]
163+
CheckpointMismatch {
164+
height: BlockHeight,
165+
expected: Id<GenBlock>,
166+
given: Id<GenBlock>,
167+
},
166168
#[error("CRITICAL: Failed to retrieve ancestor of submitted block: {0}")]
167169
GetAncestorError(#[from] GetAncestorError),
168-
#[error("Attempted to add a block before reorg limit (attempted at height: {0} while current height is: {1} and min allowed is: {2})")]
169-
AttemptedToAddBlockBeforeReorgLimit(BlockHeight, BlockHeight, BlockHeight),
170+
#[error(
171+
"Attempted to add a block before reorg limit (attempted at height: {} while current height is: {} and min allowed is: {})",
172+
common_ancestor_height,
173+
tip_block_height,
174+
min_allowed_height
175+
)]
176+
AttemptedToAddBlockBeforeReorgLimit {
177+
common_ancestor_height: BlockHeight,
178+
tip_block_height: BlockHeight,
179+
min_allowed_height: BlockHeight,
180+
},
170181
#[error("TransactionVerifier error: {0}")]
171182
TransactionVerifierError(#[from] TransactionVerifierStorageError),
172183
#[error("Error during sealing an epoch: {0}")]
@@ -178,6 +189,8 @@ pub enum CheckBlockError {
178189
},
179190
#[error("In-memory reorg failed: {0}")]
180191
InMemoryReorgFailed(#[from] InMemoryReorgError),
192+
#[error("Block {0} has already been processed and marked as invalid")]
193+
InvalidBlockAlreadyProcessed(Id<Block>),
181194
}
182195

183196
#[derive(Error, Debug, PartialEq, Eq, Clone)]

chainstate/src/detail/error_classification.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,12 @@ impl BlockProcessingErrorClassification for CheckBlockError {
169169
| CheckBlockError::ParentBlockMissing { .. }
170170
| CheckBlockError::BlockTimeOrderInvalid(_, _)
171171
| CheckBlockError::InvalidBlockRewardOutputType(_)
172-
| CheckBlockError::CheckpointMismatch(_, _)
173-
| CheckBlockError::ParentCheckpointMismatch(_, _, _)
174-
| CheckBlockError::AttemptedToAddBlockBeforeReorgLimit(_, _, _)
175-
| CheckBlockError::InvalidParent { .. } => BlockProcessingErrorClass::BadBlock,
172+
| CheckBlockError::CheckpointMismatch { .. }
173+
| CheckBlockError::AttemptedToAddBlockBeforeReorgLimit { .. }
174+
| CheckBlockError::InvalidParent { .. }
175+
| CheckBlockError::InvalidBlockAlreadyProcessed(_) => {
176+
BlockProcessingErrorClass::BadBlock
177+
}
176178

177179
CheckBlockError::BlockFromTheFuture { .. } => {
178180
BlockProcessingErrorClass::TemporarilyBadBlock

0 commit comments

Comments
 (0)