Repository: mintlayer/mintlayer-core
Pull request #648 (2023-01-24 19:47:47)

Support lmdb database map resize with minimal overhead
Replaces #558 


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index aa1a1e156..9907f4b0e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -744,7 +744,7 @@ dependencies = [
  "num-derive",
  "num-traits",
  "parity-scale-codec",
- "rand",
+ "rand 0.8.5",
  "rand_chacha",
  "ripemd",
  "rstest",
@@ -991,6 +991,23 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
 
+[[package]]
+name = "fs4"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9813c3dc174931eff4bd78609debba56465b7c1da888576d21636b601a46790"
+dependencies = [
+ "libc",
+ "rustix 0.35.13",
+ "winapi",
+]
+
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
 [[package]]
 name = "funty"
 version = "2.0.0"
@@ -1393,6 +1410,12 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "io-lifetimes"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074"
+
 [[package]]
 name = "io-lifetimes"
 version = "1.0.3"
@@ -1410,8 +1433,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
 dependencies = [
  "hermit-abi",
- "io-lifetimes",
- "rustix",
+ "io-lifetimes 1.0.3",
+ "rustix 0.36.5",
  "windows-sys 0.42.0",
 ]
 
@@ -1499,7 +1522,7 @@ dependencies = [
  "hyper",
  "jsonrpsee-types",
  "parking_lot",
- "rand",
+ "rand 0.8.5",
  "rustc-hash",
  "serde",
  "serde_json",
@@ -1639,6 +1662,12 @@ dependencies = [
  "threadpool",
 ]
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.0.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
+
 [[package]]
 name = "linux-raw-sys"
 version = "0.1.4"
@@ -1647,19 +1676,21 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
 
 [[package]]
 name = "lmdb-mintlayer"
-version = "0.15.3"
-source = "git+https://github.com/mintlayer/lmdb-rs-mintlayer.git?tag=v0.15.3#291566adab9ba4c1e17c788ff3c6422380c4d13a"
+version = "0.16.2"
+source = "git+https://github.com/mintlayer/lmdb-rs-mintlayer.git?tag=v0.16.2#fb33e56a76697dd6f484c6d21f89a99ef9145e93"
 dependencies = [
  "bitflags",
  "byteorder",
+ "fs4",
  "libc",
  "lmdb-rkv-sys",
+ "num",
 ]
 
 [[package]]
 name = "lmdb-rkv-sys"
-version = "0.11.2"
-source = "git+https://github.com/mintlayer/lmdb-rs-mintlayer.git?tag=v0.15.3#291566adab9ba4c1e17c788ff3c6422380c4d13a"
+version = "0.11.3"
+source = "git+https://github.com/mintlayer/lmdb-rs-mintlayer.git?tag=v0.16.2#fb33e56a76697dd6f484c6d21f89a99ef9145e93"
 dependencies = [
  "cc",
  "libc",
@@ -2281,7 +2312,7 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9"
 dependencies = [
- "rand",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -2402,7 +2433,7 @@ dependencies = [
  "lazy_static",
  "num-traits",
  "quick-error 2.0.1",
- "rand",
+ "rand 0.8.5",
  "rand_chacha",
  "rand_xorshift",
  "regex-syntax",
@@ -2437,6 +2468,19 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
 
+[[package]]
+name = "rand"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
+dependencies = [
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.3.1",
+ "rdrand",
+ "winapi",
+]
+
 [[package]]
 name = "rand"
 version = "0.8.5"
@@ -2458,6 +2502,15 @@ dependencies = [
  "rand_core 0.6.4",
 ]
 
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+dependencies = [
+ "rand_core 0.4.2",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.4.2"
@@ -2504,6 +2557,15 @@ dependencies = [
  "num_cpus",
 ]
 
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.16"
@@ -2658,6 +2720,20 @@ dependencies = [
  "semver 1.0.16",
 ]
 
+[[package]]
+name = "rustix"
+version = "0.35.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes 0.7.5",
+ "libc",
+ "linux-raw-sys 0.0.46",
+ "windows-sys 0.42.0",
+]
+
 [[package]]
 name = "rustix"
 version = "0.36.5"
@@ -2666,9 +2742,9 @@ checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
 dependencies = [
  "bitflags",
  "errno",
- "io-lifetimes",
+ "io-lifetimes 1.0.3",
  "libc",
- "linux-raw-sys",
+ "linux-raw-sys 0.1.4",
  "windows-sys 0.42.0",
 ]
 
@@ -2801,7 +2877,7 @@ version = "0.24.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3"
 dependencies = [
- "rand",
+ "rand 0.8.5",
  "secp256k1-sys",
 ]
 
@@ -2957,7 +3033,7 @@ dependencies = [
  "arraytools",
  "hex-literal",
  "parity-scale-codec",
- "rand",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -3108,7 +3184,7 @@ dependencies = [
  "futures-util",
  "log",
  "pin-project",
- "rand",
+ "rand 0.8.5",
  "snow",
  "thiserror",
  "tokio",
@@ -3136,7 +3212,7 @@ dependencies = [
  "http",
  "httparse",
  "log",
- "rand",
+ "rand 0.8.5",
  "sha-1 0.9.8",
 ]
 
@@ -3224,8 +3300,10 @@ version = "0.1.0"
 dependencies = [
  "lmdb-mintlayer",
  "logging",
+ "rstest",
  "storage-backend-test-suite",
  "storage-core",
+ "tempdir",
  "test-utils",
  "utils",
 ]
@@ -3291,6 +3369,16 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
 
+[[package]]
+name = "tempdir"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
+dependencies = [
+ "rand 0.4.6",
+ "remove_dir_all",
+]
+
 [[package]]
 name = "tempfile"
 version = "3.3.0"
diff --git a/chainstate/launcher/src/lib.rs b/chainstate/launcher/src/lib.rs
index 85273f3fc..349a0e91d 100644
--- a/chainstate/launcher/src/lib.rs
+++ b/chainstate/launcher/src/lib.rs
@@ -63,7 +63,12 @@ pub fn make_chainstate(
     // a different set of generic parameters in each case.
     match storage_backend {
         StorageBackendConfig::Lmdb => {
-            let storage = storage_lmdb::Lmdb::new(datadir.join(SUBDIRECTORY_LMDB));
+            let storage = storage_lmdb::Lmdb::new(
+                datadir.join(SUBDIRECTORY_LMDB),
+                Default::default(),
+                Default::default(),
+                Default::default(),
+            );
             make_chainstate_and_storage_impl(storage, chain_config, chainstate_config)
         }
         StorageBackendConfig::InMemory => {
diff --git a/chainstate/src/detail/mod.rs b/chainstate/src/detail/mod.rs
index 06996bdf1..d061a87f3 100644
--- a/chainstate/src/detail/mod.rs
+++ b/chainstate/src/detail/mod.rs
@@ -106,7 +106,7 @@ impl<S: BlockchainStorage, V: TransactionVerificationStrategy> Chainstate<S, V>
         &mut self,
     ) -> chainstate_storage::Result<chainstateref::ChainstateRef<TxRw<'_, S>, OrphanBlocksRefMut, V>>
     {
-        let db_tx = self.chainstate_storage.transaction_rw()?;
+        let db_tx = self.chainstate_storage.transaction_rw(None)?;
         Ok(chainstateref::ChainstateRef::new_rw(
             &self.chain_config,
             &self.chainstate_config,
@@ -234,8 +234,11 @@ impl<S: BlockchainStorage, V: TransactionVerificationStrategy> Chainstate<S, V>
     /// Check that transaction index state is consistent between DB and config.
     fn process_tx_index_enabled_flag(&mut self) -> Result<(), BlockError> {
         use chainstate_storage::{BlockchainStorageWrite, TransactionRw};
-        let mut db_tx =
-            self.chainstate_storage.transaction_rw().map_err(BlockError::from).log_err()?;
+        let mut db_tx = self
+            .chainstate_storage
+            .transaction_rw(None)
+            .map_err(BlockError::from)
+            .log_err()?;
 
         let tx_index_enabled = db_tx
             .get_is_mainchain_tx_index_enabled()
@@ -385,8 +388,11 @@ impl<S: BlockchainStorage, V: TransactionVerificationStrategy> Chainstate<S, V>
             .expect("Genesis not constructed correctly");
 
         // Initialize storage with given info
-        let mut db_tx =
-            self.chainstate_storage.transaction_rw().map_err(BlockError::from).log_err()?;
+        let mut db_tx = self
+            .chainstate_storage
+            .transaction_rw(None)
+            .map_err(BlockError::from)
+            .log_err()?;
         db_tx
             .set_best_block_id(&genesis_id)
             .map_err(BlockError::StorageError)
diff --git a/chainstate/storage/src/internal/mod.rs b/chainstate/storage/src/internal/mod.rs
index 509c3813b..d35d463d7 100644
--- a/chainstate/storage/src/internal/mod.rs
+++ b/chainstate/storage/src/internal/mod.rs
@@ -155,37 +155,36 @@ impl<'tx, B: storage::Backend + 'tx> Transactional<'tx> for Store<B> {
         self.0.transaction_ro().map_err(crate::Error::from).map(StoreTxRo)
     }
 
-    fn transaction_rw<'st: 'tx>(&'st self) -> crate::Result<Self::TransactionRw> {
-        self.0.transaction_rw().map_err(crate::Error::from).map(StoreTxRw)
+    fn transaction_rw<'st: 'tx>(
+        &'st self,
+        size: Option<usize>,
+    ) -> crate::Result<Self::TransactionRw> {
+        self.0.transaction_rw(size).map_err(crate::Error::from).map(StoreTxRw)
     }
 }
 
 impl<B: storage::Backend + 'static> BlockchainStorage for Store<B> {}
 
 macro_rules! delegate_to_transaction {
-    ($(fn $f:ident $args:tt -> $ret:ty;)*) => {
-        $(delegate_to_transaction!(@SELF [$f ($ret)] $args);)*
+    ($($(#[size=$s:expr])? fn $func:ident $args:tt -> $ret:ty;)*) => {
+        $(delegate_to_transaction!(@FN $(#[size = $s])? $func $args -> $ret);)*
     };
-    (@SELF $done:tt (&self $(, $($rest:tt)*)?)) => {
-        delegate_to_transaction!(
-            @BODY transaction_ro (Ok) $done ($($($rest)*)?)
-        );
-    };
-    (@SELF $done:tt (&mut self $(, $($rest:tt)*)?)) => {
-        delegate_to_transaction!(
-            @BODY transaction_rw (StoreTxRw::commit) mut $done ($($($rest)*)?)
-        );
+    (@FN $f:ident(&self $(, $arg:ident: $aty:ty)* $(,)?) -> $ret:ty) => {
+        fn $f(&self $(, $arg: $aty)*) -> $ret {
+            self.transaction_ro().and_then(|tx| tx.$f($($arg),*))
+        }
     };
-    (@BODY $txfunc:ident ($commit:path) $($mut:ident)?
-        [$f:ident ($ret:ty)]
-        ($($arg:ident: $aty:ty),* $(,)?)
-    ) => {
-        fn $f(&$($mut)? self $(, $arg: $aty)*) -> $ret {
-            let $($mut)? tx = self.$txfunc()?;
+    (@FN $(#[size=$s:expr])? $f:ident(&mut self $(, $arg:ident: $aty:ty)* $(,)?) -> $ret:ty) => {
+        fn $f(&mut self $(, $arg: $aty)*) -> $ret {
+            let size = delegate_to_transaction!(@SIZE $($s)?);
+            let mut tx = self.transaction_rw(size)?;
             let val = tx.$f($($arg),*)?;
-            $commit(tx).map(|_| val)
+            tx.commit()?;
+            Ok(val)
         }
     };
+    (@SIZE) => { None };
+    (@SIZE $s:literal) => { Some($s) };
 }
 
 impl<B: storage::Backend> BlockchainStorageRead for Store<B> {
diff --git a/chainstate/storage/src/lib.rs b/chainstate/storage/src/lib.rs
index a20dba940..74395cdff 100644
--- a/chainstate/storage/src/lib.rs
+++ b/chainstate/storage/src/lib.rs
@@ -190,7 +190,7 @@ pub trait Transactional<'t> {
     fn transaction_ro<'s: 't>(&'s self) -> crate::Result<Self::TransactionRo>;
 
     /// Start a read-write transaction.
-    fn transaction_rw<'s: 't>(&'s self) -> crate::Result<Self::TransactionRw>;
+    fn transaction_rw<'s: 't>(&'s self, size: Option<usize>) -> crate::Result<Self::TransactionRw>;
 }
 
 pub trait BlockchainStorage: BlockchainStorageWrite + for<'tx> Transactional<'tx> + Send {}
diff --git a/chainstate/storage/src/mock.rs b/chainstate/storage/src/mock.rs
index c7b994454..5cf8bc48c 100644
--- a/chainstate/storage/src/mock.rs
+++ b/chainstate/storage/src/mock.rs
@@ -183,7 +183,7 @@ mockall::mock! {
         type TransactionRo = MockStoreTxRo;
         type TransactionRw = MockStoreTxRw;
         fn transaction_ro<'st>(&'st self) -> crate::Result<MockStoreTxRo> where 'st: 'tx;
-        fn transaction_rw<'st>(&'st self) -> crate::Result<MockStoreTxRw> where 'st: 'tx;
+        fn transaction_rw<'st>(&'st self, size: Option<usize>) -> crate::Result<MockStoreTxRw> where 'st: 'tx;
     }
 
     impl crate::BlockchainStorage for Store {}
@@ -486,7 +486,7 @@ mod tests {
     fn mock_transaction() {
         // Set up the mock store
         let mut store = MockStore::new();
-        store.expect_transaction_rw().returning(|| {
+        store.expect_transaction_rw().returning(|_| {
             let mut mock_tx = MockStoreTxRw::new();
             mock_tx.expect_get_storage_version().return_const(Ok(3));
             mock_tx
@@ -498,7 +498,7 @@ mod tests {
         });
 
         // Test some code against the mock
-        let mut tx = store.transaction_rw().unwrap();
+        let mut tx = store.transaction_rw(None).unwrap();
         let v = tx.get_storage_version().unwrap();
         tx.set_storage_version(v + 1).unwrap();
         tx.commit().unwrap();
@@ -524,7 +524,7 @@ mod tests {
         block: &Block,
     ) -> &'static str {
         (|| {
-            let mut tx = store.transaction_rw().unwrap();
+            let mut tx = store.transaction_rw(None).unwrap();
             // Get current best block ID
             let _best_id = match tx.get_best_block_id()? {
                 None => return Ok("top not set"),
@@ -592,7 +592,7 @@ mod tests {
         let (block0, block1) = sample_data();
         let block1_id = block1.get_id();
         let mut store = MockStore::new();
-        store.expect_transaction_rw().returning(move || {
+        store.expect_transaction_rw().returning(move |_| {
             let mut tx = MockStoreTxRw::new();
             tx.expect_get_best_block_id().return_const(Ok(Some(block0.get_id().into())));
             tx.expect_add_block().return_const(Ok(()));
@@ -612,7 +612,7 @@ mod tests {
     fn attach_to_top_no_best_block() {
         let (_block0, block1) = sample_data();
         let mut store = MockStore::new();
-        store.expect_transaction_rw().returning(move || {
+        store.expect_transaction_rw().returning(move |_| {
             let mut tx = MockStoreTxRw::new();
             tx.expect_get_best_block_id().return_const(Ok(None));
             tx.expect_abort().return_const(());
@@ -628,7 +628,7 @@ mod tests {
         let (_block0, block1) = sample_data();
         let top_id = Id::new(H256([0x99; 32]));
         let mut store = MockStore::new();
-        store.expect_transaction_rw().returning(move || {
+        store.expect_transaction_rw().returning(move |_| {
             let mut tx = MockStoreTxRw::new();
             tx.expect_get_best_block_id().return_const(Ok(Some(top_id)));
             tx.expect_abort().return_const(());
@@ -644,7 +644,7 @@ mod tests {
         let (block0, block1) = sample_data();
         let block1_id = block1.get_id();
         let mut store = MockStore::new();
-        store.expect_transaction_rw().returning(move || {
+        store.expect_transaction_rw().returning(move |_| {
             let mut tx = MockStoreTxRw::new();
             tx.expect_get_best_block_id().return_const(Ok(Some(block0.get_id().into())));
             tx.expect_add_block().return_const(Ok(()));
diff --git a/node/src/runner.rs b/node/src/runner.rs
index bff6be8e9..e2018ac23 100644
--- a/node/src/runner.rs
+++ b/node/src/runner.rs
@@ -74,6 +74,9 @@ pub async fn initialize(
     // TODO: Replace Lmdb with Sqlite backend when it's ready
     let peerdb_storage = PeerDbStorageImpl::new(storage_lmdb::Lmdb::new(
         node_config.datadir.join("peerdb-lmdb"),
+        Default::default(),
+        Default::default(),
+        Default::default(),
     ))?;
     let p2p = manager.add_subsystem(
         "p2p",
diff --git a/p2p/src/peer_manager/peerdb/storage_impl.rs b/p2p/src/peer_manager/peerdb/storage_impl.rs
index 6484ce3eb..8c9ef5106 100644
--- a/p2p/src/peer_manager/peerdb/storage_impl.rs
+++ b/p2p/src/peer_manager/peerdb/storage_impl.rs
@@ -44,7 +44,7 @@ impl<'tx, B: storage::Backend + 'tx> PeerDbTransactional<'tx> for PeerDbStorageI
     }
 
     fn transaction_rw<'st: 'tx>(&'st self) -> crate::Result<Self::TransactionRw> {
-        self.0.transaction_rw().map_err(crate::P2pError::from).map(PeerDbStoreTxRw)
+        self.0.transaction_rw(None).map_err(crate::P2pError::from).map(PeerDbStoreTxRw)
     }
 }
 
diff --git a/storage/core/src/adaptor/locking/mod.rs b/storage/core/src/adaptor/locking/mod.rs
index 7f1e5b090..c2709cb4d 100644
--- a/storage/core/src/adaptor/locking/mod.rs
+++ b/storage/core/src/adaptor/locking/mod.rs
@@ -133,7 +133,7 @@ impl<'tx, T: 'tx + ReadOps> backend::TransactionalRo<'tx> for TransactionLockImp
 impl<'tx, T: 'tx + ReadOps + WriteOps> backend::TransactionalRw<'tx> for TransactionLockImpl<T> {
     type TxRw = TxRw<'tx, T>;
 
-    fn transaction_rw<'st: 'tx>(&'st self) -> crate::Result<Self::TxRw> {
+    fn transaction_rw<'st: 'tx>(&'st self, _: Option<usize>) -> crate::Result<Self::TxRw> {
         Ok(TxRw {
             db: self.db.write().expect("lock to be alive"),
             deltas: vec![BTreeMap::new(); self.num_maps],
diff --git a/storage/core/src/adaptor/mod.rs b/storage/core/src/adaptor/mod.rs
index a19f959a2..126dff07d 100644
--- a/storage/core/src/adaptor/mod.rs
+++ b/storage/core/src/adaptor/mod.rs
@@ -32,5 +32,5 @@ pub trait Construct: Sized {
 
 /// Core operations on storage without transaction support
 pub trait CoreOps: backend::ReadOps + backend::WriteOps + Construct {}
-// CoreOps is automatically implemented if pre-requisits are satisfied
+// CoreOps is automatically implemented if pre-requisites are satisfied
 impl<T: backend::ReadOps + backend::WriteOps + Construct> CoreOps for T {}
diff --git a/storage/core/src/backend.rs b/storage/core/src/backend.rs
index 5e142a79a..91187ae6d 100644
--- a/storage/core/src/backend.rs
+++ b/storage/core/src/backend.rs
@@ -76,7 +76,7 @@ pub trait TransactionalRw<'tx> {
     type TxRw: TxRw + 'tx;
 
     /// Start a read-write transaction
-    fn transaction_rw<'st: 'tx>(&'st self) -> crate::Result<Self::TxRw>;
+    fn transaction_rw<'st: 'tx>(&'st self, size: Option<usize>) -> crate::Result<Self::TxRw>;
 }
 
 /// Storage backend internal implementation type
diff --git a/storage/core/src/error.rs b/storage/core/src/error.rs
index 55b32cd49..b5b766461 100644
--- a/storage/core/src/error.rs
+++ b/storage/core/src/error.rs
@@ -28,7 +28,7 @@ pub enum Recoverable {
     #[error("The database has temporarily exhausted some resource")]
     TemporarilyUnavailable,
 
-    /// Database file failed to initialise. For example, if the DB file format is wrong.
+    /// Database file failed to initialize. For example, if the DB file format is wrong.
     #[error("The database file failed to initialize")]
     DbInit,
 
diff --git a/storage/lmdb/Cargo.toml b/storage/lmdb/Cargo.toml
index d34351507..6acf5552b 100644
--- a/storage/lmdb/Cargo.toml
+++ b/storage/lmdb/Cargo.toml
@@ -9,11 +9,13 @@ logging = { path = '../../logging' }
 storage-core = { path = '../core' }
 utils = { path = '../../utils' }
 
-lmdb-mintlayer = { git = 'https://github.com/mintlayer/lmdb-rs-mintlayer.git', tag = 'v0.15.3' }
+lmdb-mintlayer = { git = 'https://github.com/mintlayer/lmdb-rs-mintlayer.git', tag = 'v0.16.2' }
 
 [dev-dependencies]
 storage-backend-test-suite = { path = "../backend-test-suite" }
 test-utils = { path = "../../test-utils" }
+tempdir = "0.3"
+rstest = "0.16"
 
 [[test]]
 name = "backend"
diff --git a/storage/lmdb/src/initial_map_size.rs b/storage/lmdb/src/initial_map_size.rs
new file mode 100644
index 000000000..6f4384125
--- /dev/null
+++ b/storage/lmdb/src/initial_map_size.rs
@@ -0,0 +1,41 @@
+// Copyright (c) 2022 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::memsize::MemSize;
+
+#[derive(Default)]
+pub struct InitialMapSize {
+    initial_map_size: Option<MemSize>,
+}
+
+impl InitialMapSize {
+    pub fn into_memsize(&self) -> Option<MemSize> {
+        self.initial_map_size
+    }
+}
+
+impl From<MemSize> for InitialMapSize {
+    fn from(initial_map_size: MemSize) -> Self {
+        Self {
+            initial_map_size: Some(initial_map_size),
+        }
+    }
+}
+
+impl From<InitialMapSize> for Option<MemSize> {
+    fn from(initial_map_size: InitialMapSize) -> Self {
+        initial_map_size.initial_map_size
+    }
+}
diff --git a/storage/lmdb/src/lib.rs b/storage/lmdb/src/lib.rs
index 4e45390b9..d607ef699 100644
--- a/storage/lmdb/src/lib.rs
+++ b/storage/lmdb/src/lib.rs
@@ -14,19 +14,24 @@
 // limitations under the License.
 
 mod error;
-mod remap;
-
-pub use remap::MemSize;
+pub mod initial_map_size;
+pub mod memsize;
+pub mod resize_callback;
 
+use std::sync::atomic::{AtomicBool, Ordering};
 use std::{borrow::Cow, path::PathBuf};
 
+use initial_map_size::InitialMapSize;
 use lmdb::Cursor;
+use resize_callback::MapResizeCallback;
 use storage_core::{
     backend::{self, TransactionalRo, TransactionalRw},
     info::{DbDesc, MapDesc},
     Data, DbIndex,
 };
-use utils::sync::{Arc, RwLock, RwLockReadGuard};
+use utils::sync::Arc;
+
+pub use lmdb::{DatabaseResizeInfo, DatabaseResizeSettings};
 
 /// Identifiers of the list of databases (key-value maps)
 #[derive(Eq, PartialEq, Debug, Clone)]
@@ -67,8 +72,7 @@ impl<'tx, C: lmdb::Cursor<'tx>> Iterator for PrefixIter<'tx, C> {
 
 pub struct DbTx<'m, Tx> {
     tx: Tx,
-    dbs: &'m DbList,
-    _map_token: RwLockReadGuard<'m, remap::MemMapController>,
+    backend: &'m LmdbImpl,
 }
 
 type DbTxRo<'a> = DbTx<'a, lmdb::RoTransaction<'a>>;
@@ -82,7 +86,8 @@ impl<'s, 'i, Tx: lmdb::Transaction> backend::PrefixIter<'i> for DbTx<'s, Tx> {
         idx: DbIndex,
         prefix: Data,
     ) -> storage_core::Result<Self::Iterator> {
-        let cursor = self.tx.open_ro_cursor(self.dbs[idx]).or_else(error::process_with_err)?;
+        let cursor =
+            self.tx.open_ro_cursor(self.backend.dbs[idx]).or_else(error::process_with_err)?;
         let iter = if prefix.is_empty() {
             cursor.into_iter_start()
         } else {
@@ -95,7 +100,7 @@ impl<'s, 'i, Tx: lmdb::Transaction> backend::PrefixIter<'i> for DbTx<'s, Tx> {
 impl<Tx: lmdb::Transaction> backend::ReadOps for DbTx<'_, Tx> {
     fn get(&self, idx: DbIndex, key: &[u8]) -> storage_core::Result<Option<Cow<[u8]>>> {
         self.tx
-            .get(self.dbs[idx], &key)
+            .get(self.backend.dbs[idx], &key)
             .map_or_else(error::process_with_none, |x| Ok(Some(x.into())))
     }
 }
@@ -103,12 +108,16 @@ impl<Tx: lmdb::Transaction> backend::ReadOps for DbTx<'_, Tx> {
 impl backend::WriteOps for DbTx<'_, lmdb::RwTransaction<'_>> {
     fn put(&mut self, idx: DbIndex, key: Data, val: Data) -> storage_core::Result<()> {
         self.tx
-            .put(self.dbs[idx], &key, &val, lmdb::WriteFlags::empty())
+            .put(self.backend.dbs[idx], &key, &val, lmdb::WriteFlags::empty())
+            .map_err(|err| self.backend.schedule_map_resize_if_map_full(err))
             .or_else(error::process_with_unit)
     }
 
     fn del(&mut self, idx: DbIndex, key: &[u8]) -> storage_core::Result<()> {
-        self.tx.del(self.dbs[idx], &key, None).or_else(error::process_with_unit)
+        self.tx
+            .del(self.backend.dbs[idx], &key, None)
+            .map_err(|err| self.backend.schedule_map_resize_if_map_full(err))
+            .or_else(error::process_with_unit)
     }
 }
 
@@ -116,7 +125,9 @@ impl backend::TxRo for DbTxRo<'_> {}
 
 impl backend::TxRw for DbTxRw<'_> {
     fn commit(self) -> storage_core::Result<()> {
-        lmdb::Transaction::commit(self.tx).or_else(error::process_with_unit)
+        lmdb::Transaction::commit(self.tx)
+            .map_err(|e| self.backend.resize_if_map_full(e))
+            .or_else(error::process_with_unit)
     }
 }
 
@@ -128,14 +139,8 @@ pub struct LmdbImpl {
     /// List of open databases
     dbs: DbList,
 
-    /// Guards access to memory map.
-    ///
-    /// * Shared (read) lock is required for running transactions, both read-only and read-write.
-    /// * Exclusive (write) lock is required to reallocate the memory map
-    map_token: Arc<RwLock<remap::MemMapController>>,
-
-    /// Size required to write a transaction
-    tx_size: MemSize,
+    /// Schedule a database resize of the database map
+    map_resize_scheduled: Arc<AtomicBool>,
 }
 
 impl LmdbImpl {
@@ -145,13 +150,50 @@ impl LmdbImpl {
         start_tx: impl FnOnce(&'a lmdb::Environment) -> Result<Tx, lmdb::Error>,
     ) -> storage_core::Result<DbTx<'a, Tx>> {
         // Make sure map token is acquired before starting the transaction below
-        let _map_token = self.map_token.read().expect("mutex to be alive");
         Ok(DbTx {
             tx: start_tx(&self.env).or_else(error::process_with_err)?,
-            dbs: &self.dbs,
-            _map_token,
+            backend: self,
         })
     }
+
+    fn schedule_map_resize(&self) {
+        self.map_resize_scheduled.store(true, Ordering::SeqCst);
+    }
+
+    fn unschedule_map_resize(&self) {
+        self.map_resize_scheduled.store(false, Ordering::SeqCst);
+    }
+
+    fn resize_if_resize_scheduled(&self) {
+        // simulate an atomic test_and_set(), where we check if a resize is scheduled, and we also set it to false
+        if self
+            .map_resize_scheduled
+            .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed)
+            .unwrap_or(false)
+        {
+            self.env.do_resize(None).expect("Failed to resize after a trigger to resize");
+        }
+    }
+
+    /// If the lmdb map is full, perform a resize. This results in fixing
+    /// a recoverable error of MDB_MAP_FULL to work out-of-the-box by just
+    /// retrying one or more times
+    fn resize_if_map_full(&self, err: lmdb::Error) -> lmdb::Error {
+        if err == lmdb::Error::MapFull {
+            self.env
+                .do_resize(None)
+                .expect("Failed to resize after a write/commit failed with MDB_MAP_FULL");
+            self.unschedule_map_resize();
+        }
+        err
+    }
+
+    fn schedule_map_resize_if_map_full(&self, err: lmdb::Error) -> lmdb::Error {
+        if err == lmdb::Error::MapFull {
+            self.schedule_map_resize();
+        }
+        err
+    }
 }
 
 impl<'tx> TransactionalRo<'tx> for LmdbImpl {
@@ -165,58 +207,43 @@ impl<'tx> TransactionalRo<'tx> for LmdbImpl {
 impl<'tx> TransactionalRw<'tx> for LmdbImpl {
     type TxRw = DbTxRw<'tx>;
 
-    fn transaction_rw<'st: 'tx>(&'st self) -> storage_core::Result<Self::TxRw> {
-        remap::remap(
-            &self.env,
-            // Acquire exclusive memory map token to make sure no transactions are active for
-            // the duration of memory remapping.
-            self.map_token.write().expect("mmap lock to be alive"),
-            self.tx_size,
-        )?;
-        self.start_transaction(lmdb::Environment::begin_rw_txn)
+    fn transaction_rw<'st: 'tx>(
+        &'st self,
+        size: Option<usize>,
+    ) -> storage_core::Result<Self::TxRw> {
+        self.resize_if_resize_scheduled();
+        self.start_transaction(|env| lmdb::Environment::begin_rw_txn(env, size))
     }
 }
 
 impl utils::shallow_clone::ShallowClone for LmdbImpl {}
 impl backend::BackendImpl for LmdbImpl {}
 
-#[derive(Eq, PartialEq, Clone, Debug)]
 pub struct Lmdb {
     path: PathBuf,
     flags: lmdb::EnvironmentFlags,
-    map_size: MemSize,
-    tx_size: MemSize,
+    inital_map_size: InitialMapSize,
+    resize_settings: DatabaseResizeSettings,
+    resize_callback: MapResizeCallback,
 }
 
 impl Lmdb {
-    /// Amount of space the memory map is initialized with
-    pub const DEFAULT_MAP_SIZE: MemSize = MemSize::from_megabytes(16);
-
-    /// Amount of space needed for a transaction by default
-    pub const DEFAULT_TX_SIZE: MemSize = MemSize::from_megabytes(8);
-
     /// New LMDB database backend
-    pub fn new(path: PathBuf) -> Self {
+    pub fn new(
+        path: PathBuf,
+        inital_map_size: InitialMapSize,
+        resize_settings: DatabaseResizeSettings,
+        resize_callback: MapResizeCallback,
+    ) -> Self {
         Self {
             path,
             flags: lmdb::EnvironmentFlags::default(),
-            map_size: Self::DEFAULT_MAP_SIZE,
-            tx_size: Self::DEFAULT_TX_SIZE,
+            inital_map_size,
+            resize_settings,
+            resize_callback,
         }
     }
 
-    /// Set the initial memory map size
-    pub fn with_map_size(mut self, map_size: MemSize) -> Self {
-        self.map_size = map_size;
-        self
-    }
-
-    /// Set maximum transaction size
-    pub fn with_tx_size(mut self, tx_size: MemSize) -> Self {
-        self.tx_size = tx_size;
-        self
-    }
-
     /// Use a writable memory map.
     ///
     /// This disables some protections in exchange for better performance.
@@ -240,13 +267,25 @@ impl backend::Backend for Lmdb {
         // Attempt to create the storage directory
         std::fs::create_dir_all(&self.path).map_err(error::process_io_error)?;
 
+        let initial_map_size = self
+            .inital_map_size
+            .into_memsize()
+            .map(|v| v.as_bytes().try_into().expect("MemSize to usize conversion failed"));
+
         // Set up LMDB environment
-        let environment = lmdb::Environment::new()
-            .set_max_dbs(desc.len() as u32)
-            .set_flags(self.flags)
-            .set_map_size(self.map_size.as_bytes())
-            .open(&self.path)
-            .or_else(error::process_with_err)?;
+        let environment = lmdb::Environment::new();
+
+        let environment = if let Some(sz) = initial_map_size {
+            environment.set_map_size(sz)
+        } else {
+            environment
+        }
+        .set_resize_settings(self.resize_settings)
+        .set_resize_callback(self.resize_callback.take())
+        .set_max_dbs(desc.len() as u32)
+        .set_flags(self.flags)
+        .open(&self.path)
+        .or_else(error::process_with_err)?;
 
         // Set up all the databases
         let dbs = desc
@@ -258,8 +297,10 @@ impl backend::Backend for Lmdb {
         Ok(LmdbImpl {
             env: Arc::new(environment),
             dbs,
-            map_token: Arc::new(RwLock::new(remap::MemMapController::new())),
-            tx_size: self.tx_size,
+            map_resize_scheduled: Arc::new(AtomicBool::new(false)),
         })
     }
 }
+
+#[cfg(test)]
+mod resize_tests;
diff --git a/storage/lmdb/src/memsize.rs b/storage/lmdb/src/memsize.rs
new file mode 100644
index 000000000..d7cd671a3
--- /dev/null
+++ b/storage/lmdb/src/memsize.rs
@@ -0,0 +1,48 @@
+// Copyright (c) 2022 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Represents LMDB memory map size
+#[derive(Eq, PartialEq, PartialOrd, Ord, Clone, Copy, Debug)]
+pub struct MemSize(u64);
+
+impl MemSize {
+    pub const ZERO: Self = MemSize(0);
+
+    /// Specify in the number of bytes
+    pub const fn from_bytes(bytes: u64) -> Self {
+        Self(bytes)
+    }
+
+    /// Specify in the number of kilobytes
+    pub const fn from_kilobytes(kilobytes: u64) -> Self {
+        Self::from_bytes(1024 * kilobytes)
+    }
+
+    /// Specify in the number of megabytes
+    pub const fn from_megabytes(megabytes: u64) -> Self {
+        Self::from_kilobytes(1024 * megabytes)
+    }
+
+    /// Get raw byte count in native representation
+    pub fn as_bytes(self) -> u64 {
+        self.0
+    }
+}
+
+impl std::fmt::Display for MemSize {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}B", self.0)
+    }
+}
diff --git a/storage/lmdb/src/remap.rs b/storage/lmdb/src/remap.rs
deleted file mode 100644
index 5684cab22..000000000
--- a/storage/lmdb/src/remap.rs
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright (c) 2022 RBB S.r.l
-// opensource@mintlayer.org
-// SPDX-License-Identifier: MIT
-// Licensed under the MIT License;
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Support for memory map reallocation
-
-use logging::log;
-use utils::{sync, tap_error_log::LogError};
-
-/// Represents LMDB memory map size
-#[derive(Eq, PartialEq, PartialOrd, Ord, Clone, Copy, Debug)]
-pub struct MemSize(u64);
-
-impl MemSize {
-    /// Specify in the number of bytes
-    pub const fn from_bytes(bytes: u64) -> Self {
-        Self(bytes)
-    }
-
-    /// Specify in the number of kilobytes
-    pub const fn from_kilobytes(kilobytes: u64) -> Self {
-        Self::from_bytes(1024 * kilobytes)
-    }
-
-    /// Specify in the number of megabytes
-    pub const fn from_megabytes(megabytes: u64) -> Self {
-        Self::from_kilobytes(1024 * megabytes)
-    }
-
-    /// Get raw byte count as u64
-    pub fn as_bytes_u64(self) -> u64 {
-        self.0
-    }
-
-    /// Get raw byte count in native representation
-    pub fn as_bytes(self) -> usize {
-        self.0.try_into().expect("Ran out of address space")
-    }
-
-    /// Division, rounding up
-    pub fn div_ceil(self, rhs: Self) -> u64 {
-        // TODO: Use u64::div_ceil once stable
-        self.0 / rhs.0 + (self.0 % rhs.0 > 0) as u64
-    }
-}
-
-impl std::fmt::Display for MemSize {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}B", self.0)
-    }
-}
-
-impl std::ops::Add for MemSize {
-    type Output = MemSize;
-
-    fn add(self, rhs: Self) -> Self::Output {
-        Self(self.0 + rhs.0)
-    }
-}
-
-impl std::ops::Mul<MemSize> for u64 {
-    type Output = MemSize;
-
-    fn mul(self, rhs: MemSize) -> Self::Output {
-        MemSize(self * rhs.0)
-    }
-}
-
-/// Token representing acquisition of the memory map resource
-#[derive(Debug)]
-pub struct MemMapController();
-
-impl MemMapController {
-    pub fn new() -> Self {
-        Self()
-    }
-}
-
-/// A proof of having acquired the memory map resource exclusively
-pub type ExclusiveMemMapController<'a> = sync::RwLockWriteGuard<'a, MemMapController>;
-
-/// Memory remapping procedure. Ensure at least `headroom` free space is available
-pub fn remap(
-    env: &lmdb::Environment,
-    _map_token: ExclusiveMemMapController<'_>,
-    headroom: MemSize,
-) -> storage_core::Result<()> {
-    // Get page size
-    let page_size = {
-        let stat = env.stat().or_else(crate::error::process_with_err)?;
-        MemSize::from_bytes(stat.page_size() as u64)
-    };
-
-    // Get current occupancy info
-    let info = env.info().or_else(crate::error::process_with_err)?;
-    let current_size = MemSize::from_bytes(info.map_size() as u64);
-    let current_pages = current_size.div_ceil(page_size);
-    let used_pages = (info.last_pgno() + 1) as u64;
-    let freelist_pages = env.freelist().or_else(crate::error::process_with_err)? as u64;
-    let free_pages = (current_pages - used_pages) + freelist_pages;
-
-    // Get map size requirements
-    let required_free_pages = headroom.div_ceil(page_size);
-    let required_pages = used_pages + required_free_pages;
-    let required_size = required_pages * page_size;
-
-    log::trace!(
-        "LMDB memory: occupied = {} - {}, reserved = {}, free = {}, required = {}",
-        used_pages * page_size,
-        freelist_pages * page_size,
-        current_size,
-        free_pages * page_size,
-        required_size,
-    );
-
-    // Ensure there is enough space
-    if required_size > current_size {
-        // Remap with double of the required size
-        let new_size = 2 * required_size;
-        log::info!(
-            "Resizing LMDB memory map from {} to {}",
-            current_size,
-            new_size,
-        );
-        env.set_map_size(new_size.as_bytes())
-            .or_else(crate::error::process_with_err)
-            .log_err()?;
-    }
-
-    debug_assert!(
-        MemSize::from_bytes(env.info().expect("Map size query").map_size() as u64) >= required_size,
-        "Memory map still not big enough",
-    );
-
-    Ok(())
-}
diff --git a/storage/lmdb/src/resize_callback.rs b/storage/lmdb/src/resize_callback.rs
new file mode 100644
index 000000000..27edd6ab7
--- /dev/null
+++ b/storage/lmdb/src/resize_callback.rs
@@ -0,0 +1,46 @@
+// Copyright (c) 2022 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use lmdb::DatabaseResizeInfo;
+
+pub type MapResizeCallbackFn = dyn Fn(DatabaseResizeInfo);
+
+/// A function wrapper that represents the callback when the map within lmdb resizes
+#[derive(Default)]
+pub struct MapResizeCallback {
+    f: Option<Box<MapResizeCallbackFn>>,
+}
+
+impl MapResizeCallback {
+    pub fn new(f: Box<MapResizeCallbackFn>) -> Self {
+        Self { f: Some(f) }
+    }
+
+    pub fn take(self) -> Option<Box<MapResizeCallbackFn>> {
+        self.f
+    }
+}
+
+impl From<MapResizeCallback> for Option<Box<MapResizeCallbackFn>> {
+    fn from(f: MapResizeCallback) -> Self {
+        f.take()
+    }
+}
+
+impl From<Box<MapResizeCallbackFn>> for MapResizeCallback {
+    fn from(f: Box<MapResizeCallbackFn>) -> Self {
+        Self { f: Some(f) }
+    }
+}
diff --git a/storage/src/database/mod.rs b/storage/src/database/mod.rs
index 597de8a5f..3990f008d 100644
--- a/storage/src/database/mod.rs
+++ b/storage/src/database/mod.rs
@@ -72,9 +72,12 @@ impl<B: Backend, Sch: Schema> Storage<B, Sch> {
     }
 
     /// Start a read-write transaction
-    pub fn transaction_rw<'tx, 'st: 'tx>(&'st self) -> crate::Result<TransactionRw<'tx, B, Sch>> {
+    pub fn transaction_rw<'tx, 'st: 'tx>(
+        &'st self,
+        size: Option<usize>,
+    ) -> crate::Result<TransactionRw<'tx, B, Sch>> {
         Ok(TransactionRw {
-            dbtx: backend::TransactionalRw::transaction_rw(&self.backend)?,
+            dbtx: backend::TransactionalRw::transaction_rw(&self.backend, size)?,
             _schema: Default::default(),
         })
     }
diff --git a/storage/src/database/raw.rs b/storage/src/database/raw.rs
index 5bbbdd311..d5d9d36ed 100644
--- a/storage/src/database/raw.rs
+++ b/storage/src/database/raw.rs
@@ -183,7 +183,7 @@ mod test {
             }
 
             // Add some values, check the dump contents
-            let mut dbtx = storage.transaction_rw().unwrap();
+            let mut dbtx = storage.transaction_rw(None).unwrap();
             dbtx.get_mut::<Db0, _>().put(42, 1337).unwrap();
             dbtx.get_mut::<Db1, _>().put(21, vec![1, 2, 3, 4]).unwrap();
             dbtx.commit().unwrap();
@@ -197,7 +197,7 @@ mod test {
             }
 
             // More modifications, check contents
-            let mut dbtx = storage.transaction_rw().unwrap();
+            let mut dbtx = storage.transaction_rw(None).unwrap();
             dbtx.get_mut::<Db0, _>().del(42).unwrap();
             dbtx.get_mut::<Db1, _>().put(22, vec![1, 2]).unwrap();
             dbtx.commit().unwrap();
diff --git a/storage/src/lib.rs b/storage/src/lib.rs
index a02edc52d..6b11f5600 100644
--- a/storage/src/lib.rs
+++ b/storage/src/lib.rs
@@ -56,7 +56,7 @@
 //!     let mut store = Storage::<_, Schema>::new(storage::inmemory::InMemory::new())?;
 //!
 //!     // All store operations happen inside of a transaction.
-//!     let mut tx = store.transaction_rw()?;
+//!     let mut tx = store.transaction_rw(None)?;
 //!
 //!     // Get the storage map, identified by the index type.
 //!     let mut map = tx.get_mut::<MyMap, _>();
@@ -72,7 +72,7 @@
 //!     tx.commit()?;
 //!
 //!     // Try writing a value but abort the transaction afterwards.
-//!     let mut tx = store.transaction_rw()?;
+//!     let mut tx = store.transaction_rw(None)?;
 //!     tx.get_mut::<MyMap, _>().put("baz", &42)?;
 //!     tx.abort();
 //!


Test patch:
diff --git a/chainstate/storage/src/internal/test.rs b/chainstate/storage/src/internal/test.rs
index 3bbb4817c..3e6501ffd 100644
--- a/chainstate/storage/src/internal/test.rs
+++ b/chainstate/storage/src/internal/test.rs
@@ -157,7 +157,7 @@ fn get_set_transactions() {
         let thr1 = {
             let store = Store::clone(&store);
             utils::thread::spawn(move || {
-                let mut tx = store.transaction_rw().unwrap();
+                let mut tx = store.transaction_rw(None).unwrap();
                 let v = tx.get_storage_version().unwrap();
                 tx.set_storage_version(v + 1).unwrap();
                 tx.commit().unwrap();
@@ -191,7 +191,7 @@ fn test_storage_transactions() {
         let thr0 = {
             let store = Store::clone(&store);
             utils::thread::spawn(move || {
-                let mut tx = store.transaction_rw().unwrap();
+                let mut tx = store.transaction_rw(None).unwrap();
                 let v = tx.get_storage_version().unwrap();
                 tx.set_storage_version(v + 3).unwrap();
                 tx.commit().unwrap();
@@ -200,7 +200,7 @@ fn test_storage_transactions() {
         let thr1 = {
             let store = Store::clone(&store);
             utils::thread::spawn(move || {
-                let mut tx = store.transaction_rw().unwrap();
+                let mut tx = store.transaction_rw(None).unwrap();
                 let v = tx.get_storage_version().unwrap();
                 tx.set_storage_version(v + 5).unwrap();
                 tx.commit().unwrap();
@@ -224,7 +224,7 @@ fn test_storage_transactions_with_result_check() {
         let thr0 = {
             let store = Store::clone(&store);
             utils::thread::spawn(move || {
-                let mut tx = store.transaction_rw().unwrap();
+                let mut tx = store.transaction_rw(None).unwrap();
                 let v = tx.get_storage_version().unwrap();
                 assert!(tx.set_storage_version(v + 3).is_ok());
                 assert!(tx.commit().is_ok());
@@ -233,7 +233,7 @@ fn test_storage_transactions_with_result_check() {
         let thr1 = {
             let store = Store::clone(&store);
             utils::thread::spawn(move || {
-                let mut tx = store.transaction_rw().unwrap();
+                let mut tx = store.transaction_rw(None).unwrap();
                 let v = tx.get_storage_version().unwrap();
                 assert!(tx.set_storage_version(v + 5).is_ok());
                 assert!(tx.commit().is_ok());
diff --git a/chainstate/test-suite/src/tests/pos_accounting_reorg.rs b/chainstate/test-suite/src/tests/pos_accounting_reorg.rs
index 204e51524..b32e1ae00 100644
--- a/chainstate/test-suite/src/tests/pos_accounting_reorg.rs
+++ b/chainstate/test-suite/src/tests/pos_accounting_reorg.rs
@@ -147,7 +147,7 @@ fn stake_pool_reorg(#[case] seed: Seed) {
 
             {
                 // manually add block_a info
-                let mut db_tx = storage.transaction_rw().unwrap();
+                let mut db_tx = storage.transaction_rw(None).unwrap();
                 db_tx.set_block_index(&block_a_index).unwrap();
                 db_tx.add_block(&block_a).unwrap();
                 db_tx.commit().unwrap();
diff --git a/storage/backend-test-suite/src/basic.rs b/storage/backend-test-suite/src/basic.rs
index e38f003a5..469493b94 100644
--- a/storage/backend-test-suite/src/basic.rs
+++ b/storage/backend-test-suite/src/basic.rs
@@ -21,7 +21,7 @@ fn put_and_commit<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
     let store = backend_fn().open(desc(1)).expect("db open to succeed");
 
     // Create a transaction, modify storage and abort transaction
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, b"hello".to_vec(), b"world".to_vec()).unwrap();
     dbtx.commit().expect("commit to succeed");
 
@@ -38,7 +38,7 @@ fn put_and_abort<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
     let store = backend_fn().open(desc(1)).expect("db open to succeed");
 
     // Create a transaction, modify storage and abort transaction
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, b"hello".to_vec(), b"world".to_vec()).unwrap();
     drop(dbtx);
 
@@ -52,7 +52,7 @@ fn put_two_under_different_keys<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>)
     let store = backend_fn().open(desc(1)).expect("db open to succeed");
 
     // Create a transaction, modify storage and commit
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, b"a".to_vec(), b"0".to_vec()).unwrap();
     dbtx.put(IDX.0, b"b".to_vec(), b"1".to_vec()).unwrap();
     dbtx.commit().expect("commit to succeed");
@@ -70,7 +70,7 @@ fn put_two_under_different_keys<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>)
     drop(dbtx);
 
     // Create a transaction, modify storage and abort
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, b"a".to_vec(), b"00".to_vec()).unwrap();
     dbtx.put(IDX.0, b"b".to_vec(), b"11".to_vec()).unwrap();
     drop(dbtx);
@@ -91,7 +91,7 @@ fn put_two_under_different_keys<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>)
 fn put_twice_then_commit_read_last<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
     let store = backend_fn().open(desc(1)).expect("db open to succeed");
 
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, b"hello".to_vec(), b"a".to_vec()).unwrap();
     assert_eq!(
         dbtx.get(IDX.0, b"hello").unwrap().as_ref().map(|v| v.as_ref()).unwrap(),
@@ -114,7 +114,7 @@ fn put_twice_then_commit_read_last<B: Backend, F: BackendFn<B>>(backend_fn: Arc<
 fn put_iterator_count_matches<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
     let store = backend_fn().open(desc(1)).expect("db open to succeed");
 
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, vec![0x00], vec![]).unwrap();
     dbtx.put(IDX.0, vec![0x01], vec![]).unwrap();
     dbtx.put(IDX.0, vec![0x02], vec![]).unwrap();
@@ -129,7 +129,7 @@ fn put_and_iterate_over_prefixes<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>
     let store = backend_fn().open(desc(1)).expect("db open to succeed");
 
     // Populate the database with some values
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, b"ac".to_vec(), b"2".to_vec()).unwrap();
     dbtx.put(IDX.0, b"bf".to_vec(), b"7".to_vec()).unwrap();
     dbtx.put(IDX.0, b"ab".to_vec(), b"1".to_vec()).unwrap();
@@ -188,7 +188,7 @@ fn put_and_iterate_delete_some<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>)
     let expected_ac_0 = [("ac", "2"), ("aca", "3"), ("acb", "4")];
 
     // Populate the database with some
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, b"aa".to_vec(), b"0".to_vec()).unwrap();
     dbtx.put(IDX.0, b"ab".to_vec(), b"1".to_vec()).unwrap();
     dbtx.put(IDX.0, b"ac".to_vec(), b"2".to_vec()).unwrap();
@@ -213,7 +213,7 @@ fn put_and_iterate_delete_some<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>)
     let expected_ac_1 = [("ac", "2"), ("acb", "4")];
 
     // Delete some entries
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.del(IDX.0, b"aca").unwrap();
     dbtx.del(IDX.0, b"ab").unwrap();
     // Check updated contents
@@ -231,7 +231,7 @@ fn put_and_iterate_delete_some<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>)
     drop(dbtx);
 
     // Delete the items, this time for real
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.del(IDX.0, b"aca").unwrap();
     dbtx.del(IDX.0, b"ab").unwrap();
     // Check updated contents
diff --git a/storage/backend-test-suite/src/concurrent.rs b/storage/backend-test-suite/src/concurrent.rs
index 418f2dbc3..81115084d 100644
--- a/storage/backend-test-suite/src/concurrent.rs
+++ b/storage/backend-test-suite/src/concurrent.rs
@@ -20,7 +20,7 @@ const TEST_KEY: &[u8] = b"foo";
 fn setup<B: Backend>(backend: B, init: Vec<u8>) -> B::Impl {
     let store = backend.open(desc(1)).expect("db open to succeed");
 
-    let mut dbtx = store.transaction_rw().unwrap();
+    let mut dbtx = store.transaction_rw(None).unwrap();
     dbtx.put(IDX.0, TEST_KEY.to_vec(), init).unwrap();
     dbtx.commit().unwrap();
 
@@ -33,7 +33,7 @@ fn read_initialize_race<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
     let thr0 = thread::spawn({
         let store = store.clone();
         move || {
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.put(IDX.0, TEST_KEY.to_vec(), vec![2]).unwrap();
             dbtx.commit().unwrap();
         }
@@ -53,7 +53,7 @@ fn read_write_race<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
     let thr0 = thread::spawn({
         let store = store.clone();
         move || {
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.put(IDX.0, TEST_KEY.to_vec(), vec![2]).unwrap();
             dbtx.commit().unwrap();
         }
@@ -74,7 +74,7 @@ fn commutative_read_modify_write<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>
     let thr0 = thread::spawn({
         let store = store.clone();
         move || {
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             let v = dbtx.get(IDX.0, TEST_KEY).unwrap().unwrap();
             let b = v.first().unwrap();
             dbtx.put(IDX.0, TEST_KEY.to_vec(), vec![b + 5]).unwrap();
@@ -84,7 +84,7 @@ fn commutative_read_modify_write<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>
     let thr1 = thread::spawn({
         let store = store.clone();
         move || {
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             let v = dbtx.get(IDX.0, TEST_KEY).unwrap().unwrap();
             let b = v.first().unwrap();
             dbtx.put(IDX.0, TEST_KEY.to_vec(), vec![b + 3]).unwrap();
@@ -142,7 +142,7 @@ fn write_different_keys_and_iterate<B: Backend, F: BackendFn<B>>(backend_fn: Arc
     let thr0 = thread::spawn({
         let store = store.clone();
         move || {
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.put(IDX.0, vec![0x01], vec![0xf1]).unwrap();
             dbtx.commit().unwrap();
         }
@@ -150,7 +150,7 @@ fn write_different_keys_and_iterate<B: Backend, F: BackendFn<B>>(backend_fn: Arc
     let thr1 = thread::spawn({
         let store = store.clone();
         move || {
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.put(IDX.0, vec![0x02], vec![0xf2]).unwrap();
             dbtx.commit().unwrap();
         }
diff --git a/storage/backend-test-suite/src/property.rs b/storage/backend-test-suite/src/property.rs
index 7641a6323..5a49df4e1 100644
--- a/storage/backend-test-suite/src/property.rs
+++ b/storage/backend-test-suite/src/property.rs
@@ -76,7 +76,7 @@ fn overwrite_and_abort<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             drop(dbtx);
 
             // Create a transaction, put the value in the storage and commit
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.put(IDX.0, key.clone(), val0.clone()).unwrap();
             dbtx.commit().expect("commit to succeed");
 
@@ -89,7 +89,7 @@ fn overwrite_and_abort<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             drop(dbtx);
 
             // Create a transaction, modify storage and abort
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.put(IDX.0, key.clone(), val1.clone()).unwrap();
             drop(dbtx);
 
@@ -102,7 +102,7 @@ fn overwrite_and_abort<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             drop(dbtx);
 
             // Create a transaction, overwrite the value and commit
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.put(IDX.0, key.clone(), val1.clone()).unwrap();
             dbtx.commit().expect("commit to succeed");
 
@@ -127,7 +127,7 @@ fn add_and_delete<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             let store = backend.open(desc(NUM_DBS)).expect("db open to succeed");
 
             // Add all entries to the database
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             for ((db, key), val) in &entries {
                 dbtx.put(*db, key.clone(), val.clone()).unwrap();
             }
@@ -144,7 +144,7 @@ fn add_and_delete<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             drop(dbtx);
 
             // remove all entries
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             for (db, key) in entries.keys() {
                 dbtx.del(*db, key).unwrap();
             }
@@ -173,7 +173,7 @@ fn last_write_wins<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             let last = vals.last().cloned();
 
             // Add all entries to the database
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             for val in vals.into_iter() {
                 dbtx.put(IDX.0, key.clone(), val).unwrap();
             }
@@ -202,7 +202,7 @@ fn add_and_delete_some<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             let store = backend.open(desc(NUM_DBS)).expect("db open to succeed");
 
             // Add all entries to the database
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             for ((db, key), val) in entries1.iter().chain(entries2.iter()) {
                 dbtx.put(*db, key.clone(), val.clone()).unwrap();
             }
@@ -220,7 +220,7 @@ fn add_and_delete_some<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             drop(dbtx);
 
             // remove entries from the second set
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             for (db, key) in entries2.keys() {
                 dbtx.del(*db, key).unwrap();
             }
@@ -258,7 +258,7 @@ fn add_modify_abort_modify_commit<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F
             let store = backend.open(desc(1)).expect("db open to succeed");
 
             // Pre-populate the db with initial data, check the contents against the model
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, to_prepopulate.into_iter());
             dbtx.commit().unwrap();
             assert_eq!(model, Model::from_db(&store, IDX.0));
@@ -269,7 +269,7 @@ fn add_modify_abort_modify_commit<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F
                 tx_model.extend(to_abort.clone().into_iter());
                 tx_model
             };
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, to_abort.into_iter());
             assert_eq!(tx_model, Model::from_tx(&dbtx, IDX.0));
             drop(dbtx);
@@ -281,7 +281,7 @@ fn add_modify_abort_modify_commit<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F
                 model.extend(to_commit.clone().into_iter());
                 model
             };
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, to_commit.into_iter());
             dbtx.commit().unwrap();
             assert_eq!(model, Model::from_db(&store, IDX.0));
@@ -298,21 +298,21 @@ fn add_modify_abort_replay_commit<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F
             let store = backend.open(desc(1)).expect("db open to succeed");
 
             // Pre-populate the db with initial data, check the contents against the model
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, initial.into_iter());
             dbtx.commit().unwrap();
 
             let initial_model = Model::from_db(&store, IDX.0);
 
             // Apply another set of changes but abort the transaction, check nothing changed
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, actions.clone().into_iter());
             let modified_model = Model::from_tx(&dbtx, IDX.0);
             drop(dbtx);
             assert_eq!(Model::from_db(&store, IDX.0), initial_model);
 
             // Apply the same changes again, and check that we get to the same state after commit
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, actions.into_iter());
             dbtx.commit().unwrap();
             assert_eq!(modified_model, Model::from_db(&store, IDX.0));
@@ -329,13 +329,13 @@ fn db_writes_do_not_interfere<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             let store = backend.open(desc(2)).expect("db open to succeed");
 
             // Apply one set of operations to key-value map 0
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, actions0.into_iter());
             dbtx.commit().unwrap();
             let model = Model::from_db(&store, IDX.0);
 
             // Apply another set of operations to key-value map 1
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.1, actions1.into_iter());
             dbtx.commit().unwrap();
 
@@ -358,7 +358,7 @@ fn empty_after_abort<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
 
             // Apply one set of operations to key-value map 0
             let model = Model::from_actions(actions.clone());
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, actions.into_iter());
             for key in &keys {
                 assert_eq!(
@@ -396,7 +396,7 @@ fn prefix_iteration<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             let store = backend.open(desc(5)).expect("db open to succeed");
 
             // Populate the database
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, actions_a.iter().chain(actions_b.iter()).cloned());
             dbtx.commit().unwrap();
 
@@ -420,7 +420,7 @@ fn prefix_iteration<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             drop(dbtx);
 
             // Take all entries prefixed "a" and remove them
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             let keys_a: Vec<_> =
                 dbtx.prefix_iter(IDX.0, vec![b'a']).unwrap().map(|(k, _)| k).collect();
             for key in keys_a {
@@ -445,7 +445,7 @@ fn post_commit_consistency<B: Backend, F: BackendFn<B>>(backend_fn: Arc<F>) {
             // Open storage
             let store = backend.open(desc(1)).expect("db open to succeed");
 
-            let mut dbtx = store.transaction_rw().unwrap();
+            let mut dbtx = store.transaction_rw(None).unwrap();
             dbtx.apply_actions(IDX.0, actions.into_iter());
             let model = Model::from_tx(&dbtx, IDX.0);
             dbtx.commit().unwrap();
diff --git a/storage/lmdb/src/resize_tests.rs b/storage/lmdb/src/resize_tests.rs
new file mode 100644
index 000000000..ea42a45ee
--- /dev/null
+++ b/storage/lmdb/src/resize_tests.rs
@@ -0,0 +1,220 @@
+// Copyright (c) 2022 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::{collections::BTreeMap, sync::Mutex};
+
+use memsize::MemSize;
+use rstest::rstest;
+use storage_core::{
+    backend::{ReadOps, TxRw, WriteOps},
+    Backend,
+};
+use tempdir::TempDir;
+use test_utils::random::make_seedable_rng;
+use test_utils::random::{CryptoRng, Rng, Seed};
+
+use super::*;
+
+#[must_use]
+fn create_random_data_map_with_target_byte_size(
+    rng: &mut (impl Rng + CryptoRng),
+    required_size: usize,
+    key_max_size: usize,
+    val_max_size: usize,
+) -> BTreeMap<Vec<u8>, Vec<u8>> {
+    let mut result = BTreeMap::new();
+
+    let mut total_size = 0;
+
+    while total_size < required_size {
+        let key_size = 1 + rng.gen::<usize>() % key_max_size;
+        let key = (0..key_size).map(|_| rng.gen::<u8>()).collect::<Vec<_>>();
+        let val_size = 1 + rng.gen::<usize>() % val_max_size;
+        let val = (0..val_size).map(|_| rng.gen::<u8>()).collect::<Vec<_>>();
+        result.insert(key, val);
+
+        total_size += key_size;
+        total_size += val_size;
+    }
+
+    result
+}
+
+#[rstest]
+#[trace]
+#[case(test_utils::random::Seed::from_entropy())]
+fn auto_map_resize_between_txs(#[case] seed: Seed) {
+    utils::concurrency::model(move || {
+        let mut rng = make_seedable_rng(seed);
+
+        let resize_actions = Arc::new(Mutex::new(Vec::new()));
+
+        let resize_actions_for_check = Arc::clone(&resize_actions);
+        let resize_actions = Arc::clone(&resize_actions);
+        let resize_callback = Box::new(move |v| resize_actions.lock().unwrap().push(v));
+
+        let initial_map_size = 1 << 20;
+
+        let resize_settings = DatabaseResizeSettings {
+            min_resize_step: 1 << 16,
+            max_resize_step: 1 << 20,
+            default_resize_ratio_percentage: 10,
+            resize_trigger_percentage: 0.9,
+        };
+
+        let data_dir = TempDir::new("lmdb_resize").unwrap();
+        let lmdb = Lmdb::new(
+            data_dir.into_path(),
+            MemSize::from_bytes(initial_map_size).into(),
+            resize_settings.clone(),
+            MapResizeCallback::new(resize_callback),
+        );
+
+        let lmdb_impl = lmdb.open(DbDesc::from_iter(vec![MapDesc::new("SomeDb")])).unwrap();
+
+        // generate random values with a predefined target size that surpasses the current map size
+        let data = create_random_data_map_with_target_byte_size(
+            &mut rng,
+            (initial_map_size * 5) as usize,
+            500,
+            10000,
+        );
+
+        let mut resizes_via_commit_count = 0usize;
+
+        // write many key/val values, and while they're being written, expect that database map will grow
+        for (key, val) in &data {
+            let mut rw_tx = lmdb_impl.transaction_rw(None).unwrap();
+            rw_tx.put(DbIndex::new(0), key.clone(), val.clone()).unwrap();
+            match rw_tx.commit() {
+                Ok(_) => resizes_via_commit_count += 1,
+                Err(e) => panic!("Failed to commit: {e:?}"),
+            }
+        }
+
+        let resize_action_result = resize_actions_for_check.lock().unwrap().clone();
+        assert!(!resize_action_result.is_empty());
+        for act in resize_action_result {
+            assert!(act.old_size < act.new_size);
+            assert!(act.new_size - act.old_size >= resize_settings.min_resize_step as u64);
+            assert!(act.new_size - act.old_size <= resize_settings.max_resize_step as u64);
+        }
+
+        // ensure data is successfully written
+        let ro_tx = lmdb_impl.transaction_ro().unwrap();
+        for (key, val) in data {
+            assert_eq!(ro_tx.get(DbIndex::new(0), &key).unwrap().unwrap(), val);
+        }
+
+        assert!(resizes_via_commit_count > 0, "Not a single resize was scheduled after a transaction... this is very unlikely based on the test structure");
+    })
+}
+
+#[rstest]
+#[trace]
+#[case(test_utils::random::Seed::from_entropy())]
+fn auto_map_resize_between_puts(#[case] seed: Seed) {
+    utils::concurrency::model(move || {
+        let mut rng = make_seedable_rng(seed);
+
+        let resize_actions = Arc::new(Mutex::new(Vec::new()));
+
+        let resize_actions_for_check = Arc::clone(&resize_actions);
+        let resize_actions = Arc::clone(&resize_actions);
+        let resize_callback = Box::new(move |v| resize_actions.lock().unwrap().push(v));
+
+        let initial_map_size = 1 << 20;
+
+        let resize_settings = DatabaseResizeSettings {
+            min_resize_step: 1 << 16,
+            max_resize_step: 1 << 20,
+            default_resize_ratio_percentage: 10,
+            resize_trigger_percentage: 0.9,
+        };
+
+        let data_dir = TempDir::new("lmdb_resize").unwrap();
+        let lmdb = Lmdb::new(
+            data_dir.into_path(),
+            MemSize::from_bytes(initial_map_size).into(),
+            resize_settings.clone(),
+            MapResizeCallback::new(resize_callback),
+        );
+
+        let lmdb_impl = lmdb.open(DbDesc::from_iter(vec![MapDesc::new("SomeDb")])).unwrap();
+
+        // generate random values with a predefined target size that surpasses the current map size
+        let data = create_random_data_map_with_target_byte_size(
+            &mut rng,
+            (initial_map_size * 5) as usize,
+            500,
+            10000,
+        );
+
+        // write many key/val values, and while they're being written, expect that database map will grow
+        // In this loop, we expect two things to schedule a resize:
+        // 1. While putting in the transaction
+        // 2. While committing a transaction
+        // We focus on while writing in the transaction
+        ////////////////////////////////////////////////
+        // Data stack is the full data that should be written to the database in a stack that we pop
+        let mut data_stack: Vec<(Vec<u8>, Vec<u8>)> = data.clone().into_iter().collect();
+        let mut resizes_via_put_count = 0usize;
+        'outer: loop {
+            let mut rw_tx = lmdb_impl.transaction_rw(None).unwrap();
+            let mut data_written_in_this_cycle = Vec::new();
+            while !data_stack.is_empty() {
+                let (key, val) = data_stack.last().unwrap();
+                match rw_tx.put(DbIndex::new(0), key.clone(), val.clone()) {
+                    Ok(_) => {
+                        // on success, we continue writing and take record of that
+                        data_written_in_this_cycle.push(data_stack.pop().unwrap());
+                    }
+                    Err(_) => {
+                        // on failure, we expect a resize to happen after trying again, so we consider this cycle a failure and restore what we (unsuccessfully) wrote to the stack
+                        data_stack.extend(data_written_in_this_cycle);
+                        resizes_via_put_count += 1;
+                        continue 'outer;
+                    }
+                }
+            }
+            match rw_tx.commit() {
+                Ok(_) => break,
+                Err(_) => {
+                    // on error, we continue, but return the data that failed writing to the stack
+                    data_stack.extend(data_written_in_this_cycle);
+                    continue 'outer;
+                }
+            }
+        }
+        let resize_action_result = resize_actions_for_check.lock().unwrap().clone();
+        assert!(!resize_action_result.is_empty());
+        for act in resize_action_result {
+            assert!(act.old_size < act.new_size);
+            assert!(act.new_size - act.old_size >= resize_settings.min_resize_step as u64);
+            assert!(act.new_size - act.old_size <= resize_settings.max_resize_step as u64);
+        }
+
+        // ensure data is successfully written
+        let ro_tx = lmdb_impl.transaction_ro().unwrap();
+        for (key, val) in data {
+            assert_eq!(ro_tx.get(DbIndex::new(0), &key).unwrap().unwrap(), val);
+        }
+
+        assert!(
+        resizes_via_put_count > 0,
+        "Not a single resize was scheduled after a write/put... this is very unlikely based on the test structure"
+    );
+    })
+}
diff --git a/storage/lmdb/tests/backend.rs b/storage/lmdb/tests/backend.rs
index e69d2d2bc..70d623b71 100644
--- a/storage/lmdb/tests/backend.rs
+++ b/storage/lmdb/tests/backend.rs
@@ -24,7 +24,12 @@ fn main() {
         move || {
             // Each test case gets its own subdirectory to avoid clashes
             let test_dir = test_root.fresh_test_dir("unknown");
-            Lmdb::new(test_dir.as_ref().to_path_buf())
+            Lmdb::new(
+                test_dir.as_ref().to_path_buf(),
+                Default::default(),
+                Default::default(),
+                Default::default(),
+            )
         }
     };
 
diff --git a/storage/lmdb/tests/remap.rs b/storage/lmdb/tests/remap.rs
deleted file mode 100644
index f6b20659b..000000000
--- a/storage/lmdb/tests/remap.rs
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2022 RBB S.r.l
-// opensource@mintlayer.org
-// SPDX-License-Identifier: MIT
-// Licensed under the MIT License;
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use storage_core::{backend, info::MapDesc, Backend, DbDesc, DbIndex};
-use storage_lmdb::{Lmdb, MemSize};
-use utils::{concurrency, thread};
-
-const IDX0: DbIndex = DbIndex::new(0);
-
-fn perform_writes(
-    name: &'static str,
-    initial_size: MemSize,
-    tx_size: MemSize,
-    write_sizes: &[MemSize],
-) {
-    #[cfg(not(loom))]
-    logging::init_logging::<&std::path::Path>(None);
-    let test_root = std::sync::Arc::new(test_utils::test_root!("remap-tests").unwrap());
-
-    concurrency::model({
-        let test_root = std::sync::Arc::clone(&test_root);
-        let write_sizes = write_sizes.to_vec();
-        move || {
-            // Open a new datbase with given map size
-            let dir = test_root.fresh_test_dir(name).as_ref().to_owned();
-            let lmdb = Lmdb::new(dir)
-                .with_map_size(initial_size)
-                .with_tx_size(tx_size)
-                .open(DbDesc::from_iter(std::iter::once(MapDesc::new("map"))))
-                .unwrap();
-
-            // Try inserting values with sizes given by the `write_sizes` param
-            for (i, size) in write_sizes.iter().enumerate() {
-                let mut txrw = backend::TransactionalRw::transaction_rw(&lmdb).unwrap();
-                let val = vec![42u8; size.as_bytes()];
-                backend::WriteOps::put(&mut txrw, IDX0, i.to_le_bytes().to_vec(), val).unwrap();
-                backend::TxRw::commit(txrw).unwrap();
-            }
-        }
-    });
-
-    std::sync::Arc::try_unwrap(test_root).unwrap().delete();
-}
-
-#[test]
-fn map_too_small_initially() {
-    perform_writes(
-        "map_too_small_initially",
-        MemSize::from_kilobytes(100),
-        MemSize::from_kilobytes(300),
-        [MemSize::from_kilobytes(111)].as_ref(),
-    );
-}
-
-#[test]
-fn map_too_small_by_a_lot() {
-    perform_writes(
-        "map_too_small_by_a_lot",
-        MemSize::from_kilobytes(200),
-        MemSize::from_megabytes(55),
-        [MemSize::from_megabytes(50)].as_ref(),
-    );
-}
-
-#[test]
-fn map_too_small_after_a_number_of_writes() {
-    let write_sizes = [MemSize::from_kilobytes(100); 100];
-    perform_writes(
-        "map_too_small_after_a_number_of_writes",
-        MemSize::from_kilobytes(400),
-        MemSize::from_kilobytes(200),
-        write_sizes.as_ref(),
-    );
-}
-
-#[test]
-#[should_panic]
-fn tx_size_too_small() {
-    perform_writes(
-        "tx_size_too_small",
-        MemSize::from_kilobytes(200),
-        MemSize::from_megabytes(20),
-        [MemSize::from_megabytes(50)].as_ref(),
-    );
-}
-
-#[test]
-fn two_concurrent_writers() {
-    #[cfg(not(loom))]
-    logging::init_logging::<&std::path::Path>(None);
-    let test_root = std::sync::Arc::new(test_utils::test_root!("remap-tests").unwrap());
-
-    concurrency::model({
-        let test_root = std::sync::Arc::clone(&test_root);
-        move || {
-            // Open a new datbase with initial map size 500kB, with 500kB transactions
-            let dir = test_root.fresh_test_dir("two_concurrent_writes").as_ref().to_owned();
-            let storage = Lmdb::new(dir)
-                .with_map_size(MemSize::from_kilobytes(500))
-                .with_tx_size(MemSize::from_kilobytes(500))
-                .open(DbDesc::from_iter(std::iter::once(MapDesc::new("map"))))
-                .unwrap();
-
-            let spawn_writer = |key: Vec<u8>| {
-                let storage = storage.clone();
-                thread::spawn(move || {
-                    let mut txrw = backend::TransactionalRw::transaction_rw(&storage).unwrap();
-                    let val = vec![0x33u8; MemSize::from_kilobytes(300).as_bytes()];
-                    backend::WriteOps::put(&mut txrw, IDX0, key, val).unwrap();
-                    backend::TxRw::commit(txrw).unwrap();
-                })
-            };
-
-            let wr0 = spawn_writer(b"wr0".to_vec());
-            let wr1 = spawn_writer(b"wr1".to_vec());
-
-            wr0.join().unwrap();
-            wr1.join().unwrap();
-        }
-    });
-
-    std::sync::Arc::try_unwrap(test_root).unwrap().delete();
-}
diff --git a/storage/src/test.rs b/storage/src/test.rs
index dfc29ab29..57776f2b1 100644
--- a/storage/src/test.rs
+++ b/storage/src/test.rs
@@ -35,7 +35,7 @@ fn empty_ro() {
 fn empty_rw() {
     utils::concurrency::model(|| {
         let store = Storage::<_, Schema>::new(inmemory::InMemory::new()).unwrap();
-        let tx = store.transaction_rw().unwrap();
+        let tx = store.transaction_rw(None).unwrap();
         assert_eq!(tx.get::<Map1, _>().get(&b"foo".to_vec()), Ok(None));
     });
 }
@@ -62,7 +62,7 @@ fn prefix_iteration() {
         ];
 
         // Populate the database
-        let mut dbtx = store.transaction_rw().unwrap();
+        let mut dbtx = store.transaction_rw(None).unwrap();
         let mut map = dbtx.get_mut::<Map2, _>();
         for (key, val) in &test_values {
             map.put(key, val).unwrap();
diff --git a/test-utils/src/random.rs b/test-utils/src/random.rs
index 17e70447b..272ac97d5 100644
--- a/test-utils/src/random.rs
+++ b/test-utils/src/random.rs
@@ -13,8 +13,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crypto::random::{Rng, SeedableRng};
-use rand_chacha::{rand_core::CryptoRng, ChaChaRng};
+pub use crypto::random::{CryptoRng, Rng, SeedableRng};
+use rand_chacha::ChaChaRng;
 use std::{num::ParseIntError, str::FromStr};
 
 #[derive(Debug, Copy, Clone)]