Skip to content

Add chainstate storage version mismatch check #1098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions chainstate/launcher/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ impl Default for StorageBackendConfig {
}
}

impl StorageBackendConfig {
pub fn subdirectory_name(&self) -> Option<&str> {
match self {
StorageBackendConfig::Lmdb => Some(crate::SUBDIRECTORY_LMDB),
StorageBackendConfig::InMemory => None,
}
}
}

/// Storage configuration
#[must_use]
#[derive(Debug, Default)]
Expand Down
11 changes: 9 additions & 2 deletions chainstate/launcher/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@
//! Tools to set up chainstate together with its storage

mod config;
mod storage_compatibility;

use std::sync::Arc;

use chainstate::InitializationError;
use storage_lmdb::resize_callback::MapResizeCallback;

// Some useful reexports
pub use chainstate::{
chainstate_interface::ChainstateInterface, ChainstateConfig, ChainstateError as Error,
DefaultTransactionVerificationStrategy,
};
pub use common::chain::ChainConfig;
pub use config::{ChainstateLauncherConfig, StorageBackendConfig};
use storage_lmdb::resize_callback::MapResizeCallback;

/// Subdirectory under `datadir` where LMDB chainstate database is placed
pub const SUBDIRECTORY_LMDB: &str = "chainstate-lmdb";
Expand All @@ -36,8 +39,12 @@ fn make_chainstate_and_storage_impl<B: 'static + storage::Backend>(
chain_config: Arc<ChainConfig>,
chainstate_config: ChainstateConfig,
) -> Result<Box<dyn ChainstateInterface>, Error> {
let storage = chainstate_storage::Store::new(storage_backend)
let mut storage = chainstate_storage::Store::new(storage_backend)
.map_err(|e| Error::FailedToInitializeChainstate(e.into()))?;

storage_compatibility::check_storage_compatibility(&mut storage, chain_config.as_ref())
.map_err(InitializationError::StorageCompatibilityCheckError)?;

let chainstate = chainstate::make_chainstate(
chain_config,
chainstate_config,
Expand Down
106 changes: 106 additions & 0 deletions chainstate/launcher/src/storage_compatibility.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) 2023 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.

use chainstate::StorageCompatibilityCheckError;
use chainstate_storage::{BlockchainStorageRead, BlockchainStorageWrite};
use common::chain::ChainConfig;
use utils::ensure;

const CHAINSTATE_STORAGE_VERSION_UNINITIALIZED: u32 = 0;
const CHAINSTATE_STORAGE_VERSION_V1: u32 = 1;
const CURRENT_CHAINSTATE_STORAGE_VERSION: u32 = CHAINSTATE_STORAGE_VERSION_V1;

pub fn check_storage_compatibility<B: 'static + storage::Backend>(
storage: &mut chainstate_storage::Store<B>,
chain_config: &ChainConfig,
) -> Result<(), StorageCompatibilityCheckError> {
check_storage_version(storage)?;
check_magic_bytes(storage, chain_config)?;
check_chain_type(storage, chain_config)?;

Ok(())
}

fn check_storage_version<B: 'static + storage::Backend>(
storage: &mut chainstate_storage::Store<B>,
) -> Result<(), StorageCompatibilityCheckError> {
let storage_version = storage
.get_storage_version()
.map_err(StorageCompatibilityCheckError::StorageError)?;

if storage_version == CHAINSTATE_STORAGE_VERSION_UNINITIALIZED {
storage
.set_storage_version(CURRENT_CHAINSTATE_STORAGE_VERSION)
.map_err(StorageCompatibilityCheckError::StorageError)?;
} else {
ensure!(
storage_version == CURRENT_CHAINSTATE_STORAGE_VERSION,
StorageCompatibilityCheckError::ChainstateStorageVersionMismatch(
storage_version,
CURRENT_CHAINSTATE_STORAGE_VERSION
)
);
}
Ok(())
}

