Skip to content

Commit a949f65

Browse files
authored
add file and db backend for dump-block feature (#4693)
Signed-off-by: Jean-François <[email protected]>
1 parent 6f9acc3 commit a949f65

File tree

11 files changed

+242
-36
lines changed

11 files changed

+242
-36
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ async-trait = "0.1"
121121
bitvec = "1.0"
122122
blake3 = "=1.5"
123123
bs58 = "=0.5"
124+
cfg-if = "1.0.0"
124125
clap = { version = "4.4", features = ["derive", "cargo"] }
125126
config = "0.13"
126127
console = "0.15"

massa-execution-worker/Cargo.toml

+13-12
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ gas_calibration = [
1616
"massa_final_state/test-exports",
1717
"massa_pos_worker",
1818
"massa_db_worker",
19-
"tempfile"
19+
"tempfile",
2020
]
2121
test-exports = [
2222
"massa_execution_exports/test-exports",
@@ -29,26 +29,26 @@ test-exports = [
2929
"massa_metrics/test-exports",
3030
"massa_metrics/test-exports",
3131
"massa_db_worker",
32-
"tempfile"
32+
"tempfile",
3333
]
3434
benchmarking = [
3535
"massa-sc-runtime/gas_calibration",
3636
"criterion",
3737
"massa_pos_worker",
3838
"massa_db_worker",
39-
"tempfile"
39+
"tempfile",
4040
]
4141
metrics = []
42-
execution-trace = [
43-
"massa_execution_exports/execution-trace",
44-
]
42+
execution-trace = ["massa_execution_exports/execution-trace"]
4543
dump-block = [
4644
"prost",
47-
"massa_execution_exports/dump-block"
48-
]
49-
execution-info = [
50-
"execution-trace"
45+
"massa_execution_exports/dump-block",
46+
"db_storage_backend",
5147
]
48+
db_storage_backend = []
49+
file_storage_backend = []
50+
51+
execution-info = ["execution-trace"]
5252

5353
[dependencies]
5454
anyhow = { workspace = true }
@@ -90,7 +90,8 @@ massa_wallet = { workspace = true }
9090
massa-proto-rs = { workspace = true }
9191
schnellru = { workspace = true }
9292
prost = { version = "=0.12", optional = true }
93-
cfg-if = "1.0.0"
93+
cfg-if = { workspace = true }
94+
rocksdb = { workspace = true }
9495

9596
[dev-dependencies]
9697
massa_storage = { workspace = true }
@@ -104,7 +105,7 @@ massa_wallet = { workspace = true, features = ["test-exports"] }
104105
massa_metrics = { workspace = true, features = ["test-exports"] }
105106
massa_db_worker = { workspace = true }
106107
tempfile = { workspace = true }
107-
massa_test_framework = {workspace = true, "features" = ["test-exports"]}
108+
massa_test_framework = { workspace = true, "features" = ["test-exports"] }
108109
tokio = { workspace = true, features = ["sync"] }
109110
hex-literal = { workspace = true }
110111
mockall = { workspace = true }

massa-execution-worker/src/execution.rs

+12-13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::active_history::{ActiveHistory, HistorySearchResult};
1212
use crate::context::{ExecutionContext, ExecutionContextSnapshot};
1313
use crate::interface_impl::InterfaceImpl;
1414
use crate::stats::ExecutionStatsCounter;
15+
#[cfg(feature = "dump-block")]
16+
use crate::storage_backend::StorageBackend;
1517
use massa_async_pool::AsyncMessage;
1618
use massa_execution_exports::{
1719
EventStore, ExecutedBlockInfo, ExecutionBlockMetadata, ExecutionChannels, ExecutionConfig,
@@ -70,8 +72,6 @@ use massa_models::secure_share::SecureShare;
7072
use massa_proto_rs::massa::model::v1 as grpc_model;
7173
#[cfg(feature = "dump-block")]
7274
use prost::Message;
73-
#[cfg(feature = "dump-block")]
74-
use std::io::Write;
7575

7676
/// Used to acquire a lock on the execution context
7777
macro_rules! context_guard {
@@ -134,6 +134,8 @@ pub(crate) struct ExecutionState {
134134
pub(crate) trace_history: Arc<RwLock<TraceHistory>>,
135135
#[cfg(feature = "execution-info")]
136136
pub(crate) execution_info: Arc<RwLock<ExecutionInfo>>,
137+
#[cfg(feature = "dump-block")]
138+
block_storage_backend: Arc<RwLock<dyn StorageBackend>>,
137139
}
138140

139141
impl ExecutionState {
@@ -145,6 +147,7 @@ impl ExecutionState {
145147
///
146148
/// # returns
147149
/// A new `ExecutionState`
150+
#[allow(clippy::too_many_arguments)]
148151
pub fn new(
149152
config: ExecutionConfig,
150153
final_state: Arc<RwLock<dyn FinalStateController>>,
@@ -153,6 +156,7 @@ impl ExecutionState {
153156
channels: ExecutionChannels,
154157
wallet: Arc<RwLock<Wallet>>,
155158
massa_metrics: MassaMetrics,
159+
#[cfg(feature = "dump-block")] block_storage_backend: Arc<RwLock<dyn StorageBackend>>,
156160
) -> ExecutionState {
157161
// Get the slot at the output of which the final state is attached.
158162
// This should be among the latest final slots.
@@ -225,6 +229,8 @@ impl ExecutionState {
225229
config.max_execution_traces_slot_limit as u32,
226230
))),
227231
config,
232+
#[cfg(feature = "dump-block")]
233+
block_storage_backend,
228234
}
229235
}
230236

@@ -349,15 +355,6 @@ impl ExecutionState {
349355

350356
#[cfg(feature = "dump-block")]
351357
{
352-
let block_folder = &self.config.block_dump_folder_path;
353-
let block_file_path = block_folder.join(format!(
354-
"block_slot_{}_{}.bin",
355-
exec_out.slot.thread, exec_out.slot.period
356-
));
357-
358-
let mut fs = std::fs::File::create(block_file_path.clone())
359-
.unwrap_or_else(|_| panic!("Cannot create file: {:?}", block_file_path));
360-
361358
let mut block_ser = vec![];
362359
if let Some(block_info) = exec_out.block_info {
363360
let block_id = block_info.block_id;
@@ -390,8 +387,10 @@ impl ExecutionState {
390387
let grpc_filled_block = grpc_model::FilledBlock::from(filled_block);
391388
grpc_filled_block.encode(&mut block_ser).unwrap();
392389
}
393-
fs.write_all(&block_ser[..])
394-
.expect("Unable to write block to disk");
390+
391+
self.block_storage_backend
392+
.write()
393+
.write(&exec_out.slot, &block_ser);
395394
}
396395
}
397396

massa-execution-worker/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ mod speculative_executed_ops;
9292
mod speculative_ledger;
9393
mod speculative_roll_state;
9494
mod stats;
95+
/// Provide abstraction and implementations of a storage backend for the the
96+
/// dump-block feature
97+
pub mod storage_backend;
9598
mod worker;
9699

97100
#[cfg(feature = "execution-trace")]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use std::{
2+
fs::File,
3+
io::{Read, Write},
4+
path::PathBuf,
5+
};
6+
7+
use massa_models::slot::Slot;
8+
use rocksdb::{DBCompressionType, Options};
9+
10+
/// A trait that defines the interface for a storage backend for the dump-block
11+
/// feature.
12+
pub trait StorageBackend: Send + Sync {
13+
/// Writes the given value to the storage backend.
14+
/// The slot is used as the key to the value.
15+
fn write(&self, slot: &Slot, value: &[u8]);
16+
17+
/// Reads the value from the storage backend.
18+
/// The slot is used as the key to the value.
19+
fn read(&self, slot: &Slot) -> Option<Vec<u8>>;
20+
}
21+
22+
/// A storage backend that uses the file system as the underlying storage engine.
23+
pub struct FileStorageBackend {
24+
folder: PathBuf,
25+
}
26+
impl FileStorageBackend {
27+
/// Creates a new instance of `FileStorageBackend` with the given path.
28+
pub fn new(path: PathBuf) -> Self {
29+
Self { folder: path }
30+
}
31+
}
32+
33+
impl StorageBackend for FileStorageBackend {
34+
fn write(&self, slot: &Slot, value: &[u8]) {
35+
let block_file_path = self
36+
.folder
37+
.join(format!("block_slot_{}_{}.bin", slot.thread, slot.period));
38+
39+
let mut file = File::create(block_file_path.clone())
40+
.unwrap_or_else(|_| panic!("Cannot create file: {:?}", block_file_path));
41+
42+
file.write_all(value).expect("Unable to write to disk");
43+
}
44+
45+
fn read(&self, slot: &Slot) -> Option<Vec<u8>> {
46+
let block_file_path = self
47+
.folder
48+
.join(format!("block_slot_{}_{}.bin", slot.thread, slot.period));
49+
50+
let file = File::open(block_file_path.clone())
51+
.unwrap_or_else(|_| panic!("Cannot open file: {:?}", block_file_path));
52+
let mut reader = std::io::BufReader::new(file);
53+
let mut buffer = Vec::new();
54+
reader
55+
.read_to_end(&mut buffer)
56+
.expect("Unable to read from disk");
57+
58+
Some(buffer)
59+
}
60+
}
61+
62+
/// A storage backend that uses RocksDB as the underlying storage engine.
63+
pub struct RocksDBStorageBackend {
64+
db: rocksdb::DB,
65+
}
66+
67+
impl RocksDBStorageBackend {
68+
/// Creates a new instance of `RocksDBStorageBackend` with the given path.
69+
pub fn new(path: PathBuf) -> Self {
70+
let mut opts = Options::default();
71+
opts.create_if_missing(true);
72+
opts.set_compression_type(DBCompressionType::Lz4);
73+
74+
let db = rocksdb::DB::open(&opts, path.clone())
75+
.unwrap_or_else(|_| panic!("Failed to create storage db at {:?}", path));
76+
77+
Self { db }
78+
}
79+
}
80+
81+
impl StorageBackend for RocksDBStorageBackend {
82+
fn write(&self, slot: &Slot, value: &[u8]) {
83+
self.db
84+
.put(slot.to_bytes_key(), value)
85+
.expect("Unable to write block to db");
86+
}
87+
88+
fn read(&self, slot: &Slot) -> Option<Vec<u8>> {
89+
match self.db.get(slot.to_bytes_key()) {
90+
Ok(val) => val,
91+
Err(e) => {
92+
println!("Error: {} reading key {:?}", e, slot.to_bytes_key());
93+
None
94+
}
95+
}
96+
}
97+
}
98+
99+
#[cfg(test)]
100+
mod tests {
101+
use super::*;
102+
103+
#[test]
104+
fn test_file_storage_backend() {
105+
let slot = Slot {
106+
thread: 1,
107+
period: 1,
108+
};
109+
let value = vec![1, 2, 3];
110+
111+
let storage = FileStorageBackend::new(PathBuf::from(""));
112+
storage.write(&slot, &value);
113+
114+
let storage = FileStorageBackend::new(PathBuf::from(""));
115+
let data = storage.read(&slot);
116+
assert_eq!(data, Some(value));
117+
}
118+
119+
#[test]
120+
fn test_rocksdb_storage_backend() {
121+
let slot = Slot {
122+
thread: 1,
123+
period: 1,
124+
};
125+
let value = vec![1, 2, 3];
126+
127+
let storage = RocksDBStorageBackend::new(PathBuf::from("test_db"));
128+
storage.write(&slot, &value);
129+
drop(storage);
130+
131+
let storage = RocksDBStorageBackend::new(PathBuf::from("test_db"));
132+
let data = storage.read(&slot);
133+
assert_eq!(data, Some(value));
134+
}
135+
}

massa-execution-worker/src/tests/scenarios_mandatories.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -2852,6 +2852,8 @@ fn execution_trace_nested() {
28522852
#[cfg(feature = "dump-block")]
28532853
#[test]
28542854
fn test_dump_block() {
2855+
use crate::storage_backend::StorageBackend;
2856+
28552857
// setup the period duration
28562858
let exec_cfg = ExecutionConfig::default();
28572859
let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks();
@@ -2931,13 +2933,20 @@ fn test_dump_block() {
29312933

29322934
std::thread::sleep(Duration::from_secs(1));
29332935

2936+
// if the the storage backend for the dump-block feature is a rocksdb, this
2937+
// is mandatory (the db must be closed before we can reopen it to ckeck the
2938+
// data)
2939+
drop(universe);
2940+
29342941
let block_folder = &exec_cfg.block_dump_folder_path;
2935-
let block_file_path = block_folder.join(format!(
2936-
"block_slot_{}_{}.bin",
2937-
block_slot.thread, block_slot.period
2938-
));
2942+
#[cfg(feature = "file_storage_backend")]
2943+
let storage_backend = crate::storage_backend::FileStorageBackend::new(block_folder.to_owned());
2944+
2945+
#[cfg(feature = "db_storage_backend")]
2946+
let storage_backend =
2947+
crate::storage_backend::RocksDBStorageBackend::new(block_folder.to_owned());
29392948

2940-
let block_content = std::fs::read(block_file_path).expect("Unable to read block dump");
2949+
let block_content = storage_backend.read(&block_slot).unwrap();
29412950
let filled_block = FilledBlock::decode(&mut Cursor::new(block_content)).unwrap();
29422951
let header_content = filled_block.header.unwrap().content.unwrap();
29432952
let header_slot = header_content.slot.unwrap();

massa-execution-worker/src/tests/universe.rs

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ use std::{
44
sync::Arc,
55
};
66

7+
#[cfg(feature = "file_storage_backend")]
8+
use crate::storage_backend::FileStorageBackend;
9+
#[cfg(feature = "db_storage_backend")]
10+
use crate::storage_backend::RocksDBStorageBackend;
711
use massa_db_exports::{MassaDBConfig, MassaDBController, ShareableMassaDBController};
812
use massa_db_worker::MassaDB;
913
use massa_execution_exports::{
@@ -117,6 +121,14 @@ impl TestUniverse for ExecutionTestUniverse {
117121
std::time::Duration::from_secs(5),
118122
)
119123
.0,
124+
#[cfg(feature = "file_storage_backend")]
125+
Arc::new(RwLock::new(FileStorageBackend::new(
126+
config.block_dump_folder_path.clone(),
127+
))),
128+
#[cfg(feature = "db_storage_backend")]
129+
Arc::new(RwLock::new(RocksDBStorageBackend::new(
130+
config.block_dump_folder_path.clone(),
131+
))),
120132
);
121133
init_execution_worker(&config, &storage, module_controller.clone());
122134
let universe = Self {

0 commit comments

Comments
 (0)