Skip to content

Commit 19493db

Browse files
committed
Add pruning of cardano transactions
1 parent 0d35b83 commit 19493db

File tree

3 files changed

+181
-1
lines changed

3 files changed

+181
-1
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use sqlite::Value;
2+
3+
use mithril_common::entities::BlockNumber;
4+
use mithril_common::StdResult;
5+
6+
use crate::database::record::CardanoTransactionRecord;
7+
use crate::sqlite::{
8+
EntityCursor, Provider, SourceAlias, SqLiteEntity, SqliteConnection, WhereCondition,
9+
};
10+
11+
/// Query to delete old [CardanoTransactionRecord] from the sqlite database
12+
pub struct DeleteCardanoTransactionProvider<'conn> {
13+
connection: &'conn SqliteConnection,
14+
}
15+
16+
impl<'conn> Provider<'conn> for DeleteCardanoTransactionProvider<'conn> {
17+
type Entity = CardanoTransactionRecord;
18+
19+
fn get_connection(&'conn self) -> &'conn SqliteConnection {
20+
self.connection
21+
}
22+
23+
fn get_definition(&self, condition: &str) -> String {
24+
// it is important to alias the fields with the same name as the table
25+
// since the table cannot be aliased in a RETURNING statement in SQLite.
26+
let projection = Self::Entity::get_projection()
27+
.expand(SourceAlias::new(&[("{:cardano_tx:}", "cardano_tx")]));
28+
29+
format!("delete from cardano_tx where {condition} returning {projection}")
30+
}
31+
}
32+
33+
impl<'conn> DeleteCardanoTransactionProvider<'conn> {
34+
/// Create a new instance
35+
pub fn new(connection: &'conn SqliteConnection) -> Self {
36+
Self { connection }
37+
}
38+
39+
fn get_prune_condition(&self, number_of_block_to_keep: BlockNumber) -> WhereCondition {
40+
let number_of_block_to_keep = Value::Integer(number_of_block_to_keep.try_into().unwrap());
41+
42+
WhereCondition::new(
43+
"block_number < ((select max(block_number) from cardano_tx) - ?*)",
44+
vec![number_of_block_to_keep],
45+
)
46+
}
47+
48+
/// Prune the cardano transaction data after the given number of block.
49+
pub fn prune(
50+
&self,
51+
number_of_block_to_keep: BlockNumber,
52+
) -> StdResult<EntityCursor<CardanoTransactionRecord>> {
53+
let filters = self.get_prune_condition(number_of_block_to_keep);
54+
55+
self.find(filters)
56+
}
57+
}
58+
59+
#[cfg(test)]
60+
mod tests {
61+
use crate::database::provider::{
62+
GetCardanoTransactionProvider, InsertCardanoTransactionProvider,
63+
};
64+
use crate::database::test_helper::cardano_tx_db_connection;
65+
use crate::sqlite::GetAllProvider;
66+
67+
use super::*;
68+
69+
fn insert_transactions(connection: &SqliteConnection, records: Vec<CardanoTransactionRecord>) {
70+
let provider = InsertCardanoTransactionProvider::new(connection);
71+
let condition = provider.get_insert_many_condition(records).unwrap();
72+
let mut cursor = provider.find(condition).unwrap();
73+
cursor.next().unwrap();
74+
}
75+
76+
fn test_transaction_set() -> Vec<CardanoTransactionRecord> {
77+
vec![
78+
CardanoTransactionRecord::new("tx-hash-0", 10, 50, "block-hash-10", 1),
79+
CardanoTransactionRecord::new("tx-hash-1", 10, 51, "block-hash-10", 1),
80+
CardanoTransactionRecord::new("tx-hash-2", 11, 52, "block-hash-11", 1),
81+
CardanoTransactionRecord::new("tx-hash-3", 11, 53, "block-hash-11", 1),
82+
CardanoTransactionRecord::new("tx-hash-4", 12, 54, "block-hash-12", 1),
83+
CardanoTransactionRecord::new("tx-hash-5", 12, 55, "block-hash-12", 1),
84+
]
85+
}
86+
87+
#[test]
88+
fn test_prune_work_even_without_transactions_in_db() {
89+
let connection = cardano_tx_db_connection().unwrap();
90+
91+
let prune_provider = DeleteCardanoTransactionProvider::new(&connection);
92+
let cursor = prune_provider
93+
.prune(100)
94+
.expect("pruning shouldn't crash without transactions stored");
95+
assert_eq!(0, cursor.count());
96+
}
97+
98+
#[test]
99+
fn test_prune_keep_all_data_if_given_block_number_is_larger_than_stored_number_of_block() {
100+
let connection = cardano_tx_db_connection().unwrap();
101+
insert_transactions(&connection, test_transaction_set());
102+
103+
let prune_provider = DeleteCardanoTransactionProvider::new(&connection);
104+
let cursor = prune_provider.prune(100_000).unwrap();
105+
assert_eq!(0, cursor.count());
106+
107+
let get_provider = GetCardanoTransactionProvider::new(&connection);
108+
let cursor = get_provider.get_all().unwrap();
109+
assert_eq!(test_transaction_set().len(), cursor.count());
110+
}
111+
112+
#[test]
113+
fn test_prune_keep_only_tx_of_last_block_if_given_number_of_block_is_zero() {
114+
let connection = cardano_tx_db_connection().unwrap();
115+
insert_transactions(&connection, test_transaction_set());
116+
117+
let prune_provider = DeleteCardanoTransactionProvider::new(&connection);
118+
let cursor = prune_provider.prune(0).unwrap();
119+
assert_eq!(4, cursor.count());
120+
121+
let get_provider = GetCardanoTransactionProvider::new(&connection);
122+
let cursor = get_provider.get_all().unwrap();
123+
assert_eq!(2, cursor.count());
124+
}
125+
126+
#[test]
127+
fn test_prune_data_of_older_than_n_blocks() {
128+
let connection = cardano_tx_db_connection().unwrap();
129+
insert_transactions(&connection, test_transaction_set());
130+
131+
let prune_provider = DeleteCardanoTransactionProvider::new(&connection);
132+
let cursor = prune_provider.prune(1).unwrap();
133+
assert_eq!(2, cursor.count());
134+
135+
let get_provider = GetCardanoTransactionProvider::new(&connection);
136+
let cursor = get_provider.get_all().unwrap();
137+
assert_eq!(4, cursor.count());
138+
}
139+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
mod delete_cardano_transaction;
12
mod get_cardano_transaction;
23
mod insert_cardano_transaction;
34

5+
pub use delete_cardano_transaction::*;
46
pub use get_cardano_transaction::*;
57
pub use insert_cardano_transaction::*;

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use mithril_common::signable_builder::BlockRangeRootRetriever;
1414
use mithril_common::StdResult;
1515

1616
use crate::database::provider::{
17-
GetBlockRangeRootProvider, GetCardanoTransactionProvider,
17+
DeleteCardanoTransactionProvider, GetBlockRangeRootProvider, GetCardanoTransactionProvider,
1818
GetIntervalWithoutBlockRangeRootProvider, InsertBlockRangeRootProvider,
1919
InsertCardanoTransactionProvider,
2020
};
@@ -273,6 +273,13 @@ impl CardanoTransactionRepository {
273273

274274
Ok(transactions.collect())
275275
}
276+
277+
/// Prune the transaction strictly after the given number of block.
278+
pub async fn prune_transaction(&self, number_of_block_to_keep: BlockNumber) -> StdResult<()> {
279+
let provider = DeleteCardanoTransactionProvider::new(&self.connection);
280+
provider.prune(number_of_block_to_keep)?.next();
281+
Ok(())
282+
}
276283
}
277284