fn check_magic_bytes<B: 'static + storage::Backend>(
storage: &mut chainstate_storage::Store<B>,
chain_config: &ChainConfig,
) -> Result<(), StorageCompatibilityCheckError> {
let storage_magic_bytes = storage
.get_magic_bytes()
.map_err(StorageCompatibilityCheckError::StorageError)?;
let chain_config_magic_bytes = chain_config.magic_bytes();

match storage_magic_bytes {
Some(storage_magic_bytes) => ensure!(
&storage_magic_bytes == chain_config_magic_bytes,
StorageCompatibilityCheckError::ChainConfigMagicBytesMismatch(
storage_magic_bytes,
chain_config_magic_bytes.to_owned()
)
),
None => storage
.set_magic_bytes(chain_config_magic_bytes)
.map_err(StorageCompatibilityCheckError::StorageError)?,
};

Ok(())
}

fn check_chain_type<B: 'static + storage::Backend>(
storage: &mut chainstate_storage::Store<B>,
chain_config: &ChainConfig,
) -> Result<(), StorageCompatibilityCheckError> {
let storage_chain_type =
storage.get_chain_type().map_err(StorageCompatibilityCheckError::StorageError)?;
let chain_config_type = chain_config.chain_type().name();

match storage_chain_type {
Some(storage_chain_type) => ensure!(
storage_chain_type == chain_config_type,
StorageCompatibilityCheckError::ChainTypeMismatch(
storage_chain_type,
chain_config_type.to_owned()
)
),
None => storage
.set_chain_type(chain_config_type)
.map_err(StorageCompatibilityCheckError::StorageError)?,
};

Ok(())
}
18 changes: 18 additions & 0 deletions chainstate/src/detail/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,24 @@ pub enum InitializationError {
Block1Missing,
#[error("Genesis mismatch: {0} according to configuration, {1} inferred from storage")]
GenesisMismatch(Id<GenBlock>, Id<GenBlock>),
#[error("Storage compatibility check error: `{0}`")]
StorageCompatibilityCheckError(#[from] StorageCompatibilityCheckError),
}

#[derive(Error, Debug, PartialEq, Eq, Clone)]
pub enum StorageCompatibilityCheckError {
#[error("Block storage error: `{0}`")]
StorageError(#[from] chainstate_storage::Error),
#[error(
"Node cannot load chainstate database because the versions mismatch: db `{0}`, app `{1}`"
)]
ChainstateStorageVersionMismatch(u32, u32),
#[error(
"Chain's config magic bytes do not match the one from database : expected `{0:?}`, actual `{1:?}`"
)]
ChainConfigMagicBytesMismatch([u8; 4], [u8; 4]),
#[error("Node's chain type doesn't match the one in the database : db `{0}`, app `{1}`")]
ChainTypeMismatch(String, String),
}

