Skip to content

Commit f083934

Browse files
authored
Merge pull request #1917 from input-output-hk/jpraynaud/1840-enhance-cardano-transactions-rollbacks
Enhance Cardano transactions rollbacks
2 parents aff35a8 + 9eae92d commit f083934

File tree

11 files changed

+239
-67
lines changed

11 files changed

+239
-67
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ As a minor extension, we have adopted a slightly different versioning convention
2929

3030
- Support infinite preloading of Cardano transactions in signer.
3131

32+
- Fix Cardano transactions rollbacks creating panics in signer and aggregator.
33+
3234
- **UNSTABLE** Cardano stake distribution certification:
3335

3436
- Implement the signable and artifact builders for the signed entity type `CardanoStakeDistribution`.

Cargo.lock

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

internal/mithril-persistence/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-persistence"
3-
version = "0.2.25"
3+
version = "0.2.26"
44
description = "Common types, interfaces, and utilities to persist data for Mithril nodes."
55
authors = { workspace = true }
66
edition = { workspace = true }

internal/mithril-persistence/src/database/query/cardano_transaction/get_cardano_transaction.rs

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ impl GetCardanoTransactionQuery {
7272
Self { condition }
7373
}
7474

75-
pub fn by_slot_number(slot_number: SlotNumber) -> Self {
75+
pub fn with_highest_block_number_below_slot_number(slot_number: SlotNumber) -> Self {
7676
Self {
7777
condition: WhereCondition::new(
78-
"slot_number = ?*",
78+
"block_number = (select max(block_number) from cardano_tx where slot_number <= ?*)",
7979
vec![Value::Integer(*slot_number as i64)],
8080
),
8181
}
@@ -120,6 +120,18 @@ mod tests {
120120
.unwrap();
121121
}
122122

123+
fn transaction_record(
124+
block_number: BlockNumber,
125+
slot_number: SlotNumber,
126+
) -> CardanoTransactionRecord {
127+
CardanoTransactionRecord::new(
128+
format!("tx-hash-{}", slot_number),
129+
block_number,
130+
slot_number,
131+
format!("block-hash-{}", block_number),
132+
)
133+
}
134+
123135
#[test]
124136
fn with_highest_block_number() {
125137
let connection = cardano_tx_db_connection().unwrap();
@@ -132,30 +144,10 @@ mod tests {
132144
insert_transactions(
133145
&connection,
134146
vec![
135-
CardanoTransactionRecord::new(
136-
"tx-hash-0",
137-
BlockNumber(10),
138-
SlotNumber(50),
139-
"block-hash-10",
140-
),
141-
CardanoTransactionRecord::new(
142-
"tx-hash-1",
143-
BlockNumber(10),
144-
SlotNumber(51),
145-
"block-hash-10",
146-
),
147-
CardanoTransactionRecord::new(
148-
"tx-hash-2",
149-
BlockNumber(11),
150-
SlotNumber(54),
151-
"block-hash-11",
152-
),
153-
CardanoTransactionRecord::new(
154-
"tx-hash-3",
155-
BlockNumber(11),
156-
SlotNumber(55),
157-
"block-hash-11",
158-
),
147+
transaction_record(BlockNumber(10), SlotNumber(50)),
148+
transaction_record(BlockNumber(10), SlotNumber(51)),
149+
transaction_record(BlockNumber(11), SlotNumber(54)),
150+
transaction_record(BlockNumber(11), SlotNumber(55)),
159151
],
160152
);
161153

@@ -164,19 +156,74 @@ mod tests {
164156
.unwrap();
165157
assert_eq!(
166158
vec![
167-
CardanoTransactionRecord::new(
168-
"tx-hash-2",
169-
BlockNumber(11),
170-
SlotNumber(54),
171-
"block-hash-11"
159+
transaction_record(BlockNumber(11), SlotNumber(54)),
160+
transaction_record(BlockNumber(11), SlotNumber(55)),
161+
],
162+
records
163+
);
164+
}
165+
166+
#[test]
167+
fn with_highest_block_number_below_slot_number() {
168+
let connection = cardano_tx_db_connection().unwrap();
169+
170+
let cursor = connection
171+
.fetch(
172+
GetCardanoTransactionQuery::with_highest_block_number_below_slot_number(
173+
SlotNumber(51),
172174
),
173-
CardanoTransactionRecord::new(
174-
"tx-hash-3",
175-
BlockNumber(11),
176-
SlotNumber(55),
177-
"block-hash-11"
175+
)
176+
.unwrap();
177+
assert_eq!(0, cursor.count());
178+
179+
insert_transactions(
180+
&connection,
181+
vec![transaction_record(BlockNumber(2), SlotNumber(5))],
182+
);
183+
184+
let records: Vec<CardanoTransactionRecord> = connection
185+
.fetch_collect(
186+
GetCardanoTransactionQuery::with_highest_block_number_below_slot_number(
187+
SlotNumber(5),
178188
),
189+
)
190+
.unwrap();
191+
assert_eq!(
192+
vec![transaction_record(BlockNumber(2), SlotNumber(5)),],
193+
records
194+
);
195+
196+
insert_transactions(
197+
&connection,
198+
vec![
199+
transaction_record(BlockNumber(10), SlotNumber(50)),
200+
transaction_record(BlockNumber(11), SlotNumber(51)),
201+
transaction_record(BlockNumber(14), SlotNumber(54)),
202+
transaction_record(BlockNumber(15), SlotNumber(55)),
179203
],
204+
);
205+
206+
let records: Vec<CardanoTransactionRecord> = connection
207+
.fetch_collect(
208+
GetCardanoTransactionQuery::with_highest_block_number_below_slot_number(
209+
SlotNumber(53),
210+
),
211+
)
212+
.unwrap();
213+
assert_eq!(
214+
vec![transaction_record(BlockNumber(11), SlotNumber(51)),],
215+
records
216+
);
217+
218+
let records: Vec<CardanoTransactionRecord> = connection
219+
.fetch_collect(
220+
GetCardanoTransactionQuery::with_highest_block_number_below_slot_number(
221+
SlotNumber(54),
222+
),
223+
)
224+
.unwrap();
225+
assert_eq!(
226+
vec![transaction_record(BlockNumber(14), SlotNumber(54)),],
180227
records
181228
);
182229
}

internal/mithril-persistence/src/database/repository/cardano_transaction_repository.rs

Lines changed: 130 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,13 @@ impl CardanoTransactionRepository {
215215
Ok(())
216216
}
217217

218-
/// Get the block number for a given slot number
219-
pub async fn get_block_number_by_slot_number(
218+
/// Get the closest block number above a given slot number
219+
pub async fn get_closest_block_number_above_slot_number(
220220
&self,
221221
slot_number: SlotNumber,
222222
) -> StdResult<Option<BlockNumber>> {
223-
let query = GetCardanoTransactionQuery::by_slot_number(slot_number);
223+
let query =
224+
GetCardanoTransactionQuery::with_highest_block_number_below_slot_number(slot_number);
224225
let record = self.connection_pool.connection()?.fetch_first(query)?;
225226

226227
Ok(record.map(|r| r.block_number))
@@ -277,7 +278,7 @@ impl CardanoTransactionRepository {
277278
///
278279
/// * Remove transactions with block number strictly greater than the given block number
279280
/// * Remove block range roots that have lower bound range strictly above the given block number
280-
pub async fn remove_rolled_back_transactions_and_block_range(
281+
pub async fn remove_rolled_back_transactions_and_block_range_by_block_number(
281282
&self,
282283
block_number: BlockNumber,
283284
) -> StdResult<()> {
@@ -293,6 +294,25 @@ impl CardanoTransactionRepository {
293294

294295
Ok(())
295296
}
297+
298+
/// Remove transactions and block range roots that are in a rolled-back fork
299+
///
300+
/// * Remove transactions with closest block number strictly greater than the given slot number if exists
301+
/// * Remove block range roots that have lower bound range strictly above the aforementioned block number
302+
pub async fn remove_rolled_back_transactions_and_block_range_by_slot_number(
303+
&self,
304+
slot_number: SlotNumber,
305+
) -> StdResult<()> {
306+
if let Some(block_number) = self
307+
.get_closest_block_number_above_slot_number(slot_number)
308+
.await?
309+
{
310+
self.remove_rolled_back_transactions_and_block_range_by_block_number(block_number)
311+
.await?;
312+
}
313+
314+
Ok(())
315+
}
296316
}
297317

298318
#[async_trait]
@@ -910,7 +930,7 @@ mod tests {
910930
}
911931

912932
#[tokio::test]
913-
async fn repository_get_block_number_by_slot_number() {
933+
async fn repository_get_closest_block_number_by_slot_number() {
914934
let connection = cardano_tx_db_connection().unwrap();
915935
let repository = CardanoTransactionRepository::new(Arc::new(
916936
SqliteConnectionPool::build_from_connection(connection),
@@ -927,7 +947,7 @@ mod tests {
927947
.unwrap();
928948

929949
let transaction_block_number_retrieved = repository
930-
.get_block_number_by_slot_number(SlotNumber(500))
950+
.get_closest_block_number_above_slot_number(SlotNumber(500))
931951
.await
932952
.unwrap();
933953

@@ -1215,10 +1235,113 @@ mod tests {
12151235
.unwrap();
12161236

12171237
repository
1218-
.remove_rolled_back_transactions_and_block_range(BlockRange::LENGTH * 3)
1238+
.remove_rolled_back_transactions_and_block_range_by_block_number(BlockRange::LENGTH * 3)
12191239
.await
12201240
.unwrap();
12211241
assert_eq!(2, repository.get_all_transactions().await.unwrap().len());
12221242
assert_eq!(2, repository.get_all_block_range_root().unwrap().len());
12231243
}
1244+
1245+
#[tokio::test]
1246+
async fn remove_rolled_back_transactions_and_block_range_by_slot_number() {
1247+
fn transaction_record(
1248+
block_number: BlockNumber,
1249+
slot_number: SlotNumber,
1250+
tx_hash: &str,
1251+
) -> CardanoTransactionRecord {
1252+
CardanoTransactionRecord::new(
1253+
tx_hash,
1254+
block_number,
1255+
slot_number,
1256+
format!("block-hash-{}", block_number),
1257+
)
1258+
}
1259+
1260+
let repository = CardanoTransactionRepository::new(Arc::new(
1261+
SqliteConnectionPool::build(1, cardano_tx_db_connection).unwrap(),
1262+
));
1263+
1264+
repository
1265+
.create_transactions(vec![
1266+
transaction_record(BlockNumber(10), SlotNumber(50), "tx-hash-1"),
1267+
transaction_record(BlockNumber(11), SlotNumber(51), "tx-hash-2"),
1268+
transaction_record(BlockNumber(13), SlotNumber(52), "tx-hash-3"),
1269+
transaction_record(BlockNumber(13), SlotNumber(52), "tx-hash-4"),
1270+
transaction_record(BlockNumber(101), SlotNumber(100), "tx-hash-5"),
1271+
transaction_record(BlockNumber(202), SlotNumber(200), "tx-hash-56"),
1272+
])
1273+
.await
1274+
.unwrap();
1275+
1276+
{
1277+
repository
1278+
.remove_rolled_back_transactions_and_block_range_by_slot_number(SlotNumber(110))
1279+
.await
1280+
.expect("Failed to remove rolled back transactions");
1281+
1282+
let transactions = repository
1283+
.get_all()
1284+
.await
1285+
.unwrap()
1286+
.into_iter()
1287+
.map(|v| v.into())
1288+
.collect::<Vec<_>>();
1289+
assert_eq!(
1290+
vec![
1291+
transaction_record(BlockNumber(10), SlotNumber(50), "tx-hash-1"),
1292+
transaction_record(BlockNumber(11), SlotNumber(51), "tx-hash-2"),
1293+
transaction_record(BlockNumber(13), SlotNumber(52), "tx-hash-3"),
1294+
transaction_record(BlockNumber(13), SlotNumber(52), "tx-hash-4"),
1295+
transaction_record(BlockNumber(101), SlotNumber(100), "tx-hash-5"),
1296+
],
1297+
transactions
1298+
);
1299+
}
1300+
1301+
{
1302+
repository
1303+
.remove_rolled_back_transactions_and_block_range_by_slot_number(SlotNumber(53))
1304+
.await
1305+
.expect("Failed to remove rolled back transactions");
1306+
1307+
let transactions = repository
1308+
.get_all()
1309+
.await
1310+
.unwrap()
1311+
.into_iter()
1312+
.map(|v| v.into())
1313+
.collect::<Vec<_>>();
1314+
assert_eq!(
1315+
vec![
1316+
transaction_record(BlockNumber(10), SlotNumber(50), "tx-hash-1"),
1317+
transaction_record(BlockNumber(11), SlotNumber(51), "tx-hash-2"),
1318+
transaction_record(BlockNumber(13), SlotNumber(52), "tx-hash-3"),
1319+
transaction_record(BlockNumber(13), SlotNumber(52), "tx-hash-4"),
1320+
],
1321+
transactions
1322+
);
1323+
}
1324+
1325+
{
1326+
repository
1327+
.remove_rolled_back_transactions_and_block_range_by_slot_number(SlotNumber(51))
1328+
.await
1329+
.expect("Failed to remove rolled back transactions");
1330+
1331+
let transactions = repository
1332+
.get_all()
1333+
.await
1334+
.unwrap()
1335+
.into_iter()
1336+
.map(|v| v.into())
1337+
.collect::<Vec<_>>();
1338+
assert_eq!(
1339+
vec![
1340+
transaction_record(BlockNumber(10), SlotNumber(50), "tx-hash-1"),
1341+
transaction_record(BlockNumber(11), SlotNumber(51), "tx-hash-2"),
1342+
],
1343+
transactions
1344+
);
1345+
}
1346+
}
12241347
}

0 commit comments

Comments
 (0)