278285
#[async_trait]
@@ -835,4 +842,36 @@ mod tests {
835842
);
836843
}
837844
}
845+
846+
#[tokio::test]
847+
async fn repository_prune_transactions() {
848+
let connection = Arc::new(cardano_tx_db_connection().unwrap());
849+
let repository = CardanoTransactionRepository::new(connection);
850+
851+
// Build transactions with block numbers from 20 to 50
852+
let cardano_transactions: Vec<CardanoTransactionRecord> = (20..=50)
853+
.map(|i| CardanoTransactionRecord {
854+
transaction_hash: format!("tx-hash-{i}"),
855+
block_number: i,
856+
slot_number: i * 100,
857+
block_hash: format!("block-hash-{i}"),
858+
immutable_file_number: 1,
859+
})
860+
.collect();
861+
862+
repository
863+
.create_transactions(cardano_transactions.clone())
864+
.await
865+
.unwrap();
866+
867+
let transaction_result = repository.get_all().await.unwrap();
868+
assert_eq!(cardano_transactions.len(), transaction_result.len());
869+
870+
repository.prune_transaction(24).await.unwrap();
871+
let transaction_result = repository
872+
.get_transactions_in_range_blocks(0..26)
873+
.await
874+
.unwrap();
875+
assert_eq!(Vec::<CardanoTransactionRecord>::new(), transaction_result);
876+
}
838877
}

0 commit comments

Comments
 (0)