impl From<OrphanAddError> for Result<(), OrphanCheckError> {
Expand Down
2 changes: 1 addition & 1 deletion chainstate/src/detail/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub use self::{
pub use chainstate_types::Locator;
pub use error::{
BlockError, CheckBlockError, CheckBlockTransactionsError, DbCommittingContext,
InitializationError, OrphanCheckError,
InitializationError, OrphanCheckError, StorageCompatibilityCheckError,
};

use pos_accounting::{PoSAccountingDB, PoSAccountingOperations};
Expand Down
4 changes: 2 additions & 2 deletions chainstate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ pub use crate::{
ban_score, calculate_median_time_past, check_nft_issuance_data, check_tokens_issuance_data,
is_rfc3986_valid_symbol, BlockError, BlockSource, ChainInfo, CheckBlockError,
CheckBlockTransactionsError, ConnectTransactionError, IOPolicyError, InitializationError,
Locator, OrphanCheckError, SpendStakeError, TokenIssuanceError, TokensError,
TransactionVerifierStorageError, TxIndexError,
Locator, OrphanCheckError, SpendStakeError, StorageCompatibilityCheckError,
TokenIssuanceError, TokensError, TransactionVerifierStorageError, TxIndexError,
},
};

Expand Down
7 changes: 5 additions & 2 deletions chainstate/storage/src/internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ pub struct Store<B: storage::Backend>(storage::Storage<B, Schema>);
impl<B: storage::Backend> Store<B> {
/// Create a new chainstate storage
pub fn new(backend: B) -> crate::Result<Self> {
let mut storage = Self(storage::Storage::new(backend).map_err(crate::Error::from)?);
storage.set_storage_version(1)?;
let storage = Self(storage::Storage::new(backend).map_err(crate::Error::from)?);
Ok(storage)
}

Expand Down Expand Up @@ -203,6 +202,8 @@ macro_rules! delegate_to_transaction {
impl<B: storage::Backend> BlockchainStorageRead for Store<B> {
delegate_to_transaction! {
fn get_storage_version(&self) -> crate::Result<u32>;
fn get_magic_bytes(&self) -> crate::Result<Option<[u8; 4]>>;
fn get_chain_type(&self) -> crate::Result<Option<String>>;
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;
fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
Expand Down Expand Up @@ -356,6 +357,8 @@ impl<B: storage::Backend> PoSAccountingStorageRead<SealedStorageTag> for Store<B
impl<B: storage::Backend> BlockchainStorageWrite for Store<B> {
delegate_to_transaction! {
fn set_storage_version(&mut self, version: u32) -> crate::Result<()>;
fn set_magic_bytes(&mut self, bytes: &[u8; 4]) -> crate::Result<()>;
fn set_chain_type(&mut self, chain: &str) -> crate::Result<()>;
fn set_best_block_id(&mut self, id: &Id<GenBlock>) -> crate::Result<()>;
fn set_block_index(&mut self, block_index: &BlockIndex) -> crate::Result<()>;
fn add_block(&mut self, block: &Block) -> crate::Result<()>;
Expand Down
18 changes: 18 additions & 0 deletions chainstate/storage/src/internal/store_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ mod well_known {
declare_entry!(BestBlockId: Id<GenBlock>);
declare_entry!(UtxosBestBlockId: Id<GenBlock>);
declare_entry!(TxIndexEnabled: bool);
declare_entry!(MagicBytes: [u8; 4]);
declare_entry!(ChainType: String);
}

/// Read-only chainstate storage transaction
Expand All @@ -81,6 +83,14 @@ macro_rules! impl_read_ops {
self.read_value::<well_known::StoreVersion>().map(|v| v.unwrap_or_default())
}

fn get_magic_bytes(&self) -> crate::Result<Option<[u8; 4]>> {
self.read_value::<well_known::MagicBytes>()
}

fn get_chain_type(&self) -> crate::Result<Option<String>> {
self.read_value::<well_known::ChainType>()
}

fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>> {
self.read::<db::DBBlockIndex, _, _>(id)
}
Expand Down Expand Up @@ -388,6 +398,14 @@ impl<'st, B: storage::Backend> BlockchainStorageWrite for StoreTxRw<'st, B> {
self.write_value::<well_known::StoreVersion>(&version)
}

fn set_magic_bytes(&mut self, bytes: &[u8; 4]) -> crate::Result<()> {
self.write_value::<well_known::MagicBytes>(bytes)
}

fn set_chain_type(&mut self, chain: &str) -> crate::Result<()> {
self.write_value::<well_known::ChainType>(&chain.to_owned())
}

fn set_best_block_id(&mut self, id: &Id<GenBlock>) -> crate::Result<()> {
self.write_value::<well_known::BestBlockId>(id)
}
Expand Down
4 changes: 2 additions & 2 deletions chainstate/storage/src/internal/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn test_storage_get_default_version_in_tx() {
let store = TestStore::new_empty().unwrap();
let vtx = store.transaction_ro().unwrap().get_storage_version().unwrap();
let vst = store.get_storage_version().unwrap();
assert_eq!(vtx, 1, "Default storage version wrong");
assert_eq!(vtx, 0, "Default storage version wrong");
assert_eq!(vtx, vst, "Transaction and non-transaction inconsistency");
})
}
Expand Down Expand Up @@ -73,7 +73,7 @@ fn test_storage_manipulation() {
let mut store = TestStore::new_empty().unwrap();

// Storage version manipulation
assert_eq!(store.get_storage_version(), Ok(1));
assert_eq!(store.get_storage_version(), Ok(0));
assert_eq!(store.set_storage_version(2), Ok(()));
assert_eq!(store.get_storage_version(), Ok(2));

Expand Down
12 changes: 12 additions & 0 deletions chainstate/storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ pub trait BlockchainStorageRead:
/// Get storage version
fn get_storage_version(&self) -> crate::Result<u32>;

/// Get magic bytes
fn get_magic_bytes(&self) -> crate::Result<Option<[u8; 4]>>;

/// Get chain type name
fn get_chain_type(&self) -> crate::Result<Option<String>>;

/// Get the hash of the best block
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;

Expand Down Expand Up @@ -134,6 +140,12 @@ pub trait BlockchainStorageWrite:
/// Set storage version
fn set_storage_version(&mut self, version: u32) -> Result<()>;

/// Set magic bytes
fn set_magic_bytes(&mut self, bytes: &[u8; 4]) -> Result<()>;

/// Set chain type name
fn set_chain_type(&mut self, chain: &str) -> Result<()>;

/// Set the hash of the best block
fn set_best_block_id(&mut self, id: &Id<GenBlock>) -> Result<()>;

Expand Down
10 changes: 10 additions & 0 deletions chainstate/storage/src/mock/mock_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ mockall::mock! {

impl crate::BlockchainStorageRead for Store {
fn get_storage_version(&self) -> crate::Result<u32>;
fn get_magic_bytes(&self) -> crate::Result<Option<[u8; 4]>>;
fn get_chain_type(&self) -> crate::Result<Option<String>>;
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;
fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
Expand Down Expand Up @@ -148,6 +150,8 @@ mockall::mock! {

impl crate::BlockchainStorageWrite for Store {
fn set_storage_version(&mut self, version: u32) -> crate::Result<()>;
fn set_magic_bytes(&mut self, bytes: &[u8; 4]) -> crate::Result<()>;
fn set_chain_type(&mut self, chain: &str) -> crate::Result<()>;
fn set_best_block_id(&mut self, id: &Id<GenBlock>) -> crate::Result<()>;
fn set_block_index(&mut self, block_index: &BlockIndex) -> crate::Result<()>;
fn add_block(&mut self, block: &Block) -> crate::Result<()>;
Expand Down Expand Up @@ -297,6 +301,8 @@ mockall::mock! {

impl crate::BlockchainStorageRead for StoreTxRo {
fn get_storage_version(&self) -> crate::Result<u32>;
fn get_magic_bytes(&self) -> crate::Result<Option<[u8; 4]>>;
fn get_chain_type(&self) -> crate::Result<Option<String>>;
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;
fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
Expand Down Expand Up @@ -409,6 +415,8 @@ mockall::mock! {

impl crate::BlockchainStorageRead for StoreTxRw {
fn get_storage_version(&self) -> crate::Result<u32>;
fn get_magic_bytes(&self) -> crate::Result<Option<[u8; 4]>>;
fn get_chain_type(&self) -> crate::Result<Option<String>>;
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
Expand Down Expand Up @@ -511,6 +519,8 @@ mockall::mock! {

impl crate::BlockchainStorageWrite for StoreTxRw {
fn set_storage_version(&mut self, version: u32) -> crate::Result<()>;
fn set_magic_bytes(&mut self, bytes: &[u8; 4]) -> crate::Result<()>;
fn set_chain_type(&mut self, chain: &str) -> crate::Result<()>;
fn set_best_block_id(&mut self, id: &Id<GenBlock>) -> crate::Result<()>;
fn set_block_index(&mut self, block_index: &BlockIndex) -> crate::Result<()>;
fn add_block(&mut self, block: &Block) -> crate::Result<()>;
Expand Down
2 changes: 1 addition & 1 deletion node-lib/src/config_files/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use self::{

/// The node configuration.
#[must_use]
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NodeConfigFile {
// Subsystems configurations.
pub blockprod: Option<BlockProdConfigFile>,
Expand Down
4 changes: 4 additions & 0 deletions node-lib/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ pub enum Command {

#[derive(Args, Clone, Debug, Default)]
pub struct RunOptions {
/// Clean data dir before starting
#[clap(long)]
pub clean_data: Option<bool>,

/// Minimum number of connected peers to enable block production.
#[clap(long)]
pub blockprod_min_peers_to_produce_blocks: Option<usize>,
Expand Down
Loading