Skip to content

Commit e2aa66c

Browse files
committed
Fix bad handling of interval when computing range to compute block range root
* This computation forgot to handle the case when the last stored transaction is included at the end of the last stored block range root. When this case was seen it yielded an error instead of saying that there was no work to be done. record it derives from. This allow to write simpler tests.
1 parent 2bcb1a7 commit e2aa66c

File tree

4 files changed

+246
-184
lines changed

4 files changed

+246
-184
lines changed

mithril-aggregator/src/database/record/interval_without_block_range_root.rs

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,45 @@
1+
use std::ops::Range;
2+
3+
use anyhow::anyhow;
14
use sqlite::Row;
25

36
use mithril_common::entities::BlockNumber;
7+
use mithril_common::StdResult;
48
use mithril_persistence::sqlite::{HydrationError, Projection, SqLiteEntity};
59

610
use crate::database::record::hydrator::try_to_u64;
711

812
/// Interval of block numbers (`[start, end[`) without block ranges root.
913
pub struct IntervalWithoutBlockRangeRootRecord {
10-
/// Start of the interval
14+
/// Start of the interval, fetched from the last block range root end
1115
pub start: Option<BlockNumber>,
12-
/// End of the interval
16+
/// End of the interval, fetched from the last transaction block number
1317
pub end: Option<BlockNumber>,
1418
}
1519

20+
impl IntervalWithoutBlockRangeRootRecord {
21+
/// Deduce from self the range of blocks that are available to compute block range roots.
22+
pub fn to_range(&self) -> StdResult<Option<Range<BlockNumber>>> {
23+
match (self.start, self.end) {
24+
// No transactions stored - nothing can be computed
25+
(_, None) => Ok(None),
26+
// Transactions stored but no block range root - everything can be computed
27+
(None, Some(end)) => Ok(Some(0..(end + 1))),
28+
// Last stored transactions is at the end of the last block range - nothing to compute
29+
(Some(start), Some(end)) if (end + 1) == start => Ok(None),
30+
(Some(start), Some(end)) if end < start => {
31+
Err(anyhow!(
32+
"Inconsistent state: \
33+
Last block range root block number ({start}) is higher than the last transaction block number ({end}). \
34+
Did you forgot to prune obsolete `block_range_root` after a transaction rollback ?"
35+
))
36+
}
37+
// Nominal case
38+
(Some(start), Some(end)) => Ok(Some(start..(end + 1))),
39+
}
40+
}
41+
}
42+
1643
impl SqLiteEntity for IntervalWithoutBlockRangeRootRecord {
1744
fn hydrate(row: Row) -> Result<Self, HydrationError>
1845
where
@@ -37,3 +64,80 @@ impl SqLiteEntity for IntervalWithoutBlockRangeRootRecord {
3764
])
3865
}
3966
}
67+
68+
#[cfg(test)]
69+
mod tests {
70+
use super::*;
71+
72+
#[test]
73+
fn db_without_block_range_nor_transactions_yield_none() {
74+
let range = IntervalWithoutBlockRangeRootRecord {
75+
start: None,
76+
end: None,
77+
}
78+
.to_range()
79+
.unwrap();
80+
assert_eq!(None, range);
81+
}
82+
83+
#[test]
84+
fn db_with_only_transactions_yield_a_range() {
85+
let range = IntervalWithoutBlockRangeRootRecord {
86+
start: None,
87+
end: Some(10),
88+
}
89+
.to_range()
90+
.unwrap();
91+
assert_eq!(Some(0..11), range);
92+
}
93+
94+
#[test]
95+
fn db_with_only_block_range_and_no_transactions_yield_none() {
96+
let range = IntervalWithoutBlockRangeRootRecord {
97+
start: Some(10),
98+
end: None,
99+
}
100+
.to_range()
101+
.unwrap();
102+
assert_eq!(None, range);
103+
}
104+
105+
#[test]
106+
fn db_with_both_block_range_and_transactions_yield_a_range() {
107+
let range = IntervalWithoutBlockRangeRootRecord {
108+
start: Some(2),
109+
end: Some(10),
110+
}
111+
.to_range()
112+
.unwrap();
113+
assert_eq!(Some(2..11), range);
114+
}
115+
116+
#[test]
117+
fn db_with_last_transaction_block_number_right_at_the_end_of_the_last_block_range_yield_none() {
118+
// Reminder: the end of the block range is exclusive
119+
let range = IntervalWithoutBlockRangeRootRecord {
120+
start: Some(10),
121+
end: Some(9),
122+
}
123+
.to_range()
124+
.unwrap();
125+
assert_eq!(None, range);
126+
}
127+
128+
#[test]
129+
fn db_with_last_transaction_block_number_below_the_end_of_the_last_block_range_yield_an_error()
130+
{
131+
// Reminder: the end of the block range is exclusive
132+
let res = IntervalWithoutBlockRangeRootRecord {
133+
start: Some(10),
134+
end: Some(8),
135+
}
136+
.to_range()
137+
.unwrap_err();
138+
assert!(
139+
format!("{res:?}").contains("Inconsistent state"),
140+
"Expected 'Inconsistent state' error, got {res:?}",
141+
);
142+
}
143+
}

