Repository: input-output-hk/mithril
Pull request #509 (2022-09-16 13:37:32)

Add a record pruning limit to stores
## Content
This PR adds the ability for the store to limit the number of records they hold. When storing new records, older records are automatically deleted in the background.

The configuration setting `store_retention_limit` has been added to drive the stores. The following stores are impacted by this setting:
**Aggregator**

 - [X] `protocol_parameters`
 - [X] `verification_keys`
 - [X] `single_signatures`
 - [X] `stakes`

**Signer**

  - [X] `stakes`
  - [X] `protocol_initializer`
  

## Pre-submit checklist

- Branch
  - [X] Tests are provided (if possible)
  - [ ] Commit sequence broadly makes sense
  - [ ] Key commits have useful messages
- PR
  - [X] No clippy warnings in the CI
  - [X] Self-reviewed the diff
  - [X] Useful pull request description
  - [X] Reviewer requested
- Documentation
  - [ ] Update README file (if relevant)
  - [X] Update documentation website (if relevant)
  - [ ] Add dev blog post (if relevant)

## Comments
If the limit is set to 0, it will delete all the data in the stores.

## Issue(s)
<!-- The issue(s) this PR relates to or closes -->
Relates to #YYY or Closes #YYY


github-actions[bot]: ## Unit Test Results
    7 files  ±0    24 suites  ±0   2m 12s [:stopwatch:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "duration of all tests") -1s
332 tests +5  332 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") +5  0 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") ±0  0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests") ±0 
333 runs  +5  333 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") +5  0 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") ±0  0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests") ±0 

Results for commit 3ea12622. ± Comparison against base commit 874057e6.

[test-results]:data:application/gzip;base64,H4sIAHiAJGMC/13MSw7CIBSF4a00jB3AhZbqZgzechNiWwyPUePeRWykOvz+k5yNkZttZJdOnzoWs0sVoIqmHExyfi0WEkooW3qvsukaM+J/urtHSfwbyLj5J9gQfNhLyOvnU+5ol620x+rDYfXxD/2yuFTApDUCBgBErlU/gBITAVitz+MNzIiSkFNPmj1fzd6v/AYBAAA=


Patch:
diff --git a/docs/root/manual/developer-docs/nodes/mithril-aggregator.md b/docs/root/manual/developer-docs/nodes/mithril-aggregator.md
index 00d78053d..33a355453 100644
--- a/docs/root/manual/developer-docs/nodes/mithril-aggregator.md
+++ b/docs/root/manual/developer-docs/nodes/mithril-aggregator.md
@@ -6,7 +6,7 @@ sidebar_position: 1
 
 :::info
 
-This is the node of the **Mithril Network** responsible for collecting individual signatures from the **Mithril Signers** and aggregate them into a multisignature. The **Mithril Aggregator** uses this ability to provide certified snapshots of the **Cardano** blockchain.
+This is the node of the **Mithril Network** responsible for collecting individual signatures from the **Mithril Signers** and aggregate them into a multi-signature. The **Mithril Aggregator** uses this ability to provide certified snapshots of the **Cardano** blockchain.
 
 :::
 
