Skip to content

Commit efb9677

Browse files
authored
Merge pull request #1566 from input-output-hk/ensemble/handle-pallas-hardano-parsing-error
Handle `pallas-hardano` parsing errors
2 parents 5dad4a6 + 432ba68 commit efb9677

File tree

14 files changed

+119
-40
lines changed

14 files changed

+119
-40
lines changed

Cargo.lock

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

mithril-aggregator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-aggregator"
3-
version = "0.4.46"
3+
version = "0.4.47"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-aggregator/src/dependency_injection/builder.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,10 @@ impl DependenciesBuilder {
746746
}
747747

748748
async fn build_transaction_parser(&mut self) -> Result<Arc<dyn TransactionParser>> {
749-
let transaction_parser = CardanoTransactionParser::default();
749+
// TODO: 'allow_unparsable_block' parameter should be configurable and its default value set to false
750+
let allow_unparsable_block = true;
751+
let transaction_parser =
752+
CardanoTransactionParser::new(self.get_logger().await?, allow_unparsable_block);
750753

751754
Ok(Arc::new(transaction_parser))
752755
}

mithril-common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-common"
3-
version = "0.3.17"
3+
version = "0.3.18"
44
description = "Common types, interfaces, and utilities for Mithril nodes."
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-common/src/cardano_transaction_parser.rs

Lines changed: 98 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use anyhow::{anyhow, Context};
88
use async_trait::async_trait;
99
use pallas_hardano::storage::immutable::chunk::{read_blocks, Reader};
1010
use pallas_traverse::MultiEraBlock;
11+
use slog::{error, Logger};
1112
use std::path::Path;
1213
use tokio::sync::RwLock;
1314

@@ -87,35 +88,43 @@ struct Block {
8788
}
8889

8990
impl Block {
90-
fn try_convert(
91-
multi_era_block: MultiEraBlock,
92-
immutable_file_number: ImmutableFileNumber,
93-
) -> StdResult<Self> {
91+
fn convert(multi_era_block: MultiEraBlock, immutable_file_number: ImmutableFileNumber) -> Self {
9492
let mut transactions = Vec::new();
9593
for tx in &multi_era_block.txs() {
9694
transactions.push(tx.hash().to_string());
9795
}
98-
let block = Block {
96+
97+
Block {
9998
block_number: multi_era_block.number(),
10099
immutable_file_number,
101100
transactions,
102-
};
103-
104-
Ok(block)
101+
}
105102
}
106103
}
107104

108105
/// Cardano transaction parser
109-
pub struct CardanoTransactionParser {}
106+
pub struct CardanoTransactionParser {
107+
logger: Logger,
108+
/// When set to true, no error is returned in case of unparsable block, and an error log is written instead.
109+
/// This can occur when the crate 'pallas-hardano' doesn't support some non final encoding for a Cardano era.
110+
/// This situation should only happen on the test networks and not on the mainnet.
111+
allow_unparsable_block: bool,
112+
}
110113

111114
impl CardanoTransactionParser {
112115
/// Factory
113-
pub fn new() -> Self {
114-
Self {}
116+
pub fn new(logger: Logger, allow_unparsable_block: bool) -> Self {
117+
Self {
118+
logger,
119+
allow_unparsable_block,
120+
}
115121
}
116122

117123
/// Read blocks from immutable file
118-
fn read_blocks_from_immutable_file(immutable_file: &ImmutableFile) -> StdResult<Vec<Block>> {
124+
fn read_blocks_from_immutable_file(
125+
&self,
126+
immutable_file: &ImmutableFile,
127+
) -> StdResult<Vec<Block>> {
119128
let cardano_blocks_reader =
120129
CardanoTransactionParser::cardano_blocks_reader(immutable_file)?;
121130

@@ -127,10 +136,19 @@ impl CardanoTransactionParser {
127136
immutable_file.path
128137
)
129138
})?;
130-
blocks.push(CardanoTransactionParser::convert_to_block(
131-
&block,
132-
immutable_file,
133-
)?);
139+
match CardanoTransactionParser::convert_to_block(&block, immutable_file) {
140+
Ok(convert_to_block) => {
141+
blocks.push(convert_to_block);
142+
}
143+
Err(err) if self.allow_unparsable_block => {
144+
error!(
145+
self.logger,
146+
"The cbor encoded block could not be parsed";
147+
"error" => ?err, "immutable_file_number" => immutable_file.number
148+
);
149+
}
150+
Err(e) => return Err(e),
151+
}
134152
}
135153
Ok(blocks)
136154
}
@@ -143,12 +161,7 @@ impl CardanoTransactionParser {
143161
)
144162
})?;
145163

146-
Block::try_convert(multi_era_block, immutable_file.number).with_context(|| {
147-
format!(
148-
"CardanoTransactionParser could not read data from block in immutable file: {:?}",
149-
immutable_file.path
150-
)
151-
})
164+
Ok(Block::convert(multi_era_block, immutable_file.number))
152165
}
153166

154167
fn cardano_blocks_reader(immutable_file: &ImmutableFile) -> StdResult<Reader> {
@@ -169,12 +182,6 @@ impl CardanoTransactionParser {
169182
}
170183
}
171184

