diff --git a/CHANGELOG.md b/CHANGELOG.md index 020816ee..cf2cc3cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. +## Version: 0.52.2 + +### New + +- Made Jaeger tracing optional + +## Version: 0.52.1 + +### New + +- Bumped block version to 37 +- Supported CapFeeInGasUnits capability + ## Version: 0.50.23 ### New diff --git a/Cargo.toml b/Cargo.toml index 58232f8a..32b5f505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ build = 'common/build/build.rs' edition = '2021' name = 'ton_node' -version = '0.51.55' +version = '0.52.2' [workspace] members = [ 'storage' ] @@ -39,37 +39,35 @@ sha2 = '0.10' spin = '0.7.1' stream-cancel = '0.8.0' string-builder = '^0.2.0' -adnl = { features = [ 'client', 'node', 'server' ], git = 'https://github.com/tonlabs/ever-adnl.git', tag = '0.7.131' } +adnl = { features = [ 'client', 'node', 'server' ], git = 'https://github.com/tonlabs/ever-adnl.git', tag = '0.7.143' } catchain = { path = 'catchain' } ctrlc = { features = [ 'termination' ], version = '3.2.1' } -dht = { git = 'https://github.com/tonlabs/ever-dht.git', tag = '0.5.141' } -ever-crypto = { git = 'https://github.com/tonlabs/ever-crypto.git', tag = '0.1.91' } +dht = { git = 'https://github.com/tonlabs/ever-dht.git', tag = '0.5.154' } +ever-crypto = { git = 'https://github.com/tonlabs/ever-crypto.git', tag = '0.1.101' } lockfree = { git = 'https://github.com/tonlabs/lockfree.git' } -overlay = { git = 'https://github.com/tonlabs/ever-overlay.git', tag = '0.6.124' } -rldp = { git = 'https://github.com/tonlabs/ever-rldp.git', tag = '0.7.125' } +overlay = { git = 'https://github.com/tonlabs/ever-overlay.git', tag = '0.6.138' } +rldp = { git = 'https://github.com/tonlabs/ever-rldp.git', tag = '0.7.138' } rustracing = { optional = true, version = '0.4.0' } rustracing_jaeger = { optional = true, version = '0.4.1' } statsd = { optional = true, version = '0.15' } storage = { path = 'storage' } tokio = { features = [ 'rt-multi-thread' ], version = '1.5' } -ton_abi = { git = 'https://github.com/tonlabs/ever-abi.git', optional = true, tag = '2.3.65' } -ton_api = { git = 'https://github.com/tonlabs/ever-tl.git', package = 'ton_api', tag = '0.2.178' } -ton_block = { git = 'https://github.com/tonlabs/ever-block.git', tag = '1.9.28' } -ton_block_json = { git = 'https://github.com/tonlabs/ever-block-json.git', tag = '0.7.95' } -ton_executor = { git = 'https://github.com/tonlabs/ever-executor.git', tag = '1.15.175' } +ton_abi = { git = 'https://github.com/tonlabs/ever-abi.git', optional = true, tag = '2.3.75' } +ton_api = { git = 'https://github.com/tonlabs/ever-tl.git', package = 'ton_api', tag = '0.2.188' } +ton_block = { git = 'https://github.com/tonlabs/ever-block.git', tag = '1.9.39' } +ton_block_json = { git = 'https://github.com/tonlabs/ever-block-json.git', tag = '0.7.108' } +ton_executor = { git = 'https://github.com/tonlabs/ever-executor.git', tag = '1.15.189' } ton_types = { git = 'https://github.com/tonlabs/ever-types.git', tag = '1.12.7' } -ton_vm = { git = 'https://github.com/tonlabs/ever-vm.git', tag = '1.8.116' } +ton_vm = { git = 'https://github.com/tonlabs/ever-vm.git', tag = '1.8.127' } validator_session = { path = 'validator-session' } [features] -async_ss_storage = [ 'storage/ref_count_gc' ] ci_run = [ 'storage/ci_run' ] -default = [ 'telemetry', 'async_ss_storage', 'remp_emergency' ] +default = [ 'telemetry' ] export_key = [ 'catchain/export_key', 'ever-crypto/export_key' ] external_db = [ 'rdkafka' ] gosh = [ 'ton_block/gosh', 'ton_vm/gosh' ] metrics = [ 'statsd' ] -remp_emergency = [ ] signature_with_id = [ 'ton_block/signature_with_id', 'ton_vm/signature_with_id', 'ton_executor/signature_with_id' ] slashing = [ 'ton_abi', 'validator_session/slashing' ] telemetry = [ 'adnl/telemetry', 'dht/telemetry', 'rldp/telemetry', 'overlay/telemetry', 'storage/telemetry' ] diff --git a/catchain/Cargo.toml b/catchain/Cargo.toml index e3fb212f..4ff0d133 100644 --- a/catchain/Cargo.toml +++ b/catchain/Cargo.toml @@ -19,13 +19,13 @@ metrics-runtime = '0.13.0' rand = '0.8' regex = '1.3.1' sha2 = '0.10' -adnl = { features = [ 'node' ], git = 'https://github.com/tonlabs/ever-adnl.git', tag = '0.7.131' } -ever-crypto = { git = 'https://github.com/tonlabs/ever-crypto.git', tag = '0.1.91' } -overlay = { git = 'https://github.com/tonlabs/ever-overlay.git', tag = '0.6.124' } -rldp = { git = 'https://github.com/tonlabs/ever-rldp.git', tag = '0.7.125' } +adnl = { features = [ 'node' ], git = 'https://github.com/tonlabs/ever-adnl.git', tag = '0.7.143' } +ever-crypto = { git = 'https://github.com/tonlabs/ever-crypto.git', tag = '0.1.101' } +overlay = { git = 'https://github.com/tonlabs/ever-overlay.git', tag = '0.6.138' } +rldp = { git = 'https://github.com/tonlabs/ever-rldp.git', tag = '0.7.138' } storage = { path = '../storage' } tokio = { features = [ 'rt-multi-thread' ], version = '1.5' } -ton_api = { git = 'https://github.com/tonlabs/ever-tl.git', package = 'ton_api', tag = '0.2.178' } +ton_api = { git = 'https://github.com/tonlabs/ever-tl.git', package = 'ton_api', tag = '0.2.188' } ton_types = { git = 'https://github.com/tonlabs/ever-types.git', tag = '1.12.7' } [features] diff --git a/src/collator_test_bundle.rs b/src/collator_test_bundle.rs index 9da06761..c3bcaefd 100644 --- a/src/collator_test_bundle.rs +++ b/src/collator_test_bundle.rs @@ -176,8 +176,8 @@ pub fn create_block_handle_storage() -> BlockHandleStorage { Arc::new(NodeStateDb::in_memory()), Arc::new(NodeStateDb::in_memory()), #[cfg(feature = "telemetry")] - create_storage_telemetry(), - create_storage_allocated() + Arc::new(StorageTelemetry::default()), + Arc::new(StorageAlloc::default()), ) } @@ -185,7 +185,7 @@ pub fn create_block_handle_storage() -> BlockHandleStorage { pub fn create_engine_telemetry() -> Arc { Arc::new( EngineTelemetry { - storage: create_storage_telemetry(), + storage: Arc::new(StorageTelemetry::default()), awaiters: Metric::without_totals("", 1), catchain_clients: Metric::without_totals("", 1), cells: Metric::without_totals("", 1), @@ -202,7 +202,7 @@ pub fn create_engine_telemetry() -> Arc { pub fn create_engine_allocated() -> Arc { Arc::new( EngineAlloc { - storage: create_storage_allocated(), + storage: Arc::new(StorageAlloc::default()), awaiters: Arc::new(AtomicU64::new(0)), catchain_clients: Arc::new(AtomicU64::new(0)), overlay_clients: Arc::new(AtomicU64::new(0)), @@ -215,29 +215,6 @@ pub fn create_engine_allocated() -> Arc { ) } -#[cfg(feature = "telemetry")] -fn create_storage_telemetry() -> Arc { - Arc::new( - StorageTelemetry { - file_entries: Metric::without_totals("", 1), - handles: Metric::without_totals("", 1), - packages: Metric::without_totals("", 1), - storage_cells: Metric::without_totals("", 1) - } - ) -} - -fn create_storage_allocated() -> Arc { - Arc::new( - StorageAlloc { - file_entries: Arc::new(AtomicU64::new(0)), - handles: Arc::new(AtomicU64::new(0)), - packages: Arc::new(AtomicU64::new(0)), - storage_cells: Arc::new(AtomicU64::new(0)) - } - ) -} - pub struct CollatorTestBundle { index: CollatorTestBundleIndex, top_shard_blocks: Vec>, diff --git a/src/config.rs b/src/config.rs index 922a6da8..9aec2bdc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -73,6 +73,23 @@ impl Default for CellsGcConfig { } } +#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)] +pub struct CellsDbConfig { + pub states_db_queue_len: u32, + pub max_pss_slowdown_mcs: u32, + pub prefill_cells_cunters: bool, +} + +impl Default for CellsDbConfig { + fn default() -> Self { + Self { + states_db_queue_len: 1000, + max_pss_slowdown_mcs: 750, + prefill_cells_cunters: false, + } + } +} + #[derive(serde::Deserialize, serde::Serialize)] pub struct TonNodeConfig { log_config_name: Option, @@ -113,6 +130,8 @@ pub struct TonNodeConfig { restore_db: bool, #[serde(default)] low_memory_mode: bool, + #[serde(default)] + cells_db_config: CellsDbConfig, } pub struct TonNodeGlobalConfig(TonNodeGlobalConfigJson); @@ -207,9 +226,6 @@ pub struct RempConfig { client_enabled: bool, remp_client_pool: Option, service_enabled: bool, - #[cfg(feature="remp_emergency")] - #[serde(default)] - forcedly_disable_remp_cap: bool, } impl RempConfig { @@ -238,10 +254,6 @@ impl RempConfig { self.remp_client_pool } - #[cfg(feature="remp_emergency")] - pub fn forcedly_disable_remp_cap(&self) -> bool { - self.forcedly_disable_remp_cap - } } #[derive(Debug, Default, serde::Deserialize, serde::Serialize, Clone)] @@ -406,11 +418,6 @@ impl TonNodeConfig { config_json.connectivity_check_config.check()?; - #[cfg(not(feature = "async_ss_storage"))] - if config_json.low_memory_mode { - fail!("'low_memory_mode' is applied only with 'async_ss_storage' feature"); - } - config_json.configs_dir = configs_dir.to_string(); config_json.file_name = json_file_name.to_string(); @@ -525,6 +532,9 @@ impl TonNodeConfig { pub fn low_memory_mode(&self) -> bool { self.low_memory_mode } + pub fn cells_db_config(&self) -> &CellsDbConfig { + &self.cells_db_config + } pub fn load_global_config(&self) -> Result { diff --git a/src/engine.rs b/src/engine.rs index 4fcca6a8..d37be083 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -66,7 +66,7 @@ use crate::{ validator::telemetry::{CollatorValidatorTelemetry, RempCoreTelemetry}, }; #[cfg(feature = "telemetry")] -use adnl::telemetry::{Metric, TelemetryItem, TelemetryPrinter}; +use adnl::telemetry::{Metric, MetricBuilder, TelemetryItem, TelemetryPrinter}; use overlay::QueriesConsumer; #[cfg(feature = "metrics")] use statsd::client; @@ -125,8 +125,6 @@ pub struct Engine { sync_status: AtomicU32, low_memory_mode: bool, remp_capability: AtomicBool, - #[cfg(feature="remp_emergency")] - forcedly_disable_remp_cap: bool, test_bundles_config: CollatorTestBundlesGeneralConfig, @@ -518,14 +516,7 @@ impl Engine { #[cfg(feature = "telemetry")] let (metrics, engine_telemetry) = Self::create_telemetry(); - let storage_allocated = Arc::new( - StorageAlloc { - file_entries: Arc::new(AtomicU64::new(0)), - handles: Arc::new(AtomicU64::new(0)), - packages: Arc::new(AtomicU64::new(0)), - storage_cells: Arc::new(AtomicU64::new(0)) - } - ); + let storage_allocated = Arc::new(StorageAlloc::default()); let engine_allocated = Arc::new( EngineAlloc { storage: storage_allocated, @@ -557,9 +548,11 @@ impl Engine { } }; + let cells_db_config = general_config.cells_db_config().clone(); let db_config = InternalDbConfig { db_directory: general_config.internal_db_path().to_string(), - cells_gc_interval_sec: general_config.cells_gc_config().gc_interval_sec + cells_gc_interval_sec: general_config.cells_gc_config().gc_interval_sec, + cells_db_config: cells_db_config.clone(), }; let control_config = general_config.control_server()?; let global_config = general_config.load_global_config()?; @@ -651,6 +644,7 @@ impl Engine { enable_shard_state_persistent_gc, cells_lifetime_sec, stopper.clone(), + cells_db_config.states_db_queue_len + 10, #[cfg(feature = "telemetry")] engine_telemetry.clone(), engine_allocated.clone() @@ -727,8 +721,6 @@ impl Engine { sync_status: AtomicU32::new(0), low_memory_mode, remp_capability: AtomicBool::new(false), - #[cfg(feature="remp_emergency")] - forcedly_disable_remp_cap: remp_config.forcedly_disable_remp_cap(), test_bundles_config, shard_states_keeper: shard_states_keeper.clone(), #[cfg(feature="workchains")] @@ -971,10 +963,6 @@ impl Engine { self.remp_capability.store(value, Ordering::Relaxed); } - #[cfg(feature="remp_emergency")] - pub fn forcedly_disable_remp_cap(&self) -> bool { - self.forcedly_disable_remp_cap - } pub async fn download_and_apply_block_worker( self: Arc, @@ -1237,13 +1225,27 @@ impl Engine { fn create_metric(name: &str) -> Arc { Metric::without_totals(name, Engine::TIMEOUT_TELEMETRY_SEC) } + fn create_metric_ex(name: &str) -> Arc { + MetricBuilder::with_metric_and_period( + Metric::with_total_amount(name, Engine::TIMEOUT_TELEMETRY_SEC), + 1_000_000_000 // 1 sec in nanos + ) + } let storage_telemetry = Arc::new( StorageTelemetry { file_entries: create_metric("Alloc NODE file entries"), handles: create_metric("Alloc NODE block handles"), packages: create_metric("Alloc NODE packages"), - storage_cells: create_metric("Alloc NODE storage cells") + storage_cells: create_metric("Alloc NODE storage cells"), + shardstates_queue: create_metric("Alloc NODE shardstates queue"), + cells_counters: create_metric("Alloc NODE cells counters"), + cell_counter_from_cache: create_metric_ex("NODE read cache cell_counters/sec"), + cell_counter_from_db: create_metric_ex("NODE read db cell_counters/sec"), + updated_old_cells: create_metric_ex("NODE old format update cells/sec"), + updated_cells: create_metric_ex("NODE update cell_counters/sec"), + new_cells: create_metric_ex("NODE create new cells/sec"), + deleted_cells: create_metric_ex("NODE delete cells/sec"), } ); let engine_telemetry = Arc::new( @@ -1265,6 +1267,14 @@ impl Engine { TelemetryItem::Metric(engine_telemetry.storage.handles.clone()), TelemetryItem::Metric(engine_telemetry.storage.packages.clone()), TelemetryItem::Metric(engine_telemetry.storage.storage_cells.clone()), + TelemetryItem::Metric(engine_telemetry.storage.shardstates_queue.clone()), + TelemetryItem::Metric(engine_telemetry.storage.cells_counters.clone()), + TelemetryItem::MetricBuilder(engine_telemetry.storage.cell_counter_from_cache.clone()), + TelemetryItem::MetricBuilder(engine_telemetry.storage.cell_counter_from_db.clone()), + TelemetryItem::MetricBuilder(engine_telemetry.storage.updated_old_cells.clone()), + TelemetryItem::MetricBuilder(engine_telemetry.storage.updated_cells.clone()), + TelemetryItem::MetricBuilder(engine_telemetry.storage.new_cells.clone()), + TelemetryItem::MetricBuilder(engine_telemetry.storage.deleted_cells.clone()), TelemetryItem::Metric(engine_telemetry.awaiters.clone()), TelemetryItem::Metric(engine_telemetry.catchain_clients.clone()), TelemetryItem::Metric(engine_telemetry.cells.clone()), @@ -1363,9 +1373,6 @@ impl Engine { } fn process_ext_msg_broadcast(&self, broadcast: ExternalMessageBroadcast, src: Arc) { - #[cfg(feature="remp_emergency")] - let remp = !self.forcedly_disable_remp_cap() && self.remp_capability(); - #[cfg(not(feature="remp_emergency"))] let remp = self.remp_capability(); // just add to list if !self.is_validator() { @@ -1655,7 +1662,7 @@ impl Engine { let join_handle = tokio::spawn(async move { engine.acquire_stop(Engine::MASK_SERVICE_ARCHIVES_GC); if let Err(e) = Self::archives_gc_worker(&engine, archives_gc_block).await { - log::error!("FATAL!!! Unexpected error in archives gc: {:?}", e); + log::error!("CRITICAL!!! Unexpected error in archives gc: {:?}", e); } engine.release_stop(Engine::MASK_SERVICE_ARCHIVES_GC); }); diff --git a/src/engine_operations.rs b/src/engine_operations.rs index 31bb3ec2..0f24a8dc 100644 --- a/src/engine_operations.rs +++ b/src/engine_operations.rs @@ -709,9 +709,6 @@ impl EngineOperations for Engine { fail!("Can't process external message because node is out of sync"); } - #[cfg(feature="remp_emergency")] - let remp_way = !self.forcedly_disable_remp_cap() && self.remp_capability(); - #[cfg(not(feature="remp_emergency"))] let remp_way = self.remp_capability(); if remp_way { self.remp_client() @@ -870,10 +867,6 @@ impl EngineOperations for Engine { ) } - #[cfg(feature="remp_emergency")] - fn forcedly_disable_remp_cap(&self) -> bool { - self.forcedly_disable_remp_cap() - } // Get current list of new shard blocks with respect to last mc block. // If given mc_seq_no is not equal to last mc seq_no - function fails. diff --git a/src/engine_traits.rs b/src/engine_traits.rs index 4e42da39..060f3b5d 100644 --- a/src/engine_traits.rs +++ b/src/engine_traits.rs @@ -645,10 +645,6 @@ pub trait EngineOperations : Sync + Send { unimplemented!() } - #[cfg(feature="remp_emergency")] - fn forcedly_disable_remp_cap(&self) -> bool { - false - } async fn update_validators( &self, diff --git a/src/full_node/shard_client.rs b/src/full_node/shard_client.rs index c611bd4a..7e40a79b 100644 --- a/src/full_node/shard_client.rs +++ b/src/full_node/shard_client.rs @@ -15,7 +15,6 @@ use crate::{ block::BlockStuff, block_proof::BlockProofStuff, engine::Engine, engine_traits::{ChainRange, EngineOperations}, error::NodeError, validator::validator_utils::{calc_subset_for_workchain, check_crypto_signatures}, - shard_states_keeper::ShardStatesKeeper, }; use std::{sync::Arc, mem::drop, time::Duration}; @@ -62,6 +61,9 @@ pub fn start_shards_client( Ok(join_handle) } +// Remember about ShardStatesKeeper::MAX_CATCH_UP_DEPTH and ShardStateDb::MAX_QUEUE_LEN +pub const MC_MAX_SUPERIORITY: u32 = 500; + async fn load_master_blocks_cycle( engine: Arc, mut last_got_block_id: BlockIdExt @@ -71,6 +73,16 @@ async fn load_master_blocks_cycle( if engine.check_stop() { break Ok(()) } + if let Some(shard_client) = engine.load_shard_client_mc_block_id()? { + if shard_client.seq_no() < last_got_block_id.seq_no() && + last_got_block_id.seq_no() - shard_client.seq_no() > MC_MAX_SUPERIORITY + { + log::info!( + "load_next_master_block (block {last_got_block_id}): waiting for shard client ({shard_client})"); + tokio::time::sleep(Duration::from_secs(1)).await; + continue; + } + } last_got_block_id = match load_next_master_block(&engine, &last_got_block_id).await { Ok(id) => { attempt = 0; @@ -91,23 +103,11 @@ async fn load_master_blocks_cycle( } } -pub const MC_MAX_SUPERIORITY: u32 = ShardStatesKeeper::MAX_CATCH_UP_DEPTH / 2; - async fn load_next_master_block( engine: &Arc, prev_id: &BlockIdExt ) -> Result { - if let Some(shard_client) = engine.load_shard_client_mc_block_id()? { - if shard_client.seq_no() < prev_id.seq_no() && - prev_id.seq_no() - shard_client.seq_no() > MC_MAX_SUPERIORITY - { - log::info!( - "load_next_master_block (block {prev_id}): waiting for shard client ({shard_client})"); - tokio::time::sleep(Duration::from_secs(1)).await; - } - } - log::trace!("load_next_master_block: prev block: {}", prev_id); if let Some(prev_handle) = engine.load_block_handle(prev_id)? { if prev_handle.has_next1() { diff --git a/src/internal_db/mod.rs b/src/internal_db/mod.rs index 2ae0335f..bff49b37 100644 --- a/src/internal_db/mod.rs +++ b/src/internal_db/mod.rs @@ -14,7 +14,7 @@ use crate::{ block::BlockStuff, block_proof::BlockProofStuff, engine_traits::EngineAlloc, error::NodeError, shard_state::ShardStateStuff, types::top_block_descr::{TopBlockDescrId, TopBlockDescrStuff}, - internal_db::restore::check_db, + internal_db::restore::check_db, config::CellsDbConfig, }; #[cfg(feature = "telemetry")] @@ -32,15 +32,11 @@ use storage::{ types::BlockMeta, db::filedb::FileDb, shard_top_blocks_db::ShardTopBlocksDb, StorageAlloc, traits::Serializable, }; -#[cfg(not(feature = "async_ss_storage"))] -use storage::shardstate_db::{AllowStateGcResolver, ShardStateDb}; -#[cfg(feature = "async_ss_storage")] use storage::shardstate_db_async::{self, AllowStateGcResolver, ShardStateDb}; #[cfg(feature = "telemetry")] use storage::StorageTelemetry; use ton_block::{Block, BlockIdExt}; use ton_types::{error, fail, Result, UInt256, Cell}; -#[cfg(feature = "async_ss_storage")] use ton_types::{DoneCellsStorage, BagOfCells, BocSerialiseMode}; /// Full node state keys @@ -49,17 +45,17 @@ pub const LAST_APPLIED_MC_BLOCK: &str = "LastMcBlockId"; pub const PSS_KEEPER_MC_BLOCK: &str = "PssKeeperBlockId"; pub const SHARD_CLIENT_MC_BLOCK: &str = "ShardsClientMcBlockId"; pub const ARCHIVES_GC_BLOCK: &str = "ArchivesGcMcBlockId"; +pub const ASSUME_OLD_FORMAT_CELLS: &str = "AssumeOldFormatCells"; pub const LAST_UNNEEDED_KEY_BLOCK: &str = storage::db::rocksdb::LAST_UNNEEDED_KEY_BLOCK; pub const DB_VERSION: &str = "DbVersion"; pub const DB_VERSION_0: u32 = 0; -#[cfg(not(feature = "async_ss_storage"))] -pub const DB_VERSION_1: u32 = 1; // with fixed cells/bits counter in StorageCell +pub const _DB_VERSION_1: u32 = 1; // with fixed cells/bits counter in StorageCell pub const DB_VERSION_2: u32 = 2; // with async cells storage -#[cfg(feature = "async_ss_storage")] -pub const CURRENT_DB_VERSION: u32 = DB_VERSION_2; -#[cfg(not(feature = "async_ss_storage"))] -pub const CURRENT_DB_VERSION: u32 = DB_VERSION_1; +pub const DB_VERSION_3: u32 = 3; // with faster cells storage (separated counters) +pub const CURRENT_DB_VERSION: u32 = DB_VERSION_3; + +const CELLS_CF_NAME: &str = "cells_db"; /// Validator state keys pub(crate) const LAST_ROTATION_MC_BLOCK: &str = "LastRotationBlockId"; @@ -138,10 +134,11 @@ pub mod state_gc_resolver; pub mod restore; mod update; -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Default)] pub struct InternalDbConfig { pub db_directory: String, pub cells_gc_interval_sec: u32, + pub cells_db_config: CellsDbConfig, } pub struct InternalDb { @@ -180,6 +177,7 @@ impl InternalDb { ) -> Result { let mut db = Self::construct( config, + allow_update, #[cfg(feature = "telemetry")] telemetry, allocated, @@ -187,7 +185,8 @@ impl InternalDb { let version = db.resolve_db_version()?; if version != CURRENT_DB_VERSION { if allow_update { - db = update::update(db, version, check_stop, is_broken).await? + db = update::update(db, version, check_stop, is_broken, force_check_db, + restore_db_enabled).await? } else { fail!( "DB version {} does not correspond to current supported one {}.", @@ -205,11 +204,14 @@ impl InternalDb { async fn construct( config: InternalDbConfig, + allow_update: bool, #[cfg(feature = "telemetry")] telemetry: Arc, allocated: Arc, ) -> Result { - let db = RocksDb::with_path(config.db_directory.as_str(), "db")?; + let mut hi_perf_cfs = HashSet::new(); + hi_perf_cfs.insert("CELLS_CF_NAME".to_string()); + let db = RocksDb::with_options(config.db_directory.as_str(), "db", hi_perf_cfs, false)?; let db_catchain = RocksDb::with_path(config.db_directory.as_str(), "catchains")?; let block_handle_db = Arc::new( BlockHandleDb::with_db(db.clone(), "block_handle_db", true)? @@ -230,8 +232,30 @@ impl InternalDb { allocated.storage.clone() ) ); + + let mut assume_old_cells = false; + if let Some(db_slice) = full_node_state_db.try_get(&ASSUME_OLD_FORMAT_CELLS)? { + assume_old_cells = 1 == *db_slice.first() + .ok_or_else(|| error!("Empty value for ASSUME_OLD_FORMAT_CELLS property"))?; + } else if allow_update { + let version = if let Some(db_slice) = full_node_state_db.try_get(&DB_VERSION)? { + let mut cursor = Cursor::new(db_slice.as_ref()); + u32::deserialize(&mut cursor)? + } else { + 0 + }; + if version < DB_VERSION_3 { + assume_old_cells = true; + } + if allow_update { + full_node_state_db.put(&ASSUME_OLD_FORMAT_CELLS, &[assume_old_cells as u8])?; + } + } + log::info!("Cells db - assume old cells: {}", assume_old_cells); let shard_state_dynamic_db = Self::create_shard_state_dynamic_db( db.clone(), + &config, + assume_old_cells, #[cfg(feature = "telemetry")] telemetry.storage.clone(), allocated.storage.clone() @@ -274,7 +298,7 @@ impl InternalDb { Ok(db) } - pub fn resolve_db_version(&self) -> Result { + fn resolve_db_version(&self) -> Result { if self.block_handle_storage.is_empty()? { self.store_db_version(CURRENT_DB_VERSION)?; Ok(CURRENT_DB_VERSION) @@ -289,7 +313,7 @@ impl InternalDb { self.full_node_state_db.put(&DB_VERSION, &bytes) } - fn load_db_version(&self) -> Result { + pub fn load_db_version(&self) -> Result { if let Some(db_slice) = self.full_node_state_db.try_get(&DB_VERSION)? { let mut cursor = Cursor::new(db_slice.as_ref()); u32::deserialize(&mut cursor) @@ -300,31 +324,25 @@ impl InternalDb { fn create_shard_state_dynamic_db( db: Arc, + config: &InternalDbConfig, + assume_old_cells: bool, #[cfg(feature = "telemetry")] telemetry: Arc, - allocated: Arc + allocated: Arc, ) -> Result> { - #[cfg(not(feature = "async_ss_storage"))] { - ShardStateDb::with_db( - db, - "shardstate_db", - "cells_db", - "cells_db1", - #[cfg(feature = "telemetry")] - telemetry, - allocated - ) - } - #[cfg(feature = "async_ss_storage")] { - ShardStateDb::new( - db, - "shardstate_db", - "cells_db", - #[cfg(feature = "telemetry")] - telemetry, - allocated - ) - } + ShardStateDb::new( + db, + "shardstate_db", + CELLS_CF_NAME, + &config.db_directory, + assume_old_cells, + config.cells_db_config.states_db_queue_len, + config.cells_db_config.max_pss_slowdown_mcs, + config.cells_db_config.prefill_cells_cunters, + #[cfg(feature = "telemetry")] + telemetry, + allocated + ) } pub fn clean_shard_state_dynamic_db(&mut self) -> Result<()> { @@ -335,15 +353,16 @@ impl InternalDb { if let Err(e) = self.db.drop_table_force("shardstate_db") { log::warn!("Can't drop table \"shardstate_db\": {}", e); } - if let Err(e) = self.db.drop_table_force("cells_db") { - log::warn!("Can't drop table \"shardstate_db\": {}", e); - } - if let Err(e) = self.db.drop_table_force("cells_db1") { - log::warn!("Can't drop table \"shardstate_db\": {}", e); + if let Err(e) = self.db.drop_table_force(CELLS_CF_NAME) { + log::warn!("Can't drop table \"cells_db\": {}", e); } + let _ = self.db.drop_table_force("cells_db1"); // depricated table, used in db versions 1 & 2 + self.full_node_state_db.put(&ASSUME_OLD_FORMAT_CELLS, &[0])?; self.shard_state_dynamic_db = Self::create_shard_state_dynamic_db( self.db.clone(), + &self.config, + false, #[cfg(feature = "telemetry")] self.telemetry.storage.clone(), self.allocated.storage.clone() @@ -614,14 +633,10 @@ impl InternalDb { handle: &Arc, state: &Arc, callback_handle: Option>, - #[cfg(feature = "async_ss_storage")] callback_ss: Option>, force: bool, ) -> Result<(Arc, bool)> { - #[cfg(not(feature = "async_ss_storage"))] - let timeout = 300; - #[cfg(feature = "async_ss_storage")] let timeout = 30; let _tc = TimeChecker::new(format!("store_shard_state_dynamic {}", state.block_id()), timeout); @@ -630,34 +645,15 @@ impl InternalDb { } let _lock = handle.saving_state_lock().lock().await; if force || !handle.has_state() { - #[cfg(not(feature = "async_ss_storage"))] { - let saved_root = self.shard_state_dynamic_db.put( - state.block_id(), - state.root_cell().clone(), - )?; - if handle.set_state() { - self.store_block_handle(handle, callback_handle)?; - let state = ShardStateStuff::from_root_cell( - handle.id().clone(), - saved_root, - #[cfg(feature = "telemetry")] - &self.telemetry, - &self.allocated - )?; - return Ok((state, true)); - } - } - #[cfg(feature = "async_ss_storage")] { - self.shard_state_dynamic_db.put( - state.block_id(), - state.root_cell().clone(), - callback_ss - )?; - if handle.set_state() { - self.store_block_handle(handle, callback_handle)?; - } - return Ok((state.clone(), true)) + self.shard_state_dynamic_db.put( + state.block_id(), + state.root_cell().clone(), + callback_ss + ).await?; + if handle.set_state() { + self.store_block_handle(handle, callback_handle)?; } + return Ok((state.clone(), true)) } else { Ok((self.load_shard_state_dynamic(handle.id())?, false)) } @@ -667,31 +663,20 @@ impl InternalDb { &self, handle: &Arc, state_root: Cell, - #[cfg(feature = "async_ss_storage")] callback_ss: Option>, ) -> Result { - #[cfg(not(feature = "async_ss_storage"))] - let timeout = 300; - #[cfg(feature = "async_ss_storage")] let timeout = 30; let _tc = TimeChecker::new(format!("store_shard_state_dynamic_raw_force {}", handle.id()), timeout); let _lock = handle.saving_state_lock().lock().await; - #[cfg(not(feature = "async_ss_storage"))] { - let saved_root = self.shard_state_dynamic_db.put(handle.id(), state_root)?; - Ok(saved_root) - } - #[cfg(feature = "async_ss_storage")] { - self.shard_state_dynamic_db.put(handle.id(), state_root.clone(), callback_ss)?; - Ok(state_root) - } + self.shard_state_dynamic_db.put(handle.id(), state_root.clone(), callback_ss).await?; + Ok(state_root) } pub fn load_shard_state_dynamic_ex( &self, id: &BlockIdExt, - #[cfg(feature = "async_ss_storage")] use_cache: bool ) -> Result> { let _tc = TimeChecker::new(format!("load_shard_state_dynamic {} use", id), 30); @@ -700,7 +685,6 @@ impl InternalDb { id.clone(), self.shard_state_dynamic_db.get( id, - #[cfg(feature = "async_ss_storage")] use_cache )?, #[cfg(feature = "telemetry")] @@ -713,40 +697,10 @@ impl InternalDb { pub fn load_shard_state_dynamic(&self, id: &BlockIdExt) -> Result> { self.load_shard_state_dynamic_ex( id, - #[cfg(feature = "async_ss_storage")] true ) } - #[cfg(not(feature = "async_ss_storage"))] - pub async fn store_shard_state_persistent( - &self, - handle: &Arc, - state: &Arc, - callback: Option>, - abort: Arc bool + Send + Sync> - ) -> Result<()> { - let _tc = TimeChecker::new(format!("store_shard_state_persistent {}", state.block_id()), 10_000); - if handle.id() != state.block_id() { - fail!(NodeError::InvalidArg("`state` and `handle` mismatch".to_string())) - } - if !handle.has_persistent_state() { - let state1 = state.clone(); - let bytes = tokio::task::spawn_blocking(move || { - state1.serialize_with_abort(abort.deref()) - }).await??; - - println!("store_shard_state_persistent {} bytes", bytes.len()); - - self.shard_state_persistent_db.write_whole_file(state.block_id(), &bytes).await?; - if handle.set_persistent_state() { - self.store_block_handle(handle, callback)?; - } - } - Ok(()) - } - - #[cfg(feature = "async_ss_storage")] pub async fn store_shard_state_persistent( &self, handle: &Arc, @@ -754,19 +708,22 @@ impl InternalDb { callback: Option>, abort: Arc bool + Send + Sync> ) -> Result<()> { - let _tc = TimeChecker::new(format!("store_shard_state_persistent {}", state.block_id()), 10_000); + let root_hash = state.root_cell().repr_hash(); + log::info!("store_shard_state_persistent block id: {}, state root {:x}", + state.block_id(), root_hash); if handle.id() != state.block_id() { fail!(NodeError::InvalidArg("`state` and `handle` mismatch".to_string())) } - if !handle.has_persistent_state() { + if handle.has_persistent_state() { + log::info!("store_shard_state_persistent {:x}: already saved", root_hash); + } else { let id = handle.id().clone(); let shard_state_dynamic_db = self.shard_state_dynamic_db.clone(); let shard_state_persistent_db = self.shard_state_persistent_db.clone(); tokio::task::spawn_blocking(move || -> Result<()> { log::debug!("store_shard_state_persistent {}", id); - let cells_storage = - shard_state_dynamic_db.create_ordered_cells_storage(&state.root_cell().repr_hash())?; - let now = std::time::Instant::now(); + let cells_storage = shard_state_dynamic_db.create_ordered_cells_storage(&root_hash)?; + let now1 = std::time::Instant::now(); let root_cell = state.root_cell().clone(); // Drop state - don't keep in memory a root cell that keeps full tree! std::mem::drop(state); @@ -776,10 +733,10 @@ impl InternalDb { cells_storage, abort.deref(), )?; - log::debug!("store_shard_state_persistent {} building boc TIME {}", id, now.elapsed().as_millis()); + log::info!("store_shard_state_persistent {:x} building boc TIME {}sec", root_hash, now1.elapsed().as_secs()); let mut dest = shard_state_persistent_db.get_write_object(&id)?; - let now = std::time::Instant::now(); + let now2 = std::time::Instant::now(); boc.write_to_with_abort( &mut dest, BocSerialiseMode::Generic{ @@ -792,7 +749,10 @@ impl InternalDb { None, abort.deref(), )?; - log::debug!("store_shard_state_persistent {} ser boc TIME {}", id, now.elapsed().as_millis()); + log::info!( + "store_shard_state_persistent {:x} DONE; write boc TIME {}sec, total TIME {}sec", + root_hash, now2.elapsed().as_secs(), now1.elapsed().as_secs() + ); Ok(()) }).await??; @@ -1234,7 +1194,6 @@ impl InternalDb { Ok(()) } - #[cfg(feature = "async_ss_storage")] pub fn create_done_cells_storage( &self, root_cell_id: &UInt256 @@ -1244,24 +1203,3 @@ impl InternalDb { } -#[cfg(feature = "async_ss_storage")] -pub struct SsNotificationCallback(tokio::sync::Notify); - -#[cfg(feature = "async_ss_storage")] -#[async_trait::async_trait] -impl shardstate_db_async::Callback for SsNotificationCallback { - async fn invoke(&self, _job: shardstate_db_async::Job, _ok: bool) { - self.0.notify_one(); - } -} - -#[cfg(feature = "async_ss_storage")] -impl SsNotificationCallback { - pub fn new() -> Arc { - Arc::new(Self(tokio::sync::Notify::new())) - } - pub async fn wait(&self) { - self.0.notified().await; - } -} - diff --git a/src/internal_db/restore.rs b/src/internal_db/restore.rs index 2daa6cec..0616a0a0 100644 --- a/src/internal_db/restore.rs +++ b/src/internal_db/restore.rs @@ -2,19 +2,18 @@ use crate::{ block::{BlockIdExtExtention, BlockStuff}, internal_db::{ InternalDb, LAST_APPLIED_MC_BLOCK, SHARD_CLIENT_MC_BLOCK, LAST_ROTATION_MC_BLOCK, - PSS_KEEPER_MC_BLOCK, BlockHandle, + PSS_KEEPER_MC_BLOCK, BlockHandle, ARCHIVES_GC_BLOCK }, shard_state::ShardStateStuff, }; -#[cfg(feature = "async_ss_storage")] -use crate::shard_states_keeper::ShardStatesKeeper; -use ton_block::{BlockIdExt, MASTERCHAIN_ID, ShardIdent, SHARD_FULL}; -use ton_types::{error, fail, Result, UInt256, Cell}; -use storage::traits::Serializable; -#[cfg(feature = "async_ss_storage")] -use crate::internal_db::SsNotificationCallback; +use ton_block::{BlockIdExt, MASTERCHAIN_ID}; +use ton_types::{error, fail, Result, Cell}; +use storage::{ + traits::Serializable, dynamic_boc_rc_db::BROKEN_CELL_BEACON_FILE, + shardstate_db_async::SsNotificationCallback, +}; use std::{ - borrow::Cow, collections::{HashSet, HashMap}, fs::{write, remove_file}, io::Cursor, + borrow::Cow, collections::HashMap, fs::{write, remove_file}, io::Cursor, ops::Deref, path::Path, sync::atomic::{AtomicBool, Ordering}, time::Duration }; @@ -26,11 +25,11 @@ const SHARD_CLIENT_MC_BLOCK_CANDIDATES: u32 = 300; pub async fn check_db( mut db: InternalDb, processed_wc: i32, - restore_db: bool, + restore_db_enabled: bool, force: bool, check_stop: &(dyn Fn() -> Result<()> + Sync), is_broken: Option<&AtomicBool> -) -> Result { +) -> Result { async fn force_db_reset( err: failure::Error, @@ -54,13 +53,19 @@ pub async fn check_db( let unexpected_termination = check_unexpected_termination(&db.config.db_directory); let restoring = check_restoring(&db.config.db_directory); + let broken_cell = check_broken_cell(&db.config.db_directory); - if unexpected_termination || restoring || force { + if unexpected_termination || restoring || force || broken_cell { if force { - log::info!("Starting check & restore db process forcedly"); - } else if restore_db { - log::warn!("Previous node run was unexpectedly terminated, \ - starting check & restore process..."); + log::info!("Starting check & restore db process forcedly (with cells db refilling)"); + } else if restore_db_enabled { + if broken_cell { + log::warn!("Previous node run was terminated because of broken cell, \ + starting check & restore process with cells db refilling..."); + } else { + log::warn!("Previous node run was unexpectedly terminated, \ + starting fast check & restore process (without cells check)..."); + } } else { if unexpected_termination { log::warn!("Previous node run was terminated unexpectedly, \ @@ -80,7 +85,8 @@ pub async fn check_db( let shard_client_mc_block = restore_shard_client_mc_block( &db, &last_applied_mc_block, processed_wc, check_stop).await?; db = match restore( - db, &last_applied_mc_block, &shard_client_mc_block, processed_wc, check_stop + db, &last_applied_mc_block, &shard_client_mc_block, + processed_wc, check_stop, broken_cell || force, ).await { Ok(db) => db, Err(err) => force_db_reset(err, check_stop, is_broken).await @@ -123,6 +129,10 @@ fn set_unexpected_termination(db_dir: &str) -> Result<()> { fn reset_restoring(db_dir: &str) -> Result<()> { let path = Path::new(db_dir).join(RESTORING_BEACON_FILE); remove_file(&path)?; + let path = Path::new(db_dir).join(BROKEN_CELL_BEACON_FILE); + if path.exists() { + remove_file(&path)?; + } Ok(()) } @@ -130,6 +140,10 @@ fn check_restoring(db_dir: &str) -> bool { Path::new(db_dir).join(RESTORING_BEACON_FILE).as_path().exists() } +fn check_broken_cell(db_dir: &str) -> bool { + Path::new(db_dir).join(BROKEN_CELL_BEACON_FILE).as_path().exists() +} + fn set_restoring(db_dir: &str) -> Result<()> { let path = Path::new(db_dir).join(RESTORING_BEACON_FILE); write(&path, "")?; @@ -247,6 +261,7 @@ async fn restore( shard_client_mc_block: &BlockStuff, processed_wc: i32, check_stop: &(dyn Fn() -> Result<()> + Sync), + refill_cells_db: bool, ) -> Result { let last_mc_block = @@ -282,72 +297,33 @@ async fn restore( db.save_validator_state(LAST_ROTATION_MC_BLOCK, last_mc_block.id())?; } } - if let Some(block_id) = db.load_full_node_state(PSS_KEEPER_MC_BLOCK)? { if block_id.seq_no > last_mc_block.id().seq_no { db.save_full_node_state(PSS_KEEPER_MC_BLOCK, last_mc_block.id())?; } } + if let Some(block_id) = db.load_full_node_state(ARCHIVES_GC_BLOCK)? { + if block_id.seq_no > last_mc_block.id().seq_no { + db.save_full_node_state(ARCHIVES_GC_BLOCK, last_mc_block.id())?; + } + } - let min_mc_state_id = calc_min_mc_state_id(&db, last_mc_block.id()).await?; + if !refill_cells_db { + log::info!("Fast restore successfully finished"); + return Ok(db); + } - log::info!("Checking shard states..."); + // If there was broken cell or special flag set - check blocks and restore cells db + log::info!("Checking blocks..."); + let min_mc_state_id = calc_min_mc_state_id(&db, last_mc_block.id()).await?; let mut mc_block = Cow::Borrowed(last_mc_block); - let mut broken_cells = false; - let mut checked_cells = HashSet::new(); let mut persistent_state_handle = None; - #[cfg(feature = "async_ss_storage")] - let mut there_was_fully_saved = false; - #[cfg(feature = "async_ss_storage")] - let max_catch_up_seqno = if min_mc_state_id.seq_no() > ShardStatesKeeper::MAX_CATCH_UP_DEPTH { - min_mc_state_id.seq_no() - ShardStatesKeeper::MAX_CATCH_UP_DEPTH - } else { - 0 - }; loop { check_stop()?; - let shard_blocks = check_shard_client_mc_block( + check_shard_client_mc_block( &db, &mc_block, processed_wc, persistent_state_handle.is_none(), check_stop).await?; - #[cfg(not(feature = "async_ss_storage"))] - if mc_block.id().seq_no() >= min_mc_state_id.seq_no() && !broken_cells { - broken_cells = !check_mc_and_shards_states( - &db, - &mc_block, - &mut checked_cells, - check_stop, - &shard_blocks, - )?; - } - - #[cfg(feature = "async_ss_storage")] - if !broken_cells { - let cur_seq_no = mc_block.id().seq_no(); - if !there_was_fully_saved && cur_seq_no < max_catch_up_seqno { - // There was not fully saved state in avaliable depth. - // States keeper fails if someone will try to load state. - broken_cells = true; - } else if cur_seq_no > min_mc_state_id.seq_no() || !there_was_fully_saved { - // if state is needed or no one fully loaded state was found - let broken = !check_mc_and_shards_states( - &db, - &mc_block, - &mut checked_cells, - check_stop, - &shard_blocks, - )?; - // Some number of new states might be not saved. - // If there_was_fully_saved is true all newer that min_mc_state_id - // must be fully saved too - if !broken { - there_was_fully_saved = true; - } else if there_was_fully_saved { - broken_cells = true; - } - } - } - if persistent_state_handle.is_some() { break; } @@ -355,32 +331,8 @@ async fn restore( // check prev master block let prev_id = mc_block.construct_prev_id()?.0; - // if prev is zerostate check it and exit cycle + // if prev is zerostate - exit cycle if prev_id.seq_no() == 0 { - if min_mc_state_id.seq_no() == 0 && !broken_cells { - if let Err(e) = check_state(&db, &prev_id, &mut checked_cells, check_stop) { - log::warn!("Error while checking state {} {}", prev_id, e); - broken_cells = true; - } else { - // check wc zerostate - let zerostate = db.load_shard_state_dynamic(&prev_id)?; - let workchains = zerostate.config_params()?.workchains()?; - let wc = workchains.get(&processed_wc)?.ok_or_else(|| { - error!("workchains doesn't have description for workchain {}", processed_wc) - })?; - let wc_zerostate_id = BlockIdExt { - shard_id: ShardIdent::with_tagged_prefix(processed_wc, SHARD_FULL)?, - seq_no: 0, - root_hash: wc.zerostate_root_hash, - file_hash: wc.zerostate_file_hash, - }; - if let Err(e) = check_state(&db, &wc_zerostate_id, &mut checked_cells, check_stop) { - log::warn!("Error while checking state {} {}", wc_zerostate_id, e); - broken_cells = true; - } - } - } - persistent_state_handle = Some(db.load_block_handle(&prev_id)? .ok_or_else(|| error!("there is no handle for zerostate {}", prev_id))?); break; @@ -389,7 +341,7 @@ async fn restore( // if this mc block has persistent state - end cycle let prev_handle = db.load_block_handle(&prev_id)? .ok_or_else(|| error!("there is no handle for block {}", prev_id))?; - if prev_handle.has_persistent_state() { + if prev_handle.has_persistent_state() && prev_handle.id().seq_no <= min_mc_state_id.seq_no() { persistent_state_handle = Some(prev_handle); } @@ -398,50 +350,25 @@ async fn restore( log::debug!("restore: mc block looks good {}", prev_id); }; - if broken_cells { - log::warn!("Shard states db is broken, it will be clear and restored from persistent \ - states and blocks. It will take some time..."); - db.reset_unapplied_handles()?; - db.clean_shard_state_dynamic_db()?; - log::debug!("Shard states db was cleaned"); - restore_states( - &db, - persistent_state_handle - .ok_or_else(|| error!("internal error: persistent_state_handle is None"))?.deref(), - &min_mc_state_id, - processed_wc, - check_stop - ).await?; - } + log::warn!("Shard states db will be clear and restored from persistent \ + states and blocks. It will take some time..."); + db.reset_unapplied_handles()?; + db.clean_shard_state_dynamic_db()?; + log::debug!("Shard states db was cleaned"); + restore_states( + &db, + persistent_state_handle + .ok_or_else(|| error!("INTERNAL ERROR: persistent_state_handle is None"))?.deref(), + &min_mc_state_id, + processed_wc, + check_stop + ).await?; log::info!("Restore successfully finished"); Ok(db) } -fn check_mc_and_shards_states( - db: &InternalDb, - mc_block: &BlockStuff, - checked_cells: &mut HashSet, - check_stop: &(dyn Fn() -> Result<()> + Sync), - shard_blocks: &[BlockIdExt], -) -> Result { - - if let Err(e) = check_state(&db, mc_block.id(), checked_cells, check_stop) { - log::warn!("Error while checking state {} {}", mc_block.id(), e); - return Ok(false) - } - - for block_id in shard_blocks { - if let Err(e) = check_state(&db, block_id, checked_cells, check_stop) { - log::warn!("Error while checking state {} {}", block_id, e); - return Ok(false) - } - } - - Ok(true) -} - async fn check_shard_client_mc_block( db: &InternalDb, block: &BlockStuff, @@ -690,75 +617,23 @@ async fn calc_min_mc_state_id( } } } + let archives_gc = db.load_full_node_state(ARCHIVES_GC_BLOCK)?.ok_or_else( + || error!("INTERNAL ERROR: No archives GC block id") + )?; + if archives_gc.seq_no() < min_id.seq_no() { + min_id = &archives_gc; + } let handle = db.load_block_handle(&min_id)? .ok_or_else(|| error!("there is no handle for block {}", min_id))?; let block = db.load_block_data(&handle).await?; let min_ref_mc_seqno = block.block().read_info()?.min_ref_mc_seqno(); - let min_ref_mc_handle = db.find_mc_block_by_seq_no(min_ref_mc_seqno) - .or_else(|_| db.find_mc_block_by_seq_no_without_state(min_ref_mc_seqno))?; + let min_ref_mc_handle = db.find_mc_block_by_seq_no_without_state(min_ref_mc_seqno)?; log::trace!("calc_min_mc_state_id: {}", min_ref_mc_handle.id()); Ok(min_ref_mc_handle.id().clone()) } -fn check_state( - db: &InternalDb, - id: &BlockIdExt, - checked_cells: &mut HashSet, - check_stop: &(dyn Fn() -> Result<()> + Sync), -) -> Result<()> { - log::trace!("check_state {}", id); - - fn check_cell( - cell: Cell, - checked_cells: &mut HashSet, - check_stop: &(dyn Fn() -> Result<()> + Sync), - ) -> Result<(u64, u64)> { - check_stop()?; - const MAX_56_BITS: u64 = 0x00FF_FFFF_FFFF_FFFFu64; - let mut expected_cells = 1_u64; - let mut expected_bits = cell.bit_length() as u64; - let new_cell = checked_cells.insert(cell.repr_hash()); - for i in 0..cell.references_count() { - let child = cell.reference(i)?; - if new_cell { - let (c, b) = check_cell(child, checked_cells, check_stop)?; - expected_cells = expected_cells.saturating_add(c); - expected_bits = expected_bits.saturating_add(b); - } else { - expected_cells = expected_cells.saturating_add(child.tree_cell_count()); - expected_bits = expected_bits.saturating_add(child.tree_bits_count()); - } - if expected_bits > MAX_56_BITS { - expected_bits = MAX_56_BITS; - } - if expected_cells > MAX_56_BITS { - expected_cells = MAX_56_BITS; - } - } - - if cell.tree_cell_count() != expected_cells { - fail!("cell {:x} stored cell count {} != expected {}", - cell.repr_hash(), cell.tree_cell_count(), expected_cells); - } - if cell.tree_bits_count() != expected_bits { - fail!("cell {} stored bit count {} != expected {}", - cell.repr_hash(), cell.tree_bits_count(), expected_bits); - } - Ok((expected_cells, expected_bits)) - } - - let root = db.shard_state_dynamic_db.get( - id, - #[cfg(feature = "async_ss_storage")] - false - )?; - check_cell(root, checked_cells, check_stop)?; - - Ok(()) -} - async fn restore_states( db: &InternalDb, persistent_state_handle: &BlockHandle, @@ -910,17 +785,14 @@ async fn restore_chain( } if store_states { - #[cfg(feature = "async_ss_storage")] let callback = SsNotificationCallback::new(); db.store_shard_state_dynamic_raw_force( &block_handle, state_root.clone(), - #[cfg(feature = "async_ss_storage")] Some(callback.clone()), ).await?; - #[cfg(feature = "async_ss_storage")] callback.wait().await; } diff --git a/src/internal_db/state_gc_resolver.rs b/src/internal_db/state_gc_resolver.rs index 55b638f3..093a2389 100644 --- a/src/internal_db/state_gc_resolver.rs +++ b/src/internal_db/state_gc_resolver.rs @@ -11,9 +11,6 @@ * limitations under the License. */ -#[cfg(not(feature = "async_ss_storage"))] -use storage::shardstate_db::AllowStateGcResolver; -#[cfg(feature = "async_ss_storage")] use storage::shardstate_db_async::AllowStateGcResolver; use ton_block::{BlockIdExt, ShardIdent}; use ton_types::Result; diff --git a/src/internal_db/update.rs b/src/internal_db/update.rs index 16e14c19..10cd236c 100644 --- a/src/internal_db/update.rs +++ b/src/internal_db/update.rs @@ -1,56 +1,48 @@ use crate::internal_db::{ - InternalDb, CURRENT_DB_VERSION, DB_VERSION_0, restore::check_db + InternalDb, CURRENT_DB_VERSION, DB_VERSION_0, DB_VERSION_3, restore::check_db }; -#[cfg(feature = "async_ss_storage")] use crate::internal_db::DB_VERSION_2; -#[cfg(not(feature = "async_ss_storage"))] -use crate::internal_db::DB_VERSION_1; use std::sync::atomic::AtomicBool; use ton_types::{Result, fail}; pub async fn update( mut db: InternalDb, - mut version: u32, + version: u32, check_stop: &(dyn Fn() -> Result<()> + Sync), - is_broken: Option<&AtomicBool> + is_broken: Option<&AtomicBool>, + force_check_db: bool, + restore_db_enabled: bool, ) -> Result { if version == CURRENT_DB_VERSION { return Ok(db) } - #[cfg(not(feature = "async_ss_storage"))] - if version == DB_VERSION_0 { - // 0 -> 1 - log::info!( - "Detected old database version 0. This version possibly contains wrong cells and bits \ - counters in cells DB. Need to restore database"); - db = check_db(db, 0, true, true, check_stop, is_broken).await?; - version = DB_VERSION_1; - db.store_db_version(version)?; - } - - #[cfg(feature = "async_ss_storage")] if version < DB_VERSION_2 { if version == DB_VERSION_0 { log::info!( "Detected old database version 0. This version possibly contains wrong cells and bits \ counters in cells DB. Need to restore database" ); + } else { + log::info!( + "Detected old database version {}. This version contains shard states DB \ + in old format. Async shard states DB has another format. Need to refill \ + DB using restore procedure.", version + ); } - log::info!( - "Detected old database version {}. This version contains shard states DB \ - in old format. Async shard states DB has another format. Need to refill \ - DB using restore procedure.", version - ); if let Err(e) = db.clean_shard_state_dynamic_db() { log::warn!("Clear shard state db: {}", e); } db = check_db(db, 0, true, true, check_stop, is_broken).await?; - version = DB_VERSION_2; - db.store_db_version(version)?; - } - - if version != CURRENT_DB_VERSION { + db.store_db_version(DB_VERSION_3)?; + } else if version == DB_VERSION_2 { + log::info!( + "Detected old database version 2. This version contains performance issue in cells DB. \ + Database will update on the fly." + ); + db = check_db(db, 0, restore_db_enabled, force_check_db, check_stop, is_broken).await?; + db.store_db_version(DB_VERSION_3)?; + } else if version != CURRENT_DB_VERSION { fail!("Wrong database version {}, supported: {}", version, CURRENT_DB_VERSION); } diff --git a/src/jaeger.rs b/src/jaeger.rs index c5a6c458..8221add2 100644 --- a/src/jaeger.rs +++ b/src/jaeger.rs @@ -33,7 +33,7 @@ struct JaegerHelper { } lazy_static::lazy_static! { - static ref JAEGER: Mutex = Mutex::new(JaegerHelper::new("r-node")); + static ref JAEGER: Option> = JaegerHelper::new("r-node"); } pub fn init_jaeger() { @@ -43,35 +43,40 @@ pub fn init_jaeger() { #[cfg(feature = "external_db")] pub fn message_from_kafka_received(kf_key: &[u8]) { - let msg_id_bytes = kf_key[0..32].to_vec(); - tokio::task::spawn_blocking(move || match JAEGER.lock() { - Ok(mut helper) => { - if msg_id_bytes.len() == 32 { - let msg_id = hex::encode(&msg_id_bytes); - helper.send_span(msg_id, "kafka msg received".to_string()); - } else { - log::error!(target: "jaeger", "Corrupted key field in message from q-server"); + if let Some(jaeger) = JAEGER.as_ref() { + let msg_id_bytes = kf_key[0..32].to_vec(); + tokio::task::spawn_blocking(move || match jaeger.lock() { + Ok(mut helper) => { + if msg_id_bytes.len() == 32 { + let msg_id = hex::encode(&msg_id_bytes); + helper.send_span(msg_id, "kafka msg received".to_string()); + } else { + log::error!(target: "jaeger", "Corrupted key field in message from q-server"); + } } - } - Err(e) => { - log::error!(target: "jaeger", "Mutex locking error: {}", e); - } - }); + Err(e) => { + log::error!(target: "jaeger", "Mutex locking error: {}", e); + } + }); + } } pub fn broadcast_sended(msg_id: String) { - tokio::task::spawn_blocking(move || match JAEGER.lock() { - Ok(mut helper) => { - helper.send_span(msg_id, "broadcast sended".to_string()); - } - Err(e) => { - log::error!(target: "jaeger", "Mutex locking error: {}", e); - } - }); + if let Some(jaeger) = JAEGER.as_ref() { + tokio::task::spawn_blocking(move || match jaeger.lock() { + Ok(mut helper) => { + helper.send_span(msg_id, "broadcast sended".to_string()) + }, + Err(e) => { + log::error!(target: "jaeger", "Mutex locking error: {}", e); + } + }); + } } impl JaegerHelper { - pub fn new(service_name: &str) -> JaegerHelper { + + pub fn new(service_name: &str) -> Option> { let (span_tx, span_rx) = crossbeam_channel::bounded(1000); let tracer = Tracer::with_sender(AllSampler, span_tx); let mut reporter = match JaegerCompactReporter::new(service_name) { @@ -97,21 +102,31 @@ impl JaegerHelper { }; let agent_url = format!("{}:{}", agent_host, agent_port) .parse() - .and_then(|v| Ok(std::net::SocketAddr::V4(v))) - .expect("Invalid JAEGER_* env. Can't parse string to ip address"); + .and_then(|v| Ok(std::net::SocketAddr::V4(v))); + let Ok(agent_url) = agent_url else { + log::error!( + target: "jaeger", + "Invalid JAEGER_* env. Can't parse string to ip address" + ); + return None + }; match reporter.set_agent_addr(agent_url) { - Ok(_) => { - log::info!(target: "jaeger", "Init done with addr {}:{}", agent_host, agent_port) - } - Err(e) => { - log::error!(target: "jaeger", "Can't set agent address to jaeger library. Internal rust_jaegertracing error: {}", e) - } + Ok(_) => log::info!( + target: "jaeger", + "Init done with addr {}:{}", agent_host, agent_port + ), + Err(e) => log::error!( + target: "jaeger", + "Can't set agent address to jaeger library. Internal rust_jaegertracing error: {}", + e + ) } - JaegerHelper { + let ret = JaegerHelper { tracer, reporter, span_rx, - } + }; + Some(Mutex::new(ret)) } pub fn send_span(&mut self, msg_id: String, span_name: String) { diff --git a/src/main.rs b/src/main.rs index bcc4a4eb..3c877da5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -378,13 +378,11 @@ fn main() { .arg(clap::Arg::with_name("initial_sync_disabled") .short("i") .long("initial-sync-disabled") - .value_name("initial sync disable flag") .help("use this flag to sync from zero_state")) .arg(clap::Arg::with_name("force_check_db") .short("f") .long("force-check-db") - .value_name("force check db flag") - .help("start check & restore db process forcedly")); + .help("start check & restore db process forcedly with refilling cells database")); let matches = app.get_matches(); diff --git a/src/network/catchain_client.rs b/src/network/catchain_client.rs index 173c8d8d..29a48a01 100644 --- a/src/network/catchain_client.rs +++ b/src/network/catchain_client.rs @@ -179,6 +179,8 @@ impl CatchainClient { ).read_boxed()?; #[cfg(feature = "telemetry")] let tag = tag_from_boxed_object(&query); + #[cfg(not(feature = "telemetry"))] + let tag = 0; let query = TaggedTlObject { object: query, #[cfg(feature = "telemetry")] diff --git a/src/network/control.rs b/src/network/control.rs index 74c7f759..800fe05b 100644 --- a/src/network/control.rs +++ b/src/network/control.rs @@ -457,12 +457,17 @@ impl Subscriber for ControlQuerySubscriber { }; log::info!("query (control server): {:?}", query); let query = match query.downcast::() { - Ok(_) => return QueryResult::consume(self.process_generate_keypair().await?, None), + Ok(_) => return QueryResult::consume( + self.process_generate_keypair().await?, + #[cfg(feature = "telemetry")] + None + ), Err(query) => query }; let query = match query.downcast::() { Ok(query) => return QueryResult::consume_boxed( self.export_public_key(query.key_hash.as_slice())?, + #[cfg(feature = "telemetry")] None ), Err(query) => query @@ -470,6 +475,7 @@ impl Subscriber for ControlQuerySubscriber { let query = match query.downcast::() { Ok(query) => return QueryResult::consume( self.process_sign_data(query.key_hash.as_slice(), &query.data)?, + #[cfg(feature = "telemetry")] None ), Err(query) => query @@ -479,6 +485,7 @@ impl Subscriber for ControlQuerySubscriber { self.add_validator_permanent_key( query.key_hash.as_slice(), query.election_date, query.ttl ).await?, + #[cfg(feature = "telemetry")] None ), Err(query) => query @@ -488,6 +495,7 @@ impl Subscriber for ControlQuerySubscriber { self.add_validator_temp_key( query.permanent_key_hash.as_slice(), query.key_hash.as_slice(), query.ttl )?, + #[cfg(feature = "telemetry")] None ), Err(query) => query @@ -497,6 +505,7 @@ impl Subscriber for ControlQuerySubscriber { self.add_validator_adnl_address( query.permanent_key_hash.as_slice(), query.key_hash.as_slice(), query.ttl ).await?, + #[cfg(feature = "telemetry")] None ), Err(query) => query @@ -504,6 +513,7 @@ impl Subscriber for ControlQuerySubscriber { let query = match query.downcast::() { Ok(query) => return QueryResult::consume_boxed( self.add_adnl_address(query.key_hash.as_slice(), query.category)?, + #[cfg(feature = "telemetry")] None ), Err(query) => query @@ -512,7 +522,8 @@ impl Subscriber for ControlQuerySubscriber { Ok(query) => { return QueryResult::consume_boxed( self.prepare_bundle(query.block_id.clone()).await?, - None + #[cfg(feature = "telemetry")] + None ) }, Err(query) => query @@ -524,6 +535,7 @@ impl Subscriber for ControlQuerySubscriber { ).collect(); return QueryResult::consume_boxed( self.prepare_future_bundle(prev_block_ids).await?, + #[cfg(feature = "telemetry")] None ) }, @@ -534,6 +546,7 @@ impl Subscriber for ControlQuerySubscriber { let message_data = query.body.0; return QueryResult::consume_boxed( self.redirect_external_message(&message_data).await?, + #[cfg(feature = "telemetry")] None ) } @@ -542,7 +555,11 @@ impl Subscriber for ControlQuerySubscriber { let query = match query.downcast::() { Ok(account) => { let answer = self.get_account_state(account.account_address).await?; - return QueryResult::consume_boxed(answer, None) + return QueryResult::consume_boxed( + answer, + #[cfg(feature = "telemetry")] + None + ) }, Err(query) => query }; @@ -551,21 +568,33 @@ impl Subscriber for ControlQuerySubscriber { let param_number = query.param_list.iter().next().ok_or_else(|| error!("Invalid param_number"))?; let answer = self.get_config_params(*param_number as u32).await?; - return QueryResult::consume_boxed(answer.into_boxed(), None) + return QueryResult::consume_boxed( + answer.into_boxed(), + #[cfg(feature = "telemetry")] + None + ) }, Err(query) => query }; let query = match query.downcast::() { Ok(_) => { let answer = self.get_all_config_params().await?; - return QueryResult::consume_boxed(answer.into_boxed(), None) + return QueryResult::consume_boxed( + answer.into_boxed(), + #[cfg(feature = "telemetry")] + None + ) }, Err(query) => query }; let query = match query.downcast::() { Ok(_) => { let answer = self.get_stats().await?; - return QueryResult::consume_boxed(answer.into_boxed(), None) + return QueryResult::consume_boxed( + answer.into_boxed(), + #[cfg(feature = "telemetry")] + None + ) }, Err(query) => query }; @@ -573,6 +602,7 @@ impl Subscriber for ControlQuerySubscriber { Ok(query) => { return QueryResult::consume_boxed( self.set_states_gc_interval(query.interval_ms as u32)?, + #[cfg(feature = "telemetry")] None ) } diff --git a/src/network/remp.rs b/src/network/remp.rs index 04364a69..5ced9068 100644 --- a/src/network/remp.rs +++ b/src/network/remp.rs @@ -111,7 +111,7 @@ impl RempNode { #[cfg(feature = "telemetry")] tag: self.tag_message }; - if let Err(e) = self.adnl.send_custom(&tagged_data, peers) { + if let Err(e) = self.adnl.send_custom(&tagged_data, &peers) { fail!("Error while sending RempMessage {:x} via message: {}", id, e); } Ok(()) @@ -285,7 +285,7 @@ impl SendCombinedReceipt for SendCombinedReceiptByAdnl { tag: self.adnl_tag }; let peers = AdnlPeers::with_keys(self_adnl_id, to.clone()); - if let Err(e) = self.adnl.send_custom(&tagged_data, peers) { + if let Err(e) = self.adnl.send_custom(&tagged_data, &peers) { log::error!("send_combined_receipt: {:?}", e); } Ok(()) diff --git a/src/shard_state.rs b/src/shard_state.rs index d41d072e..292d0a1a 100644 --- a/src/shard_state.rs +++ b/src/shard_state.rs @@ -28,9 +28,6 @@ use ton_types::{ deserialize_cells_tree_inmem_with_abort, UInt256 }; -#[cfg(not(feature = "async_ss_storage"))] -use ton_types::{BocSerialiseMode, BagOfCells}; - // #[derive(Debug, Default, Clone, Eq, PartialEq)] // It is a wrapper around various shard state's representations and properties. declare_counted!( @@ -243,24 +240,24 @@ impl ShardStateStuff { // Ok(bytes.into_inner()) // } - #[cfg(not(feature = "async_ss_storage"))] - pub fn serialize_with_abort(&self, abort: &dyn Fn() -> bool) -> Result> { - let mut bytes = Vec::::new(); - let boc = BagOfCells::with_params(vec!(&self.root), vec!(), abort)?; - boc.write_to_with_abort( - &mut bytes, - BocSerialiseMode::Generic{ - index: true, - crc: false, - cache_bits: false, - flags: 0 - }, - None, - None, - abort - )?; - Ok(bytes) - } +// Unused +// pub fn serialize_with_abort(&self, abort: &dyn Fn() -> bool) -> Result> { +// let mut bytes = Vec::::new(); +// let boc = BagOfCells::with_params(vec!(&self.root), vec!(), abort)?; +// boc.write_to_with_abort( +// &mut bytes, +// BocSerialiseMode::Generic{ +// index: true, +// crc: false, +// cache_bits: false, +// flags: 0 +// }, +// None, +// None, +// abort +// )?; +// Ok(bytes) +// } pub fn config_params(&self) -> Result<&ConfigParams> { Ok(&self.shard_state_extra()?.config) diff --git a/src/shard_states_keeper.rs b/src/shard_states_keeper.rs index 30f5a5c4..a57cb3c4 100644 --- a/src/shard_states_keeper.rs +++ b/src/shard_states_keeper.rs @@ -7,17 +7,12 @@ use crate::{ engine::{Engine, Stopper}, boot, }; -#[cfg(feature = "async_ss_storage")] -use crate::internal_db::SsNotificationCallback; #[cfg(feature = "telemetry")] use crate::engine_traits::EngineTelemetry; use storage::{ - block_handle_db::BlockHandle, + block_handle_db::BlockHandle, shardstate_db_async::AllowStateGcResolver, + shardstate_db_async::SsNotificationCallback, }; -#[cfg(not(feature = "async_ss_storage"))] -use storage::shardstate_db::AllowStateGcResolver; -#[cfg(feature = "async_ss_storage")] -use storage::shardstate_db_async::AllowStateGcResolver; use ton_block::{BlockIdExt, ShardIdent}; use ton_types::{fail, error, Result, UInt256}; use adnl::common::add_unbound_object_to_map; @@ -42,6 +37,7 @@ pub struct ShardStatesKeeper { states: lockfree::map::Map>, enable_persistent_gc: bool, stopper: Arc, + max_catch_up_depth: u32, #[cfg(feature = "telemetry")] telemetry: Arc, allocated: Arc, @@ -56,6 +52,7 @@ impl ShardStatesKeeper { enable_shard_state_persistent_gc: bool, cells_lifetime_sec: u64, stopper: Arc, + max_catch_up_depth: u32, #[cfg(feature = "telemetry")] telemetry: Arc, allocated: Arc @@ -71,6 +68,7 @@ impl ShardStatesKeeper { enable_persistent_gc: enable_shard_state_persistent_gc, states: lockfree::map::Map::new(), stopper, + max_catch_up_depth, #[cfg(feature = "telemetry")] telemetry, allocated, @@ -149,45 +147,30 @@ impl ShardStatesKeeper { self.check_stop()?; - #[cfg(feature = "async_ss_storage")] { - let (cb1, cb2) = if let Some(state_data) = persistent_state { - // while boot - zerostate and init persistent state are saved using this parameter - self.db.store_shard_state_persistent_raw(&handle, state_data, None).await?; - let cb = SsNotificationCallback::new(); - ( - Some(cb.clone() as Arc), - Some(cb) - ) - } else { - (None, None) - }; - self.db.store_shard_state_dynamic( - handle, - &state, - None, - #[cfg(feature = "async_ss_storage")] - cb1, - force - ).await?; - - if let Some(cb) = cb2 { - cb.wait().await; - // reload state after saving just to free fully loaded tree - // and use lazy loaded cells futher - state = self.db.load_shard_state_dynamic(handle.id())?; - } - } - #[cfg(not(feature = "async_ss_storage"))] { - if let Some(state_data) = persistent_state { - // while boot zerostate and init persistent state are saved using this parameter - self.db.store_shard_state_persistent_raw(&handle, state_data, None).await?; - } - (state, _) = self.db.store_shard_state_dynamic( - handle, - &state, - None, - force - ).await?; + let (cb1, cb2) = if let Some(state_data) = persistent_state { + // while boot - zerostate and init persistent state are saved using this parameter + self.db.store_shard_state_persistent_raw(&handle, state_data, None).await?; + let cb = SsNotificationCallback::new(); + ( + Some(cb.clone() as Arc), + Some(cb) + ) + } else { + (None, None) + }; + self.db.store_shard_state_dynamic( + handle, + &state, + None, + cb1, + force + ).await?; + + if let Some(cb) = cb2 { + cb.wait().await; + // reload state after saving just to free fully loaded tree + // and use lazy loaded cells futher + state = self.db.load_shard_state_dynamic(handle.id())?; } let saved = add_unbound_object_to_map( @@ -215,7 +198,6 @@ impl ShardStatesKeeper { low_memory_mode, handle.id() ); - #[cfg(feature = "async_ss_storage")] let state_root = { let mut deserialiser = BocDeserializer::new(); if low_memory_mode { @@ -227,11 +209,6 @@ impl ShardStatesKeeper { .deserialize_inmem(data.clone())? .withdraw_one_root()? }; - #[cfg(not(feature = "async_ss_storage"))] - let state_root = BocDeserializer::new() - .set_abort(&|| self.stopper.check_stop()) - .deserialize_inmem(data.clone())? - .withdraw_one_root()?; log::info!( "check_and_store_state: deserialized (low_memory_mode: {}) {} TIME {}", @@ -281,7 +258,6 @@ impl ShardStatesKeeper { Ok(()) } - pub const MAX_CATCH_UP_DEPTH: u32 = 2000; const MAX_NEW_STATE_OFFSET: u32 = 50; async fn catch_up_state( &self, @@ -323,45 +299,71 @@ impl ShardStatesKeeper { id, latest_id); } - let state = self.restore_state_recursive(id, 0).await?; + let state = self.restore_state_recursive(id).await?; log::trace!("catch_up_state {} CATCHED UP - TIME {}ms", id, now.elapsed().as_millis()); Ok(state) } - #[async_recursion::async_recursion] async fn restore_state_recursive( &self, id: &BlockIdExt, - depth: u32, ) -> Result> { - self.check_stop()?; + let try_get_state = |id: &BlockIdExt| { + if let Some(state) = self.states.get(id) { + log::trace!("load_state {} FROM CACHE", id); + Some(state.val().clone()) + } else if let Ok(state) = self.db.load_shard_state_dynamic(id) { + self.states.insert(id.clone(), state.clone()); + Some(state) + } else { + None + } + }; - if depth > Self::MAX_CATCH_UP_DEPTH { - fail!("restore_state_recursive: max depth achived on id {}", id); + if let Some(state) = try_get_state(id) { + return Ok(state) } - if let Some(state) = self.states.get(id) { - log::trace!("load_state {} FROM CACHE", id); - Ok(state.val().clone()) - } else if let Ok(state) = self.db.load_shard_state_dynamic(id) { - self.states.insert(id.clone(), state.clone()); - Ok(state) - } else { + + let top_id = id.clone(); + let mut stack = vec!(id.clone()); + loop { + + self.check_stop()?; + + if stack.len() as u32 > self.max_catch_up_depth { + fail!("restore_state_recursive: max depth achived on id {}", id); + } + + let id = stack.last().ok_or_else(|| error!("INTERNAL ERROR: restore_state_recursive: stask is empty"))?; + let handle = self.db.load_block_handle(id)?.ok_or_else( || error!("Cannot load handle for {}", id) )?; let block = self.db.load_block_data(&handle).await?; let prev_root = match block.construct_prev_id()? { (prev, None) => { - self.restore_state_recursive(&prev, depth + 1).await? - .root_cell().clone() + if let Some(pr) = try_get_state(&prev) { + pr.root_cell().clone() + } else { + stack.push(prev.clone()); + continue; + } }, (prev1, Some(prev2)) => { - let root1 = self.restore_state_recursive(&prev1, depth + 1).await? - .root_cell().clone(); - let root2 = self.restore_state_recursive(&prev2, depth + 1).await? - .root_cell().clone(); + let root1 = if let Some(pr) = try_get_state(&prev1) { + pr.root_cell().clone() + } else { + stack.push(prev1.clone()); + continue; + }; + let root2 = if let Some(pr) = try_get_state(&prev2) { + pr.root_cell().clone() + } else { + stack.push(prev2.clone()); + continue; + }; ShardStateStuff::construct_split_root(root1, root2)? } }; @@ -390,7 +392,17 @@ impl ShardStatesKeeper { self.store_state(&handle, state.clone(), None, true).await?; - Ok(state) + stack.pop().ok_or_else(|| error!("INTERNAL ERROR: restore_state_recursive: stask is empty when pop()"))?; + + if stack.is_empty() { + if *state.block_id() != top_id { + fail!( + "INTERNAL ERROR: restore_state_recursive: found state is wrong {} != {}", + state.block_id(), top_id + ); + } + return Ok(state); + } } } @@ -462,7 +474,7 @@ impl ShardStatesKeeper { } }; self.wait_and_store_persistent_state( - engine.deref(), &handle, Arc::new(|| false)).await; + engine.deref(), &handle, abort.clone()).await; if engine.check_stop() { return Ok(()); } @@ -568,7 +580,6 @@ impl ShardStatesKeeper { loop { match self.db.load_shard_state_dynamic_ex( id, - #[cfg(feature = "async_ss_storage")] false ) { Ok(ss) => break Ok(ss), diff --git a/src/validating_utils.rs b/src/validating_utils.rs index 4bfbc80d..67185fb0 100644 --- a/src/validating_utils.rs +++ b/src/validating_utils.rs @@ -37,7 +37,9 @@ pub fn supported_capabilities() -> u64 { GlobalCapabilities::CapStorageFeeToTvm as u64 | GlobalCapabilities::CapStcontNewFormat as u64 | GlobalCapabilities::CapFastStorageStatBugfix as u64 | - GlobalCapabilities::CapResolveMerkleCell as u64; + GlobalCapabilities::CapResolveMerkleCell as u64 | + GlobalCapabilities::CapFeeInGasUnits as u64 | + GlobalCapabilities::CapBounceAfterFailedAction as u64; #[cfg(feature = "gosh")] let caps = caps | GlobalCapabilities::CapDiff as u64; #[cfg(feature = "signature_with_id")] @@ -46,7 +48,7 @@ pub fn supported_capabilities() -> u64 { } pub fn supported_version() -> u32 { - 36 + 37 } pub fn check_this_shard_mc_info( diff --git a/src/validator/collator.rs b/src/validator/collator.rs index f5ab83aa..6ac60902 100644 --- a/src/validator/collator.rs +++ b/src/validator/collator.rs @@ -1253,10 +1253,6 @@ impl Collator { log::trace!("{}: do_collate", self.collated_block_descr); let ext_messages = self.engine.get_external_messages(&self.shard)?; - #[cfg(feature="remp_emergency")] - let remp = !self.engine.forcedly_disable_remp_cap() && - mc_data.config().has_capability(GlobalCapabilities::CapRemp); - #[cfg(not(feature="remp_emergency"))] let remp = mc_data.config().has_capability(GlobalCapabilities::CapRemp); let remp_messages = if remp { Some(self.engine.get_remp_messages(&self.shard)?) @@ -2606,10 +2602,6 @@ impl Collator { let data = ton_types::serialize_toc(&cell)?; block_id.file_hash = UInt256::calc_file_hash(&data); - #[cfg(feature="remp_emergency")] - let remp = !self.engine.forcedly_disable_remp_cap() && - mc_data.config().has_capability(GlobalCapabilities::CapRemp); - #[cfg(not(feature="remp_emergency"))] let remp = mc_data.config().has_capability(GlobalCapabilities::CapRemp); if remp { let (accepted, rejected, ignored) = collator_data.withdraw_remp_msg_statuses(); diff --git a/src/validator/message_cache.rs b/src/validator/message_cache.rs index 2d8b3a9a..1e246082 100644 --- a/src/validator/message_cache.rs +++ b/src/validator/message_cache.rs @@ -251,7 +251,7 @@ impl MessageCacheMessages { /// There are two possible consistent message statuses: /// 1. Message present will all structures /// 2. Only message status present (for futures) - fn is_message_consistent(&self, id: &UInt256) -> bool { + fn _is_message_consistent(&self, id: &UInt256) -> bool { if self.in_messages(id) { self.in_message_statuses(id) && self.in_message_shards(id) && self.in_message_master_cc(id) } @@ -292,7 +292,7 @@ impl MessageCacheMessages { old_cc_seqno+2 <= new_cc_seqno } - pub fn is_expired(&self, message_id: &UInt256, new_cc_seqno: u32) -> Result { + pub fn _is_expired(&self, message_id: &UInt256, new_cc_seqno: u32) -> Result { match self.message_master_cc.get(message_id) { None => fail!("Message {:x} was not found: cannot check its expiration time", message_id), Some(old_cc_seqno) => Ok(Self::cc_expired(old_cc_seqno.1, new_cc_seqno)) diff --git a/src/validator/validate_query.rs b/src/validator/validate_query.rs index a4954fe2..7303e177 100644 --- a/src/validator/validate_query.rs +++ b/src/validator/validate_query.rs @@ -1524,10 +1524,6 @@ impl ValidateQuery { &mut acc_state_hash ).map_err(|err| error!("transaction {:x} of account {:x} is invalid : {}", trans_lt, acc_id, err))?; - #[cfg(feature="remp_emergency")] - let remp = !engine.forcedly_disable_remp_cap() && - base.config_params.has_capability(GlobalCapabilities::CapRemp); - #[cfg(not(feature="remp_emergency"))] let remp = base.config_params.has_capability(GlobalCapabilities::CapRemp); if remp { if let Some((id, is_internal)) = msg_info { diff --git a/src/validator/validator_manager.rs b/src/validator/validator_manager.rs index 911d800f..a909e26e 100644 --- a/src/validator/validator_manager.rs +++ b/src/validator/validator_manager.rs @@ -606,7 +606,7 @@ impl ValidatorManagerImpl { cc_seqno, &self.config ); - + let session_info = SessionInfo::new(ident.clone(), session_id.clone(), vsubset.clone()); let old_shards: Vec = prev_blocks.iter().map(|blk| blk.shard_id.clone()).collect(); self.session_history.new_session_after(session_info.clone(), old_shards.clone())?; diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 029bfa65..1ebb09c9 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -13,18 +13,18 @@ hex = '0.4' lazy_static = '1.4.0' log = '0.4' log4rs = '1.2' -rand = '0.8' serde = '1.0.114' serde_cbor = '0.11.1' serde_derive = '1.0.114' sha2 = '0.10' strum = '0.18.0' strum_macros = '0.18.0' -adnl = { git = 'https://github.com/tonlabs/ever-adnl.git', tag = '0.7.131' } +adnl = { git = 'https://github.com/tonlabs/ever-adnl.git', tag = '0.7.143' } lockfree = { git = 'https://github.com/tonlabs/lockfree.git' } +rand = { features = [ 'small_rng' ], version = '0.8' } tokio = { features = [ 'fs', 'rt-multi-thread' ], version = '1.5' } -ton_api = { git = 'https://github.com/tonlabs/ever-tl.git', package = 'ton_api', tag = '0.2.178' } -ton_block = { git = 'https://github.com/tonlabs/ever-block.git', tag = '1.9.28' } +ton_api = { git = 'https://github.com/tonlabs/ever-tl.git', package = 'ton_api', tag = '0.2.188' } +ton_block = { git = 'https://github.com/tonlabs/ever-block.git', tag = '1.9.39' } ton_types = { git = 'https://github.com/tonlabs/ever-types.git', tag = '1.12.7' } [build-dependencies] @@ -32,8 +32,7 @@ cc = { features = [ 'parallel' ], version = '=1.0.61' } [features] ci_run = [ ] -default = [ 'ref_count_gc' ] -ref_count_gc = [ ] +default = [ ] telemetry = [ ] [target.'cfg(unix)'.dependencies.rocksdb] diff --git a/storage/src/cell_db.rs b/storage/src/cell_db.rs index 248b234b..bcb93f65 100644 --- a/storage/src/cell_db.rs +++ b/storage/src/cell_db.rs @@ -17,10 +17,6 @@ use crate::{ types::{StorageCell}, }; -#[cfg(not(feature = "ref_count_gc"))] -use crate::dynamic_boc_db::DynamicBocDb; - -#[cfg(feature = "ref_count_gc")] use crate::dynamic_boc_rc_db::DynamicBocDb; use std::{sync::Arc, io::{Cursor, Write}}; @@ -34,14 +30,14 @@ impl CellDb { &self, cell_id: &UInt256, boc_db: Arc, - #[cfg(feature = "ref_count_gc")] - use_cache: bool - ) -> Result { + use_cache: bool, + with_parents_count: bool, + ) -> Result<(StorageCell, u32)> { StorageCell::deserialize( boc_db, self.db.get(cell_id)?.as_ref(), - #[cfg(feature = "ref_count_gc")] - use_cache + use_cache, + with_parents_count, ) } @@ -49,15 +45,15 @@ impl CellDb { &self, cell_id: &UInt256, boc_db: Arc, - #[cfg(feature = "ref_count_gc")] - use_cache: bool - ) -> Result> { + use_cache: bool, + with_parents_count: bool, + ) -> Result> { if let Some(data) = self.db.try_get(cell_id)? { Ok(Some(StorageCell::deserialize( boc_db, data.as_ref(), - #[cfg(feature = "ref_count_gc")] - use_cache + use_cache, + with_parents_count, )?)) } else { Ok(None) diff --git a/storage/src/db/memorydb.rs b/storage/src/db/memorydb.rs index 8796a696..43ca8dab 100644 --- a/storage/src/db/memorydb.rs +++ b/storage/src/db/memorydb.rs @@ -84,10 +84,10 @@ impl Kvc for MemoryDb { /// Implementation of readable key-value collection for MemoryDb. Actual implementation is blocking. impl KvcReadable for MemoryDb { - fn try_get(&self, key: &K) -> Result> { + fn try_get_raw(&self, key: &[u8]) -> Result> { Ok(self.map()? .lock().unwrap() - .get(key.key()) + .get(key) .map(|vec| vec.clone().into())) } @@ -114,17 +114,17 @@ impl KvcReadable for MemoryDb { /// Implementation of wriatable key-value collection for MemoryDb. Actual implementation is blocking. impl KvcWriteable for MemoryDb { - fn put(&self, key: &K, value: &[u8]) -> Result<()> { + fn put_raw(&self, key: &[u8], value: &[u8]) -> Result<()> { self.map()? .lock().unwrap() - .insert(key.key().to_vec(), value.to_vec()); + .insert(key.to_vec(), value.to_vec()); Ok(()) } - fn delete(&self, key: &K) -> Result<()> { + fn delete_raw(&self, key: &[u8]) -> Result<()> { self.map()? .lock().unwrap() - .remove(key.key()); + .remove(key); Ok(()) } } @@ -172,11 +172,11 @@ impl MemoryDbTransaction { } impl KvcTransaction for MemoryDbTransaction { - fn put(&mut self, key: &K, value: &[u8]) -> Result<()> { + fn put_raw(&mut self, key: &[u8], value: &[u8]) -> Result<()> { self.pending.lock().unwrap().push( PendingOperation::Put( Pair { - key: key.key().to_vec(), + key: key.to_vec(), value: value.to_vec(), } ) @@ -184,9 +184,9 @@ impl KvcTransaction for MemoryDbTransaction { Ok(()) } - fn delete(&mut self, key: &K) -> Result<()> { + fn delete_raw(&mut self, key: &[u8]) -> Result<()> { self.pending.lock().unwrap().push( - PendingOperation::Delete(key.key().to_vec()) + PendingOperation::Delete(key.to_vec()) ); Ok(()) } diff --git a/storage/src/db/rocksdb.rs b/storage/src/db/rocksdb.rs index 1a1091d5..060c2b8f 100644 --- a/storage/src/db/rocksdb.rs +++ b/storage/src/db/rocksdb.rs @@ -25,7 +25,7 @@ use rocksdb::{ }; use std::{ fmt::{Debug, Formatter}, ops::Deref, path::Path, sync::{Arc, atomic::{AtomicI32, Ordering}}, - io::Cursor, + io::Cursor, collections::HashSet, }; use ton_types::{fail, error, Result}; use ton_block::BlockIdExt; @@ -36,61 +36,39 @@ pub const NODE_STATE_DB_NAME: &str = "node_state_db"; #[derive(Debug)] pub struct RocksDb { db: Option>, - locks: lockfree::map::Map + locks: lockfree::map::Map, + hi_perf_cfs: HashSet } impl RocksDb { /// Creates new instance with given path pub fn with_path(path: &str, name: &str) -> Result> { - Self::with_options(path, name, |_| {}, false) + Self::with_options(path, name, HashSet::new(), false) } /// Creates new instance read only with given path pub fn read_only(path: &str, name: &str) -> Result> { - Self::with_options(path, name, |_| {}, true) + Self::with_options(path, name, HashSet::new(), true) } /// Creates new instance with given path and ability to additionally configure options pub fn with_options( path: &str, name: &str, - configure_options: impl Fn(&mut Options), + hi_perf_cfs: HashSet, read_only: bool ) -> Result> { let path = Path::new(path); let path = path.join(name); - let cache = Cache::new_lru_cache(1 << 30).unwrap(); //1Gb block cache for one instance - let mut block_opts = BlockBasedOptions::default(); - block_opts.set_block_cache(&cache); - // save in LRU block cache also indexes and bloom filters - block_opts.set_cache_index_and_filter_blocks(true); - // keep indexes and filters in block cache until tablereader freed - block_opts.set_pin_l0_filter_and_index_blocks_in_cache(true); - block_opts.set_block_size(16 << 10); - // use latest available format version with new implementation of bloom filters - block_opts.set_format_version(5); - - let mut options = Options::default(); - options.create_if_missing(true); - options.set_max_total_wal_size(512 << 20); // 512Mb for one instance - options.set_max_background_jobs(4); - // allow background async incrementall file sync to disk by 1Mb per sync - options.set_bytes_per_sync(1 << 20); - options.set_block_based_table_factory(&block_opts); - options.create_missing_column_families(true); - options.enable_statistics(); - options.set_dump_malloc_stats(true); - - configure_options(&mut options); + let options = Self::build_db_options(); let mut iteration = 1; loop { - let cfs = DBWithThreadMode::::list_cf(&options, &path) - .unwrap_or_default(); + let cfs = DBWithThreadMode::::list_cf(&options, &path).unwrap_or_default(); log::info!( target: "storage", @@ -99,10 +77,19 @@ impl RocksDb { ); iteration += 1; + let cfs_opt = cfs.clone().into_iter() + .map(|cf| { + let opt = if hi_perf_cfs.contains(&cf) { + Self::build_hi_perf_cf_options() + } else { + Options::default() + }; + rocksdb::ColumnFamilyDescriptor::new(cf, opt) + }); let db = if read_only { - DBWithThreadMode::::open_cf_for_read_only(&options, &path, cfs.clone(), false)? + DBWithThreadMode::::open_cf_descriptors_read_only(&options, &path, cfs_opt, false)? } else { - DBWithThreadMode::::open_cf(&options, &path, cfs.clone())? + DBWithThreadMode::::open_cf_descriptors(&options, &path, cfs_opt)? }; @@ -120,12 +107,90 @@ impl RocksDb { let db = Self { db: Some(db), - locks: lockfree::map::Map::new() + locks: lockfree::map::Map::new(), + hi_perf_cfs, }; return Ok(Arc::new(db)) } } + fn build_hi_perf_cf_options() -> Options { + + let mut options = Options::default(); + let mut block_opts = BlockBasedOptions::default(); + + // specified cache for blocks. + let cache = Cache::new_lru_cache(1024 * 1024 * 1024).unwrap(); + block_opts.set_block_cache(&cache); + + // save in LRU block cache also indexes and bloom filters + block_opts.set_cache_index_and_filter_blocks(true); + + // keep indexes and filters in block cache until tablereader freed + block_opts.set_pin_l0_filter_and_index_blocks_in_cache(true); + + // Setup bloom filter with length of 10 bits per key. + // This length provides less than 1% false positive rate. + block_opts.set_bloom_filter(10.0, false); + + options.set_block_based_table_factory(&block_opts); + + // Enable whole key bloom filter in memtable. + options.set_memtable_whole_key_filtering(true); + + // Amount of data to build up in memory (backed by an unsorted log + // on disk) before converting to a sorted on-disk file. + // + // Larger values increase performance, especially during bulk loads. + // Up to max_write_buffer_number write buffers may be held in memory + // at the same time, + // so you may wish to adjust this parameter to control memory usage. + // Also, a larger write buffer will result in a longer recovery time + // the next time the database is opened. + options.set_write_buffer_size(1024 * 1024 * 1024); + + // The maximum number of write buffers that are built up in memory. + // The default and the minimum number is 2, so that when 1 write buffer + // is being flushed to storage, new writes can continue to the other + // write buffer. + // If max_write_buffer_number > 3, writing will be slowed down to + // options.delayed_write_rate if we are writing to the last write buffer + // allowed. + options.set_max_write_buffer_number(4); + + // if prefix_extractor is set and memtable_prefix_bloom_size_ratio is not 0, + // create prefix bloom for memtable with the size of + // write_buffer_size * memtable_prefix_bloom_size_ratio. + // If it is larger than 0.25, it is sanitized to 0.25. + let transform = rocksdb::SliceTransform::create_fixed_prefix(10); + options.set_prefix_extractor(transform); + options.set_memtable_prefix_bloom_ratio(0.1); + + options + } + + fn build_db_options() -> Options { + + let mut options = Options::default(); + + // If true, the database will be created if it is missing. + options.create_if_missing(true); + + // By default, RocksDB uses only one background thread for flush and + // compaction. Calling this function will set it up such that total of + // `total_threads` is used. Good value for `total_threads` is the number of + // cores. + options.increase_parallelism(4); + + // If true, missing column families will be automatically created. + options.create_missing_column_families(true); + + options.enable_statistics(); + options.set_dump_malloc_stats(true); + + options + } + fn clean_up_old_cf(db: &DBWithThreadMode, cfs: &[String]) -> Result { if let Some(cf) = db.cf_handle(NODE_STATE_DB_NAME) { @@ -169,8 +234,12 @@ impl RocksDb { // Error is occured if column family is already created fn create_cf(&self, name: &str) -> Result<()> { - let opts = Options::default(); - self.db().create_cf(name, &opts)?; + let opt = if self.hi_perf_cfs.contains(name) { + Self::build_hi_perf_cf_options() + } else { + Options::default() + }; + self.db().create_cf(name, &opt)?; Ok(()) } @@ -236,8 +305,8 @@ impl KvcReadable for RocksDb { self.db().path().to_str().unwrap() } - fn try_get(&self, key: &K) -> Result> { - let ret = self.db().get_pinned(key.key())?; + fn try_get_raw(&self, key: &[u8]) -> Result> { + let ret = self.db().get_pinned(key)?; Ok(ret.map(|value| value.into())) } @@ -254,12 +323,12 @@ impl KvcReadable for RocksDb { } impl KvcWriteable for RocksDb { - fn put(&self, key: &K, value: &[u8]) -> Result<()> { - Ok(self.db().put(key.key(), value)?) + fn put_raw(&self, key: &[u8], value: &[u8]) -> Result<()> { + Ok(self.db().put(key, value)?) } - fn delete(&self, key: &K) -> Result<()> { - Ok(self.db().delete(key.key())?) + fn delete_raw(&self, key: &[u8]) -> Result<()> { + Ok(self.db().delete(key)?) } } @@ -360,11 +429,11 @@ impl KvcReadable for RocksDbTable { self.family.as_str() } - fn try_get(&self, key: &K) -> Result> { + fn try_get_raw(&self, key: &[u8]) -> Result> { if let Some(lock) = self.db.locks.get(&self.family) { let lock = lock.val(); if lock.fetch_add(1, Ordering::Relaxed) >= 0 { - let ret = self.db.get_pinned_cf(&self.cf()?, key.key()); + let ret = self.db.get_pinned_cf(&self.cf()?, key); lock.fetch_sub(1, Ordering::Relaxed); return Ok(ret?.map(|value| value.into())) } @@ -402,11 +471,11 @@ impl KvcReadable for RocksDbTable { /// Implementation of writable key-value collection for RocksDB. Actual implementation is blocking. impl KvcWriteable for RocksDbTable { - fn put(&self, key: &K, value: &[u8]) -> Result<()> { + fn put_raw(&self, key: &[u8], value: &[u8]) -> Result<()> { if let Some(lock) = self.db.locks.get(&self.family) { let lock = lock.val(); if lock.fetch_add(1, Ordering::Relaxed) >= 0 { - let ret = self.db.put_cf(&self.cf()?, key.key(), value); + let ret = self.db.put_cf(&self.cf()?, key, value); lock.fetch_sub(1, Ordering::Relaxed); return Ok(ret?) } @@ -414,15 +483,15 @@ impl KvcWriteable for RocksDbTable { fail!("Attempt to write into dropped table {}", self.family) } - fn delete(&self, key: &K) -> Result<()> { + fn delete_raw(&self, key: &[u8]) -> Result<()> { if let Some(lock) = self.db.locks.get(&self.family) { let lock = lock.val(); if lock.fetch_add(1, Ordering::Relaxed) >= 0 { - let ret = self.db.delete_cf(&self.cf()?, key.key()); + let ret = self.db.delete_cf(&self.cf()?, key); lock.fetch_sub(1, Ordering::Relaxed); return Ok(ret?) } - } + } fail!("Attempt to delete from dropped table {}", self.family) } @@ -475,8 +544,8 @@ impl Kvc for RocksDbSnapshot<'_> { } impl KvcReadable for RocksDbSnapshot<'_> { - fn try_get(&self, key: &K) -> Result> { - Ok(self.snapshot.get_cf(&self.cf()?, key.key())?.map(|value| value.into())) + fn try_get_raw(&self, key: &[u8]) -> Result> { + Ok(self.snapshot.get_cf(&self.cf()?, key)?.map(|value| value.into())) } fn for_each(&self, predicate: &mut dyn FnMut(&[u8], &[u8]) -> Result) -> Result { for iter in self.snapshot.iterator_cf(&self.cf()?, IteratorMode::Start) { @@ -519,16 +588,16 @@ impl RocksDbTransaction { } impl<'db, K: DbKey + Send + Sync> KvcTransaction for RocksDbTransaction { - fn put(&mut self, key: &K, value: &[u8]) -> Result<()> { + fn put_raw(&mut self, key: &[u8], value: &[u8]) -> Result<()> { let mut batch = self.batch.take().unwrap(); - batch.put_cf(&self.cf()?, key.key(), value); + batch.put_cf(&self.cf()?, key, value); self.batch = Some(batch); Ok(()) } - fn delete(&mut self, key: &K) -> Result<()> { + fn delete_raw(&mut self, key: &[u8]) -> Result<()> { let mut batch = self.batch.take().unwrap(); - batch.delete_cf(&self.cf()?, key.key()); + batch.delete_cf(&self.cf()?, key); self.batch = Some(batch); Ok(()) } diff --git a/storage/src/db/traits/sync_traits.rs b/storage/src/db/traits/sync_traits.rs index 3c33a326..d8989ef0 100644 --- a/storage/src/db/traits/sync_traits.rs +++ b/storage/src/db/traits/sync_traits.rs @@ -37,7 +37,11 @@ pub trait KvcReadable: Kvc { } /// Tries to get value from collection by the key; returns Ok(None) if the key not found - fn try_get(&self, key: &K) -> Result>; + fn try_get_raw(&self, key: &[u8]) -> Result>; + + fn try_get(&self, key: &K) -> Result> { + self.try_get_raw(key.key()) + } /// Gets value from collection by the key fn get(&self, key: &K) -> Result { @@ -86,10 +90,18 @@ pub trait KvcReadable: Kvc { /// Trait for writable key-value collections pub trait KvcWriteable: KvcReadable { /// Puts value into collection by the key - fn put(&self, key: &K, value: &[u8]) -> Result<()>; + fn put(&self, key: &K, value: &[u8]) -> Result<()> { + self.put_raw(key.key(), value) + } + + fn put_raw(&self, key: &[u8], value: &[u8]) -> Result<()>; /// Deletes value from collection by the key - fn delete(&self, key: &K) -> Result<()>; + fn delete(&self, key: &K) -> Result<()> { + self.delete_raw(key.key()) + } + + fn delete_raw(&self, key: &[u8]) -> Result<()>; } /// Trait for key-value collections with the ability of take snapshots @@ -109,10 +121,16 @@ pub trait KvcTransactional: KvcWriteable { /// on destroy, if not committed. pub trait KvcTransaction { /// Adds put operation into transaction (batch) - fn put(&mut self, key: &K, value: &[u8]) -> Result<()>; + fn put(&mut self, key: &K, value: &[u8]) -> Result<()> { + self.put_raw(key.key(), value) + } + fn put_raw(&mut self, key: &[u8], value: &[u8]) -> Result<()>; /// Adds delete operation into transaction (batch) - fn delete(&mut self, key: &K) -> Result<()>; + fn delete(&mut self, key: &K) -> Result<()> { + self.delete_raw(key.key()) + } + fn delete_raw(&mut self, key: &[u8]) -> Result<()>; /// Removes all pending operations from transaction (batch) fn clear(&mut self); diff --git a/storage/src/dynamic_boc_db.rs b/storage/src/dynamic_boc_db.rs deleted file mode 100644 index 0ca4cb70..00000000 --- a/storage/src/dynamic_boc_db.rs +++ /dev/null @@ -1,238 +0,0 @@ -/* -* Copyright (C) 2019-2021 TON Labs. All Rights Reserved. -* -* Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use -* this file except in compliance with the 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 TON DEV software governing permissions and -* limitations under the License. -*/ - -use crate::{ - StorageAlloc, cell_db::CellDb, - /*dynamic_boc_diff_writer::{DynamicBocDiffFactory, DynamicBocDiffWriter},*/ - types::{StorageCell}, db::traits::KvcTransaction, TARGET, -}; -#[cfg(feature = "telemetry")] -use crate::StorageTelemetry; -#[cfg(all(test, feature = "telemetry"))] -use crate::tests::utils::create_storage_telemetry; -use std::{ops::{Deref, DerefMut}, sync::{Arc, RwLock, Weak}, time}; -#[cfg(feature = "telemetry")] -use std::sync::atomic::Ordering; -//#[cfg(test)] -//use std::path::Path; -use ton_types::{Cell, Result, CellImpl, MAX_LEVEL, fail, error, UInt256}; - -//#[derive(Debug)] -pub struct DynamicBocDb { - db: Arc, - another_db: Option>, - cells: Arc>>>, - db_index: u32, - #[cfg(feature = "telemetry")] - telemetry: Arc, - allocated: Arc -} - -impl DynamicBocDb { - - /// Constructs new instance using in-memory key-value collection - -/* - /// Constructs new instance using RocksDB with given path -*/ - - /// Constructs new instance using given key-value collection implementation - pub(crate) fn with_db( - db: Arc, - db_index: u32, - another_db: Option>, - #[cfg(feature = "telemetry")] - telemetry: Arc, - allocated: Arc - ) -> Self { - Self { - db: Arc::clone(&db), - cells: Arc::new(RwLock::new(fnv::FnvHashMap::default())), - db_index, - another_db, - #[cfg(feature = "telemetry")] - telemetry, - allocated - } - } - - pub fn cell_db(&self) -> &Arc { - &self.db - } - - pub fn cells_map(&self) -> Arc>>> { - Arc::clone(&self.cells) - } - - pub fn db_index(&self) -> u32 { - self.db_index - } - - /// Converts tree of cells into DynamicBoc - pub fn save_as_dynamic_boc( - self: &Arc, - root_cell: &dyn CellImpl, - check_stop: &(dyn Fn() -> Result<()> + Sync), - ) -> Result { - let mut transaction = self.db.begin_transaction()?; - let mut visited = fnv::FnvHashSet::default(); - let root_id = root_cell.hash(MAX_LEVEL); - - let written_count = self.save_tree_of_cells_recursive( - root_cell, - transaction.as_mut(), - &mut visited, - &root_id, - check_stop - )?; - - transaction.commit()?; - for h in visited { - log::trace!(target: TARGET, "DynamicBocDb::save_as_dynamic_boc id {:x} root_cell_id {:x} db_index {}", - h, root_id, self.db_index); - } - - Ok(written_count) - } - - /// Gets root cell from key-value storage - pub fn load_dynamic_boc(self: &Arc, root_cell_id: &UInt256) -> Result { - let storage_cell = self.load_cell(root_cell_id)?; - - Ok(Cell::with_cell_impl_arc(storage_cell)) - } - - pub(crate) fn load_cell(self: &Arc, cell_id: &UInt256) -> Result> { - let in_cache = if let Some(cell) = self.cells.read() - .expect("Poisoned RwLock") - .get(cell_id) - { - if let Some(cell) = Weak::upgrade(cell) { - log::trace!( - target: TARGET, - "DynamicBocDb::load_cell from cache id {:x} db_index {}", - cell_id, self.db_index - ); - return Ok(cell); - } - // Even if the cell is disposed, we will load and store it later, - // so we don't need to remove garbage here. - true - } else { - false - }; - let storage_cell = Arc::new( - match self.load_cell_from_db(cell_id) { - Ok(cell) => cell, - Err(e) => { - log::error!("FATAL! {}", e); - std::thread::sleep(time::Duration::from_millis(2_000)); - std::process::exit(0xFF); - } - } - ); - #[cfg(feature = "telemetry")] - self.telemetry.storage_cells.update(self.allocated.storage_cells.load(Ordering::Relaxed)); - self.cells.write() - .expect("Poisoned RwLock") - .insert(cell_id.clone(), Arc::downgrade(&storage_cell)); - - log::trace!(target: TARGET, "DynamicBocDb::load_cell from DB id {:x} db_index {} in_cache {}", - cell_id, self.db_index, in_cache); - - Ok(storage_cell) - } - - pub(crate) fn allocated(&self) -> &StorageAlloc { - &self.allocated - } - - fn load_cell_from_db(self: &Arc, cell_id: &UInt256) -> Result { - match CellDb::get_cell(self.db.deref(), cell_id, Arc::clone(self)) { - Ok(cell) => Ok(cell), - Err(e) => { - if let Some(another_db) = self.another_db.as_ref() { - let cell = CellDb::get_cell(another_db.deref(), cell_id, Arc::clone(self)) - .map_err(|e| error!( - "Can't load cell from both dbs id {:x} db_index {} in_cache false error: {}", - cell_id, self.db_index, e - ))?; - - // Restore only one cell. If caller requests referenced cell - we will restore it the same way. - self.db.put(cell_id, &StorageCell::serialize(&cell)?)?; - - log::warn!("A cell was restored from another db id {:x} db_index {}", - cell_id, self.db_index); - - Ok(cell) - } else { - fail!( - "Can't load cell id {:x} db_index {} in_cache false error: {}", - cell_id, self.db_index, e - ); - } - } - } - } - - fn save_tree_of_cells_recursive( - &self, - cell: &dyn CellImpl, - transaction: &mut dyn KvcTransaction, - visited: &mut fnv::FnvHashSet, - root_id: &UInt256, - check_stop: &(dyn Fn() -> Result<()> + Sync), - ) -> Result { - check_stop()?; - let cell_id = cell.hash(MAX_LEVEL); - if visited.contains(&cell_id) { - Ok(0) - } else if self.db.contains(&cell_id)? { - log::trace!( - target: TARGET, - "DynamicBocDb::save_tree_of_cells_recursive already in DB id {:x} root_cell_id {:x} db_index {}", - cell_id, root_id, self.db_index); - Ok(0) - } else { - transaction.put(&cell_id, &StorageCell::serialize(cell)?); - visited.insert(cell_id); - - let mut count = 1; - for i in 0..cell.references_count() { - count += self.save_tree_of_cells_recursive( - cell.reference(i)?.deref(), - transaction, - visited, - root_id, - check_stop - )?; - } - - Ok(count) - } - } -} - -impl Deref for DynamicBocDb { - type Target = Arc; - - fn deref(&self) -> &Self::Target { - &self.db - } -} - -impl DerefMut for DynamicBocDb { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.db - } -} diff --git a/storage/src/dynamic_boc_rc_db.rs b/storage/src/dynamic_boc_rc_db.rs index 7cb32e0b..662064ff 100644 --- a/storage/src/dynamic_boc_rc_db.rs +++ b/storage/src/dynamic_boc_rc_db.rs @@ -17,23 +17,29 @@ use crate::{ }; #[cfg(feature = "telemetry")] use crate::StorageTelemetry; -#[cfg(all(test, feature = "telemetry"))] -use crate::tests::utils::create_storage_telemetry; -use std::{ops::{Deref, DerefMut}, sync::{Arc, RwLock, Weak}, io::Cursor}; -#[cfg(feature = "telemetry")] use std::{ - sync::atomic::Ordering + ops::{Deref, DerefMut}, + sync::{Arc, RwLock, Weak, atomic::{AtomicU32, Ordering}}, + io::Cursor, + path::Path, + borrow::Cow, + time::Duration, + fs::write, }; //#[cfg(test)] //use std::path::Path; use ton_types::{ - Cell, Result, CellImpl, MAX_LEVEL, fail, DoneCellsStorage, UInt256, OrderedCellsStorage, - ByteOrderRead, + Cell, Result, MAX_LEVEL, fail, DoneCellsStorage, UInt256, OrderedCellsStorage, + ByteOrderRead, error, }; +pub const BROKEN_CELL_BEACON_FILE: &str = "ton_node.broken_cell"; + //#[derive(Debug)] pub struct DynamicBocDb { db: Arc, + db_root_path: String, + assume_old_cells: bool, cells: Arc>>>, #[cfg(feature = "telemetry")] telemetry: Arc, @@ -45,12 +51,30 @@ enum VisitedCell { cell: Cell, parents_count: u32, }, - Updated(StorageCell) + Updated { + cell_id: UInt256, + parents_count: u32, + }, + UpdatedOldFormat { + cell: Cell, + parents_count: u32, + }, } impl VisitedCell { - fn with_storage_cell(cell: StorageCell) -> Self { - Self::Updated(cell) + fn with_raw_counter(cell_id: UInt256, parents_count: &[u8]) -> Result { + let mut reader = Cursor::new(parents_count); + Ok(Self::Updated { + cell_id, + parents_count: reader.read_le_u32()?, + }) + } + + fn with_counter(cell_id: UInt256, parents_count: u32) -> Self { + Self::Updated { + cell_id, + parents_count, + } } fn with_new_cell(cell: Cell) -> Self { @@ -60,41 +84,106 @@ impl VisitedCell { } } + fn with_old_format_cell(cell: Cell, parents_count: u32) -> Self { + Self::UpdatedOldFormat{ + cell, + parents_count + } + } + fn inc_parents_count(&mut self) -> Result { - match self { - VisitedCell::New{parents_count, ..} => { - if *parents_count == u32::MAX { - fail!("Parents count has reached the maximum value"); - } - *parents_count += 1; - Ok(*parents_count) - } - VisitedCell::Updated(sc) => sc.inc_parents_count() + let parents_count = match self { + VisitedCell::New{parents_count, ..} => parents_count, + VisitedCell::Updated{parents_count, ..} => parents_count, + VisitedCell::UpdatedOldFormat{parents_count, ..} => parents_count, + }; + if *parents_count == u32::MAX { + fail!("Parents count has reached the maximum value"); + } + *parents_count += 1; + Ok(*parents_count) + } + + fn dec_parents_count(&mut self) -> Result { + let parents_count = match self { + VisitedCell::New{parents_count, ..} => parents_count, + VisitedCell::Updated{parents_count, ..} => parents_count, + VisitedCell::UpdatedOldFormat{parents_count, ..} => parents_count, + }; + if *parents_count == 0 { + fail!("Can't decrement - parents count is already zero"); } + *parents_count -= 1; + Ok(*parents_count) } - fn serialize(&self) -> Result> { + fn parents_count(&self) -> u32 { match self { - VisitedCell::Updated(cell) => { - cell.serialize_self() - }, + VisitedCell::New{parents_count, ..} => *parents_count, + VisitedCell::Updated{parents_count, ..} => *parents_count, + VisitedCell::UpdatedOldFormat{parents_count, ..} => *parents_count, + } + } + + fn serialize_counter(&self) -> ([u8; 33], [u8; 4]) { + let (id, counter) = match self { VisitedCell::New{cell, parents_count} => { - StorageCell::serialize(cell.deref(), *parents_count) + (Cow::Owned(cell.repr_hash()), parents_count) + } + VisitedCell::Updated{cell_id, parents_count} => { + (Cow::Borrowed(cell_id), parents_count) + } + VisitedCell::UpdatedOldFormat{cell, parents_count} => { + (Cow::Owned(cell.repr_hash()), parents_count) + } + }; + (build_counter_key(&id), counter.to_le_bytes()) + } + + fn serialize_cell(&self) -> Result>> { + match self { + VisitedCell::Updated{..} => Ok(None), + VisitedCell::New{cell, ..} => { + let data = StorageCell::serialize(cell.deref())?; + Ok(Some(data)) + } + VisitedCell::UpdatedOldFormat{cell, ..} => { + let data = StorageCell::serialize(cell.deref())?; + Ok(Some(data)) } } } + + fn cell(&self) -> Option<&Cell> { + match self { + VisitedCell::New{cell, ..} => Some(cell), + VisitedCell::Updated{..} => None, + VisitedCell::UpdatedOldFormat{cell, ..} => Some(cell), + } + } +} + +fn build_counter_key(cell_id: &UInt256) -> [u8; 33] { + let mut key = [0_u8; 33]; + key[..32].copy_from_slice(cell_id.as_slice()); + key[32] = 0; + key } impl DynamicBocDb { pub(crate) fn with_db( - db: Arc, + db: Arc, + db_root_path: &str, + assume_old_cells: bool, #[cfg(feature = "telemetry")] telemetry: Arc, allocated: Arc ) -> Self { Self { db: Arc::clone(&db), + db_root_path: db_root_path.to_string(), + assume_old_cells, cells: Arc::new(RwLock::new(fnv::FnvHashMap::default())), #[cfg(feature = "telemetry")] telemetry, @@ -116,10 +205,16 @@ impl DynamicBocDb { root_cell: Cell, is_state_root: bool, check_stop: &(dyn Fn() -> Result<()> + Sync), + cells_counters: &mut Option>, + full_filled_counters: bool, ) -> Result<()> { let root_id = root_cell.hash(MAX_LEVEL); log::debug!(target: TARGET, "DynamicBocDb::save_boc {:x}", root_id); + if full_filled_counters && self.assume_old_cells { + fail!("Full filled counters is not supported with assume_old_cells"); + } + if is_state_root && self.db.contains(&root_id)? { log::warn!(target: TARGET, "DynamicBocDb::save_boc ALREADY EXISTS {}", root_id); return Ok(()); @@ -131,7 +226,9 @@ impl DynamicBocDb { root_cell, &mut visited, &root_id, - check_stop + check_stop, + cells_counters, + full_filled_counters, )?; log::debug!( @@ -141,9 +238,18 @@ impl DynamicBocDb { ); let now = std::time::Instant::now(); + let mut created = 0; let mut transaction = self.db.begin_transaction()?; - for (id, cell) in visited.iter() { - transaction.put(id, &cell.serialize()?)?; + for (id, vc) in visited.iter() { + // cell + if let Some(data) = vc.serialize_cell()? { + transaction.put(id, &data)?; + created += 1; + } + + // counter + let (k, v) = vc.serialize_counter(); + transaction.put_raw(&k, &v)?; } log::debug!( target: TARGET, @@ -159,9 +265,13 @@ impl DynamicBocDb { now.elapsed().as_millis() ); + let updated = visited.len() - created; + #[cfg(feature = "telemetry")] { + self.telemetry.new_cells.update(created as u64); + self.telemetry.updated_cells.update(updated as u64); + } - - log::debug!(target: TARGET, "DynamicBocDb::save_boc {:x} saved {}", root_id, visited.len()); + log::debug!(target: TARGET, "DynamicBocDb::save_boc {:x} created {} updated {}", root_id, created, updated); Ok(()) } @@ -173,42 +283,89 @@ impl DynamicBocDb { Ok(Cell::with_cell_impl_arc(storage_cell)) } + + pub fn fill_counters( + self: &Arc, + check_stop: &(dyn Fn() -> Result<()> + Sync), + cells_counters: &mut fnv::FnvHashMap, + ) -> Result<()> { + self.db.for_each(&mut |key, value| { + if key.len() == 33 && key[32] == 0 { + let cell_id = UInt256::from_slice(&key[0..32]); + let mut reader = Cursor::new(value); + let counter = reader.read_le_u32()?; + cells_counters.insert(cell_id, counter); + } + check_stop()?; + Ok(true) + })?; + Ok(()) + } + // Is not thread-safe! pub fn delete_boc( self: &Arc, root_cell_id: &UInt256, check_stop: &(dyn Fn() -> Result<()> + Sync), + cells_counters: &mut Option>, + full_filled_counters: bool, ) -> Result<()> { - log::debug!(target: TARGET, "DynamicBocDb::delete_boc {}", root_cell_id); + log::debug!(target: TARGET, "DynamicBocDb::delete_boc {:x}", root_cell_id); + + if full_filled_counters && self.assume_old_cells { + fail!("Full filled counters is not supported with assume_old_cells"); + } + let mut visited = fnv::FnvHashMap::default(); self.delete_cells_recursive( root_cell_id, &mut visited, root_cell_id, check_stop, + cells_counters, + full_filled_counters, )?; let mut deleted = 0; let mut transaction = self.db.begin_transaction()?; for (id, cell) in visited.iter() { - if cell.parents_count() == 0 { + let counter_key = build_counter_key(&id); + let counter = cell.parents_count(); + if counter == 0 { transaction.delete(id)?; + // if there is no counter with the key, then it will be just ignored + transaction.delete_raw(&counter_key)?; deleted += 1; } else { - transaction.put(id, &StorageCell::serialize(cell, cell.parents_count())?)?; + transaction.put_raw(&counter_key, &counter.to_le_bytes())?; + + // update old format cell + if let Some(cell) = cell.serialize_cell()? { + transaction.put(id, &cell)?; + } } } transaction.commit()?; + let updated = visited.len() - deleted; + #[cfg(feature = "telemetry")] { + self.telemetry.deleted_cells.update(deleted as u64); + self.telemetry.updated_cells.update(updated as u64); + } + log::debug!( target: TARGET, - "DynamicBocDb::delete_boc {} deleted {} updated {}", - root_cell_id, deleted, visited.len() - deleted + "DynamicBocDb::delete_boc {:x} deleted {} updated {}", + root_cell_id, deleted, updated ); Ok(()) } - pub(crate) fn load_cell(self: &Arc, cell_id: &UInt256, use_cache: bool) -> Result> { + pub(crate) fn load_cell( + self: &Arc, + cell_id: &UInt256, + use_cache: bool, + ) -> Result> { let in_cache = use_cache && if let Some(cell) = self.cells.read() .expect("Poisoned RwLock") .get(cell_id) @@ -227,17 +384,38 @@ impl DynamicBocDb { } else { false }; - let storage_cell = Arc::new( - match CellDb::get_cell(self.db.deref(), cell_id, Arc::clone(self), use_cache) { - Ok(cell) => cell, - Err(e) => { - fail!("Can't load cell {:x} from db, error: {}", cell_id, e); - // log::error!("FATAL! Can't load cell {:x} from db, error: {}", cell_id, e); - // std::thread::sleep(time::Duration::from_millis(2_000)); - // std::process::exit(0xFF); - } + + let storage_cell_data = match self.db.get(cell_id) { + Ok(data) => data, + Err(e) => { + log::error!("FATAL!"); + log::error!("FATAL! Can't load cell {:x} from db, error: {:?}", cell_id, e); + log::error!("FATAL!"); + + let path = Path::new(&self.db_root_path).join(BROKEN_CELL_BEACON_FILE); + write(&path, "")?; + + std::thread::sleep(Duration::from_millis(100)); + std::process::exit(0xFF); } - ); + }; + + let storage_cell = match StorageCell::deserialize(self.clone(), &storage_cell_data, use_cache, false) + .or_else(|_| StorageCell::deserialize(self.clone(), &storage_cell_data, use_cache, true)) + { + Ok((cell, _)) => Arc::new(cell), + Err(e) => { + log::error!("FATAL!"); + log::error!("FATAL! Can't deserialize cell {:x} from db, error: {:?}", cell_id, e); + log::error!("FATAL!"); + + let path = Path::new(&self.db_root_path).join(BROKEN_CELL_BEACON_FILE); + write(&path, "")?; + + std::thread::sleep(Duration::from_millis(100)); + std::process::exit(0xFF); + } + }; #[cfg(feature = "telemetry")] self.telemetry.storage_cells.update(self.allocated.storage_cells.load(Ordering::Relaxed)); @@ -267,107 +445,191 @@ impl DynamicBocDb { visited: &mut fnv::FnvHashMap, root_id: &UInt256, check_stop: &(dyn Fn() -> Result<()> + Sync), + cells_counters: &mut Option>, + full_filled_counters: bool, ) -> Result<()> { - check_stop()?; - let cell_id = cell.hash(MAX_LEVEL); - if let Some(c) = visited.get_mut(&cell_id) { - // Cell is new or was load from base, so need to inc counter one more time. - let pc = c.inc_parents_count()?; - log::trace!( - target: TARGET, - "DynamicBocDb::save_cells_recursive updated cell {:x} inc counter {} root_cell_id {:x}", - cell_id, pc, root_id - ); - Ok(()) - } else if let Some(mut c) = self.db.try_get_cell(&cell_id, Arc::clone(self), false)? { - // Cell was load from base first time. - let pc = c.inc_parents_count()?; - log::trace!( - target: TARGET, - "DynamicBocDb::save_cells_recursive updated new cell {:x} inc counter {} root_cell_id {:x}", - cell_id, pc, root_id - ); - visited.insert(cell_id, VisitedCell::with_storage_cell(c)); - Ok(()) - } else { + check_stop()?; + let cell_id = cell.repr_hash(); + + let (counter, _cell) = self.load_and_update_cell( + &cell_id, + visited, + root_id, + cells_counters, + full_filled_counters, + |visited_cell| visited_cell.inc_parents_count(), + "DynamicBocDb::save_cells_recursive" + )?; + if counter.is_none() { // New cell. let c = VisitedCell::with_new_cell(cell.clone()); + visited.insert(cell_id.clone(), c); + if let Some(counters) = cells_counters.as_mut() { + counters.insert(cell_id.clone(), 1); + } log::trace!( target: TARGET, - "DynamicBocDb::save_cells_recursive added new cell {:x} root_cell_id {:x}", + "DynamicBocDb::save_cells_recursive {:x} new cell root_cell_id {:x}", cell_id, root_id ); - visited.insert(cell_id, c); + for i in 0..cell.references_count() { self.save_cells_recursive( cell.reference(i)?, visited, root_id, - check_stop + check_stop, + cells_counters, + full_filled_counters, )?; } - Ok(()) } + + Ok(()) } fn delete_cells_recursive( self: &Arc, cell_id: &UInt256, - visited: &mut fnv::FnvHashMap, + visited: &mut fnv::FnvHashMap, root_id: &UInt256, check_stop: &(dyn Fn() -> Result<()> + Sync), + cells_counters: &mut Option>, + full_filled_counters: bool, ) -> Result<()> { + check_stop()?; - let cell = loop { - if let Some(c) = visited.get_mut(cell_id) { - break c; - } else { - match self.db.get_cell(&cell_id, Arc::clone(self), false) { - Ok(c) => { - // This is a nightly-only experimental API. Uncomment and delete loop when will stable. - //visited.try_insert(cell_id.clone(), c) - // .map_err(|_| error!("Internal errror in DynamicBocDb::delete_cells_recursive"))? - visited.insert(cell_id.clone(), c); - continue; - }, - Err(e) => { - log::warn!( - "DynamicBocDb::delete_cells_recursive unknown cell with id {:x} {}", - cell_id, e - ); - return Ok(()) - } + if let (Some(counter), cell) = self.load_and_update_cell( + cell_id, + visited, + root_id, + cells_counters, + full_filled_counters, + |visited_cell| visited_cell.dec_parents_count(), + "DynamicBocDb::delete_cells_recursive", + )? { + if counter == 0 { + if let Some(counters) = cells_counters.as_mut() { + counters.remove(cell_id); } - }; - }; - let parents_count = cell.dec_parents_count()?; + let cell = if let Some(c) = cell { + c + } else { + Cell::with_cell_impl(self.db.get_cell(&cell_id, Arc::clone(self), false, false)?.0) + }; + + for i in 0..cell.references_count() { + self.delete_cells_recursive( + &cell.reference_repr_hash(i)?, + visited, + root_id, + check_stop, + cells_counters, + full_filled_counters, + )?; + } + } + } else { + log::warn!( + "DynamicBocDb::delete_cells_recursive unknown cell with id {:x} root_cell_id {:x}", + cell_id, root_id + ); + } - log::trace!( - target: TARGET, - "DynamicBocDb::delete_cells_recursive update cell {:x} parents_count {} root_cell_id {:x}", - cell_id, parents_count, root_id - ); + Ok(()) + } - if parents_count == 0 { - let references_count = cell.references_count(); - let mut references = Vec::with_capacity(references_count); - for i in 0..cell.references_count() { - references.push(cell.reference_id(i)); - } - for r in references { - self.delete_cells_recursive( - &r, - visited, - root_id, - check_stop + fn load_and_update_cell( + self: &Arc, + cell_id: &UInt256, + visited: &mut fnv::FnvHashMap, + root_id: &UInt256, + cells_counters: &mut Option>, + full_filled_counters: bool, + update_cell: impl Fn(&mut VisitedCell) -> Result, + op_name: &str, + ) -> Result<(Option, Option)> { + + if let Some(visited_cell) = visited.get_mut(&cell_id) { + // Cell was already updated while this operation, just update counter + let new_counter = update_cell(visited_cell)?; + if let Some(counters) = cells_counters.as_mut() { + let counter = counters.get_mut(&cell_id).ok_or_else( + || error!("INTERNAL ERROR: cell from 'visited' is not presented in `cells_counters`") )?; + *counter = new_counter; } + log::trace!( + target: TARGET, + "{} {:x} update visited {} root_cell_id {:x}", + op_name, cell_id, new_counter, root_id + ); + return Ok((Some(new_counter), visited_cell.cell().cloned())); } - Ok(()) + if let Some(counter) = cells_counters.as_mut().and_then(|cc| cc.get_mut(&cell_id)) { + // Cell's counter is in cache - update it + let mut visited_cell = VisitedCell::with_counter(cell_id.clone(), *counter); + *counter = update_cell(&mut visited_cell)?; + visited.insert(cell_id.clone(), visited_cell); + log::trace!( + target: TARGET, + "{} {:x} update counter {} root_cell_id {:x}", + op_name, cell_id, counter, root_id + ); + #[cfg(feature = "telemetry")] + self.telemetry.cell_counter_from_cache.update(1); + + return Ok((Some(*counter), None)); + } + + if !full_filled_counters { + if let Some(counter_raw) = self.db.try_get_raw(&build_counter_key(&cell_id))? { + // Cell's counter is in DB - load it and update + let mut visited_cell = VisitedCell::with_raw_counter(cell_id.clone(), &counter_raw)?; + let counter = update_cell(&mut visited_cell)?; + visited.insert(cell_id.clone(), visited_cell); + if let Some(counters) = cells_counters.as_mut() { + counters.insert(cell_id.clone(), counter); + } + log::trace!( + target: TARGET, + "{} {:x} load counter {} root_cell_id {:x}", + op_name, cell_id, counter, root_id + ); + #[cfg(feature = "telemetry")] + self.telemetry.cell_counter_from_db.update(1); + + return Ok((Some(counter), None)); + } + } + + if self.assume_old_cells { + if let Some((cell, counter)) = self.db.try_get_cell(&cell_id, Arc::clone(self), false, true)? { + // Old cell without external counter + let cell = Cell::with_cell_impl(cell); + let mut visited_cell = VisitedCell::with_old_format_cell(cell.clone(), counter); + let counter = update_cell(&mut visited_cell)?; + visited.insert(cell_id.clone(), visited_cell); + if let Some(counters) = cells_counters.as_mut() { + counters.insert(cell_id.clone(), counter); + } + log::trace!( + target: TARGET, + "{} {:x} update old cell {} root_cell_id {:x}", + op_name, cell_id, counter, root_id + ); + #[cfg(feature = "telemetry")] + self.telemetry.updated_old_cells.update(1); + + return Ok((Some(counter), Some(cell))); + } + } + + Ok((None, None)) } } @@ -410,7 +672,7 @@ impl DoneCellsStorageAdapter { impl DoneCellsStorage for DoneCellsStorageAdapter { fn insert(&mut self, index: u32, cell: Cell) -> Result<()> { self.index.put(&index.into(), cell.repr_hash().as_slice())?; - self.boc_db.clone().save_boc(cell, false, &|| Ok(()))?; + self.boc_db.clone().save_boc(cell, false, &|| Ok(()), &mut None, false)?; Ok(()) } @@ -434,6 +696,8 @@ pub struct OrderedCellsStorageAdapter { index1: IndexedUint256Db, // reverted index in boc (u32) -> cell id (u256) index2: IndexedUint32Db, // cell id (u256) -> reverted index in boc (u32) cells_count: u32, + slowdown_mcs: Arc, + index_db_path: String, } impl OrderedCellsStorageAdapter { @@ -441,6 +705,7 @@ impl OrderedCellsStorageAdapter { db: Arc, boc_db: Arc, index_db_path: impl ToString, + slowdown_mcs: Arc, ) -> Result { let index_db_path = index_db_path.to_string(); let path1 = format!("{}_1", index_db_path); @@ -452,8 +717,17 @@ impl OrderedCellsStorageAdapter { index1: IndexedUint256Db::with_db(db.clone(), path1, true)?, index2: IndexedUint32Db::with_db(db.clone(), path2, true)?, cells_count: 0, + slowdown_mcs, + index_db_path: index_db_path.to_string(), }) } + fn slowdown(&self) -> u32 { + let timeout = self.slowdown_mcs.load(Ordering::Relaxed); + if timeout > 0 { + std::thread::sleep(Duration::from_micros(timeout as u64)); + } + timeout + } } impl OrderedCellsStorage for OrderedCellsStorageAdapter { @@ -468,12 +742,29 @@ impl OrderedCellsStorage for OrderedCellsStorageAdapter { self.index1.put(&index.into(), hash.as_slice())?; self.index2.put(&hash, &index.to_le_bytes())?; self.cells_count += 1; + + let slowdown = self.slowdown(); + if self.cells_count % 1000 == 0 { + log::info!( + "OrderedCellsStorage::push_cell {} cells: {}, slowdown mcs: {}", + self.index_db_path, self.cells_count, slowdown + ); + } Ok(()) } fn get_cell_by_index(&self, index: u32) -> Result { let id = UInt256::from_slice(self.index1.get(&index.into())?.as_ref()).into(); - Ok(Cell::with_cell_impl_arc(self.boc_db.clone().load_cell(&id, false)?)) + let cell = Cell::with_cell_impl_arc(self.boc_db.clone().load_cell(&id, false)?); + + let slowdown = self.slowdown(); + if index % 1000 == 0 { + log::info!( + "OrderedCellsStorage::get_cell_by_index {} index: {}, total cells: {}, slowdown mcs: {}", + self.index_db_path, index, self.cells_count, slowdown + ); + } + Ok(cell) } fn get_rev_index_by_hash(&self, hash: &UInt256) -> Result { let data = self.index2.get(hash)?; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 5634ba72..fb04d06c 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -18,23 +18,17 @@ pub mod block_info_db; pub mod catchain_persistent_db; mod cell_db; pub mod db; -#[cfg(not(feature = "ref_count_gc"))] -mod dynamic_boc_db; -#[cfg(feature = "ref_count_gc")] -mod dynamic_boc_rc_db; +pub mod dynamic_boc_rc_db; mod error; mod macros; pub mod node_state_db; -#[cfg(not(feature = "ref_count_gc"))] -pub mod shardstate_db; -#[cfg(feature = "ref_count_gc")] pub mod shardstate_db_async; pub mod traits; pub mod types; pub mod shard_top_blocks_db; #[cfg(feature = "telemetry")] -use adnl::telemetry::Metric; +use adnl::telemetry::{Metric, MetricBuilder}; use std::{sync::{Arc, atomic::AtomicU64}, time::{Duration, Instant}}; pub struct TimeChecker { @@ -72,14 +66,42 @@ pub struct StorageTelemetry { pub file_entries: Arc, pub handles: Arc, pub packages: Arc, - pub storage_cells: Arc + pub storage_cells: Arc, + pub shardstates_queue: Arc, + pub cells_counters: Arc, + pub cell_counter_from_cache: Arc, + pub cell_counter_from_db: Arc, + pub updated_old_cells: Arc, + pub updated_cells: Arc, + pub new_cells: Arc, + pub deleted_cells: Arc, +} +#[cfg(feature = "telemetry")] +impl Default for StorageTelemetry { + fn default() -> Self { + Self { + file_entries: Metric::without_totals("", 1), + handles: Metric::without_totals("", 1), + packages: Metric::without_totals("", 1), + storage_cells: Metric::without_totals("", 1), + shardstates_queue: Metric::without_totals("", 1), + cells_counters: Metric::without_totals("", 1), + cell_counter_from_cache: MetricBuilder::with_metric_and_period(Metric::with_total_amount("", 1), 1000000000), + cell_counter_from_db: MetricBuilder::with_metric_and_period(Metric::with_total_amount("", 1), 1000000000), + updated_old_cells: MetricBuilder::with_metric_and_period(Metric::with_total_amount("", 1), 1000000000), + updated_cells: MetricBuilder::with_metric_and_period(Metric::with_total_amount("", 1), 1000000000), + new_cells: MetricBuilder::with_metric_and_period(Metric::with_total_amount("", 1), 1000000000), + deleted_cells: MetricBuilder::with_metric_and_period(Metric::with_total_amount("", 1), 1000000000), + } + } } +#[derive(Default)] pub struct StorageAlloc { pub file_entries: Arc, pub handles: Arc, pub packages: Arc, - pub storage_cells: Arc + pub storage_cells: Arc, } pub(crate) const TARGET: &str = "storage"; diff --git a/storage/src/shardstate_db.rs b/storage/src/shardstate_db.rs deleted file mode 100644 index e588a217..00000000 --- a/storage/src/shardstate_db.rs +++ /dev/null @@ -1,654 +0,0 @@ -/* -* Copyright (C) 2019-2021 TON Labs. All Rights Reserved. -* -* Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use -* this file except in compliance with the 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 TON DEV software governing permissions and -* limitations under the License. -*/ - -use crate::{ - StorageAlloc, cell_db::CellDb, - db::{rocksdb::RocksDbTable, traits::{DbKey, KvcWriteable, KvcTransaction}}, - dynamic_boc_db::DynamicBocDb, /*dynamic_boc_diff_writer::DynamicBocDiffWriter,*/ - traits::Serializable, types::{Reference, StorageCell}, - TARGET, -}; -#[cfg(feature = "telemetry")] -use crate::StorageTelemetry; -#[cfg(all(test, feature = "telemetry"))] -use crate::tests::utils::create_storage_telemetry; -use fnv::FnvHashSet; -use crate::db::rocksdb::RocksDb; -use std::{ - io::{Cursor, Read, Write}, ops::Deref, - sync::{Arc, atomic::{AtomicU8, AtomicU32, Ordering}}, - time::{Duration, Instant, SystemTime, UNIX_EPOCH}, -}; -use ton_block::BlockIdExt; -use ton_types::{ByteOrderRead, Cell, Result, fail, UInt256}; - -pub(crate) struct DbEntry { - pub cell_id: UInt256, // TODO: remove this id - pub block_id: BlockIdExt, - pub db_index: u32, - pub save_utime: u64, -} - -impl DbEntry { - pub fn with_params(cell_id: UInt256, block_id: BlockIdExt, db_index: u32, save_utime: u64) -> Self { - Self { cell_id, block_id, db_index, save_utime } - } -} - -impl Serializable for DbEntry { - fn serialize(&self, writer: &mut T) -> Result<()> { - writer.write_all(self.cell_id.key())?; - self.block_id.serialize(writer)?; - writer.write_all(&self.db_index.to_le_bytes())?; - writer.write_all(&self.save_utime.to_le_bytes())?; - Ok(()) - } - - fn deserialize(reader: &mut T) -> Result { - let mut buf = [0; 32]; - reader.read_exact(&mut buf)?; - let cell_id = buf.into(); - let block_id = BlockIdExt::deserialize(reader)?; - let db_index = reader.read_le_u32().unwrap_or(0); // use 0 db by default - let save_utime = reader.read_le_u64().unwrap_or(0); - - Ok(Self { cell_id, block_id, db_index, save_utime }) - } -} - -pub struct ShardStateDb { - shardstate_db: Arc>, - current_dynamic_boc_db_index: AtomicU32, - dynamic_boc_db_0: Arc, - dynamic_boc_db_0_writers: AtomicU32, - dynamic_boc_db_1: Arc, - dynamic_boc_db_1_writers: AtomicU32, - stop: AtomicU8 -} - -const SHARDSTATES_GC_LAG_SEC: u64 = 300; - -impl ShardStateDb { - - const MASK_GC_STARTED: u8 = 0x01; - const MASK_STOPPED: u8 = 0x80; - - /// Constructs new instance using in-memory key-value collections - - /// Constructs new instance using RocksDB with given paths - pub fn with_db( - db: Arc, - shardstate_db_path: impl ToString, - cell_db_path: impl ToString, - cell_db_path_additional: impl ToString, - #[cfg(feature = "telemetry")] - telemetry: Arc, - allocated: Arc - ) -> Result> { - let ret = Self::with_dbs( - Arc::new(RocksDbTable::with_db(db.clone(), shardstate_db_path)?), - Arc::new(CellDb::with_db(db.clone(), cell_db_path)?), - Arc::new(CellDb::with_db(db.clone(), cell_db_path_additional)?), - #[cfg(feature = "telemetry")] - telemetry, - allocated - ); - Ok(ret) - } - - /// Constructs new instance using given key-value collection implementations - fn with_dbs( - shardstate_db: Arc>, - cell_db_0: Arc, - cell_db_1: Arc, - #[cfg(feature = "telemetry")] - telemetry: Arc, - allocated: Arc - ) -> Arc { - Arc::new(Self { - shardstate_db, - current_dynamic_boc_db_index: AtomicU32::new(0), - dynamic_boc_db_0: Arc::new(DynamicBocDb::with_db( - cell_db_0.clone(), - 0, - Some(cell_db_1.clone()), - #[cfg(feature = "telemetry")] - telemetry.clone(), - allocated.clone() - )), - dynamic_boc_db_0_writers: AtomicU32::new(0), - dynamic_boc_db_1: Arc::new(DynamicBocDb::with_db( - cell_db_1, - 1, - Some(cell_db_0), - #[cfg(feature = "telemetry")] - telemetry, - allocated - )), - dynamic_boc_db_1_writers: AtomicU32::new(0), - stop: AtomicU8::new(0) - }) - } - - pub fn start_gc( - self: Arc, - gc_resolver: Arc, - run_interval_adjustable_sec: Arc, - ) { - // When test-blockchains deploys all validator-nodes start approx at one time, - // so all GC start at one time all nodes. It can reduce whole blockchain's performans, - // so let's mix start time next way - let run_gc_interval = run_interval_adjustable_sec.load(Ordering::Relaxed) as u64; - let mut rng = rand::thread_rng(); - let rand_initial_sleep = rand::Rng::gen_range(&mut rng, 0..run_gc_interval); - - self.stop.fetch_or(Self::MASK_GC_STARTED, Ordering::Relaxed); - tokio::spawn(async move { - - fn check_and_stop(stop: &AtomicU8) -> bool { - if (stop.load(Ordering::Relaxed) & ShardStateDb::MASK_STOPPED) != 0 { - stop.fetch_and(!ShardStateDb::MASK_GC_STARTED, Ordering::Relaxed); - true - } else { - false - } - } - - async fn sleep_nicely(stop: &AtomicU8, sleep_for_sec: u64) -> bool { - let start = Instant::now(); - loop { - tokio::time::sleep(Duration::from_secs(1)).await; - if check_and_stop(stop) { - return false - } - if start.elapsed().as_secs() > sleep_for_sec { - return true - } - } - } - - if !sleep_nicely(&self.stop, rand_initial_sleep).await { - return - } - - let mut last_gc_duration = Duration::from_secs(0); - loop { - let run_gc_interval = - Duration::from_secs(run_interval_adjustable_sec.load(Ordering::Relaxed) as u64); - if run_gc_interval > last_gc_duration { - if !sleep_nicely( - &self.stop, - (run_gc_interval - last_gc_duration).as_secs() - ).await { - return - } - } - - // Take unused db's index - let current_db_index = self.current_dynamic_boc_db_index.load(Ordering::Relaxed); - let (collected_db, _another_db, collected_db_writers) = - match current_db_index { - 1 => { - (&self.dynamic_boc_db_0, &self.dynamic_boc_db_1, &self.dynamic_boc_db_0_writers) - }, - 0 => { - (&self.dynamic_boc_db_1, &self.dynamic_boc_db_0, &self.dynamic_boc_db_1_writers) - }, - _ => { - log::error!(target: TARGET, "Invalid `current_dynamic_boc_db_index` while GC"); - last_gc_duration = Duration::from_secs(0); - continue; - } - }; - - // wait while all wtirers finish operation with unused db - let writers_waiting_start = Instant::now(); - while collected_db_writers.load(Ordering::Relaxed) > 0 { - tokio::time::sleep(Duration::from_secs(1)).await; - let time = writers_waiting_start.elapsed().as_millis(); - if time > 1000 { - log::warn!(target: TARGET, "Too long awaiting of dynamic boc db writing {}ms", time); - } - } - - // start GC - log::info!(target: TARGET, "Statring GC for db {}", collected_db.db_index()); - let collecting_start = Instant::now(); - let self_ = self.clone(); - let collected_db_ = collected_db.clone(); - let gc_resolver_ = gc_resolver.clone(); - let result = tokio::task::spawn_blocking(move || - gc( - self_.shardstate_db(), - collected_db_, - gc_resolver_, - &|| { - if (self_.stop.load(Ordering::Relaxed) & ShardStateDb::MASK_STOPPED) != 0 { - fail!("GC was stopped") - } - Ok(()) - } - ) - ).await; - last_gc_duration = collecting_start.elapsed(); - match result { - Err(e) => { - log::error!(target: TARGET, "Panic while GC for db {}: {}, TIME: {}ms", - collected_db.db_index(), e, last_gc_duration.as_millis()); - }, - Ok(Err(e)) => { - if check_and_stop(&self.stop) { - log::info!(target: TARGET, "GC for db {}: was stopped", - collected_db.db_index()); - return; - } - log::error!(target: TARGET, "Error while GC for db {}: {}, TIME: {}ms", - collected_db.db_index(), e, last_gc_duration.as_millis()); - }, - Ok(Ok(gc_stat)) => { - log::info!( - target: TARGET, - "Finished GC for db {}\n\ - collected cells {:>8}\n\ - marked cells {:>8}\n\ - total cells {:>8}\n\ - roots to sweep {:>8}\n\ - marked roots {:>8}\n\ - mark time {:>8} ms\n\ - sweep time {:>8} ms\n\ - total time {:>8} ms", - collected_db.db_index(), - gc_stat.collected_cells, - gc_stat.marked_cells, - gc_stat.collected_cells + gc_stat.marked_cells, - gc_stat.roots_to_sweep, - gc_stat.marked_roots, - gc_stat.mark_time.as_millis(), - gc_stat.sweep_time.as_millis(), - (gc_stat.mark_time + gc_stat.sweep_time).as_millis() - ); - } - } - - // Change current db's index - self.current_dynamic_boc_db_index.store(collected_db.db_index(), Ordering::Relaxed); - }; - }); - } - - pub async fn stop(&self) { - self.stop.fetch_or(Self::MASK_STOPPED, Ordering::Relaxed); - loop { - tokio::time::sleep(Duration::from_secs(1)).await; - if !self.is_gc_run() { - break; - } - } - } - - pub fn is_gc_run(&self) -> bool { - self.stop.load(Ordering::Relaxed) & Self::MASK_GC_STARTED != 0 - } - - /// Returns reference to shardstates database - pub fn shardstate_db(&self) -> Arc> { - Arc::clone(&self.shardstate_db) - } - - /// Stores cells from given tree which don't exist in the storage. - /// Returns root cell which is implemented as StorageCell. - /// So after store() origin shard state's cells might be dropped. - pub fn put( - &self, - id: &BlockIdExt, - state_root: Cell, - ) -> Result { - let saved_root = match self.current_dynamic_boc_db_index.load(Ordering::Relaxed) { - 0 => { - self.dynamic_boc_db_0_writers.fetch_add(1, Ordering::Relaxed); - let r = self.put_internal(id, state_root, &self.dynamic_boc_db_0); - self.dynamic_boc_db_0_writers.fetch_sub(1, Ordering::Relaxed); - r? - }, - 1 => { - self.dynamic_boc_db_1_writers.fetch_add(1, Ordering::Relaxed); - let r = self.put_internal(id, state_root, &self.dynamic_boc_db_1); - self.dynamic_boc_db_1_writers.fetch_sub(1, Ordering::Relaxed); - r? - }, - _ => fail!("Invalid `current_dynamic_boc_db_index`") - }; - - Ok(saved_root) - } - - pub fn put_internal( - &self, - id: &BlockIdExt, - state_root: Cell, - boc_db: &Arc, - ) -> Result { - let cell_id = UInt256::from(state_root.repr_hash()); - - log::trace!(target: TARGET, "ShardStateDb::put_internal start id {} root_cell_id {:x} db_index {}", - id, cell_id, boc_db.db_index()); - - let check_stop = || -> Result<()> { - if (self.stop.load(Ordering::Relaxed) & ShardStateDb::MASK_STOPPED) != 0 { - fail!("Stopped") - } - Ok(()) - }; - - let c = tokio::task::block_in_place( - || boc_db.save_as_dynamic_boc(state_root.deref(), &check_stop) - )?; - - let save_utime = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); - let db_entry = DbEntry::with_params(cell_id.clone(), id.clone(), boc_db.db_index(), save_utime); - let mut buf = Vec::new(); - db_entry.serialize(&mut Cursor::new(&mut buf))?; - - self.shardstate_db.put(id, buf.as_slice())?; - // (self.shardstate_db as dyn KvcWriteable::).put(id, buf.as_slice())?; - - log::trace!(target: TARGET, "ShardStateDb::put_internal finish id {} root_cell_id {:x} db_index {} written: {} cells", - id, cell_id, boc_db.db_index(), c); - - let root_cell = boc_db.load_dynamic_boc(&db_entry.cell_id)?; - - Ok(root_cell) - } - - /// Loads previously stored root cell - pub fn get(&self, id: &BlockIdExt) -> Result { - let db_entry = DbEntry::from_slice(&self.shardstate_db.get(id)?)?; - - log::trace!(target: TARGET, "ShardStateDb::get id {} cell_id {:x} db_index {}", - id, db_entry.cell_id, db_entry.db_index); - - let boc_db = match db_entry.db_index { - 1 => &self.dynamic_boc_db_1, - 0 => &self.dynamic_boc_db_0, - index => fail!("Invalid db's index {}", index) - }; - let root_cell = boc_db.load_dynamic_boc(&db_entry.cell_id)?; - - Ok(root_cell) - } - -} - -pub trait AllowStateGcResolver: Send + Sync { - fn allow_state_gc(&self, block_id: &BlockIdExt, save_utime: u64, gc_utime: u64) -> Result; -} - -#[derive(Default)] -pub struct GcStatistic { - pub collected_cells: usize, - pub marked_cells: usize, - pub roots_to_sweep: usize, - pub marked_roots: usize, - pub mark_time: Duration, - pub sweep_time: Duration, -} - -pub fn gc( - shardstate_db: Arc>, - cleaned_boc_db: Arc, - gc_resolver: Arc, - check_stopped: &dyn Fn() -> Result<()>, -) -> Result { - - let mark_started = Instant::now(); - let shardstate_db_ = shardstate_db.clone(); - let cleaned_boc_db_ = cleaned_boc_db.clone(); - let gc_resolver_ = gc_resolver.clone(); - let (marked, to_sweep, marked_roots) = mark( - shardstate_db_.deref(), - cleaned_boc_db_.deref(), - gc_resolver_.deref(), - SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(), - check_stopped, - )?; - - let marked_cells = marked.len(); - let mark_time = mark_started.elapsed(); - let roots_to_sweep = to_sweep.len(); - - if to_sweep.is_empty() { - Ok(GcStatistic { - collected_cells: 0, - marked_cells, - roots_to_sweep, - marked_roots, - mark_time, - sweep_time: Duration::default(), - }) - } else { - - let sweep_started = Instant::now(); - let collected_cells = sweep( - shardstate_db, - cleaned_boc_db, - to_sweep, - marked, - check_stopped, - )?; - - Ok(GcStatistic { - collected_cells, - marked_cells, - roots_to_sweep, - marked_roots, - mark_time, - sweep_time: sweep_started.elapsed(), - }) - } -} -type MarkResult = Result<(FnvHashSet, Vec<(BlockIdExt, UInt256)>, usize)>; - -fn mark( - shardstate_db: &dyn KvcWriteable, - dynamic_boc_db: &DynamicBocDb, - gc_resolver: &dyn AllowStateGcResolver, - gc_time: u64, - check_stopped: &dyn Fn() -> Result<()>, -) -> MarkResult { - // We should leave last states in DB to avoid full state saving when DB will active. - // 1) enumerate all roots with their save-time - // 2) remember max time and max masterchain root - // 3) do not sweep last masterchain root and shardchains roots - // for last SHARDSTATES_GC_LAG_SEC seconds - - let mut last_mc_seqno = 0; - let mut roots = Vec::new(); - let mut max_shard_utime = 0; - - shardstate_db.for_each(&mut |_key, value| { - check_stopped()?; - let db_entry = DbEntry::from_slice(value)?; - if db_entry.db_index == dynamic_boc_db.db_index() { - if db_entry.block_id.shard().is_masterchain() { - if db_entry.block_id.seq_no() > last_mc_seqno { - last_mc_seqno = db_entry.block_id.seq_no(); - } - } else { - if db_entry.save_utime > max_shard_utime{ - max_shard_utime = db_entry.save_utime; - } - } - roots.push(db_entry); - } - Ok(true) - })?; - - let mut to_mark = Vec::new(); - let mut to_sweep = Vec::new(); - for root in roots { - check_stopped()?; - let is_mc = root.block_id.shard().is_masterchain(); - if gc_resolver.allow_state_gc(&root.block_id, root.save_utime, gc_time)? - && (!is_mc || root.block_id.seq_no() < last_mc_seqno) - && (is_mc || root.save_utime + SHARDSTATES_GC_LAG_SEC < max_shard_utime) - { - log::trace!(target: TARGET, "mark to_sweep block_id {} cell_id {:x} db_index {} utime {}", - root.block_id, root.cell_id, dynamic_boc_db.db_index(), root.save_utime); - to_sweep.push((root.block_id, root.cell_id)); - } else { - log::trace!(target: TARGET, "mark to_mark block_id {} cell_id {:x} db_index {} utime {}", - root.block_id, root.cell_id, dynamic_boc_db.db_index(), root.save_utime); - to_mark.push(root.cell_id); - } - } - - let marked_roots = to_mark.len(); - let mut marked = FnvHashSet::default(); - if !to_sweep.is_empty() { - for cell_id in to_mark { - mark_subtree_recursive(dynamic_boc_db, cell_id.clone(), cell_id, &mut marked, check_stopped)?; - } - } - - Ok((marked, to_sweep, marked_roots)) -} - -fn mark_subtree_recursive( - dynamic_boc_db: &DynamicBocDb, - cell_id: UInt256, - root_id: UInt256, - marked: &mut FnvHashSet, - check_stopped: &dyn Fn() -> Result<()>, -) -> Result<()> { - check_stopped()?; - if !marked.contains(&cell_id) { - match load_cell_references(dynamic_boc_db, &cell_id) { - Ok(references) => { - marked.insert(cell_id.clone()); - - log::trace!( - target: TARGET, - "mark_subtree_recursive marked cell {} root: {:x} db_index {}", - cell_id, root_id, dynamic_boc_db.db_index(), - ); - - for reference in references { - mark_subtree_recursive(dynamic_boc_db, reference.hash().into(), root_id.clone(), - marked, check_stopped)?; - } - }, - Err(err) => { - log::error!( - target: TARGET, - "mark_subtree_recursive can't load cell cell {} root: {:x} db_index {} error: {}", - cell_id, root_id, dynamic_boc_db.db_index(), err, - ); - } - } - } - Ok(()) -} - -fn sweep( - shardstate_db: Arc>, - dynamic_boc_db: Arc, - to_sweep: Vec<(BlockIdExt, UInt256)>, - marked: FnvHashSet, - check_stopped: &dyn Fn() -> Result<()>, -) -> Result { - if to_sweep.is_empty() { - return Ok(0); - } - - let mut deleted_count = 0; - let mut sweeped = FnvHashSet::default(); - - let mut transaction = dynamic_boc_db.begin_transaction()?; - - for (block_id, cell_id) in to_sweep { - - deleted_count += sweep_cells_recursive( - dynamic_boc_db.as_ref(), - transaction.as_mut(), - &cell_id, - &cell_id, - &marked, - &mut sweeped, - check_stopped, - )?; - log::trace!(target: TARGET, "GC::sweep block_id {}", block_id); - - shardstate_db.delete(&block_id)?; - } - - transaction.commit()?; - - Ok(deleted_count) -} - -fn sweep_cells_recursive( - dynamic_boc_db: &DynamicBocDb, - transaction: &mut dyn KvcTransaction, - cell_id: &UInt256, - root_id: &UInt256, - marked: &FnvHashSet, - sweeped: &mut FnvHashSet, - check_stopped: &dyn Fn() -> Result<()>, -) -> Result { - check_stopped()?; - if marked.contains(cell_id) || sweeped.contains(cell_id) { - Ok(0) - } else { - let mut deleted_count = 0; - sweeped.insert(cell_id.clone()); - - match load_cell_references(dynamic_boc_db, cell_id) { - Ok(references) => { - for reference in references { - deleted_count += sweep_cells_recursive( - dynamic_boc_db, - transaction, - &reference.hash().into(), - root_id, - marked, - sweeped, - check_stopped - )?; - } - - log::trace!( - target: TARGET, - "sweep_cells_recursive delete cell id: {:x} root: {:x} db_index: {}", - cell_id, root_id, dynamic_boc_db.db_index(), - ); - - transaction.delete(cell_id); - - deleted_count += 1; - } - Err(err) => { - log::error!( - target: TARGET, - "sweep_cells_recursive can't load cell id: {:x} root: {:x} db_index: {} error: {}", - cell_id, root_id, dynamic_boc_db.db_index(), err, - ); - } - } - Ok(deleted_count) - } -} - -fn load_cell_references(dynamic_boc_db: &DynamicBocDb, cell_id: &UInt256) -> Result> { - let slice = dynamic_boc_db.cell_db().get(cell_id)?; - StorageCell::deserialize_references(slice.as_ref()) -} diff --git a/storage/src/shardstate_db_async.rs b/storage/src/shardstate_db_async.rs index 563cc8a9..af2ca86f 100644 --- a/storage/src/shardstate_db_async.rs +++ b/storage/src/shardstate_db_async.rs @@ -20,13 +20,11 @@ use crate::{ }; #[cfg(feature = "telemetry")] use crate::StorageTelemetry; -#[cfg(all(test, feature = "telemetry"))] -use crate::tests::utils::create_storage_telemetry; use crate::db::rocksdb::RocksDb; use std::{ io::{Cursor, Read, Write}, - sync::{Arc, atomic::{AtomicU8, AtomicU32, AtomicI32, Ordering}}, - time::{Duration, Instant, SystemTime, UNIX_EPOCH}, + sync::{Arc, atomic::{AtomicU8, AtomicU32, Ordering}}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; use ton_block::BlockIdExt; use ton_types::{ @@ -79,6 +77,24 @@ pub trait Callback: Sync + Send { async fn invoke(&self, job: Job, ok: bool); } +pub struct SsNotificationCallback(tokio::sync::Notify); + +#[async_trait::async_trait] +impl Callback for SsNotificationCallback { + async fn invoke(&self, _job: Job, _ok: bool) { + self.0.notify_one(); + } +} + +impl SsNotificationCallback { + pub fn new() -> Arc { + Arc::new(Self(tokio::sync::Notify::new())) + } + pub async fn wait(&self) { + self.0.notified().await; + } +} + impl Job { pub fn block_id(&self) -> &BlockIdExt { match self { @@ -93,19 +109,31 @@ pub struct ShardStateDb { shardstate_db: Arc>, dynamic_boc_db: Arc, storer: tokio::sync::mpsc::UnboundedSender<(Job, Option>)>, - in_queue: AtomicI32, + in_queue: AtomicU32, stop: AtomicU8, + max_queue_len: u32, + max_pss_slowdown_mcs: u32, + full_filled_counters: bool, + pss_slowdown_mcs: Arc, + #[cfg(feature = "telemetry")] + telemetry: Arc, } impl ShardStateDb { const MASK_GC_STARTED: u8 = 0x01; + const MASK_WORKER: u8 = 0x02; const MASK_STOPPED: u8 = 0x80; pub fn new( db: Arc, - shardstate_db_path: impl ToString, - cell_db_path: impl ToString, + shardstate_db_path: &str, + cell_db_path: &str, + db_root_path: &str, + assume_old_cells: bool, + max_queue_len: u32, + max_pss_slowdown_mcs: u32, + full_filled_counters: bool, #[cfg(feature = "telemetry")] telemetry: Arc, allocated: Arc @@ -113,18 +141,30 @@ impl ShardStateDb { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + if assume_old_cells && full_filled_counters { + fail!("assume_old_cells and full_filled_counters can't be enabled at the same time") + } + let ss_db = Arc::new(Self { db: db.clone(), shardstate_db: Arc::new(RocksDbTable::with_db(db.clone(), shardstate_db_path, true)?), dynamic_boc_db: Arc::new(DynamicBocDb::with_db( Arc::new(CellDb::with_db(db.clone(), cell_db_path, true)?), + db_root_path, + assume_old_cells, #[cfg(feature = "telemetry")] telemetry.clone(), allocated.clone() )), storer: sender, - in_queue: AtomicI32::new(0), - stop: AtomicU8::new(0) + in_queue: AtomicU32::new(0), + stop: AtomicU8::new(0), + max_queue_len, + max_pss_slowdown_mcs, + full_filled_counters, + pss_slowdown_mcs: Arc::new(AtomicU32::new(0)), + #[cfg(feature = "telemetry")] + telemetry, }); tokio::spawn({ @@ -154,27 +194,95 @@ impl ShardStateDb { } } - async fn sleep_nicely(stop: &AtomicU8, sleep_for_sec: u64) -> bool { - let start = Instant::now(); + async fn sleep_nicely(stop: &AtomicU8, mut sleep_for_ms: u64) -> bool { + const TIMEOUT_STEP: u64 = 1000; loop { - tokio::time::sleep(Duration::from_secs(1)).await; + let interval = if sleep_for_ms > TIMEOUT_STEP { + TIMEOUT_STEP + } else { + sleep_for_ms + }; + tokio::time::sleep(Duration::from_millis(interval)).await; if check_and_stop(stop) { return false } - if start.elapsed().as_secs() > sleep_for_sec { - return true - } + if sleep_for_ms <= TIMEOUT_STEP { + return true; + } + sleep_for_ms -= TIMEOUT_STEP; + } + } + + async fn wait_queue(in_queue: &AtomicU32, stop: &AtomicU8, max_queue_len: u32) -> bool { + loop { + let in_queue = in_queue.load(Ordering::Relaxed); + if in_queue >= max_queue_len { + log::warn!( + target: TARGET, + "ShardStateDb GC: waiting for queue (current queue length: {})", + in_queue + ); + if !sleep_nicely(stop, 1000).await { + return false; + } + } else { + return true; + } } } + log::debug!(target: TARGET, "ShardStateDb GC: started worker"); + + let mut to_delete: Vec = vec!(); loop { let run_gc_interval = run_interval_adjustable_sec.load(Ordering::Relaxed) as u64; + if to_delete.len() == 0 { + log::debug!(target: TARGET, "ShardStateDb GC: waiting for {run_gc_interval}sec..."); + if !sleep_nicely(&self.stop, run_gc_interval * 1000).await { + return; + } + } else { + let interval_ms = (run_gc_interval * 1000) / (to_delete.len() + 1) as u64; + while let Some(id) = to_delete.pop() { + if !wait_queue(&self.in_queue, &self.stop, self.max_queue_len).await { + return; + } + let in_queue = self.in_queue.fetch_add(1, Ordering::Relaxed) + 1; - if !sleep_nicely(&self.stop, run_gc_interval).await { - return; + let now = std::time::Instant::now(); + let callback = SsNotificationCallback::new(); + if let Err(e) = self.storer.send((Job::DeleteState(id.clone()), Some(callback.clone()))) { + log::error!( + target: TARGET, + "Can't send state to delete from db, id {}", + e.0.0.block_id() + ); + } else { + #[cfg(feature = "telemetry")] + self.telemetry.shardstates_queue.update(std::cmp::max(0, in_queue) as u64); + log::trace!(target: TARGET, "ShardStateDb GC: in_queue {}", in_queue); + + callback.wait().await; + let elapsed = now.elapsed().as_millis() as u64; + if elapsed > interval_ms { + log::warn!( + target: TARGET, + "ShardStateDb GC: deleting state {} was slower then given \ + interval, TIME {} ms, slot {} ms", + id, elapsed, interval_ms, + ); + } else { + if !sleep_nicely(&self.stop, interval_ms - elapsed).await { + return; + } + } + } + } } - let mut to_delete = vec!(); + log::debug!(target: TARGET, "ShardStateDb GC: collecting states to delete"); + + let mut kept = 0; self.shardstate_db.for_each(&mut |_key, value| { if check_and_stop(&self.stop) { return Ok(false); @@ -189,6 +297,7 @@ impl ShardStateDb { to_delete.push(entry.block_id); }, Ok(false) => { + kept += 1; log::debug!( target: TARGET, "ShardStateDb GC: keep id {}", entry.block_id); }, @@ -201,22 +310,16 @@ impl ShardStateDb { Ok(true) }).expect("Can't return error"); - for id in to_delete { - if check_and_stop(&self.stop) { - return; - } - tokio::task::yield_now().await; - if let Err(e) = self.storer.send((Job::DeleteState(id), None)) { - log::error!( - target: TARGET, - "Can't send state to delete from db, id {}", - e.0.0.block_id() - ); - } else { - let in_queue = self.in_queue.fetch_add(1, Ordering::Relaxed) + 1; - log::trace!("ShardStateDb GC: in_queue {}", in_queue); - } - } + log::debug!( + target: TARGET, + "ShardStateDb GC: collected {} states to delete, kept {}", + to_delete.len(), kept + ); + + // Sort ids by decreasing seqno. This way differences between + // states will be smaller, so each delete operation will be faster + // (last in the vector - the earliest state - will be deleted first) + to_delete.sort_by(|a, b| b.seq_no().cmp(&a.seq_no())); } }); } @@ -225,7 +328,8 @@ impl ShardStateDb { self.stop.fetch_or(Self::MASK_STOPPED, Ordering::Relaxed); loop { tokio::time::sleep(Duration::from_secs(1)).await; - if !self.is_gc_run() { + if !self.is_run() { + tokio::time::sleep(Duration::from_secs(1)).await; break; } } @@ -235,11 +339,15 @@ impl ShardStateDb { self.stop.load(Ordering::Relaxed) & Self::MASK_GC_STARTED != 0 } + fn is_run(&self) -> bool { + self.stop.load(Ordering::Relaxed) & (Self::MASK_GC_STARTED | Self::MASK_WORKER) != 0 + } + pub fn shardstate_db(&self) -> Arc> { Arc::clone(&self.shardstate_db) } - pub fn put( + pub async fn put( &self, id: &BlockIdExt, state_root: Cell, @@ -251,10 +359,28 @@ impl ShardStateDb { "ShardStateDb::put id {} root_cell_id {:x}", id, root_id ); + + loop { + let in_queue = self.in_queue.load(Ordering::Relaxed); + if in_queue >= self.max_queue_len { + log::warn!( + target: TARGET, + "ShardStateDb::put id {} root_cell_id {:x} waiting for queue (current queue length: {})", + id, root_id, in_queue + ); + tokio::time::sleep(Duration::from_secs(1)).await; + } else { + break; + } + } + + let in_queue = self.in_queue.fetch_add(1, Ordering::Relaxed) + 1; + self.storer.send((Job::PutState(state_root, id.clone()), callback)) .map_err(|_| error!("Can't send state to put into db, id {}, root {}", id, root_id))?; - let in_queue = self.in_queue.fetch_add(1, Ordering::Relaxed) + 1; + #[cfg(feature = "telemetry")] + self.telemetry.shardstates_queue.update(std::cmp::max(0, in_queue) as u64); log::trace!("ShardStateDb put: in_queue {}", in_queue); Ok(()) @@ -290,6 +416,7 @@ impl ShardStateDb { self.db.clone(), self.dynamic_boc_db.clone(), format!("{:x}", root_cell_id), + self.pss_slowdown_mcs.clone(), )?) } @@ -297,7 +424,40 @@ impl ShardStateDb { self: Arc, mut receiver: tokio::sync::mpsc::UnboundedReceiver<(Job, Option>)> ) { - let check_stop = || self.stop.load(Ordering::Relaxed) & Self::MASK_STOPPED != 0; + self.stop.fetch_or(Self::MASK_WORKER, Ordering::Relaxed); + + let check_stop = || { + if self.stop.load(Ordering::Relaxed) & Self::MASK_STOPPED != 0 { + self.stop.fetch_and(!ShardStateDb::MASK_WORKER, Ordering::Relaxed); + true + } else { + false + } + }; + let mut cells_counters = Some(fnv::FnvHashMap::default()); + + let ss_db = Arc::clone(&self); + + if self.full_filled_counters { + let now = std::time::Instant::now(); + if let Err(e) = tokio::task::block_in_place(|| { + let check_stop = || { + if ss_db.stop.load(Ordering::Relaxed) & Self::MASK_STOPPED != 0 { + fail!("Stopped") + } else { + Ok(()) + } + }; + ss_db.dynamic_boc_db.fill_counters(&check_stop, cells_counters.as_mut().unwrap()) + }) { + log::error!("ShardStateDb worker: fill_counters error {}", e); + } + log::info!( + "ShardStateDb worker: fill_counters TIME {}ms counters {}", + now.elapsed().as_millis(), cells_counters.as_ref().unwrap().len() + ); + } + loop { if check_stop() { return; @@ -306,11 +466,19 @@ impl ShardStateDb { Ok(Some((job, callback))) => { let in_queue = self.in_queue.fetch_sub(1, Ordering::Relaxed) - 1; - log::debug!("ShardStateDb worker: in_queue {}", in_queue); + #[cfg(feature = "telemetry")] { + self.telemetry.shardstates_queue.update(std::cmp::max(0, in_queue) as u64); + self.telemetry.cells_counters.update(cells_counters.as_ref().unwrap().len() as u64); + } + let slowdown = self.max_pss_slowdown_mcs * (in_queue / self.max_queue_len); + self.pss_slowdown_mcs.store(slowdown, Ordering::Relaxed); + log::debug!("ShardStateDb worker: in_queue {}, pss slowdown {}mcs", in_queue, slowdown); match &job { Job::PutState(cell, id) => { - let ok = if let Err(e) = self.clone().put_internal(id, cell.clone()).await { + let ok = if let Err(e) = self.clone().put_internal(id, cell.clone(), + &mut cells_counters, self.full_filled_counters) + { if check_stop() { return; } @@ -326,7 +494,9 @@ impl ShardStateDb { } } Job::DeleteState(id) => { - let ok = if let Err(e) = self.clone().delete_internal(id).await { + let ok = if let Err(e) = self.clone().delete_internal(id, + &mut cells_counters, self.full_filled_counters) + { if check_stop() { return; } @@ -348,7 +518,13 @@ impl ShardStateDb { } } - pub async fn put_internal(self: Arc, id: &BlockIdExt, state_root: Cell) -> Result<()> { + pub fn put_internal( + self: Arc, + id: &BlockIdExt, + state_root: Cell, + cells_counters: &mut Option>, + full_filled_cells: bool, + ) -> Result<()> { let cell_id = UInt256::from(state_root.repr_hash()); log::debug!( @@ -367,7 +543,7 @@ impl ShardStateDb { } let ss_db = self.clone(); - tokio::task::spawn_blocking(move || { + tokio::task::block_in_place(|| { let check_stop = || { if ss_db.stop.load(Ordering::Relaxed) & Self::MASK_STOPPED != 0 { fail!("Stopped") @@ -375,8 +551,8 @@ impl ShardStateDb { Ok(()) } }; - ss_db.dynamic_boc_db.save_boc(state_root, true, &check_stop) - }).await??; + ss_db.dynamic_boc_db.save_boc(state_root, true, &check_stop, cells_counters, full_filled_cells) + })?; let save_utime = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); let db_entry = DbEntry::with_params(id.clone(), cell_id.clone(), save_utime); @@ -394,13 +570,18 @@ impl ShardStateDb { Ok(()) } - pub async fn delete_internal(self: Arc, id: &BlockIdExt) -> Result<()> { + pub fn delete_internal( + self: Arc, + id: &BlockIdExt, + cells_counters: &mut Option>, + full_filled_cells: bool, + ) -> Result<()> { log::debug!(target: TARGET, "ShardStateDb::delete_internal id {}", id); let db_entry = DbEntry::from_slice(&self.shardstate_db.get(id)?)?; let ss_db = self.clone(); - tokio::task::spawn_blocking(move || { + tokio::task::block_in_place(|| { let check_stop = || { if ss_db.stop.load(Ordering::Relaxed) & Self::MASK_STOPPED != 0 { fail!("Stopped") @@ -408,8 +589,8 @@ impl ShardStateDb { Ok(()) } }; - ss_db.dynamic_boc_db.delete_boc(&db_entry.cell_id, &check_stop) - }).await??; + ss_db.dynamic_boc_db.delete_boc(&db_entry.cell_id, &check_stop, cells_counters, full_filled_cells) + })?; self.shardstate_db.delete(id)?; diff --git a/storage/src/types/storage_cell.rs b/storage/src/types/storage_cell.rs index b10bc86e..1a5e5f58 100644 --- a/storage/src/types/storage_cell.rs +++ b/storage/src/types/storage_cell.rs @@ -19,12 +19,8 @@ use ton_types::{ MAX_LEVEL, MAX_REFERENCES_COUNT }; -#[cfg(not(feature = "ref_count_gc"))] -use crate::dynamic_boc_db::DynamicBocDb; -#[cfg(feature = "ref_count_gc")] use ton_types::fail; -#[cfg(feature = "ref_count_gc")] use crate::dynamic_boc_rc_db::DynamicBocDb; pub struct StorageCell { @@ -33,9 +29,6 @@ pub struct StorageCell { boc_db: Arc, tree_bits_count: u64, tree_cell_count: u64, - #[cfg(feature = "ref_count_gc")] - parents_count: u32, - #[cfg(feature = "ref_count_gc")] use_cache: bool, } @@ -50,14 +43,17 @@ impl StorageCell { pub fn deserialize( boc_db: Arc, data: &[u8], - #[cfg(feature = "ref_count_gc")] - use_cache: bool - ) -> Result { + use_cache: bool, + with_parents_count: bool, + ) -> Result<(Self, u32)> { debug_assert!(!data.is_empty()); let mut reader = Cursor::new(data); - #[cfg(feature = "ref_count_gc")] - let parents_count = reader.read_le_u32()?; + let parents_count = if with_parents_count { + reader.read_le_u32()? + } else { + 0 + }; let cell_data = CellData::deserialize(&mut reader)?; let references_count = cell_data.references_count(); let mut references = Vec::with_capacity(references_count as usize); @@ -68,37 +64,45 @@ impl StorageCell { let tree_bits_count = reader.read_le_u64()?; let tree_cell_count = reader.read_le_u64()?; + let read = reader.position(); + let data_len = data.len(); + if read != data_len as u64 { + fail!("There is more data after storage cell deserialisation (read: {}, data len: {})", + read, data_len); + } + CELL_COUNT.fetch_add(1, Ordering::Relaxed); boc_db.allocated().storage_cells.fetch_add(1, Ordering::Relaxed); - Ok(Self { + let cell = Self { cell_data, references: RwLock::new(references), boc_db, tree_bits_count, tree_cell_count, - #[cfg(feature = "ref_count_gc")] - parents_count, - #[cfg(feature = "ref_count_gc")] use_cache, - }) + }; + Ok((cell, parents_count)) } pub fn cell_count() -> u64 { CELL_COUNT.load(Ordering::Relaxed) } - #[cfg(feature = "ref_count_gc")] pub fn deserialize_parents_count(data: &[u8]) -> Result { let parents_count = Cursor::new(data).read_le_u32()?; Ok(parents_count) } - pub fn deserialize_references(data: &[u8]) -> Result> { + pub fn deserialize_references( + data: &[u8], + with_parents_count: bool, + ) -> Result> { debug_assert!(!data.is_empty()); let mut reader = Cursor::new(data); - #[cfg(feature = "ref_count_gc")] - let _parents_count = reader.read_le_u32()?; + if with_parents_count { + let _parents_count = reader.read_le_u32()?; + } let cell_data = CellData::deserialize(&mut reader)?; let references_count = cell_data.references_count(); let mut references = Vec::with_capacity(references_count); @@ -113,9 +117,6 @@ impl StorageCell { let mut data = Vec::new(); - #[cfg(feature = "ref_count_gc")] - data.write_all(&self.parents_count.to_le_bytes())?; - self.cell_data.serialize(&mut data)?; let references = &self.references.read().expect("Poisoned RwLock"); @@ -132,9 +133,7 @@ impl StorageCell { } pub fn serialize( - cell: &dyn CellImpl, - #[cfg(feature = "ref_count_gc")] - parents_count: u32, + cell: &dyn CellImpl ) -> Result> { let references_count = cell.references_count() as u8; @@ -142,9 +141,6 @@ impl StorageCell { let mut data = Vec::new(); - #[cfg(feature = "ref_count_gc")] - data.write_all(&parents_count.to_le_bytes())?; - cell.cell_data().serialize(&mut data)?; for i in 0..references_count { @@ -161,10 +157,7 @@ impl StorageCell { pub fn with_cell( cell: &dyn CellImpl, - #[cfg(feature = "ref_count_gc")] - parents_count: u32, boc_db: Arc, - #[cfg(feature = "ref_count_gc")] use_cache: bool, ) -> Result { let references_count = cell.references_count(); @@ -181,9 +174,6 @@ impl StorageCell { boc_db, tree_bits_count: cell.tree_bits_count(), tree_cell_count: cell.tree_bits_count(), - #[cfg(feature = "ref_count_gc")] - parents_count, - #[cfg(feature = "ref_count_gc")] use_cache }) } @@ -207,7 +197,6 @@ impl StorageCell { let cell_id = hash; let storage_cell = self.boc_db.load_cell( &cell_id, - #[cfg(feature = "ref_count_gc")] self.use_cache )?; self.references.write().expect("Poisoned RwLock")[index] = Reference::Loaded(Arc::clone(&storage_cell)); @@ -219,28 +208,6 @@ impl StorageCell { self.references.read().expect("Poisoned RwLock")[index].hash() } - #[cfg(feature = "ref_count_gc")] - pub(crate) fn parents_count(&self) -> u32 { - self.parents_count - } - - #[cfg(feature = "ref_count_gc")] - pub(crate) fn inc_parents_count(&mut self) -> Result { - if self.parents_count == u32::MAX { - fail!("Parents count has reached the maximum value"); - } - self.parents_count += 1; - Ok(self.parents_count) - } - - #[cfg(feature = "ref_count_gc")] - pub(crate) fn dec_parents_count(&mut self) -> Result { - if self.parents_count == 0 { - fail!("Parents count has reached zero value"); - } - self.parents_count -= 1; - Ok(self.parents_count) - } } impl CellImpl for StorageCell { diff --git a/validator-session/Cargo.toml b/validator-session/Cargo.toml index 90d6de8e..c94d0750 100644 --- a/validator-session/Cargo.toml +++ b/validator-session/Cargo.toml @@ -17,10 +17,10 @@ metrics-runtime = '0.13' rand = '0.8' sha2 = '0.10' catchain = { path = '../catchain' } -ever-crypto = { git = 'https://github.com/tonlabs/ever-crypto.git', tag = '0.1.91' } +ever-crypto = { git = 'https://github.com/tonlabs/ever-crypto.git', tag = '0.1.101' } storage = { path = '../storage' } -ton_api = { git = 'https://github.com/tonlabs/ever-tl.git', package = 'ton_api', tag = '0.2.178' } -ton_block = { git = 'https://github.com/tonlabs/ever-block.git', tag = '1.9.28' } +ton_api = { git = 'https://github.com/tonlabs/ever-tl.git', package = 'ton_api', tag = '0.2.188' } +ton_block = { git = 'https://github.com/tonlabs/ever-block.git', tag = '1.9.39' } ton_types = { git = 'https://github.com/tonlabs/ever-types.git', tag = '1.12.7' } [features]