@@ -22,9 +22,9 @@ This is the node of the **Mithril Network** responsible for collecting individua
 
 The [Mithril test networks](../../../manual/developer-docs/references.md#mithril-networks) are:
 
-* `preview`: Test network with magic id `2`, implemented on the IOG hosted Mitril Aggregator
+* `preview`: Test network with magic id `2`, implemented on the IOG hosted Mithril Aggregator
 * `preprod`: Test network with magic id `1`, not implemented yet on the IOG hosted Mithril Aggregator
-* `testnet`: Legacy test network with magic id `1097911063`, used to be on the IOG hosted Mitril Aggregator, now deprecated
+* `testnet`: Legacy test network with magic id `1097911063`, used to be on the IOG hosted Mithril Aggregator, now deprecated
 
 In this documentation, we use the generic `testnet` identifier, but you need to replace it with the identifier of the network that runs on your Cardano node
 
@@ -275,7 +275,7 @@ Here are the subcommands available:
 | **serve** | Aggregator runs its HTTP server in nominal mode and orchestrates multi signatures production |
 | **help** | Print this message or the help of the given subcommand(s) |
 | **genesis export** | Export genesis payload to sign with genesis secret key |
-| **genesis import** | Import genesis signature (paylod signed with genesis secret key) and create & import a genesis certificate in the store |
+| **genesis import** | Import genesis signature (payload signed with genesis secret key) and create & import a genesis certificate in the store |
 | **genesis bootstrap** | Bootstrap a genesis certificate (test only usage) |
 
 ## Configuration parameters
@@ -283,7 +283,7 @@ Here are the subcommands available:
 The configuration parameters are set either:
 
 * In a configuration file (depending on the `--run-mode` parameter). If runtime mode is `testnet` the file is located in `./conf/testnet.json`.
-* The value can be overriden by an environment variable whose name is the parameter name uppercased.
+* The value can be overridden by an environment variable whose name is the parameter name uppercased.
 
 Here is a list of the available parameters:
 
@@ -291,7 +291,7 @@ Here is a list of the available parameters:
 |-----------|---------------------|:---------------------:|----------------------|-------------|---------------|---------|:---------:|
 | `server_ip` | `--server-ip` | - | `SERVER_IP` | Listening server IP | `0.0.0.0` | - | :heavy_check_mark: |  
 | `server_port` | `--server-port` | - | `SERVER_PORT` | Listening server port | `8080` | - | :heavy_check_mark: |
-| `verbose` | `--verbose` | `-v` | `VERBOSE` | Verbosity level | - | Parsed from number of occurences: `-v` for `Warning`, `-vv` for `Info`, `-vvv` for `Debug` and `-vvvv` for `Trace` | :heavy_check_mark: |
+| `verbose` | `--verbose` | `-v` | `VERBOSE` | Verbosity level | - | Parsed from number of occurrences: `-v` for `Warning`, `-vv` for `Info`, `-vvv` for `Debug` and `-vvvv` for `Trace` | :heavy_check_mark: |
 | `run_mode` | `--run-mode` | `-r` | `RUN_MODE` | Runtime mode | `dev` | - | :heavy_check_mark: |
 | `db_directory` | `--db-directory` | - | `DB_DIRECTORY` | Directory to snapshot from the **Cardano Node** | `/db` | - | :heavy_check_mark: |
 | `snapshot_directory` | `--snapshot-directory` | - | `SNAPSHOT_DIRECTORY` | Directory to store local snapshots of the **Cardano Node** | `.` | - | :heavy_check_mark: |
@@ -301,9 +301,10 @@ Here is a list of the available parameters:
 | `snapshot_store_type` | - | - | `SNAPSHOT_STORE_TYPE` | Type of snapshot store to use | - | `gcp` or `local` | :heavy_check_mark: |
 | `snapshot_uploader_type` | - | - | `SNAPSHOT_UPLOADER_TYPE` | Type of snapshot uploader to use | - | `gcp` or `local` | :heavy_check_mark: |
 | `run_interval` | - | - | `RUN_INTERVAL` | Interval between two runtime cycles in ms | - | `60000` | :heavy_check_mark: |
-| `data_stores_directory` | - | - | `data_stores_directory` | Directory to store aggregator data (Certificates, Snapshots, Protocol Parameters, ...) | - | `./mithril-aggregator/stores` | :heavy_check_mark: |
+| `data_stores_directory` | - | - | `data_stores_directory` | Directory to store Aggregator data (Certificates, Snapshots, Protocol Parameters, ...) | - | `./mithril-aggregator/stores` | :heavy_check_mark: |
 | `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | :heavy_check_mark: |
 | `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket used by the Cardano CLI tool to communicate with the Cardano node | - | `/tmp/cardano.sock` | :heavy_check_mark: |
 | `url_snapshot_manifest` | - | - | `URL_SNAPSHOT_MANIFEST` | Snapshots manifest location | - | Only if `snapshot_store_type` is `gcp`, else it should be `` | :heavy_check_mark: |
 | `genesis_verification_key` | - | - | `GENESIS_VERIFICATION_KEY` | Genesis verification key | - | - | :heavy_check_mark: |
 | `genesis_secret_key` | - | - | `GENESIS_SECRET_KEY` | Genesis secret key, :warning: for test only | - | - | - |
+| `store_retention_limit` | - | - | `STORE_RETENTION_LIMIT` | Maximum number of records in stores. If not set, no limit is set. | - | - | - |
diff --git a/docs/root/manual/developer-docs/nodes/mithril-signer.md b/docs/root/manual/developer-docs/nodes/mithril-signer.md
index 982aa9253..f27ea1c87 100644
--- a/docs/root/manual/developer-docs/nodes/mithril-signer.md
+++ b/docs/root/manual/developer-docs/nodes/mithril-signer.md
@@ -173,13 +173,13 @@ make docker-run
 The configuration parameters are set either:
 
 * In a configuration file (depending on the `--run-mode` parameter). If runtime mode is `testnet` the file is located in `./conf/testnet.json`.
-* The value can be overriden by an environment variable whose name is the parameter name uppercased.
+* The value can be overridden by an environment variable whose name is the parameter name uppercased.
 
 Here is a list of the available parameters:
 
 | Parameter | Command Line (long) |  Command Line (short) | Environment Variable | Description | Default Value | Example | Mandatory |
 |-----------|---------------------|:---------------------:|----------------------|-------------|---------------|---------|:---------:|
-| `verbose` | `--verbose` | `-v` | `VERBOSE` | Verbosity level | - | Parsed from number of occurences: `-v` for `Warning`, `-vv` for `Info`, `-vvv` for `Debug` and `-vvvv` for `Trace` | :heavy_check_mark: |
+| `verbose` | `--verbose` | `-v` | `VERBOSE` | Verbosity level | - | Parsed from number of occurrences: `-v` for `Warning`, `-vv` for `Info`, `-vvv` for `Debug` and `-vvvv` for `Trace` | :heavy_check_mark: |
 | `run_mode` | `--run-mode` | `-r` | `RUN_MODE` | Runtime mode | `dev` | - | :heavy_check_mark: |
 | `db_directory` | `--db-directory` | - | `DB_DIRECTORY` | Directory to snapshot from the **Cardano Node** | `/db` | - | :heavy_check_mark: |
 | `network` | - | - | `NETWORK` | Cardano network | - | `testnet` or `mainnet` or `devnet` | :heavy_check_mark: |
@@ -188,3 +188,4 @@ Here is a list of the available parameters:
 | `run_interval` | - | - | `RUN_INTERVAL` | Interval between two runtime cycles in ms | - | `60000` | :heavy_check_mark: |
 | `aggregator_endpoint` | - | - | `AGGREGATOR_ENDPOINT` | Aggregator node endpoint | - | `https://aggregator.api.mithril.network/aggregator` | :heavy_check_mark: |
 | `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store signer data (Stakes, Protocol initializers, ...) | - | `./mithril-signer/stores` | :heavy_check_mark: |
+| `store_retention_limit` | - | - | `STORE_RETENTION_LIMIT` | Maximum number of records in stores. If not set, no limit is set. | - | - | - |
diff --git a/docs/root/manual/developer-docs/references.md b/docs/root/manual/developer-docs/references.md
index 4d6c04c9c..a88381441 100644
--- a/docs/root/manual/developer-docs/references.md
+++ b/docs/root/manual/developer-docs/references.md
@@ -8,7 +8,7 @@ Welcome to the Mithril API references doc!
 
 :::info
 
-This page gathers the exernal developer documentations available for Mithril. They are intended for a technical audience only.
+This page gathers the external developer documentations available for Mithril. They are intended for a technical audience only.
 
 :::
 
diff --git a/docs/root/manual/getting-started/run-signer-node.md b/docs/root/manual/getting-started/run-signer-node.md
index 7c66bb211..31dc287d4 100644
--- a/docs/root/manual/getting-started/run-signer-node.md
+++ b/docs/root/manual/getting-started/run-signer-node.md
@@ -150,6 +150,7 @@ Replace this value with the correct user. We assume that the user used to run th
   * `CARDANO_NODE_SOCKET_PATH=/cardano/ipc/node.socket`: replace with the path to the IPC file (`CARDANO_NODE_SOCKET_PATH` env var)
   * `CARDANO_CLI_PATH=/app/bin/cardano-cli`: replace with the path to the `cardano-cli` executable
   * `DATA_STORES_DIRECTORY=/opt/mithril/mithril-signer/stores`: replace with the path to a folder where the **Mithril Signer** will store its data (`/opt/mithril/mithril-signer/stores` e.g.)
+  * `STORE_RETENTION_LIMIT`: if set, this will limit the number of records in some internal stores (5 is a good fit).
 
 :::
 
@@ -160,10 +161,12 @@ sudo cat > /opt/mithril/mithril-signer.env << EOF
 PARTY_ID=YOUR_POOL_ID_BECH32
 NETWORK=testnet
 AGGREGATOR_ENDPOINT=https://aggregator.api.mithril.network/aggregator
-RUN_INTERVAL=60000 DB_DIRECTORY=/cardano/db
+RUN_INTERVAL=60000
+DB_DIRECTORY=/cardano/db
 CARDANO_NODE_SOCKET_PATH=/cardano/ipc/node.socket
 CARDANO_CLI_PATH=/app/bin/cardano-cli
 DATA_STORES_DIRECTORY=/opt/mithril/mithril-signer/stores
+STORE_RETENTION_LIMIT=5
 EOF
 ```
 
diff --git a/docs/root/manual/welcome.md b/docs/root/manual/welcome.md
index f9b9cfa64..82fd6badd 100644
--- a/docs/root/manual/welcome.md
+++ b/docs/root/manual/welcome.md
@@ -23,7 +23,7 @@ This node is in charge of **producing single signatures that are then combined i
 
 * **Mithril Client**:
 
-This node is in charge of **verifying and restoring a snapshot** that will allow a lightning fast boostrapping of a Cardano full node.
+This node is in charge of **verifying and restoring a snapshot** that will allow a lightning fast bootstrapping of a Cardano full node.
 
 :::tip
 
diff --git a/docs/root/mithril/mithril-network/aggregator.md b/docs/root/mithril/mithril-network/aggregator.md
index 95f6b3468..bfcfc6595 100644
--- a/docs/root/mithril/mithril-network/aggregator.md
+++ b/docs/root/mithril/mithril-network/aggregator.md
@@ -39,7 +39,7 @@ An important fact is that the **Mithril Aggregator** is trustless:
 
 * Anyone could run an aggregator on the network.
 
-* It does not brodcast any _sensitive_ information (such as the _message_ that needs to be signed). The signer nodes are responsible for computing these information directy from a **Cardano Node** on top of which they run.
+* It does not broadcast any _sensitive_ information (such as the _message_ that needs to be signed). The signer nodes are responsible for computing these information directy from a **Cardano Node** on top of which they run.
 
 Also, in order to aggregate single signatures into **Mithril multi signatures**, the *_Mithril Aggregator_ does not need to represent any share of the total stakes of the **Cardano Network**.
 
@@ -47,7 +47,7 @@ Also, in order to aggregate single signatures into **Mithril multi signatures**,
 
 The **Mithril Aggregator** is in charge of producing the **Mithril multi signatures** and their associated certificates for a part and/or all of the ledger state (the snapshots):
 
-* The previously registered **Mithril Signers** are able to produce individual signatures. They send these individual signatures to the **Mithril Aggregator** that checks their validaty and then store them.
+* The previously registered **Mithril Signers** are able to produce individual signatures. They send these individual signatures to the **Mithril Aggregator** that checks their validity and then store them.
 
 * Once the **quorum** of individual signatures has been reached, the **Mithril Aggregator** is able to produce a multi signature.
 
diff --git a/mithril-aggregator/config/preview.json b/mithril-aggregator/config/preview.json
index 3400a3cb6..1c4018702 100644
--- a/mithril-aggregator/config/preview.json
+++ b/mithril-aggregator/config/preview.json
@@ -12,5 +12,6 @@
     "snapshot_store_type": "local",
     "snapshot_uploader_type": "gcp",
     "stores_directory": "./mithril-aggregator/stores",
-    "genesis_verification_key": "5b3132372c37332c3132342c3136312c362c3133372c3133312c3231332c3230372c3131372c3139382c38352c3137362c3139392c3136322c3234312c36382c3132332c3131392c3134352c31332c3233322c3234332c34392c3232392c322c3234392c3230352c3230352c33392c3233352c34345d"
+    "genesis_verification_key": "5b3132372c37332c3132342c3136312c362c3133372c3133312c3231332c3230372c3131372c3139382c38352c3137362c3139392c3136322c3234312c36382c3132332c3131392c3134352c31332c3233322c3234332c34392c3232392c322c3234392c3230352c3230352c33392c3233352c34345d",
+    "limit_keys_in_stores": 5
 }