172-
impl Default for CardanoTransactionParser {
173-
fn default() -> Self {
174-
Self::new()
175-
}
176-
}
177-
178185
#[async_trait]
179186
impl TransactionParser for CardanoTransactionParser {
180187
async fn parse(&self, dirpath: &Path, beacon: &Beacon) -> StdResult<Vec<CardanoTransaction>> {
@@ -186,8 +193,9 @@ impl TransactionParser for CardanoTransactionParser {
186193
let mut transactions: Vec<CardanoTransaction> = vec![];
187194

188195
for immutable_file in &immutable_chunks {
189-
let blocks =
190-
Self::read_blocks_from_immutable_file(immutable_file).with_context(|| {
196+
let blocks = self
197+
.read_blocks_from_immutable_file(immutable_file)
198+
.with_context(|| {
191199
format!(
192200
"CardanoTransactionParser could read blocks from immutable file: '{}'.",
193201
immutable_file.path.display()
@@ -219,6 +227,11 @@ mod tests {
219227

220228
use super::*;
221229

230+
use crate::test_utils::TempDir;
231+
232+
use slog::Drain;
233+
use std::{fs::File, sync::Arc};
234+
222235
fn get_number_of_immutable_chunk_in_dir(dir: &Path) -> usize {
223236
ImmutableFile::list_completed_in_dir(dir)
224237
.unwrap()
@@ -227,6 +240,15 @@ mod tests {
227240
.len()
228241
}
229242

243+
fn create_file_logger(filepath: &Path) -> slog::Logger {
244+
let writer = File::create(filepath).unwrap();
245+
let decorator = slog_term::PlainDecorator::new(writer);
246+
let drain = slog_term::CompactFormat::new(decorator).build().fuse();
247+
let drain = slog_async::Async::new(drain).build().fuse();
248+
249+
Logger::root(Arc::new(drain), slog::o!())
250+
}
251+
230252
#[tokio::test]
231253
async fn test_parse_expected_number_of_transactions() {
232254
// We known the number of transactions in those prebuilt immutables
@@ -239,7 +261,8 @@ mod tests {
239261
..Beacon::default()
240262
};
241263
let tx_count: usize = immutable_files.iter().map(|(_, count)| *count).sum();
242-
let cardano_transaction_parser = CardanoTransactionParser::new();
264+
let cardano_transaction_parser =
265+
CardanoTransactionParser::new(Logger::root(slog::Discard, slog::o!()), false);
243266

244267
let transactions = cardano_transaction_parser
245268
.parse(db_path, &beacon)
@@ -249,6 +272,48 @@ mod tests {
249272
assert_eq!(transactions.len(), tx_count);
250273
}
251274

275+
#[tokio::test]
276+
async fn test_parse_should_error_with_unparsable_block_format() {
277+
let db_path = Path::new("../mithril-test-lab/test_data/parsing_error/immutable/");
278+
let beacon = Beacon {
279+
immutable_file_number: 4831,
280+
..Beacon::default()
281+
};
282+
let cardano_transaction_parser =
283+
CardanoTransactionParser::new(Logger::root(slog::Discard, slog::o!()), false);
284+
285+
let result = cardano_transaction_parser.parse(db_path, &beacon).await;
286+
287+
assert!(result.is_err());
288+
}
289+
290+
#[tokio::test]
291+
async fn test_parse_should_log_error_with_unparsable_block_format() {
292+
let temp_dir = TempDir::create(
293+
"cardano_transaction_parser",
294+
"test_parse_should_log_error_with_unparsable_block_format",
295+
);
296+
let filepath = temp_dir.join("test.log");
297+
let db_path = Path::new("../mithril-test-lab/test_data/parsing_error/immutable/");
298+
let beacon = Beacon {
299+
immutable_file_number: 4831,
300+
..Beacon::default()
301+
};
302+
// We create a block to drop the logger and force a flush before we read the log file.
303+
{
304+
let cardano_transaction_parser =
305+
CardanoTransactionParser::new(create_file_logger(&filepath), true);
306+
307+
cardano_transaction_parser
308+
.parse(db_path, &beacon)
309+
.await
310+
.unwrap();
311+
}
312+
313+
let log_file = std::fs::read_to_string(&filepath).unwrap();
314+
assert!(log_file.contains("The cbor encoded block could not be parsed"));
315+
}
316+
252317
#[tokio::test]
253318
async fn test_parse_up_to_given_beacon() {
254319
// We known the number of transactions in those prebuilt immutables
@@ -261,7 +326,8 @@ mod tests {
261326
..Beacon::default()
262327
};
263328
let tx_count: usize = immutable_files.iter().map(|(_, count)| *count).sum();
264-
let cardano_transaction_parser = CardanoTransactionParser::new();
329+
let cardano_transaction_parser =
330+
CardanoTransactionParser::new(Logger::root(slog::Discard, slog::o!()), false);
265331

266332
let transactions = cardano_transaction_parser
267333
.parse(db_path, &beacon)

mithril-signer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-signer"
3-
version = "0.2.114"
3+
version = "0.2.115"
44
description = "A Mithril Signer"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-signer/src/runtime/signer_services.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,12 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> {
267267
));
268268
let mithril_stake_distribution_signable_builder =
269269
Arc::new(MithrilStakeDistributionSignableBuilder::default());
270-
let transaction_parser = Arc::new(CardanoTransactionParser::default());
270+
// TODO: 'allow_unparsable_block' parameter should be configurable and its default value set to false
271+
let allow_unparsable_block = true;
272+
let transaction_parser = Arc::new(CardanoTransactionParser::new(
273+
slog_scope::logger(),
274+
allow_unparsable_block,
275+
));
271276
let transaction_store = Arc::new(CardanoTransactionRepository::new(
272277
transaction_sqlite_connection,
273278
));

mithril-test-lab/test_data/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
> [!NOTE]
88
> The immutable files (`.chunk`, `.primary` and `.secondary`) files are data that are the result of
99
> the `mithril-end-to-end` test command execution.
10+
> The `parsing_error/` directory contains the `04831` and `04832` immutable files with some unparsable blocks in the first one (`04831` have been produced by the Sanchonet network, and `04832` created manually).
11+
> They are needed for testing of the Cardano transactions parser.
191 KB
Binary file not shown.
16.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)