mithril-aggregator/src/database/repository/cardano_transaction_repository.rs

Lines changed: 17 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::ops::Range;
22
use std::sync::Arc;
33

4-
use anyhow::{anyhow, Context};
4+
use anyhow::Context;
55
use async_trait::async_trait;
66

77
use mithril_common::crypto_helper::MKTreeNode;
@@ -208,18 +208,7 @@ impl TransactionStore for CardanoTransactionRepository {
208208
match row {
209209
// Should be impossible - the request as written in the provider always returns a single row
210210
None => panic!("IntervalWithoutBlockRangeProvider should always return a single row"),
211-
Some(interval) => match (interval.start, interval.end) {
212-
(_, None) => Ok(None),
213-
(None, Some(end)) => Ok(Some(0..(end + 1))),
214-
(Some(start), Some(end)) if end < start => {
215-
Err(anyhow!(
216-
"Inconsistent state: \
217-
Last block range root block number ({start}) is higher than the last transaction block number ({end}). \
218-
Did you forgot to prune obsolete `block_range_root` after a transaction rollback ?"
219-
))
220-
}
221-
(Some(start), Some(end)) => Ok(Some(start..(end + 1))),
222-
},
211+
Some(interval) => interval.to_range(),
223212
}
224213
}
225214

@@ -573,14 +562,15 @@ mod tests {
573562
let connection = Arc::new(cardano_tx_db_connection().unwrap());
574563
let repository = CardanoTransactionRepository::new(connection);
575564

576-
{
577-
let interval = repository
578-
.get_block_interval_without_block_range_root()
579-
.await
580-
.unwrap();
581-
582-
assert_eq!(None, interval);
583-
}
565+
// The last block range give the lower bound
566+
let last_block_range = BlockRange::from_block_number(0);
567+
repository
568+
.store_block_range_roots(vec![(
569+
last_block_range.clone(),
570+
MKTreeNode::from_hex("AAAA").unwrap(),
571+
)])
572+
.await
573+
.unwrap();
584574

585575
// The last transaction block number give the upper bound
586576
let last_transaction_block_number = BlockRange::LENGTH * 4;
@@ -589,78 +579,15 @@ mod tests {
589579
.await
590580
.unwrap();
591581

592-
{
593-
let interval = repository
594-
.get_block_interval_without_block_range_root()
595-
.await
596-
.unwrap();
597-
598-
assert_eq!(Some(0..(last_transaction_block_number + 1)), interval);
599-
}
600-
{
601-
// The last block range give the lower bound
602-
let last_block_range = BlockRange::from_block_number(0);
603-
repository
604-
.store_block_range_roots(vec![(
605-
last_block_range.clone(),
606-
MKTreeNode::from_hex("AAAA").unwrap(),
607-
)])
608-
.await
609-
.unwrap();
610-
611-
let interval = repository
612-
.get_block_interval_without_block_range_root()
613-
.await
614-
.unwrap();
615-
616-
assert_eq!(
617-
Some(last_block_range.end..(last_transaction_block_number + 1)),
618-
interval
619-
);
620-
}
621-
}
622-
623-
#[tokio::test]
624-
async fn repository_get_block_interval_without_block_range_root_when_last_block_range_higher_than_stored_transactions(
625-
) {
626-
let connection = Arc::new(cardano_tx_db_connection().unwrap());
627-
let repository = CardanoTransactionRepository::new(connection);
628-
629-
let last_block_range = BlockRange::from_block_number(BlockRange::LENGTH * 10);
630-
repository
631-
.store_block_range_roots(vec![(
632-
last_block_range.clone(),
633-
MKTreeNode::from_hex("AAAA").unwrap(),
634-
)])
582+
let interval = repository
583+
.get_block_interval_without_block_range_root()
635584
.await
636585
.unwrap();
637586

638-
// Only block range in db
639-
{
640-
let interval = repository
641-
.get_block_interval_without_block_range_root()
642-
.await
643-
.unwrap();
644-
645-
assert_eq!(None, interval);
646-
}
647-
// Inconsistent state: Highest transaction block number is below the last computed block range
648-
{
649-
let last_transaction_block_number = last_block_range.end - 10;
650-
repository
651-
.create_transaction("tx-1", last_transaction_block_number, 50, "block-1", 99)
652-
.await
653-
.unwrap();
654-
655-
let res = repository
656-
.get_block_interval_without_block_range_root()
657-
.await;
658-
659-
assert!(
660-
res.is_err(),
661-
"Inconsistent state should raise an error, found:\n{res:?}"
662-
);
663-
}
587+
assert_eq!(
588+
Some(last_block_range.end..(last_transaction_block_number + 1)),
589+
interval
590+
);
664591
}
665592

666593
#[tokio::test]

mithril-signer/src/database/record/interval_without_block_range_root.rs

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,45 @@
1+
use std::ops::Range;
2+
3+
use anyhow::anyhow;
14
use sqlite::Row;
25

36
use mithril_common::entities::BlockNumber;
7+
use mithril_common::StdResult;
48
use mithril_persistence::sqlite::{HydrationError, Projection, SqLiteEntity};
59

610
use crate::database::record::hydrator::try_to_u64;
711

812
/// Interval of block numbers (`[start, end[`) without block ranges root.
913
pub struct IntervalWithoutBlockRangeRootRecord {
10-
/// Start of the interval
14+
/// Start of the interval, fetched from the last block range root end
1115
pub start: Option<BlockNumber>,
12-
/// End of the interval
16+
/// End of the interval, fetched from the last transaction block number
1317
pub end: Option<BlockNumber>,
1418
}
1519

20+
impl IntervalWithoutBlockRangeRootRecord {
21+
/// Deduce from self the range of blocks that are available to compute block range roots.
22+
pub fn to_range(&self) -> StdResult<Option<Range<BlockNumber>>> {
23+
match (self.start, self.end) {
24+
// No transactions stored - nothing can be computed
25+
(_, None) => Ok(None),
26+
// Transactions stored but no block range root - everything can be computed
27+
(None, Some(end)) => Ok(Some(0..(end + 1))),
28+
// Last stored transactions is at the end of the last block range - nothing to compute
29+
(Some(start), Some(end)) if (end + 1) == start => Ok(None),
30+
(Some(start), Some(end)) if end < start => {
31+
Err(anyhow!(
32+
"Inconsistent state: \
33+
Last block range root block number ({start}) is higher than the last transaction block number ({end}). \
34+
Did you forgot to prune obsolete `block_range_root` after a transaction rollback ?"
35+
))
36+
}
37+
// Nominal case
38+
(Some(start), Some(end)) => Ok(Some(start..(end + 1))),
39+
}
40+
}
41+
}
42+
1643
impl SqLiteEntity for IntervalWithoutBlockRangeRootRecord {
1744
fn hydrate(row: Row) -> Result<Self, HydrationError>
1845
where
@@ -37,3 +64,80 @@ impl SqLiteEntity for IntervalWithoutBlockRangeRootRecord {
3764
])
3865
}
3966
}
67+
68+
#[cfg(test)]
69+
mod tests {
70+
use super::*;
71+
72+
#[test]
73+
fn db_without_block_range_nor_transactions_yield_none() {
74+
let range = IntervalWithoutBlockRangeRootRecord {
75+
start: None,
76+
end: None,
77+
}
78+
.to_range()
79+
.unwrap();
80+
assert_eq!(None, range);
81+
}
82+
83+
#[test]
84+
fn db_with_only_transactions_yield_a_range() {
85+
let range = IntervalWithoutBlockRangeRootRecord {
86+
start: None,
87+
end: Some(10),
88+
}
89+
.to_range()
90+
.unwrap();
91+
assert_eq!(Some(0..11), range);
92+
}
93+
94+
#[test]
95+
fn db_with_only_block_range_and_no_transactions_yield_none() {
96+
let range = IntervalWithoutBlockRangeRootRecord {
97+
start: Some(10),
98+
end: None,
99+
}
100+
.to_range()
101+
.unwrap();
102+
assert_eq!(None, range);
103+
}
104+
105+
#[test]
106+
fn db_with_both_block_range_and_transactions_yield_a_range() {
107+
let range = IntervalWithoutBlockRangeRootRecord {
108+
start: Some(2),
109+
end: Some(10),
110+
}
111+
.to_range()
112+
.unwrap();
113+
assert_eq!(Some(2..11), range);
114+
}
115+
116+
#[test]
117+
fn db_with_last_transaction_block_number_right_at_the_end_of_the_last_block_range_yield_none() {
118+
// Reminder: the end of the block range is exclusive
119+
let range = IntervalWithoutBlockRangeRootRecord {
120+
start: Some(10),
121+
end: Some(9),
122+
}
123+
.to_range()
124+
.unwrap();
125+
assert_eq!(None, range);
126+
}
127+
128+
#[test]
129+
fn db_with_last_transaction_block_number_below_the_end_of_the_last_block_range_yield_an_error()
130+
{
131+
// Reminder: the end of the block range is exclusive
132+
let res = IntervalWithoutBlockRangeRootRecord {
133+
start: Some(10),
134+
end: Some(8),
135+
}
136+
.to_range()
137+
.unwrap_err();
138+
assert!(
139+
format!("{res:?}").contains("Inconsistent state"),
140+
"Expected 'Inconsistent state' error, got {res:?}",
141+
);
142+
}
143+
}

0 commit comments

Comments
 (0)