\ No newline at end of file
diff --git a/mithril-aggregator/src/configuration.rs b/mithril-aggregator/src/configuration.rs
index fc070f086..40ad33210 100644
--- a/mithril-aggregator/src/configuration.rs
+++ b/mithril-aggregator/src/configuration.rs
@@ -64,6 +64,12 @@ pub struct Configuration {
 
     /// Genesis verification key
     pub genesis_verification_key: String,
+
+    /// Max number of records in stores.
+    /// When new records are added, oldest records are automatically deleted so
+    /// there can always be at max the number of records specified by this
+    /// setting.
+    pub store_retention_limit: Option<usize>,
 }
 
 /// Snapshot store type enumerates the different kinds of snapshot stores.
diff --git a/mithril-aggregator/src/dependency.rs b/mithril-aggregator/src/dependency.rs
index 337d8c563..3e27206e6 100644
--- a/mithril-aggregator/src/dependency.rs
+++ b/mithril-aggregator/src/dependency.rs
@@ -199,6 +199,7 @@ pub mod tests {
             snapshot_directory: PathBuf::new(),
             data_stores_directory: PathBuf::new(),
             genesis_verification_key: key_encode_hex(&genesis_verification_key).unwrap(),
+            store_retention_limit: None,
         };
         let snapshot_store = Arc::new(LocalSnapshotStore::new(
             Box::new(MemoryAdapter::new(None).unwrap()),
@@ -211,16 +212,22 @@ pub mod tests {
         let certificate_store = Arc::new(CertificateStore::new(Box::new(
             MemoryAdapter::new(None).unwrap(),
         )));
-        let verification_key_store = Arc::new(VerificationKeyStore::new(Box::new(
-            MemoryAdapter::new(None).unwrap(),
-        )));
-        let stake_store = Arc::new(StakeStore::new(Box::new(MemoryAdapter::new(None).unwrap())));
-        let single_signature_store = Arc::new(SingleSignatureStore::new(Box::new(
-            MemoryAdapter::new(None).unwrap(),
-        )));
-        let protocol_parameters_store = Arc::new(ProtocolParametersStore::new(Box::new(
-            MemoryAdapter::new(None).unwrap(),
-        )));
+        let verification_key_store = Arc::new(VerificationKeyStore::new(
+            Box::new(MemoryAdapter::new(None).unwrap()),
+            config.store_retention_limit,
+        ));
+        let stake_store = Arc::new(StakeStore::new(
+            Box::new(MemoryAdapter::new(None).unwrap()),
+            config.store_retention_limit,
+        ));
+        let single_signature_store = Arc::new(SingleSignatureStore::new(
+            Box::new(MemoryAdapter::new(None).unwrap()),
+            config.store_retention_limit,
+        ));
+        let protocol_parameters_store = Arc::new(ProtocolParametersStore::new(
+            Box::new(MemoryAdapter::new(None).unwrap()),
+            None,
+        ));
         let multi_signer = MultiSignerImpl::new(
             verification_key_store.clone(),
             stake_store.clone(),
diff --git a/mithril-aggregator/src/main.rs b/mithril-aggregator/src/main.rs
index e21c7d2af..63dda1b11 100644
--- a/mithril-aggregator/src/main.rs
+++ b/mithril-aggregator/src/main.rs
@@ -50,21 +50,28 @@ async fn main() -> Result<(), Box<dyn Error>> {
         "certificate",
         sqlite_db_path.clone(),
     )?)));
-    let verification_key_store = Arc::new(VerificationKeyStore::new(Box::new(SQLiteAdapter::new(
-        "verification_key",
-        sqlite_db_path.clone(),
-    )?)));
-    let stake_store = Arc::new(StakeStore::new(Box::new(SQLiteAdapter::new(
-        "stake",
-        sqlite_db_path.clone(),
-    )?)));
-    let single_signature_store = Arc::new(SingleSignatureStore::new(Box::new(SQLiteAdapter::new(
-        "single_signature",
-        sqlite_db_path.clone(),
-    )?)));
-    let protocol_parameters_store = Arc::new(ProtocolParametersStore::new(Box::new(
-        SQLiteAdapter::new("protocol_parameters", sqlite_db_path)?,
-    )));
+    let verification_key_store = Arc::new(VerificationKeyStore::new(
+        Box::new(SQLiteAdapter::new(
+            "verification_key",
+            sqlite_db_path.clone(),
+        )?),
+        config.store_retention_limit,
+    ));
+    let stake_store = Arc::new(StakeStore::new(
+        Box::new(SQLiteAdapter::new("stake", sqlite_db_path.clone())?),
+        config.store_retention_limit,
+    ));
+    let single_signature_store = Arc::new(SingleSignatureStore::new(
+        Box::new(SQLiteAdapter::new(
+            "single_signature",
+            sqlite_db_path.clone(),
+        )?),
+        config.store_retention_limit,
+    ));
+    let protocol_parameters_store = Arc::new(ProtocolParametersStore::new(
+        Box::new(SQLiteAdapter::new("protocol_parameters", sqlite_db_path)?),
+        config.store_retention_limit,
+    ));
     let multi_signer = Arc::new(RwLock::new(MultiSignerImpl::new(
         verification_key_store.clone(),
         stake_store.clone(),
diff --git a/mithril-aggregator/src/multi_signer.rs b/mithril-aggregator/src/multi_signer.rs
index 91ab24d70..12e70adff 100644
--- a/mithril-aggregator/src/multi_signer.rs
+++ b/mithril-aggregator/src/multi_signer.rs
@@ -799,37 +799,45 @@ mod tests {
                 None,
             )
             .unwrap(),
-        ));
-        let stake_store = StakeStore::new(Box::new(
-            MemoryAdapter::<entities::Epoch, HashMap<entities::PartyId, entities::Stake>>::new(
-                None,
-            )
-            .unwrap(),
-        ));
-        let single_signature_store =
-            SingleSignatureStore::new(Box::new(
+        ), None);
+        let stake_store = StakeStore::new(
+            Box::new(
+                MemoryAdapter::<entities::Epoch, HashMap<entities::PartyId, entities::Stake>>::new(
+                    None,
+                )
+                .unwrap(),
+            ),
+            None,
+        );
+        let single_signature_store = SingleSignatureStore::new(
+            Box::new(
                 MemoryAdapter::<
                     entities::Beacon,
                     HashMap<entities::PartyId, entities::SingleSignatures>,
                 >::new(None)
                 .unwrap(),
-            ));
-        let protocol_parameters_store = ProtocolParametersStore::new(Box::new(
-            MemoryAdapter::<entities::Epoch, entities::ProtocolParameters>::new(Some(vec![
-                (
-                    beacon.epoch.offset_to_signer_retrieval_epoch().unwrap(),
-                    fake_data::protocol_parameters(),
-                ),
-                (
-                    beacon
-                        .epoch
-                        .offset_to_next_signer_retrieval_epoch()
-                        .unwrap(),
-                    fake_data::protocol_parameters(),
-                ),
-            ]))
-            .unwrap(),
-        ));
+            ),
+            None,
+        );
+        let protocol_parameters_store = ProtocolParametersStore::new(
+            Box::new(
+                MemoryAdapter::<entities::Epoch, entities::ProtocolParameters>::new(Some(vec![
+                    (
+                        beacon.epoch.offset_to_signer_retrieval_epoch().unwrap(),
+                        fake_data::protocol_parameters(),
+                    ),
+                    (
+                        beacon
+                            .epoch
+                            .offset_to_next_signer_retrieval_epoch()
+                            .unwrap(),
+                        fake_data::protocol_parameters(),
+                    ),
+                ]))
+                .unwrap(),
+            ),
+            None,
+        );
         let mut multi_signer = MultiSignerImpl::new(
             Arc::new(verification_key_store),
             Arc::new(stake_store),
diff --git a/mithril-aggregator/src/store/protocol_parameters_store.rs b/mithril-aggregator/src/store/protocol_parameters_store.rs
index 13884bedf..06794b27f 100644
--- a/mithril-aggregator/src/store/protocol_parameters_store.rs
+++ b/mithril-aggregator/src/store/protocol_parameters_store.rs
@@ -2,7 +2,7 @@ use async_trait::async_trait;
 use tokio::sync::RwLock;
 
 use mithril_common::entities::{Epoch, ProtocolParameters};
-use mithril_common::store::{adapter::StoreAdapter, StoreError};
+use mithril_common::store::{adapter::StoreAdapter, StoreError, StorePruner};
 
 type Adapter = Box<dyn StoreAdapter<Key = Epoch, Record = ProtocolParameters>>;
 
@@ -25,17 +25,35 @@ pub trait ProtocolParametersStorer {
 /// `ProtocolParameter` store.
 pub struct ProtocolParametersStore {
     adapter: RwLock<Adapter>,
+    retention_len: Option<usize>,
 }
 
 impl ProtocolParametersStore {
     /// Create an instance of `ProtocolParameterStore`.
-    pub fn new(adapter: Adapter) -> Self {
+    pub fn new(adapter: Adapter, retention_len: Option<usize>) -> Self {
         Self {
             adapter: RwLock::new(adapter),
+            retention_len,
         }
     }
 }
 
+#[async_trait]
+impl StorePruner for ProtocolParametersStore {
+    type Key = Epoch;
+    type Record = ProtocolParameters;
+
+    fn get_adapter(
+        &self,
+    ) -> &RwLock<Box<dyn StoreAdapter<Key = Self::Key, Record = Self::Record>>> {
+        &self.adapter
+    }
+
+    fn get_max_records(&self) -> Option<usize> {
+        self.retention_len
+    }
+}
+
 #[async_trait]
 impl ProtocolParametersStorer for ProtocolParametersStore {
     async fn save_protocol_parameters(
@@ -49,6 +67,7 @@ impl ProtocolParametersStorer for ProtocolParametersStore {
             .await
             .store_record(&epoch, &protocol_parameters)
             .await?;
+        self.prune().await?;
 
         Ok(previous_protocol_parameters)
     }
@@ -71,6 +90,7 @@ mod tests {
 
     fn setup_protocol_parameters(nb_epoch: u64) -> Vec<(Epoch, ProtocolParameters)> {
         let mut values: Vec<(Epoch, ProtocolParameters)> = Vec::new();
+
         for epoch in 1..=nb_epoch {
             let mut protocol_parameters = fake_data::protocol_parameters();
             protocol_parameters.k += epoch;
@@ -79,7 +99,7 @@ mod tests {
         values
     }
 
-    fn init_store(nb_epoch: u64) -> ProtocolParametersStore {
+    fn init_store(nb_epoch: u64, retention_len: Option<usize>) -> ProtocolParametersStore {
         let values = setup_protocol_parameters(nb_epoch);
 
         let values = if !values.is_empty() {
@@ -88,13 +108,14 @@ mod tests {
             None
         };
         let adapter: MemoryAdapter<Epoch, ProtocolParameters> = MemoryAdapter::new(values).unwrap();
-        ProtocolParametersStore::new(Box::new(adapter))
+
+        ProtocolParametersStore::new(Box::new(adapter), retention_len)
     }
 
     #[tokio::test]
     async fn save_key_in_empty_store() {
         let protocol_parameters = setup_protocol_parameters(1);
-        let store = init_store(0);
+        let store = init_store(0, None);
         let res = store
             .save_protocol_parameters(
                 protocol_parameters[0].0,
@@ -109,7 +130,7 @@ mod tests {
     #[tokio::test]
     async fn update_protocol_parameters_in_store() {
         let protocol_parameters = setup_protocol_parameters(2);
-        let store = init_store(1);
+        let store = init_store(1, None);
         let res = store
             .save_protocol_parameters(
                 protocol_parameters[0].0,
@@ -123,7 +144,7 @@ mod tests {
 
     #[tokio::test]
     async fn get_protocol_parameters_for_empty_epoch() {
-        let store = init_store(2);
+        let store = init_store(2, None);
         let res = store.get_protocol_parameters(Epoch(0)).await.unwrap();
 
         assert!(res.is_none());
@@ -131,9 +152,57 @@ mod tests {
 
     #[tokio::test]
     async fn get_protocol_parameters_for_existing_epoch() {
-        let store = init_store(2);
+        let store = init_store(2, None);
         let res = store.get_protocol_parameters(Epoch(1)).await.unwrap();
 
         assert!(res.is_some());
     }
+
+    #[tokio::test]
+    async fn test_retention_length() {
+        let store = init_store(2, Some(2));
+        let protocol_parameters = setup_protocol_parameters(3);
+        let _ = store
+            .save_protocol_parameters(Epoch(3), (&protocol_parameters[2].1).to_owned())
+            .await
+            .unwrap();
+        assert_eq!(None, store.get_protocol_parameters(Epoch(1)).await.unwrap());
+        let res = store.get_protocol_parameters(Epoch(2)).await.unwrap();
+        assert!(res.is_some());
+    }
+
+    #[tokio::test]
+    async fn test_retention_length_on_update() {
+        let store = init_store(4, Some(2));
+        let mut protocol_parameters = store
+            .get_protocol_parameters(Epoch(1))
+            .await
+            .unwrap()
+            .expect("There should be a protocol parameter for this epoch");
+        protocol_parameters.k += 1;
+        store
+            .save_protocol_parameters(Epoch(1), protocol_parameters)
+            .await
+            .unwrap();
+        assert!(store
+            .get_protocol_parameters(Epoch(4))
+            .await
+            .unwrap()
+            .is_some());
+        assert!(store
+            .get_protocol_parameters(Epoch(3))
+            .await
+            .unwrap()
+            .is_some());
+        assert!(store
+            .get_protocol_parameters(Epoch(1))
+            .await
+            .unwrap()
+            .is_none());
+        assert!(store
+            .get_protocol_parameters(Epoch(2))
+            .await
+            .unwrap()
+            .is_none());
+    }
 }
diff --git a/mithril-aggregator/src/store/single_signature_store.rs b/mithril-aggregator/src/store/single_signature_store.rs
index f75db0d7c..a872602b7 100644
--- a/mithril-aggregator/src/store/single_signature_store.rs
+++ b/mithril-aggregator/src/store/single_signature_store.rs
@@ -1,4 +1,5 @@
 use async_trait::async_trait;
+use mithril_common::store::StorePruner;
 use std::collections::HashMap;
 use tokio::sync::RwLock;
 
@@ -27,17 +28,35 @@ pub trait SingleSignatureStorer {
 /// Store for [SingleSignatures].
 pub struct SingleSignatureStore {
     adapter: RwLock<Adapter>,
+    retention_limit: Option<usize>,
 }
 
 impl SingleSignatureStore {
     /// Create a new instance.
-    pub fn new(adapter: Adapter) -> Self {
+    pub fn new(adapter: Adapter, retention_limit: Option<usize>) -> Self {
         Self {
             adapter: RwLock::new(adapter),
+            retention_limit,
         }
     }
 }
 
+#[async_trait]
+impl StorePruner for SingleSignatureStore {
+    type Key = Beacon;
+    type Record = HashMap<PartyId, SingleSignatures>;
+
+    fn get_adapter(
+        &self,
+    ) -> &RwLock<Box<dyn StoreAdapter<Key = Self::Key, Record = Self::Record>>> {
+        &self.adapter
+    }
+
+    fn get_max_records(&self) -> Option<usize> {
+        self.retention_limit
+    }
+}
+
 #[async_trait]
 impl SingleSignatureStorer for SingleSignatureStore {
     async fn save_single_signatures(
@@ -62,6 +81,7 @@ impl SingleSignatureStorer for SingleSignatureStore {
             .await
             .store_record(beacon, &single_signatures_per_party_id)
             .await?;
+        self.prune().await?;
 
         Ok(prev_single_signature)
     }
@@ -84,6 +104,7 @@ mod tests {
         nb_immutable_file_numbers: u64,
         single_signers_per_epoch: u64,
         single_signatures_per_signers: u64,
+        retention_limit: Option<usize>,
     ) -> SingleSignatureStore {
         let mut values: Vec<(Beacon, HashMap<PartyId, SingleSignatures>)> = Vec::new();
 
@@ -114,12 +135,12 @@ mod tests {
         };
         let adapter: MemoryAdapter<Beacon, HashMap<PartyId, SingleSignatures>> =
             MemoryAdapter::new(values).unwrap();
-        SingleSignatureStore::new(Box::new(adapter))
+        SingleSignatureStore::new(Box::new(adapter), retention_limit)
     }
 
     #[tokio::test]
     async fn save_key_in_empty_store() {
-        let store = init_store(0, 0, 0);
+        let store = init_store(0, 0, 0, None);
         let res = store
             .save_single_signatures(
                 &Beacon::new("devnet".to_string(), 1, 0),
@@ -137,7 +158,7 @@ mod tests {
 
     #[tokio::test]
     async fn add_signature_for_new_party_id() {
-        let store = init_store(1, 1, 5);
+        let store = init_store(1, 1, 5, None);
         let beacon = Beacon::new("devnet".to_string(), 1, 1);
 
         assert_eq!(
@@ -171,7 +192,7 @@ mod tests {
 
     #[tokio::test]
     async fn get_single_signatures_for_empty_epoch() {
-        let store = init_store(2, 1, 1);
+        let store = init_store(2, 1, 1, None);
         let res = store
             .get_single_signatures(&Beacon::new("devnet".to_string(), 1, 0))
             .await
@@ -182,7 +203,7 @@ mod tests {
 
     #[tokio::test]
     async fn get_single_signatures_for_existing_epoch() {
-        let store = init_store(2, 2, 2);
+        let store = init_store(2, 2, 2, None);
         let res = store
             .get_single_signatures(&Beacon::new("devnet".to_string(), 1, 1))
             .await
@@ -191,4 +212,33 @@ mod tests {
         assert!(res.is_some());
         assert_eq!(2, res.unwrap().len());
     }
+
+    #[tokio::test]
+    async fn using_retention_limit() {
+        let store = init_store(2, 2, 2, Some(2));
+        let beacon = Beacon::new("devnet".to_string(), 1, 1);
+
+        assert!(store
+            .get_single_signatures(&beacon)
+            .await
+            .unwrap()
+            .is_some());
+        let _ = store
+            .save_single_signatures(
+                &Beacon::new("devnet".to_string(), 2, 3),
+                &SingleSignatures {
+                    party_id: "0".to_string(),
+                    signature: "OK".to_string(),
+                    won_indexes: vec![1, 5],
+                },
+            )
+            .await
+            .unwrap();
+
+        assert!(store
+            .get_single_signatures(&beacon)
+            .await
+            .unwrap()
+            .is_none());
+    }
 }
diff --git a/mithril-aggregator/src/store/verification_key_store.rs b/mithril-aggregator/src/store/verification_key_store.rs
index 129b9475f..808db7129 100644
--- a/mithril-aggregator/src/store/verification_key_store.rs
+++ b/mithril-aggregator/src/store/verification_key_store.rs
@@ -1,4 +1,5 @@
 use async_trait::async_trait;
+use mithril_common::store::StorePruner;
 use std::collections::HashMap;
 use tokio::sync::RwLock;
 
@@ -26,17 +27,35 @@ pub trait VerificationKeyStorer {
 /// Store for the `VerificationKey`.
 pub struct VerificationKeyStore {
     adapter: RwLock<Adapter>,
+    retention_limit: Option<usize>,
 }
 
 impl VerificationKeyStore {
     /// Create a new instance.
-    pub fn new(adapter: Adapter) -> Self {
+    pub fn new(adapter: Adapter, retention_limit: Option<usize>) -> Self {
         Self {
             adapter: RwLock::new(adapter),
+            retention_limit,
         }
     }
 }
 
+#[async_trait]
+impl StorePruner for VerificationKeyStore {
+    type Key = Epoch;
+    type Record = HashMap<PartyId, Signer>;
+
+    fn get_adapter(
+        &self,
+    ) -> &RwLock<Box<dyn StoreAdapter<Key = Self::Key, Record = Self::Record>>> {
+        &self.adapter
+    }
+
+    fn get_max_records(&self) -> Option<usize> {
+        self.retention_limit
+    }
+}
+
 #[async_trait]
 impl VerificationKeyStorer for VerificationKeyStore {
     async fn save_verification_key(
@@ -54,6 +73,7 @@ impl VerificationKeyStorer for VerificationKeyStore {
             .await
             .store_record(&epoch, &signers)
             .await?;
+        self.prune().await?;
 
         Ok(prev_signer)
     }
@@ -73,7 +93,11 @@ mod tests {
 
     use mithril_common::store::adapter::MemoryAdapter;
 
-    fn init_store(nb_epoch: u64, signers_per_epoch: u64) -> VerificationKeyStore {
+    fn init_store(
+        nb_epoch: u64,
+        signers_per_epoch: u64,
+        retention_limit: Option<usize>,
+    ) -> VerificationKeyStore {
         let mut values: Vec<(Epoch, HashMap<PartyId, Signer>)> = Vec::new();
 
         for epoch in 1..=nb_epoch {
@@ -99,12 +123,12 @@ mod tests {
         };
         let adapter: MemoryAdapter<Epoch, HashMap<PartyId, Signer>> =
             MemoryAdapter::new(values).unwrap();
-        VerificationKeyStore::new(Box::new(adapter))
+        VerificationKeyStore::new(Box::new(adapter), retention_limit)
     }
 
     #[tokio::test]
     async fn save_key_in_empty_store() {
-        let store = init_store(0, 0);
+        let store = init_store(0, 0, None);
         let res = store
             .save_verification_key(
                 Epoch(0),
@@ -121,7 +145,7 @@ mod tests {
 
     #[tokio::test]
     async fn update_signer_in_store() {
-        let store = init_store(1, 1);
+        let store = init_store(1, 1, None);
         let res = store
             .save_verification_key(
                 Epoch(1),
@@ -145,7 +169,7 @@ mod tests {
 
     #[tokio::test]
     async fn get_verification_keys_for_empty_epoch() {
-        let store = init_store(2, 1);
+        let store = init_store(2, 1, None);
         let res = store.get_verification_keys(Epoch(0)).await.unwrap();
 
         assert!(res.is_none());
@@ -153,10 +177,35 @@ mod tests {
 
     #[tokio::test]
     async fn get_verification_keys_for_existing_epoch() {
-        let store = init_store(2, 2);
+        let store = init_store(2, 2, None);
         let res = store.get_verification_keys(Epoch(1)).await.unwrap();
 
         assert!(res.is_some());
         assert_eq!(2, res.unwrap().len());
     }
+
+    #[tokio::test]
+    async fn check_retention_limit() {
+        let store = init_store(2, 2, Some(2));
+        assert!(store
+            .get_verification_keys(Epoch(1))
+            .await
+            .unwrap()
+            .is_some());
+        let _ = store
+            .save_verification_key(
+                Epoch(3),
+                Signer {
+                    party_id: "party_id".to_string(),
+                    verification_key: "whatever".to_string(),
+                },
+            )
+            .await
+            .unwrap();
+        assert!(store
+            .get_verification_keys(Epoch(1))
+            .await
+            .unwrap()
+            .is_none());
+    }
 }
diff --git a/mithril-common/src/store/adapter/memory_adapter.rs b/mithril-common/src/store/adapter/memory_adapter.rs
index 25e651e70..43f752339 100644
--- a/mithril-common/src/store/adapter/memory_adapter.rs
+++ b/mithril-common/src/store/adapter/memory_adapter.rs
@@ -53,15 +53,9 @@ where
         let key = (*key).clone();
         let record = (*record).clone();
 
-        if self.values.insert(key.clone(), record).is_some() {
-            if let Some(ix) = self.index.iter().position(|k| k == &key) {
-                let _ = self.index.remove(ix);
-            } else {
-                // the index was corrupted
-                // doing nothing will repair it.
-            }
+        if self.values.insert(key.clone(), record).is_none() {
+            self.index.push(key);
         }
-        self.index.push(key);
 
         Ok(())
     }
@@ -91,6 +85,8 @@ where
     }
 
     async fn remove(&mut self, key: &Self::Key) -> Result<Option<Self::Record>, AdapterError> {
+        self.index.retain(|k| *k != *key);
+
         Ok(self.values.remove(key))
     }
 
@@ -106,6 +102,8 @@ where
 
 #[cfg(test)]
 mod tests {
+    use std::borrow::Borrow;
+
     use super::*;
 
     fn init_adapter(nb: u64) -> MemoryAdapter<u64, String> {
@@ -184,9 +182,12 @@ mod tests {
         let mut adapter = init_adapter(2);
 
         assert!(adapter.store_record(&1, &"one".to_string()).await.is_ok());
-        let vals = adapter.get_last_n_records(1).await.unwrap();
+        let vals = adapter.get_last_n_records(2).await.unwrap();
 
-        assert_eq!((1, "one".to_string()), vals[0]);
+        assert_eq!(
+            vec![(2, "value 2".to_string()), (1, "one".to_string())],
+            vals
+        );
     }
 
     #[tokio::test]
@@ -196,6 +197,7 @@ mod tests {
 
         assert_eq!("value 1".to_string(), record);
         assert!(!adapter.record_exists(&1).await.unwrap());
+        assert_eq!(1, adapter.index.len())
     }
 
     #[tokio::test]
@@ -204,6 +206,7 @@ mod tests {
         let maybe_record = adapter.remove(&0).await.unwrap();
 
         assert!(maybe_record.is_none());
+        assert_eq!(2, adapter.index.len())
     }
 
     #[tokio::test]
@@ -224,4 +227,18 @@ mod tests {
 
         assert_eq!(0, records.count());
     }
+
+    #[tokio::test]
+    async fn check_get_last_n_modified_records() {
+        let mut adapter = init_adapter(3);
+        adapter
+            .store_record(&1, "updated record".to_string().borrow())
+            .await
+            .unwrap();
+        let values = adapter.get_last_n_records(2).await.unwrap();
+        assert_eq!(
+            vec![(3, "value 3".to_string()), (2, "value 2".to_string())],
+            values
+        );
+    }
 }
diff --git a/mithril-common/src/store/mod.rs b/mithril-common/src/store/mod.rs
index afee1eb58..baeb24121 100644
--- a/mithril-common/src/store/mod.rs
+++ b/mithril-common/src/store/mod.rs
@@ -5,6 +5,8 @@ pub mod adapter;
 pub mod adapter_migration;
 mod error;
 mod stake_store;
+mod store_pruner;
 
 pub use error::StoreError;
 pub use stake_store::{StakeStore, StakeStorer};
+pub use store_pruner::StorePruner;
diff --git a/mithril-common/src/store/stake_store.rs b/mithril-common/src/store/stake_store.rs
index 32def5a89..176145bd4 100644
--- a/mithril-common/src/store/stake_store.rs
+++ b/mithril-common/src/store/stake_store.rs
@@ -3,7 +3,7 @@ use tokio::sync::RwLock;
 
 use crate::entities::{Epoch, StakeDistribution};
 
-use super::{adapter::StoreAdapter, StoreError};
+use super::{adapter::StoreAdapter, StoreError, StorePruner};
 
 type Adapter = Box<dyn StoreAdapter<Key = Epoch, Record = StakeDistribution>>;
 
@@ -31,17 +31,35 @@ pub trait StakeStorer {
 /// A [StakeStorer] that use a [StoreAdapter] to store data.
 pub struct StakeStore {
     adapter: RwLock<Adapter>,
+    retention_limit: Option<usize>,
 }
 
 impl StakeStore {
     /// StakeStore factory
-    pub fn new(adapter: Adapter) -> Self {
+    pub fn new(adapter: Adapter, retention_limit: Option<usize>) -> Self {
         Self {
             adapter: RwLock::new(adapter),
+            retention_limit,
         }
     }
 }
 
+#[async_trait]
+impl StorePruner for StakeStore {
+    type Key = Epoch;
+    type Record = StakeDistribution;
+
+    fn get_adapter(
+        &self,
+    ) -> &RwLock<Box<dyn StoreAdapter<Key = Self::Key, Record = Self::Record>>> {
+        &self.adapter
+    }
+
+    fn get_max_records(&self) -> Option<usize> {
+        self.retention_limit
+    }
+}
+
 #[async_trait]
 impl StakeStorer for StakeStore {
     async fn save_stakes(
@@ -49,9 +67,16 @@ impl StakeStorer for StakeStore {
         epoch: Epoch,
         stakes: StakeDistribution,
     ) -> Result<Option<StakeDistribution>, StoreError> {
-        let mut adapter = self.adapter.write().await;
-        let signers = adapter.get_record(&epoch).await?;
-        adapter.store_record(&epoch, &stakes).await?;
+        let signers = {
+            let mut adapter = self.adapter.write().await;
+            let signers = adapter.get_record(&epoch).await?;
+            adapter.store_record(&epoch, &stakes).await?;
+
+            signers
+        };
+        // it is important the adapter gets out of the scope to free the write lock it holds.
+        // Otherwise the method below will hang forever waiting for the lock.
+        self.prune().await?;
 
         Ok(signers)
     }
@@ -77,7 +102,11 @@ mod tests {
     use super::super::adapter::MemoryAdapter;
     use super::*;
 
-    fn init_store(nb_epoch: u64, signers_per_epoch: u64) -> StakeStore {
+    fn init_store(
+        nb_epoch: u64,
+        signers_per_epoch: u64,
+        retention_limit: Option<usize>,
+    ) -> StakeStore {
         let mut values: Vec<(Epoch, StakeDistribution)> = Vec::new();
 
         for epoch in 1..=nb_epoch {
@@ -96,12 +125,12 @@ mod tests {
             None
         };
         let adapter: MemoryAdapter<Epoch, StakeDistribution> = MemoryAdapter::new(values).unwrap();
-        StakeStore::new(Box::new(adapter))
+        StakeStore::new(Box::new(adapter), retention_limit)
     }
 
     #[tokio::test]
     async fn save_key_in_empty_store() {
-        let store = init_store(0, 0);
+        let store = init_store(0, 0, None);
         let res = store
             .save_stakes(Epoch(1), HashMap::from([("1".to_string(), 123)]))
             .await
@@ -112,7 +141,7 @@ mod tests {
 
     #[tokio::test]
     async fn update_signer_in_store() {
-        let store = init_store(1, 1);
+        let store = init_store(1, 1, None);
         let res = store
             .save_stakes(Epoch(1), HashMap::from([("1".to_string(), 123)]))
             .await
@@ -126,7 +155,7 @@ mod tests {
 
     #[tokio::test]
     async fn get_stakes_for_empty_epoch() {
-        let store = init_store(2, 1);
+        let store = init_store(2, 1, None);
         let res = store
             .get_stakes(Epoch(0))
             .await
@@ -137,7 +166,7 @@ mod tests {
 
     #[tokio::test]
     async fn get_stakes_for_existing_epoch() {
-        let store = init_store(2, 2);
+        let store = init_store(2, 2, None);
         let res = store
             .get_stakes(Epoch(1))
             .await
@@ -146,4 +175,14 @@ mod tests {
         assert!(res.is_some());
         assert_eq!(2, res.expect("Query result should not be empty.").len());
     }
+
+    #[tokio::test]
+    async fn check_retention_limit() {
+        let store = init_store(2, 2, Some(2));
+        let _res = store
+            .save_stakes(Epoch(3), HashMap::from([("1".to_string(), 123)]))
+            .await
+            .unwrap();
+        assert!(store.get_stakes(Epoch(1)).await.unwrap().is_none());
+    }
 }
diff --git a/mithril-common/src/store/store_pruner.rs b/mithril-common/src/store/store_pruner.rs
new file mode 100644
index 000000000..75c26ea2d
--- /dev/null
+++ b/mithril-common/src/store/store_pruner.rs
@@ -0,0 +1,126 @@
+use async_trait::async_trait;
+use tokio::sync::RwLock;
+
+use super::{adapter::StoreAdapter, StoreError};
+
+/// Implementing this trait will make store able to limit the number of the
+/// stored records by pruning them if a limit is set.
+#[async_trait]
+pub trait StorePruner {
+    /// The key type
+    type Key: Sync + Send;
+
+    /// The record type
+    type Record: Sync + Send;
+
+    /// This trait requires a way to get the internal adapter.
+    fn get_adapter(&self)
+        -> &RwLock<Box<dyn StoreAdapter<Key = Self::Key, Record = Self::Record>>>;
+
+    /// Return the maximum number of elements that can exist in this store. If None, there is no limit.
+    fn get_max_records(&self) -> Option<usize>;
+
+    /// Prune elements exceeding the specified limit.
+    async fn prune(&self) -> Result<(), StoreError> {
+        let retention_len = self.get_max_records().unwrap_or(usize::MAX);
+        let lock = self.get_adapter();
+        let mut adapter = lock.write().await;
+
+        for (epoch, _record) in adapter
+            .get_last_n_records(usize::MAX)
+            .await?
+            .into_iter()
+            .skip(retention_len)
+        {
+            adapter.remove(&epoch).await?;
+        }
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::cmp::min;
+
+    use crate::store::adapter::SQLiteAdapter;
+
+    use super::*;
+
+    struct TestStore {
+        adapter: RwLock<Box<dyn StoreAdapter<Key = u64, Record = String>>>,
+        record_limit: Option<usize>,
+    }
+
+    impl StorePruner for TestStore {
+        type Key = u64;
+        type Record = String;
+
+        fn get_adapter(
+            &self,
+        ) -> &RwLock<Box<dyn StoreAdapter<Key = Self::Key, Record = Self::Record>>> {
+            &self.adapter
+        }
+
+        fn get_max_records(&self) -> Option<usize> {
+            self.record_limit
+        }
+    }
+
+    fn get_data(n: u64) -> Vec<(u64, String)> {
+        let n = min(n, 6);
+        let words = ["one", "two", "three", "four", "five", "six"];
+        let mut values: Vec<(u64, String)> = Vec::new();
+
+        for index in 0..n {
+            values.push((index, words[index as usize].to_string()));
+        }
+
+        values
+    }
+
+    async fn get_adapter(data_len: u64) -> SQLiteAdapter<u64, String> {
+        let mut adapter: SQLiteAdapter<u64, String> = SQLiteAdapter::new("whatever", None).unwrap();
+
+        for (key, record) in get_data(data_len) {
+            adapter.store_record(&key, &record).await.unwrap();
+        }
+
+        adapter
+    }
+
+    #[tokio::test]
+    async fn test_no_prune() {
+        for data_len in 1_u64..=6 {
+            let store = TestStore {
+                adapter: RwLock::new(Box::new(get_adapter(data_len).await)),
+                record_limit: None,
+            };
+
+            store.prune().await.unwrap();
+            assert_eq!(
+                data_len as usize,
+                store.adapter.read().await.get_iter().await.unwrap().count(),
+                "test no pruning with dataset length = {}",
+                data_len
+            );
+        }
+    }
+    #[tokio::test]
+    async fn test_with_pruning() {
+        for data_len in 1_u64..=6 {
+            let store = TestStore {
+                adapter: RwLock::new(Box::new(get_adapter(6).await)),
+                record_limit: Some(data_len as usize),
+            };
+
+            store.prune().await.unwrap();
+            assert_eq!(
+                data_len as usize,
+                store.adapter.read().await.get_iter().await.unwrap().count(),
+                "test pruning with retention limit = {}",
+                data_len
+            );
+        }
+    }
+}
diff --git a/mithril-signer/config/preview.json b/mithril-signer/config/preview.json
index 05d6d6a58..1f850b412 100644
--- a/mithril-signer/config/preview.json
+++ b/mithril-signer/config/preview.json
@@ -4,5 +4,6 @@
     "party_id": 0,
     "run_interval": 20000,
     "db_directory": "/db",
-    "data_stores_directory": "./mithril-signer/stores"
+    "data_stores_directory": "./mithril-signer/stores",
+    "store_retention_limit": 5
 }
\ No newline at end of file
diff --git a/mithril-signer/src/certificate_handler.rs b/mithril-signer/src/certificate_handler.rs
index 0e6395fa5..a868fe59e 100644
--- a/mithril-signer/src/certificate_handler.rs
+++ b/mithril-signer/src/certificate_handler.rs
@@ -281,6 +281,7 @@ mod tests {
             run_interval: 100,
             db_directory: Path::new("./db").to_path_buf(),
             data_stores_directory: Path::new("./stores").to_path_buf(),
+            store_retention_limit: None,
         };
         (server, config)
     }
diff --git a/mithril-signer/src/entities.rs b/mithril-signer/src/entities.rs
index 3d07bf570..c0c81e0d9 100644
--- a/mithril-signer/src/entities.rs
+++ b/mithril-signer/src/entities.rs
@@ -35,6 +35,9 @@ pub struct Config {
 
     /// Directory to store signer data (Stakes, Protocol initializers, ...)
     pub data_stores_directory: PathBuf,
+
+    /// Store retention limit. If set to None, no limit will be set.
+    pub store_retention_limit: Option<usize>,
 }
 
 impl Config {
diff --git a/mithril-signer/src/protocol_initializer_store.rs b/mithril-signer/src/protocol_initializer_store.rs
index 4bcb316c0..9f0ff31aa 100644
--- a/mithril-signer/src/protocol_initializer_store.rs
+++ b/mithril-signer/src/protocol_initializer_store.rs
@@ -1,19 +1,13 @@
 use async_trait::async_trait;
-use thiserror::Error;
+use mithril_common::store::{StoreError, StorePruner};
 use tokio::sync::RwLock;
 
 use mithril_common::crypto_helper::ProtocolInitializer;
 use mithril_common::entities::Epoch;
-use mithril_common::store::adapter::{AdapterError, StoreAdapter};
+use mithril_common::store::adapter::StoreAdapter;
 
 type Adapter = Box<dyn StoreAdapter<Key = Epoch, Record = ProtocolInitializer>>;
 
-#[derive(Debug, Error)]
-pub enum ProtocolInitializerStoreError {
-    #[error("adapter error {0}")]
-    AdapterError(#[from] AdapterError),
-}
-
 #[async_trait]
 /// Store the ProtocolInitializer used for each Epoch. This is useful because
 /// protocol parameters and stake distribution change over time.
@@ -23,47 +17,66 @@ pub trait ProtocolInitializerStorer: Sync + Send {
         &self,
         epoch: Epoch,
         protocol_initializer: ProtocolInitializer,
-    ) -> Result<Option<ProtocolInitializer>, ProtocolInitializerStoreError>;
+    ) -> Result<Option<ProtocolInitializer>, StoreError>;
 
     /// Fetch a protocol initializer if any saved for the given Epoch.
     async fn get_protocol_initializer(
         &self,
         epoch: Epoch,
-    ) -> Result<Option<ProtocolInitializer>, ProtocolInitializerStoreError>;
+    ) -> Result<Option<ProtocolInitializer>, StoreError>;
 
     /// Return the list of the N last saved protocol initializers if any.
     async fn get_last_protocol_initializer(
         &self,
         last: usize,
-    ) -> Result<Vec<(Epoch, ProtocolInitializer)>, ProtocolInitializerStoreError>;
+    ) -> Result<Vec<(Epoch, ProtocolInitializer)>, StoreError>;
 }
 /// Implementation of the ProtocolInitializerStorer
 pub struct ProtocolInitializerStore {
     adapter: RwLock<Adapter>,
+    retention_limit: Option<usize>,
 }
 
 impl ProtocolInitializerStore {
     /// Create a new ProtocolInitializerStore.
-    pub fn new(adapter: Adapter) -> Self {
+    pub fn new(adapter: Adapter, retention_limit: Option<usize>) -> Self {
         Self {
             adapter: RwLock::new(adapter),
+            retention_limit,
         }
     }
 }
 
+#[async_trait]
+impl StorePruner for ProtocolInitializerStore {
+    type Key = Epoch;
+    type Record = ProtocolInitializer;
+
+    fn get_adapter(
+        &self,
+    ) -> &RwLock<Box<dyn StoreAdapter<Key = Self::Key, Record = Self::Record>>> {
+        &self.adapter
+    }
+
+    fn get_max_records(&self) -> Option<usize> {
+        self.retention_limit
+    }
+}
+
 #[async_trait]
 impl ProtocolInitializerStorer for ProtocolInitializerStore {
     async fn save_protocol_initializer(
         &self,
         epoch: Epoch,
         protocol_initializer: ProtocolInitializer,
-    ) -> Result<Option<ProtocolInitializer>, ProtocolInitializerStoreError> {
+    ) -> Result<Option<ProtocolInitializer>, StoreError> {
         let previous_protocol_initializer = self.adapter.read().await.get_record(&epoch).await?;
         self.adapter
             .write()
             .await
             .store_record(&epoch, &protocol_initializer)
             .await?;
+        self.prune().await?;
 
         Ok(previous_protocol_initializer)
     }
@@ -71,7 +84,7 @@ impl ProtocolInitializerStorer for ProtocolInitializerStore {
     async fn get_protocol_initializer(
         &self,
         epoch: Epoch,
-    ) -> Result<Option<ProtocolInitializer>, ProtocolInitializerStoreError> {
+    ) -> Result<Option<ProtocolInitializer>, StoreError> {
         let record = self.adapter.read().await.get_record(&epoch).await?;
         Ok(record)
     }
@@ -79,7 +92,7 @@ impl ProtocolInitializerStorer for ProtocolInitializerStore {
     async fn get_last_protocol_initializer(
         &self,
         last: usize,
-    ) -> Result<Vec<(Epoch, ProtocolInitializer)>, ProtocolInitializerStoreError> {
+    ) -> Result<Vec<(Epoch, ProtocolInitializer)>, StoreError> {
         let records = self.adapter.read().await.get_last_n_records(last).await?;
 
         Ok(records)
@@ -110,7 +123,7 @@ mod tests {
         values
     }
 
-    fn init_store(nb_epoch: u64) -> ProtocolInitializerStore {
+    fn init_store(nb_epoch: u64, retention_limit: Option<usize>) -> ProtocolInitializerStore {
         let values = setup_protocol_initializers(nb_epoch);
 
         let values = if !values.is_empty() {
@@ -120,13 +133,13 @@ mod tests {
         };
         let adapter: MemoryAdapter<Epoch, ProtocolInitializer> =
             MemoryAdapter::new(values).unwrap();
-        ProtocolInitializerStore::new(Box::new(adapter))
+        ProtocolInitializerStore::new(Box::new(adapter), retention_limit)
     }
 
     #[tokio::test]
     async fn save_key_in_empty_store() {
         let protocol_initializers = setup_protocol_initializers(1);
-        let store = init_store(0);
+        let store = init_store(0, None);
         let res = store
             .save_protocol_initializer(
                 protocol_initializers[0].0,
@@ -141,7 +154,7 @@ mod tests {
     #[tokio::test]
     async fn update_protocol_initializer_in_store() {
         let protocol_initializers = setup_protocol_initializers(2);
-        let store = init_store(1);
+        let store = init_store(1, None);
         let res = store
             .save_protocol_initializer(
                 protocol_initializers[0].0,
@@ -156,7 +169,7 @@ mod tests {
 
     #[tokio::test]
     async fn get_protocol_initializer_for_empty_epoch() {
-        let store = init_store(2);
+        let store = init_store(2, None);
         let res = store.get_protocol_initializer(Epoch(0)).await.unwrap();
 
         assert!(res.is_none());
@@ -164,9 +177,28 @@ mod tests {
 
     #[tokio::test]
     async fn get_protocol_initializer_for_existing_epoch() {
-        let store = init_store(2);
+        let store = init_store(2, None);
         let res = store.get_protocol_initializer(Epoch(1)).await.unwrap();
 
         assert!(res.is_some());
     }
+
+    #[tokio::test]
+    async fn check_retention_limit() {
+        let store = init_store(4, Some(2));
+        let protocol_initializers = setup_protocol_initializers(1);
+        let _ = store
+            .save_protocol_initializer(
+                protocol_initializers[0].0,
+                (&protocol_initializers[0].1).to_owned(),
+            )
+            .await
+            .unwrap();
+
+        assert!(store
+            .get_protocol_initializer(Epoch(1))
+            .await
+            .unwrap()
+            .is_none());
+    }
 }
diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs
index e96ab4bbd..6a2f0c400 100644
--- a/mithril-signer/src/runtime/runner.rs
+++ b/mithril-signer/src/runtime/runner.rs
@@ -417,7 +417,7 @@ mod tests {
         let adapter: MemoryAdapter<Epoch, ProtocolInitializer> = MemoryAdapter::new(None).unwrap();
         let chain_observer = Arc::new(FakeObserver::default());
         SignerServices {
-            stake_store: Arc::new(StakeStore::new(Box::new(DumbStoreAdapter::new()))),
+            stake_store: Arc::new(StakeStore::new(Box::new(DumbStoreAdapter::new()), None)),
             certificate_handler: Arc::new(DumbCertificateHandler::default()),
             chain_observer: chain_observer.clone(),
             digester: Arc::new(DumbImmutableDigester::new(DIGESTER_RESULT, true)),
@@ -427,7 +427,10 @@ mod tests {
                 Arc::new(DumbImmutableFileObserver::default()),
                 CardanoNetwork::TestNet(42),
             )),
-            protocol_initializer_store: Arc::new(ProtocolInitializerStore::new(Box::new(adapter))),
+            protocol_initializer_store: Arc::new(ProtocolInitializerStore::new(
+                Box::new(adapter),
+                None,
+            )),
         }
     }
 
@@ -446,6 +449,7 @@ mod tests {
             party_id: "1".to_string(),
             run_interval: 100,
             data_stores_directory: PathBuf::new(),
+            store_retention_limit: None,
         };
 
         SignerRunner {
diff --git a/mithril-signer/src/runtime/signer_services.rs b/mithril-signer/src/runtime/signer_services.rs
index 08aa8c43d..53cd84a9d 100644
--- a/mithril-signer/src/runtime/signer_services.rs
+++ b/mithril-signer/src/runtime/signer_services.rs
@@ -51,9 +51,13 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> {
         }
 
         let sqlite_db_path = Some(self.config.data_stores_directory.join("signer.sqlite3"));
-        let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new(Box::new(
-            SQLiteAdapter::new("protocol_initializer", sqlite_db_path.clone())?,
-        )));
+        let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new(
+            Box::new(SQLiteAdapter::new(
+                "protocol_initializer",
+                sqlite_db_path.clone(),
+            )?),
+            self.config.store_retention_limit,
+        ));
         let single_signer = Arc::new(MithrilSingleSigner::new(self.config.party_id.clone()));
         let certificate_handler = Arc::new(CertificateHandlerHTTPClient::new(
             self.config.aggregator_endpoint.clone(),
@@ -62,10 +66,10 @@ impl<'a> ServiceBuilder for ProductionServiceBuilder<'a> {
             self.config.db_directory.clone(),
             slog_scope::logger(),
         ));
-        let stake_store = Arc::new(StakeStore::new(Box::new(SQLiteAdapter::new(
-            "stake",
-            sqlite_db_path,
-        )?)));
+        let stake_store = Arc::new(StakeStore::new(
+            Box::new(SQLiteAdapter::new("stake", sqlite_db_path)?),
+            self.config.store_retention_limit,
+        ));
         let chain_observer = Arc::new(CardanoCliChainObserver::new(Box::new(
             CardanoCliRunner::new(
                 self.config.cardano_cli_path.clone(),
@@ -147,6 +151,7 @@ mod tests {
             run_interval: 1000,
             db_directory: PathBuf::new(),
             data_stores_directory: stores_dir.clone(),
+            store_retention_limit: None,
         };
 
         assert!(!stores_dir.exists());


Test patch:
diff --git a/mithril-aggregator/tests/test_extensions/dependency.rs b/mithril-aggregator/tests/test_extensions/dependency.rs
index ced15a71d..49b9f40b1 100644
--- a/mithril-aggregator/tests/test_extensions/dependency.rs
+++ b/mithril-aggregator/tests/test_extensions/dependency.rs
@@ -43,6 +43,7 @@ pub async fn initialize_dependencies(
         snapshot_directory: PathBuf::new(),
         data_stores_directory: PathBuf::new(),
         genesis_verification_key: key_encode_hex(&genesis_verification_key).unwrap(),
+        store_retention_limit: None,
     };
     let certificate_pending_store = Arc::new(CertificatePendingStore::new(Box::new(
         MemoryAdapter::new(None).unwrap(),
@@ -50,16 +51,22 @@ pub async fn initialize_dependencies(
     let certificate_store = Arc::new(CertificateStore::new(Box::new(
         MemoryAdapter::new(None).unwrap(),
     )));
-    let verification_key_store = Arc::new(VerificationKeyStore::new(Box::new(
-        MemoryAdapter::new(None).unwrap(),
-    )));
-    let stake_store = Arc::new(StakeStore::new(Box::new(MemoryAdapter::new(None).unwrap())));
-    let single_signature_store = Arc::new(SingleSignatureStore::new(Box::new(
-        MemoryAdapter::new(None).unwrap(),
-    )));
-    let protocol_parameters_store = Arc::new(ProtocolParametersStore::new(Box::new(
-        MemoryAdapter::new(None).unwrap(),
-    )));
+    let verification_key_store = Arc::new(VerificationKeyStore::new(
+        Box::new(MemoryAdapter::new(None).unwrap()),
+        config.store_retention_limit,
+    ));
+    let stake_store = Arc::new(StakeStore::new(
+        Box::new(MemoryAdapter::new(None).unwrap()),
+        config.store_retention_limit,
+    ));
+    let single_signature_store = Arc::new(SingleSignatureStore::new(
+        Box::new(MemoryAdapter::new(None).unwrap()),
+        config.store_retention_limit,
+    ));
+    let protocol_parameters_store = Arc::new(ProtocolParametersStore::new(
+        Box::new(MemoryAdapter::new(None).unwrap()),
+        None,
+    ));
     let multi_signer = MultiSignerImpl::new(
         verification_key_store.clone(),
         stake_store.clone(),
diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs
index 63e59b667..30f8c0f1f 100644
--- a/mithril-signer/tests/test_extensions/state_machine_tester.rs
+++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs
@@ -56,6 +56,7 @@ impl StateMachineTester {
             party_id: "99999999999999999999999999999999".to_string(),
             run_interval: 5000,
             data_stores_directory: PathBuf::new(),
+            store_retention_limit: None,
         };
 
         let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter);
@@ -78,11 +79,15 @@ impl StateMachineTester {
         ));
         let certificate_handler = Arc::new(FakeAggregator::new(beacon_provider.clone()));
         let digester = Arc::new(DumbImmutableDigester::new("DIGEST", true));
-        let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new(Box::new(
-            MemoryAdapter::new(None).unwrap(),
-        )));
+        let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new(
+            Box::new(MemoryAdapter::new(None).unwrap()),
+            config.store_retention_limit,
+        ));
         let single_signer = Arc::new(MithrilSingleSigner::new(config.party_id.clone()));
-        let stake_store = Arc::new(StakeStore::new(Box::new(MemoryAdapter::new(None).unwrap())));
+        let stake_store = Arc::new(StakeStore::new(
+            Box::new(MemoryAdapter::new(None).unwrap()),
+            config.store_retention_limit,
+        ));
 
         let services = SignerServices {
             certificate_handler: certificate_handler.clone(),