Repository: iotaledger/iota-core
Pull request #441 (2023-10-18 01:44:47)

Feat: Reset logic for engine to roll-back in memory.
This PR introduces a mechanism to Reset an engine to its clean state after the last commitment, without having to restart the entire engine.

This PR only addresses the parts of the code that are relevant for the commitments as other things will eventually get evicted anyway, but we should maybe revisit the TODOs that I left in the code in a separate PR, once this is no longer a blocker.


Patch:
diff --git a/go.mod b/go.mod
index d5a4441e..4e3018cb 100644
--- a/go.mod
+++ b/go.mod
@@ -10,19 +10,19 @@ require (
 	github.com/google/uuid v1.4.0
 	github.com/gorilla/websocket v1.5.1
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
-	github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/app v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/logger v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05
+	github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/app v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/logger v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc
 	github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231110132801-e38d9fbdd467
 	github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231110132251-8abdb05cce43
 	github.com/iotaledger/iota.go/v4 v4.0.0-20231110131407-263d0662856b
@@ -89,7 +89,7 @@ require (
 	github.com/huin/goupnp v1.3.0 // indirect
 	github.com/iancoleman/orderedmap v0.3.0 // indirect
 	github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7 // indirect
-	github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05 // indirect
+	github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc // indirect
 	github.com/ipfs/boxo v0.13.1 // indirect
 	github.com/ipfs/go-cid v0.4.1 // indirect
 	github.com/ipfs/go-datastore v0.6.0 // indirect
diff --git a/go.sum b/go.sum
index 3751f323..eea324bb 100644
--- a/go.sum
+++ b/go.sum
@@ -275,34 +275,34 @@ github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJ
 github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
 github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7 h1:dTrD7X2PTNgli6EbS4tV9qu3QAm/kBU3XaYZV2xdzys=
 github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7/go.mod h1:ZRdPu684P0fQ1z8sXz4dj9H5LWHhz4a9oCtvjunkSrw=
-github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05 h1:9iULukNrHusPXVUrRuv2vZef+rQvEPYKusssfsKFlqY=
-github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05/go.mod h1:gbUvr01B5ha530GnNm8K2OsHXOd2BtzBYOMxyTX3iDg=
-github.com/iotaledger/hive.go/app v0.0.0-20231110120127-f90b276b7c05 h1:6aowDCuwor/e9KK0ZSKrlbGlDmpDrXZ9N+Xgrfnlfks=
-github.com/iotaledger/hive.go/app v0.0.0-20231110120127-f90b276b7c05/go.mod h1:+riYmeLApkLlj4+EpuJpEJAsj/KGfD7cqLGy7oTsPOM=
-github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05 h1:ZmAyqGmFNFH53vF7JYovt1ggwXOxrKx2byY9MH0mBmM=
-github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05 h1:sB8LMhiD9be6FRQ5ZHg7KuvhSN9rCqRzn3+AWy93F1k=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05/go.mod h1:CdixkrB7VdQzEDlVuwsxPtsiJL/WXrQgz3PELIqlLko=
-github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05 h1:DLXHQHB2FlhjNxI4URfTzDKPbVnQX8GXP56FIaENsv4=
-github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05/go.mod h1:OQ9EVTTQT1mkO/16BgwSIyQlAhEg+Cptud/yutevWsI=
-github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05 h1:h5/SlErbJXVedzehbECXtaHfH3u4IR9Neo8jpgxElSw=
-github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05/go.mod h1:JE8cbZSvzbB5TrwXibg6M0B7ck35YxF30ItHBzQRlgc=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05 h1:ZhbitKTZSFch1STCJSA6/2q4141ep1s+yd3i9frcNf0=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05 h1:BYfe6r0+hEBzLHxwh43u36tWUQlFv+zRFBwBItZvjGo=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05/go.mod h1:ytfKoHr/nF8u0y0G4mamfG0yjFtJiJVk0kgjnPOtsSY=
-github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05 h1:08CMaSb7xkgjj/vzxXHmPf+0eFqvX6TtqRBYFiwlrZY=
-github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05/go.mod h1:6Ee7i6b4tuTHuRYnPP8VUb0wr9XFI5qlqtnttBd9jRg=
-github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05 h1:ik4psDJWl8oQNh1W1jGgIY4KSh6Ba1R1tz17bWWR9bU=
-github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05/go.mod h1:vzO4/wRkEJDEZb/9fD10oKU9k1bj4qLir2Uhl5U1FkM=
-github.com/iotaledger/hive.go/logger v0.0.0-20231110120127-f90b276b7c05 h1:kAJRWFRCccdE3NXnl6NmYGNoYILUoRHSb2ryc8r1tBM=
-github.com/iotaledger/hive.go/logger v0.0.0-20231110120127-f90b276b7c05/go.mod h1:w1psHM2MuKsen1WdsPKrpqElYH7ZOQ+YdQIgJZg4HTo=
-github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05 h1:54OAQkw65vchnMDihq45Efy/rv2PD4UXx7UBxn6GUOk=
-github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05/go.mod h1:DrZPvUvLarK8C2qb+3H2vdypp/MuhpQmB3iMJbDCr/Q=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05 h1:FfTVlEMXz2yCFnyjGkOoihNMNwWtrd2S8ADsZvQJNLk=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05/go.mod h1:FoH3T6yKlZJp8xm8K+zsQiibSynp32v21CpWx8xkek8=
-github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05 h1:iMuBtSS/LWZsWu0LBB3/mtMMN4a/GkLmGbNPYaEjVGM=
-github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs=
+github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc h1:PsArE43UkLymmDy9r7n42Yd1pv1iq4FwSx3iv2Mo+vc=
+github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc/go.mod h1:gbUvr01B5ha530GnNm8K2OsHXOd2BtzBYOMxyTX3iDg=
+github.com/iotaledger/hive.go/app v0.0.0-20231110191152-7135670285dc h1:jMbElktKULtS8pA8MK5i5BTbOy+dtwAOGmVSZ5x6J2s=
+github.com/iotaledger/hive.go/app v0.0.0-20231110191152-7135670285dc/go.mod h1:+riYmeLApkLlj4+EpuJpEJAsj/KGfD7cqLGy7oTsPOM=
+github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc h1:qeE5T8LXGjKaFduWCt06CXsUTkhfHNx6hOD5xYP31QU=
+github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc h1:dyguf5k/eVGyv94ISm/FDtInOktce6koo+QtJvAPUT8=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc/go.mod h1:CdixkrB7VdQzEDlVuwsxPtsiJL/WXrQgz3PELIqlLko=
+github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc h1:3wT7e5fRdDnnomkM6xPD110BCFz66MaXKxYUvLFuYkc=
+github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc/go.mod h1:OQ9EVTTQT1mkO/16BgwSIyQlAhEg+Cptud/yutevWsI=
+github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc h1:YQUKGFcOBGKSrok++Er5SZTtQx0UHTRgH4cvlHVOiwc=
+github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc/go.mod h1:JE8cbZSvzbB5TrwXibg6M0B7ck35YxF30ItHBzQRlgc=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc h1:sNFIiT+gEE6UlftfiBdrsUBIJtnhV6EpwVRw2YpbhUc=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc h1:3fsqfM2NqfhrewVdlKT3MHcXxVNvUCSP7P32il1ypa0=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc/go.mod h1:ytfKoHr/nF8u0y0G4mamfG0yjFtJiJVk0kgjnPOtsSY=
+github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc h1:OrQBscQTsAzAJGwVs7qlPgczbvufsbENkOYRmyM+CF4=
+github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc/go.mod h1:6Ee7i6b4tuTHuRYnPP8VUb0wr9XFI5qlqtnttBd9jRg=
+github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc h1:joYrsSZuVG3DfAQR9iS3qjnMExJ0qNp2+369sxb1Y4g=
+github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc/go.mod h1:vzO4/wRkEJDEZb/9fD10oKU9k1bj4qLir2Uhl5U1FkM=
+github.com/iotaledger/hive.go/logger v0.0.0-20231110191152-7135670285dc h1:p4K5bCNRVmbzVXZUa53Hg8s6gCW+tYjhG1f3C+1F044=
+github.com/iotaledger/hive.go/logger v0.0.0-20231110191152-7135670285dc/go.mod h1:w1psHM2MuKsen1WdsPKrpqElYH7ZOQ+YdQIgJZg4HTo=
+github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc h1:dN9VYzV53oz2TlHHGtRtqaGvMDvFRW0Uh433z13k6+E=
+github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc/go.mod h1:DrZPvUvLarK8C2qb+3H2vdypp/MuhpQmB3iMJbDCr/Q=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc h1:/DIsAs3PWCNkHoLXR2+uW34VAvZvfiCCJYA/rczfnmw=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc/go.mod h1:FoH3T6yKlZJp8xm8K+zsQiibSynp32v21CpWx8xkek8=
+github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc h1:Dp9sOvU2B7xoyX28bYZgUUDAIqMCBhsmK2vWhIgDyWE=
+github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs=
 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231110132801-e38d9fbdd467 h1:2FNiPAUbHOJ+mLI1aU81QaoitbkebxJWUEylPdnC2Lc=
 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231110132801-e38d9fbdd467/go.mod h1:bXOm6f+0zP19Ku/ozcSWZQiJb9ge9X7gg1TEcpRexUQ=
 github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231110132251-8abdb05cce43 h1:Rs1vQypwaWvs+BqQWoGu6ToVl2F8eSErJabd5lmO4Pw=
diff --git a/pkg/core/buffer/unsolid_commitment_buffer.go b/pkg/core/buffer/unsolid_commitment_buffer.go
index 3bd70a01..27df0dd5 100644
--- a/pkg/core/buffer/unsolid_commitment_buffer.go
+++ b/pkg/core/buffer/unsolid_commitment_buffer.go
@@ -136,3 +136,12 @@ func (u *UnsolidCommitmentBuffer[V]) GetValuesAndEvict(commitmentID iotago.Commi
 
 	return values
 }
+
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (u *UnsolidCommitmentBuffer[V]) Reset() {
+	u.mutex.Lock()
+	defer u.mutex.Unlock()
+
+	u.blockBuffers.Clear()
+	u.commitmentBuffer.Each(func(key iotago.CommitmentID, _ types.Empty) { u.commitmentBuffer.Remove(key) })
+}
diff --git a/pkg/protocol/block_dispatcher.go b/pkg/protocol/block_dispatcher.go
index 74ab0a65..4e70c1f9 100644
--- a/pkg/protocol/block_dispatcher.go
+++ b/pkg/protocol/block_dispatcher.go
@@ -262,31 +262,11 @@ func (b *BlockDispatcher) processWarpSyncResponse(commitmentID iotago.Commitment
 
 	b.processedWarpSyncRequests.Add(commitmentID)
 
-	// If the engine is "dirty" we need to restore the state of the engine to the state of the chain commitment.
-	// As we already decided to switch and sync to this chain we should make sure that processing the blocks from the commitment
-	// leads to the verified commitment.
-	if targetEngine.Notarization.AcceptedBlocksCount(commitmentID.Slot()) > 0 {
-		b.protocol.activeEngineMutex.Lock()
-
-		newEngine, err := b.protocol.EngineManager.RollbackEngine(commitmentID.Slot() - 1)
-		if err != nil {
-			return ierrors.Wrapf(err, "failed to rollback engine for slot %d", commitmentID.Slot())
-		}
-		b.protocol.Events.MainEngineRestarted.Trigger(newEngine)
-
-		newEngine.SetChainID(targetEngine.ChainID())
-
-		if err := b.protocol.EngineManager.SetActiveInstance(newEngine); err != nil {
-			return ierrors.Wrap(err, "failed to set active engine instance")
-		}
-
-		b.protocol.linkToEngine(newEngine)
-
-		b.protocol.mainEngine.Shutdown()
-		b.protocol.mainEngine = newEngine
-		targetEngine = newEngine
-		b.protocol.activeEngineMutex.Unlock()
+	// make sure the engine is clean and requires a warp-sync before we start processing the blocks
+	if targetEngine.Workers.WaitChildren(); targetEngine.Storage.Settings().LatestCommitment().ID().Slot() > commitmentID.Slot() {
+		return nil
 	}
+	targetEngine.Reset()
 
 	// Once all blocks are booked we
 	//   1. Mark all transactions as accepted
diff --git a/pkg/protocol/engine/accounts/accountsledger/manager.go b/pkg/protocol/engine/accounts/accountsledger/manager.go
index 72929503..dbdb75e1 100644
--- a/pkg/protocol/engine/accounts/accountsledger/manager.go
+++ b/pkg/protocol/engine/accounts/accountsledger/manager.go
@@ -353,6 +353,15 @@ func (m *Manager) AddAccount(output *utxoledger.Output, blockIssuanceCredits iot
 	return nil
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (m *Manager) Reset() {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+
+	m.blockBurns.Clear()
+	m.latestSupportedVersionSignals.Clear()
+}
+
 func (m *Manager) rollbackAccountTo(accountData *accounts.AccountData, targetSlot iotago.SlotIndex) (wasDestroyed bool, err error) {
 	// to reach targetSlot, we need to rollback diffs from the current latestCommittedSlot down to targetSlot + 1
 	for diffSlot := m.latestCommittedSlot; diffSlot > targetSlot; diffSlot-- {
diff --git a/pkg/protocol/engine/blockdag/blockdag.go b/pkg/protocol/engine/blockdag/blockdag.go
index 77b4164e..af9e1e8d 100644
--- a/pkg/protocol/engine/blockdag/blockdag.go
+++ b/pkg/protocol/engine/blockdag/blockdag.go
@@ -16,5 +16,8 @@ type BlockDAG interface {
 	// without requesting it.
 	GetOrRequestBlock(blockID iotago.BlockID) (block *blocks.Block, requested bool)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/engine/blockdag/inmemoryblockdag/blockdag.go b/pkg/protocol/engine/blockdag/inmemoryblockdag/blockdag.go
index 7164679c..9d9e3b16 100644
--- a/pkg/protocol/engine/blockdag/inmemoryblockdag/blockdag.go
+++ b/pkg/protocol/engine/blockdag/inmemoryblockdag/blockdag.go
@@ -149,6 +149,11 @@ func (b *BlockDAG) GetOrRequestBlock(blockID iotago.BlockID) (block *blocks.Bloc
 	})
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (b *BlockDAG) Reset() {
+	b.uncommittedSlotBlocks.Reset()
+}
+
 func (b *BlockDAG) Shutdown() {
 	b.TriggerStopped()
 	b.workers.Shutdown()
diff --git a/pkg/protocol/engine/blocks/blocks.go b/pkg/protocol/engine/blocks/blocks.go
index 3b534a74..3ae4e69a 100644
--- a/pkg/protocol/engine/blocks/blocks.go
+++ b/pkg/protocol/engine/blocks/blocks.go
@@ -93,3 +93,8 @@ func (b *Blocks) StoreBlock(block *Block) (stored bool) {
 
 	return storage.Set(block.ID(), block)
 }
+
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (b *Blocks) Reset() {
+	b.blocks.Clear()
+}
diff --git a/pkg/protocol/engine/booker/booker.go b/pkg/protocol/engine/booker/booker.go
index fa906438..f8a4ff14 100644
--- a/pkg/protocol/engine/booker/booker.go
+++ b/pkg/protocol/engine/booker/booker.go
@@ -5,5 +5,8 @@ import (
 )
 
 type Booker interface {
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/engine/booker/inmemorybooker/booker.go b/pkg/protocol/engine/booker/inmemorybooker/booker.go
index 2b9196ff..fb307866 100644
--- a/pkg/protocol/engine/booker/inmemorybooker/booker.go
+++ b/pkg/protocol/engine/booker/inmemorybooker/booker.go
@@ -116,6 +116,9 @@ func (b *Booker) Queue(block *blocks.Block) error {
 	return nil
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (b *Booker) Reset() { /* nothing to reset but comply with interface */ }
+
 func (b *Booker) Shutdown() {
 	b.TriggerStopped()
 }
diff --git a/pkg/protocol/engine/clock/blocktime/clock.go b/pkg/protocol/engine/clock/blocktime/clock.go
index e0141565..3c2279d0 100644
--- a/pkg/protocol/engine/clock/blocktime/clock.go
+++ b/pkg/protocol/engine/clock/blocktime/clock.go
@@ -55,18 +55,19 @@ func NewProvider(opts ...options.Option[Clock]) module.Provider[*engine.Engine,
 				asyncOpt := event.WithWorkerPool(c.workerPool)
 				c.HookStopped(lo.Batch(
 					e.Events.BlockGadget.BlockAccepted.Hook(func(block *blocks.Block) {
-						c.advanceAccepted(block.IssuingTime())
+						c.acceptedTime.Advance(block.IssuingTime())
 					}, asyncOpt).Unhook,
 
 					e.Events.BlockGadget.BlockConfirmed.Hook(func(block *blocks.Block) {
-						c.advanceConfirmed(block.IssuingTime())
+						c.confirmedTime.Advance(block.IssuingTime())
 					}, asyncOpt).Unhook,
 
 					e.Events.SlotGadget.SlotFinalized.Hook(func(slot iotago.SlotIndex) {
 						timeProvider := e.APIForSlot(slot).TimeProvider()
 						slotEndTime := timeProvider.SlotEndTime(slot)
 
-						c.onSlotFinalized(slotEndTime)
+						c.acceptedTime.Advance(slotEndTime)
+						c.confirmedTime.Advance(slotEndTime)
 					}, asyncOpt).Unhook,
 				))
 			})
@@ -98,29 +99,13 @@ func (c *Clock) Snapshot() *clock.Snapshot {
 	}
 }
 
+// Reset resets the time values tracked in the clock to the given time.
+func (c *Clock) Reset(newTime time.Time) {
+	c.acceptedTime.Reset(newTime)
+	c.confirmedTime.Reset(newTime)
+}
+
 func (c *Clock) Shutdown() {
 	c.workerPool.Shutdown()
 	c.TriggerStopped()
 }
-
-func (c *Clock) advanceAccepted(time time.Time) {
-	c.Lock()
-	defer c.Unlock()
-
-	c.acceptedTime.Advance(time)
-}
-
-func (c *Clock) advanceConfirmed(time time.Time) {
-	c.Lock()
-	defer c.Unlock()
-
-	c.confirmedTime.Advance(time)
-}
-
-func (c *Clock) onSlotFinalized(slotEndTime time.Time) {
-	c.Lock()
-	defer c.Unlock()
-
-	c.acceptedTime.Advance(slotEndTime)
-	c.confirmedTime.Advance(slotEndTime)
-}
diff --git a/pkg/protocol/engine/clock/blocktime/relativetime.go b/pkg/protocol/engine/clock/blocktime/relativetime.go
index ccb5d488..bc531c3e 100644
--- a/pkg/protocol/engine/clock/blocktime/relativetime.go
+++ b/pkg/protocol/engine/clock/blocktime/relativetime.go
@@ -81,6 +81,17 @@ func (c *RelativeTime) Advance(newTime time.Time) (updated bool) {
 	return true
 }
 
+// Reset resets the time value to the given time (resetting monotonicity of the relative time).
+func (c *RelativeTime) Reset(newTime time.Time) {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
+
+	c.timeUpdateOffset = time.Now()
+	c.time = newTime
+
+	c.OnUpdated.Trigger(c.time)
+}
+
 // determineTimeUpdateOffset determines the new timeUpdateOffset that is in sync with the monotonic clock.
 func (c *RelativeTime) determineTimeUpdateOffset(newTime time.Time) time.Time {
 	diff := time.Since(c.timeUpdateOffset)
diff --git a/pkg/protocol/engine/clock/clock.go b/pkg/protocol/engine/clock/clock.go
index c485e7bb..1d1f4869 100644
--- a/pkg/protocol/engine/clock/clock.go
+++ b/pkg/protocol/engine/clock/clock.go
@@ -16,6 +16,9 @@ type Clock interface {
 	// Snapshot returns a snapshot of all time values tracked in the clock read atomically.
 	Snapshot() *Snapshot
 
+	// Reset resets the time values tracked in the clock to the given time.
+	Reset(newTime time.Time)
+
 	// Interface embeds the required methods of the module.Interface.
 	module.Interface
 }
diff --git a/pkg/protocol/engine/commitmentfilter/accountsfilter/commitmentfilter.go b/pkg/protocol/engine/commitmentfilter/accountsfilter/commitmentfilter.go
index a088c3d8..842f06c6 100644
--- a/pkg/protocol/engine/commitmentfilter/accountsfilter/commitmentfilter.go
+++ b/pkg/protocol/engine/commitmentfilter/accountsfilter/commitmentfilter.go
@@ -190,6 +190,9 @@ func (c *CommitmentFilter) evaluateBlock(block *blocks.Block) {
 	c.events.BlockAllowed.Trigger(block)
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (c *CommitmentFilter) Reset() { /* nothing to reset but comply with interface */ }
+
 func (c *CommitmentFilter) Shutdown() {
 	c.TriggerStopped()
 }
diff --git a/pkg/protocol/engine/commitmentfilter/commitmentfilter.go b/pkg/protocol/engine/commitmentfilter/commitmentfilter.go
index 499bdf99..156f56e9 100644
--- a/pkg/protocol/engine/commitmentfilter/commitmentfilter.go
+++ b/pkg/protocol/engine/commitmentfilter/commitmentfilter.go
@@ -9,5 +9,8 @@ type CommitmentFilter interface {
 	// ProcessPreFilteredBlock processes block from the given source.
 	ProcessPreFilteredBlock(block *blocks.Block)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/drr/drrbuffer.go b/pkg/protocol/engine/congestioncontrol/scheduler/drr/drrbuffer.go
index 86dc0dac..40207e99 100644
--- a/pkg/protocol/engine/congestioncontrol/scheduler/drr/drrbuffer.go
+++ b/pkg/protocol/engine/congestioncontrol/scheduler/drr/drrbuffer.go
@@ -42,6 +42,19 @@ func (b *BufferQueue) NumActiveIssuers() int {
 	return b.activeIssuers.Size()
 }
 
+func (b *BufferQueue) Clear() {
+	select {
+	case <-b.blockChan:
+	default:
+	}
+
+	b.activeIssuers.ForEachKey(func(issuedID iotago.AccountID) bool {
+		b.RemoveIssuerQueue(issuedID)
+
+		return true
+	})
+}
+
 // Size returns the total number of blocks in BufferQueue.
 func (b *BufferQueue) Size() int {
 	return b.size
diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go b/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go
index 91778700..6b4a6458 100644
--- a/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go
+++ b/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go
@@ -2,6 +2,7 @@ package drr
 
 import (
 	"math"
+	"sync"
 	"time"
 
 	"github.com/iotaledger/hive.go/core/safemath"
@@ -40,6 +41,7 @@ type Scheduler struct {
 
 	deficits *shrinkingmap.ShrinkingMap[iotago.AccountID, Deficit]
 
+	workersWg      sync.WaitGroup
 	shutdownSignal chan struct{}
 
 	blockCache *blocks.Blocks
@@ -69,6 +71,7 @@ func NewProvider(opts ...options.Option[Scheduler]) module.Provider[*engine.Engi
 				if s.apiProvider.APIForSlot(commitment.Slot()).TimeProvider().SlotsBeforeNextEpoch(commitment.Slot()) == 0 {
 					s.bufferMutex.Lock()
 					defer s.bufferMutex.Unlock()
+
 					committee, exists := s.seatManager.CommitteeInSlot(commitment.Slot() + 1)
 					if !exists {
 						s.errorHandler(ierrors.Errorf("committee does not exist in committed slot %d", commitment.Slot()+1))
@@ -79,11 +82,12 @@ func NewProvider(opts ...options.Option[Scheduler]) module.Provider[*engine.Engi
 					s.validatorBuffer.buffer.ForEach(func(accountID iotago.AccountID, validatorQueue *ValidatorQueue) bool {
 						if !committee.HasAccount(accountID) {
 							s.shutdownValidatorQueue(validatorQueue)
-							s.validatorBuffer.Delete(accountID)
 						}
 
 						return true
 					})
+
+					s.validatorBuffer.Clear()
 				}
 			})
 			e.Ledger.HookInitialized(func() {
@@ -103,7 +107,6 @@ func NewProvider(opts ...options.Option[Scheduler]) module.Provider[*engine.Engi
 			e.Events.Booker.BlockBooked.Hook(func(block *blocks.Block) {
 				s.AddBlock(block)
 				s.selectBlockToScheduleWithLocking()
-
 			})
 			e.Events.Ledger.AccountCreated.Hook(func(accountID iotago.AccountID) {
 				s.bufferMutex.Lock()
@@ -137,18 +140,27 @@ func New(apiProvider iotago.APIProvider, opts ...options.Option[Scheduler]) *Sch
 }
 
 func (s *Scheduler) Shutdown() {
-	s.validatorBuffer.buffer.ForEach(func(_ iotago.AccountID, validatorQueue *ValidatorQueue) bool {
+	s.bufferMutex.Lock()
+	defer s.bufferMutex.Unlock()
+
+	// validator workers need to be shut down first, otherwise they will hang on the shutdown channel.
+	s.validatorBuffer.buffer.ForEach(func(accountID iotago.AccountID, validatorQueue *ValidatorQueue) bool {
 		s.shutdownValidatorQueue(validatorQueue)
 
 		return true
 	})
+	s.validatorBuffer.Clear()
+
 	close(s.shutdownSignal)
 	s.TriggerStopped()
+
+	s.workersWg.Wait()
 }
 
 // Start starts the scheduler.
 func (s *Scheduler) Start() {
 	s.shutdownSignal = make(chan struct{}, 1)
+	s.workersWg.Add(1)
 	go s.basicBlockLoop()
 
 	s.TriggerInitialized()
@@ -244,6 +256,22 @@ func (s *Scheduler) AddBlock(block *blocks.Block) {
 	}
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (s *Scheduler) Reset() {
+	s.bufferMutex.Lock()
+	defer s.bufferMutex.Unlock()
+
+	// Validator workers need to be signaled to exit.
+	s.validatorBuffer.buffer.ForEach(func(_ iotago.AccountID, validatorQueue *ValidatorQueue) bool {
+		s.shutdownValidatorQueue(validatorQueue)
+
+		return true
+	})
+
+	s.basicBuffer.Clear()
+	s.validatorBuffer.Clear()
+}
+
 func (s *Scheduler) enqueueBasicBlock(block *blocks.Block) {
 	s.bufferMutex.Lock()
 	defer s.bufferMutex.Unlock()
@@ -306,6 +334,7 @@ func (s *Scheduler) enqueueValidationBlock(block *blocks.Block) {
 }
 
 func (s *Scheduler) basicBlockLoop() {
+	defer s.workersWg.Done()
 	var blockToSchedule *blocks.Block
 loop:
 	for {
@@ -329,6 +358,7 @@ loop:
 }
 
 func (s *Scheduler) validatorLoop(validatorQueue *ValidatorQueue) {
+	defer s.workersWg.Done()
 	var blockToSchedule *blocks.Block
 loop:
 	for {
@@ -391,7 +421,6 @@ func (s *Scheduler) selectBlockToScheduleWithLocking() {
 		return true
 	})
 	s.selectBasicBlockWithoutLocking()
-
 }
 
 func (s *Scheduler) selectValidationBlockWithoutLocking(validatorQueue *ValidatorQueue) bool {
@@ -715,11 +744,12 @@ func (s *Scheduler) deficitFromWork(work iotago.WorkScore) Deficit {
 func (s *Scheduler) addValidator(accountID iotago.AccountID) *ValidatorQueue {
 	validatorQueue := NewValidatorQueue(accountID)
 	s.validatorBuffer.Set(accountID, validatorQueue)
+	s.workersWg.Add(1)
 	go s.validatorLoop(validatorQueue)
 
 	return validatorQueue
 }
 
 func (s *Scheduler) shutdownValidatorQueue(validatorQueue *ValidatorQueue) {
-	validatorQueue.shutdownSignal <- struct{}{}
+	close(validatorQueue.shutdownSignal)
 }
diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/drr/validatorqueue.go b/pkg/protocol/engine/congestioncontrol/scheduler/drr/validatorqueue.go
index 900262b8..56542a0c 100644
--- a/pkg/protocol/engine/congestioncontrol/scheduler/drr/validatorqueue.go
+++ b/pkg/protocol/engine/congestioncontrol/scheduler/drr/validatorqueue.go
@@ -226,3 +226,11 @@ func (b *ValidatorBuffer) Delete(accountID iotago.AccountID) {
 
 	b.buffer.Delete(accountID)
 }
+
+func (b *ValidatorBuffer) Clear() {
+	b.buffer.ForEachKey(func(accountID iotago.AccountID) bool {
+		b.Delete(accountID)
+
+		return true
+	})
+}
diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/passthrough/scheduler.go b/pkg/protocol/engine/congestioncontrol/scheduler/passthrough/scheduler.go
index 1c034ac7..f85b9fd2 100644
--- a/pkg/protocol/engine/congestioncontrol/scheduler/passthrough/scheduler.go
+++ b/pkg/protocol/engine/congestioncontrol/scheduler/passthrough/scheduler.go
@@ -71,3 +71,6 @@ func (s *Scheduler) AddBlock(block *blocks.Block) {
 		s.events.BlockScheduled.Trigger(block)
 	}
 }
+
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (s *Scheduler) Reset() { /* nothing to reset but comply with interface */ }
diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/scheduler.go b/pkg/protocol/engine/congestioncontrol/scheduler/scheduler.go
index 99710655..ed407dc3 100644
--- a/pkg/protocol/engine/congestioncontrol/scheduler/scheduler.go
+++ b/pkg/protocol/engine/congestioncontrol/scheduler/scheduler.go
@@ -11,7 +11,7 @@ type Scheduler interface {
 	AddBlock(*blocks.Block)
 	// IsBlockIssuerReady returns true if the block issuer is ready to issuer a block, i.e., if the block issuer were to add a block to the scheduler, would it be scheduled.
 	IsBlockIssuerReady(iotago.AccountID, ...*blocks.Block) bool
-	// BufferSize returns the current buffer size of the Scheduler as block count.
+	// BasicBufferSize returns the current buffer size of the Scheduler as block count.
 	BasicBufferSize() int
 	// ValidatorBufferSize returns the current buffer size of the Scheduler as block count.
 	ValidatorBufferSize() int
@@ -23,6 +23,8 @@ type Scheduler interface {
 	IssuerQueueWork(issuerID iotago.AccountID) iotago.WorkScore
 	// ValidatorQueueBlockCount returns the queue size of the given validator as block count.
 	ValidatorQueueBlockCount(validatorID iotago.AccountID) int
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
 
 	module.Interface
 }
diff --git a/pkg/protocol/engine/consensus/blockgadget/gadget.go b/pkg/protocol/engine/consensus/blockgadget/gadget.go
index 26d83683..32d82770 100644
--- a/pkg/protocol/engine/consensus/blockgadget/gadget.go
+++ b/pkg/protocol/engine/consensus/blockgadget/gadget.go
@@ -10,4 +10,7 @@ type Gadget interface {
 
 	TrackWitnessWeight(votingBlock *blocks.Block)
 	SetAccepted(block *blocks.Block) bool
+
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
 }
diff --git a/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go b/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go
index 3e973f7c..e55a639b 100644
--- a/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go
+++ b/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go
@@ -70,6 +70,9 @@ func (g *Gadget) Shutdown() {
 	g.TriggerStopped()
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (g *Gadget) Reset() {}
+
 // propagate performs a breadth-first past cone walk starting at initialBlockIDs. evaluateFunc is called for every block visited
 // and needs to return whether to continue the walk further.
 func (g *Gadget) propagate(initialBlockIDs iotago.BlockIDs, evaluateFunc func(block *blocks.Block) bool) {
diff --git a/pkg/protocol/engine/consensus/slotgadget/slotgadget.go b/pkg/protocol/engine/consensus/slotgadget/slotgadget.go
index 3d6b6eb5..c1ebb6c1 100644
--- a/pkg/protocol/engine/consensus/slotgadget/slotgadget.go
+++ b/pkg/protocol/engine/consensus/slotgadget/slotgadget.go
@@ -5,5 +5,8 @@ import (
 )
 
 type Gadget interface {
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/engine/consensus/slotgadget/totalweightslotgadget/gadget.go b/pkg/protocol/engine/consensus/slotgadget/totalweightslotgadget/gadget.go
index 9dd33e10..78e09984 100644
--- a/pkg/protocol/engine/consensus/slotgadget/totalweightslotgadget/gadget.go
+++ b/pkg/protocol/engine/consensus/slotgadget/totalweightslotgadget/gadget.go
@@ -80,6 +80,11 @@ func NewProvider(opts ...options.Option[Gadget]) module.Provider[*engine.Engine,
 	})
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (g *Gadget) Reset() {
+	// TODO: reset slotTrackers
+}
+
 func (g *Gadget) Shutdown() {
 	g.TriggerStopped()
 	g.workers.Shutdown()
diff --git a/pkg/protocol/engine/engine.go b/pkg/protocol/engine/engine.go
index 089d8d5c..f5022488 100644
--- a/pkg/protocol/engine/engine.go
+++ b/pkg/protocol/engine/engine.go
@@ -211,10 +211,44 @@ func New(
 		func(e *Engine) {
 			fmt.Println("Engine Settings", e.Storage.Settings().String())
 		},
+		(*Engine).Reset,
 		(*Engine).TriggerInitialized,
 	)
 }
 
+func (e *Engine) ProcessBlockFromPeer(block *model.Block, source peer.ID) {
+	e.Filter.ProcessReceivedBlock(block, source)
+	e.Events.BlockProcessed.Trigger(block.ID())
+}
+
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (e *Engine) Reset() {
+	e.BlockRequester.Clear()
+	e.Storage.Reset()
+	e.EvictionState.Reset()
+	e.Filter.Reset()
+	e.CommitmentFilter.Reset()
+	e.BlockCache.Reset()
+	e.BlockDAG.Reset()
+	e.Booker.Reset()
+	e.Ledger.Reset()
+	e.BlockGadget.Reset()
+	e.SlotGadget.Reset()
+	e.Notarization.Reset()
+	e.Attestations.Reset()
+	e.SybilProtection.Reset()
+	e.Scheduler.Reset()
+	e.TipManager.Reset()
+	e.TipSelection.Reset()
+	e.Retainer.Reset()
+	e.SyncManager.Reset()
+	e.UpgradeOrchestrator.Reset()
+
+	latestCommittedSlot := e.Storage.Settings().LatestCommitment().Slot()
+	latestCommittedTime := e.APIForSlot(latestCommittedSlot).TimeProvider().SlotEndTime(latestCommittedSlot)
+	e.Clock.Reset(latestCommittedTime)
+}
+
 func (e *Engine) Shutdown() {
 	if !e.WasShutdown() {
 		e.TriggerShutdown()
@@ -243,11 +277,6 @@ func (e *Engine) Shutdown() {
 	}
 }
 
-func (e *Engine) ProcessBlockFromPeer(block *model.Block, source peer.ID) {
-	e.Filter.ProcessReceivedBlock(block, source)
-	e.Events.BlockProcessed.Trigger(block.ID())
-}
-
 func (e *Engine) BlockFromCache(id iotago.BlockID) (*blocks.Block, bool) {
 	return e.BlockCache.Block(id)
 }
diff --git a/pkg/protocol/engine/eviction/state.go b/pkg/protocol/engine/eviction/state.go
index 063f2292..e09eafdf 100644
--- a/pkg/protocol/engine/eviction/state.go
+++ b/pkg/protocol/engine/eviction/state.go
@@ -288,6 +288,8 @@ func (s *State) Rollback(lowerTarget iotago.SlotIndex, targetIndex iotago.SlotIn
 	return nil
 }
 
+func (s *State) Reset() { /* nothing to reset but comply with interface */ }
+
 // latestNonEmptySlot returns the latest slot that contains a rootblock.
 func (s *State) latestNonEmptySlot() iotago.SlotIndex {
 	latestNonEmptySlotBytes, err := s.latestNonEmptyStore.Get([]byte{latestNonEmptySlotKey})
diff --git a/pkg/protocol/engine/filter/blockfilter/filter.go b/pkg/protocol/engine/filter/blockfilter/filter.go
index 77950c5c..daf8cac3 100644
--- a/pkg/protocol/engine/filter/blockfilter/filter.go
+++ b/pkg/protocol/engine/filter/blockfilter/filter.go
@@ -117,6 +117,9 @@ func (f *Filter) ProcessReceivedBlock(block *model.Block, source peer.ID) {
 	f.events.BlockPreAllowed.Trigger(block)
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (f *Filter) Reset() { /* nothing to reset but comply with interface */ }
+
 func (f *Filter) Shutdown() {
 	f.TriggerStopped()
 }
diff --git a/pkg/protocol/engine/filter/filter.go b/pkg/protocol/engine/filter/filter.go
index 25c0092a..f9e6b991 100644
--- a/pkg/protocol/engine/filter/filter.go
+++ b/pkg/protocol/engine/filter/filter.go
@@ -11,5 +11,8 @@ type Filter interface {
 	// ProcessReceivedBlock processes block from the given source.
 	ProcessReceivedBlock(block *model.Block, source peer.ID)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/engine/ledger/ledger.go b/pkg/protocol/engine/ledger/ledger.go
index da542bb4..b038b9ca 100644
--- a/pkg/protocol/engine/ledger/ledger.go
+++ b/pkg/protocol/engine/ledger/ledger.go
@@ -43,5 +43,8 @@ type Ledger interface {
 	Export(writer io.WriteSeeker, targetSlot iotago.SlotIndex) error
 	TrackBlock(block *blocks.Block)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/engine/ledger/ledger/ledger.go b/pkg/protocol/engine/ledger/ledger/ledger.go
index 0b9207f3..e0968864 100644
--- a/pkg/protocol/engine/ledger/ledger/ledger.go
+++ b/pkg/protocol/engine/ledger/ledger/ledger.go
@@ -349,6 +349,12 @@ func (l *Ledger) RMCManager() *rmc.Manager {
 	return l.rmcManager
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (l *Ledger) Reset() {
+	l.memPool.Reset()
+	l.accountsLedger.Reset()
+}
+
 func (l *Ledger) Shutdown() {
 	l.TriggerStopped()
 	l.conflictDAG.Shutdown()
diff --git a/pkg/protocol/engine/mempool/mempool.go b/pkg/protocol/engine/mempool/mempool.go
index bb0ba101..682f76ce 100644
--- a/pkg/protocol/engine/mempool/mempool.go
+++ b/pkg/protocol/engine/mempool/mempool.go
@@ -28,4 +28,7 @@ type MemPool[VoteRank conflictdag.VoteRankType[VoteRank]] interface {
 	StateDiff(slot iotago.SlotIndex) (StateDiff, error)
 
 	Evict(slot iotago.SlotIndex)
+
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
 }
diff --git a/pkg/protocol/engine/mempool/state_diff.go b/pkg/protocol/engine/mempool/state_diff.go
index ba0968a5..67fca2c6 100644
--- a/pkg/protocol/engine/mempool/state_diff.go
+++ b/pkg/protocol/engine/mempool/state_diff.go
@@ -23,4 +23,7 @@ type StateDiff interface {
 
 	// Mutations returns an authenticated data structure that allows to commit to the applied mutations.
 	Mutations() ads.Set[iotago.Identifier, iotago.TransactionID]
+
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset() error
 }
diff --git a/pkg/protocol/engine/mempool/v1/mempool.go b/pkg/protocol/engine/mempool/v1/mempool.go
index af731584..f1b3abc0 100644
--- a/pkg/protocol/engine/mempool/v1/mempool.go
+++ b/pkg/protocol/engine/mempool/v1/mempool.go
@@ -197,6 +197,37 @@ func (m *MemPool[VoteRank]) stateDiff(slot iotago.SlotIndex) (*StateDiff, error)
 	return lo.Return1(m.stateDiffs.GetOrCreate(slot, func() *StateDiff { return NewStateDiff(slot, kv) })), nil
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (m *MemPool[VoteRank]) Reset() {
+	m.stateDiffs.ForEachKey(func(slot iotago.SlotIndex) bool {
+		if slot > m.lastEvictedSlot {
+			if stateDiff, deleted := m.stateDiffs.DeleteAndReturn(slot); deleted {
+				if err := stateDiff.Reset(); err != nil {
+					m.errorHandler(ierrors.Wrapf(err, "failed to reset state diff for slot %d", slot))
+				}
+			}
+		}
+
+		return true
+	})
+
+	attachmentsToDelete := make([]iotago.SlotIndex, 0)
+	m.attachments.ForEach(func(slot iotago.SlotIndex, _ *shrinkingmap.ShrinkingMap[iotago.BlockID, *SignedTransactionMetadata]) {
+		if slot > m.lastEvictedSlot {
+			attachmentsToDelete = append(attachmentsToDelete, slot)
+		}
+	})
+
+	for _, slot := range attachmentsToDelete {
+		if evictedAttachments := m.attachments.Evict(slot); evictedAttachments != nil {
+			evictedAttachments.ForEach(func(id iotago.BlockID, metadata *SignedTransactionMetadata) bool {
+				metadata.evictAttachment(id)
+				return true
+			})
+		}
+	}
+}
+
 // Evict evicts the slot with the given slot from the MemPool.
 func (m *MemPool[VoteRank]) Evict(slot iotago.SlotIndex) {
 	if evictedAttachments := func() *shrinkingmap.ShrinkingMap[iotago.BlockID, *SignedTransactionMetadata] {
diff --git a/pkg/protocol/engine/mempool/v1/state_diff.go b/pkg/protocol/engine/mempool/v1/state_diff.go
index f5cbf214..305eeef1 100644
--- a/pkg/protocol/engine/mempool/v1/state_diff.go
+++ b/pkg/protocol/engine/mempool/v1/state_diff.go
@@ -104,6 +104,30 @@ func (s *StateDiff) RollbackTransaction(transaction *TransactionMetadata) error
 	return nil
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (s *StateDiff) Reset() error {
+	s.spentOutputs = shrinkingmap.New[mempool.StateID, mempool.StateMetadata]()
+	s.createdOutputs = shrinkingmap.New[mempool.StateID, mempool.StateMetadata]()
+	s.executedTransactions = orderedmap.New[iotago.TransactionID, mempool.TransactionMetadata]()
+	s.stateUsageCounters = shrinkingmap.New[mempool.StateID, int]()
+
+	transactionIDs := make([]iotago.TransactionID, 0)
+	if err := s.mutations.Stream(func(transactionID iotago.TransactionID) error {
+		transactionIDs = append(transactionIDs, transactionID)
+		return nil
+	}); err != nil {
+		return ierrors.Wrapf(err, "failed to stream mutations from state diff")
+	}
+
+	for _, transactionID := range transactionIDs {
+		if _, err := s.mutations.Delete(transactionID); err != nil {
+			return ierrors.Wrapf(err, "failed to delete transaction with %s from state diff", transactionID)
+		}
+	}
+
+	return nil
+}
+
 func (s *StateDiff) compactStateChanges(stateMetadata *StateMetadata, usageCounter int) {
 	switch {
 	case usageCounter > 0:
diff --git a/pkg/protocol/engine/notarization/notarization.go b/pkg/protocol/engine/notarization/notarization.go
index e84b7508..1c3e35a5 100644
--- a/pkg/protocol/engine/notarization/notarization.go
+++ b/pkg/protocol/engine/notarization/notarization.go
@@ -15,5 +15,8 @@ type Notarization interface {
 
 	AcceptedBlocksCount(index iotago.SlotIndex) int
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/engine/notarization/slotnotarization/manager.go b/pkg/protocol/engine/notarization/slotnotarization/manager.go
index 18a3a9a3..6dd1c36c 100644
--- a/pkg/protocol/engine/notarization/slotnotarization/manager.go
+++ b/pkg/protocol/engine/notarization/slotnotarization/manager.go
@@ -129,6 +129,11 @@ func (m *Manager) ForceCommitUntil(commitUntilSlot iotago.SlotIndex) error {
 	return nil
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (m *Manager) Reset() {
+	m.slotMutations.Reset()
+}
+
 // IsBootstrapped returns if the Manager finished committing all pending slots up to the current acceptance time.
 func (m *Manager) IsBootstrapped() bool {
 	// If acceptance time is somewhere in the middle of slot 10, then the latest committable index is 4 (with minCommittableAge=6),
diff --git a/pkg/protocol/engine/notarization/slotnotarization/slotmutations.go b/pkg/protocol/engine/notarization/slotnotarization/slotmutations.go
index 08aa1079..aedee8fe 100644
--- a/pkg/protocol/engine/notarization/slotnotarization/slotmutations.go
+++ b/pkg/protocol/engine/notarization/slotnotarization/slotmutations.go
@@ -61,15 +61,9 @@ func (m *SlotMutations) Evict(index iotago.SlotIndex) error {
 	return nil
 }
 
-func (m *SlotMutations) Reset(index iotago.SlotIndex) {
-	m.evictionMutex.Lock()
-	defer m.evictionMutex.Unlock()
-
-	for i := m.latestCommittedIndex; i > index; i-- {
-		m.acceptedBlocksBySlot.Delete(i)
-	}
-
-	m.latestCommittedIndex = index
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (m *SlotMutations) Reset() {
+	m.acceptedBlocksBySlot.Clear()
 }
 
 // AcceptedBlocks returns the set of accepted blocks for the given slot.
diff --git a/pkg/protocol/engine/syncmanager/syncmanager.go b/pkg/protocol/engine/syncmanager/syncmanager.go
index 6f0c0cb5..82b1b5ff 100644
--- a/pkg/protocol/engine/syncmanager/syncmanager.go
+++ b/pkg/protocol/engine/syncmanager/syncmanager.go
@@ -31,6 +31,9 @@ type SyncManager interface {
 	// LastPrunedEpoch returns the last pruned epoch index.
 	LastPrunedEpoch() (iotago.EpochIndex, bool)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	// Shutdown shuts down the SyncManager.
 	Shutdown()
 
diff --git a/pkg/protocol/engine/syncmanager/trivialsyncmanager/syncmanager.go b/pkg/protocol/engine/syncmanager/trivialsyncmanager/syncmanager.go
index 38fd0bb0..c15a29b8 100644
--- a/pkg/protocol/engine/syncmanager/trivialsyncmanager/syncmanager.go
+++ b/pkg/protocol/engine/syncmanager/trivialsyncmanager/syncmanager.go
@@ -149,6 +149,11 @@ func (s *SyncManager) SyncStatus() *syncmanager.SyncStatus {
 	}
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (s *SyncManager) Reset() {
+	// TODO: reset last accepted block slot and so on ...
+}
+
 func (s *SyncManager) Shutdown() {
 	s.TriggerStopped()
 }
diff --git a/pkg/protocol/engine/tipmanager/tipmanager.go b/pkg/protocol/engine/tipmanager/tipmanager.go
index 2d1d3a17..878f2b67 100644
--- a/pkg/protocol/engine/tipmanager/tipmanager.go
+++ b/pkg/protocol/engine/tipmanager/tipmanager.go
@@ -36,6 +36,9 @@ type TipManager interface {
 	// Evict evicts a block from the TipManager.
 	Evict(slot iotago.SlotIndex)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	// Interface embeds the required methods of the module.Interface.
 	module.Interface
 }
diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tipmanager.go
index 32162956..b343c6f7 100644
--- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go
+++ b/pkg/protocol/engine/tipmanager/v1/tipmanager.go
@@ -105,6 +105,16 @@ func (t *TipManager) Evict(slot iotago.SlotIndex) {
 	}
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (t *TipManager) Reset() {
+	t.evictionMutex.Lock()
+	defer t.evictionMutex.Unlock()
+
+	t.tipMetadataStorage.Clear()
+	lo.ForEach(t.strongTipSet.Keys(), func(id iotago.BlockID) { t.strongTipSet.Delete(id) })
+	lo.ForEach(t.weakTipSet.Keys(), func(id iotago.BlockID) { t.strongTipSet.Delete(id) })
+}
+
 // Shutdown marks the TipManager as shutdown.
 func (t *TipManager) Shutdown() {
 	t.TriggerShutdown()
diff --git a/pkg/protocol/engine/tipselection/tipselection.go b/pkg/protocol/engine/tipselection/tipselection.go
index 44bbae79..9361b64b 100644
--- a/pkg/protocol/engine/tipselection/tipselection.go
+++ b/pkg/protocol/engine/tipselection/tipselection.go
@@ -15,6 +15,9 @@ type TipSelection interface {
 	// SetAcceptanceTime updates the acceptance time of the TipSelection.
 	SetAcceptanceTime(acceptanceTime time.Time) (previousTime time.Time)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	// Interface embeds the required methods of the module.Interface.
 	module.Interface
 }
diff --git a/pkg/protocol/engine/tipselection/v1/tip_selection.go b/pkg/protocol/engine/tipselection/v1/tip_selection.go
index 9e200125..34d62e40 100644
--- a/pkg/protocol/engine/tipselection/v1/tip_selection.go
+++ b/pkg/protocol/engine/tipselection/v1/tip_selection.go
@@ -11,6 +11,7 @@ import (
 	"github.com/iotaledger/hive.go/lo"
 	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/options"
+	"github.com/iotaledger/hive.go/runtime/syncutils"
 	"github.com/iotaledger/hive.go/runtime/timed"
 	"github.com/iotaledger/iota-core/pkg/model"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
@@ -57,6 +58,12 @@ type TipSelection struct {
 	// optMaxWeakReferences contains the maximum number of weak references that are allowed.
 	optMaxWeakReferences int
 
+	// livenessThresholdQueueMutex is used to synchronize access to the liveness threshold queue.
+	livenessThresholdQueueMutex syncutils.RWMutex
+
+	// acceptanceTimeMutex is used to synchronize access to the acceptance time.
+	acceptanceTimeMutex syncutils.RWMutex
+
 	// Module embeds the required methods of the module.Interface.
 	module.Module
 }
@@ -152,9 +159,18 @@ func (t *TipSelection) SelectTips(amount int) (references model.ParentReferences
 
 // SetAcceptanceTime updates the acceptance time of the TipSelection.
 func (t *TipSelection) SetAcceptanceTime(acceptanceTime time.Time) (previousValue time.Time) {
+	t.acceptanceTimeMutex.RLock()
+	defer t.acceptanceTimeMutex.RUnlock()
+
 	return t.acceptanceTime.Set(acceptanceTime)
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (t *TipSelection) Reset() {
+	t.resetAcceptanceTime()
+	t.resetLivenessThresholdQueue()
+}
+
 // Shutdown triggers the shutdown of the TipSelection.
 func (t *TipSelection) Shutdown() {
 	t.TriggerShutdown()
@@ -243,6 +259,9 @@ func (t *TipSelection) isValidWeakTip(block *blocks.Block) bool {
 
 // triggerLivenessThreshold triggers the liveness threshold for all tips that have reached the given threshold.
 func (t *TipSelection) triggerLivenessThreshold(threshold time.Time) {
+	t.livenessThresholdQueueMutex.RLock()
+	defer t.livenessThresholdQueueMutex.RUnlock()
+
 	for _, tip := range t.livenessThresholdQueue.PopUntil(threshold) {
 		if dynamicLivenessThreshold := tip.Block().IssuingTime().Add(t.livenessThreshold(tip)); dynamicLivenessThreshold.After(threshold) {
 			t.livenessThresholdQueue.Push(tip, dynamicLivenessThreshold)
@@ -252,6 +271,24 @@ func (t *TipSelection) triggerLivenessThreshold(threshold time.Time) {
 	}
 }
 
+func (t *TipSelection) resetLivenessThresholdQueue() {
+	t.livenessThresholdQueueMutex.Lock()
+	defer t.livenessThresholdQueueMutex.Unlock()
+
+	t.livenessThresholdQueue = timed.NewPriorityQueue[tipmanager.TipMetadata](true)
+}
+
+func (t *TipSelection) resetAcceptanceTime() {
+	t.acceptanceTimeMutex.Lock()
+	defer t.acceptanceTimeMutex.Unlock()
+
+	t.acceptanceTime = reactive.NewVariable[time.Time](monotonicallyIncreasing)
+
+	t.acceptanceTime.OnUpdate(func(_ time.Time, acceptanceTime time.Time) {
+		t.triggerLivenessThreshold(acceptanceTime)
+	})
+}
+
 // WithMaxStrongParents is an option for the TipSelection that allows to configure the maximum number of strong parents.
 func WithMaxStrongParents(maxStrongParents int) options.Option[TipSelection] {
 	return func(tipManager *TipSelection) {
diff --git a/pkg/protocol/engine/upgrade/orchestrator.go b/pkg/protocol/engine/upgrade/orchestrator.go
index eadecbab..33b8dced 100644
--- a/pkg/protocol/engine/upgrade/orchestrator.go
+++ b/pkg/protocol/engine/upgrade/orchestrator.go
@@ -17,5 +17,8 @@ type Orchestrator interface {
 
 	RestoreFromDisk(slot iotago.SlotIndex) error
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go b/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go
index bca67f26..cd771ec0 100644
--- a/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go
+++ b/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go
@@ -134,6 +134,11 @@ func NewOrchestrator(errorHandler func(error),
 	)
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (o *Orchestrator) Reset() {
+	// TODO: clean up latestSignals
+}
+
 func (o *Orchestrator) Shutdown() {
 	o.TriggerStopped()
 }
diff --git a/pkg/protocol/enginemanager/enginemanager.go b/pkg/protocol/enginemanager/enginemanager.go
index a1ade86d..b35e2327 100644
--- a/pkg/protocol/enginemanager/enginemanager.go
+++ b/pkg/protocol/enginemanager/enginemanager.go
@@ -307,7 +307,7 @@ func (e *EngineManager) rollbackStorage(newStorage *storage.Storage, slot iotago
 		return ierrors.Wrap(err, "failed to rollback settings")
 	}
 
-	if err := newStorage.RollbackPrunable(slot); err != nil {
+	if err := newStorage.Rollback(slot); err != nil {
 		return ierrors.Wrap(err, "failed to rollback prunable data")
 	}
 
diff --git a/pkg/protocol/sybilprotection/sybilprotection.go b/pkg/protocol/sybilprotection/sybilprotection.go
index 74db9aa0..0968ec42 100644
--- a/pkg/protocol/sybilprotection/sybilprotection.go
+++ b/pkg/protocol/sybilprotection/sybilprotection.go
@@ -27,5 +27,8 @@ type SybilProtection interface {
 	Import(io.ReadSeeker) error
 	Export(io.WriteSeeker, iotago.SlotIndex) error
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	module.Interface
 }
diff --git a/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go b/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
index 28e86f00..7500e656 100644
--- a/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
+++ b/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
@@ -268,6 +268,11 @@ func (o *SybilProtection) Export(writer io.WriteSeeker, targetSlot iotago.SlotIn
 	return o.performanceTracker.Export(writer, targetSlot)
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (o *SybilProtection) Reset() {
+	// TODO: check if performance tracker needs to be reset
+}
+
 func (o *SybilProtection) slotFinalized(slot iotago.SlotIndex) {
 	o.mutex.Lock()
 	defer o.mutex.Unlock()
diff --git a/pkg/retainer/retainer.go b/pkg/retainer/retainer.go
index 10f1ebbc..ffcf2450 100644
--- a/pkg/retainer/retainer.go
+++ b/pkg/retainer/retainer.go
@@ -16,6 +16,9 @@ type Retainer interface {
 	RetainBlockFailure(iotago.BlockID, apimodels.BlockFailureReason)
 	RetainTransactionFailure(iotago.BlockID, error)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	// Interface embeds the required methods of the module.Interface.
 	module.Interface
 }
diff --git a/pkg/retainer/retainer/retainer.go b/pkg/retainer/retainer/retainer.go
index 933cf87a..c61c1fdc 100644
--- a/pkg/retainer/retainer/retainer.go
+++ b/pkg/retainer/retainer/retainer.go
@@ -142,6 +142,11 @@ func NewProvider() module.Provider[*engine.Engine, retainer.Retainer] {
 	})
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (r *Retainer) Reset() {
+	// TODO: check if something needs to be cleaned here (author of the retainer)
+}
+
 func (r *Retainer) Shutdown() {
 	r.workerPool.Shutdown()
 }
diff --git a/pkg/storage/permanent/settings.go b/pkg/storage/permanent/settings.go
index c5cf892a..afa3aece 100644
--- a/pkg/storage/permanent/settings.go
+++ b/pkg/storage/permanent/settings.go
@@ -23,6 +23,7 @@ const (
 	snapshotImportedKey = iota
 	latestCommitmentKey
 	latestFinalizedSlotKey
+	latestStoredSlotKey
 	protocolVersionEpochMappingKey
 	futureProtocolParametersKey
 	protocolParametersKey
@@ -35,6 +36,7 @@ type Settings struct {
 	storeSnapshotImported            *kvstore.TypedValue[bool]
 	storeLatestCommitment            *kvstore.TypedValue[*model.Commitment]
 	storeLatestFinalizedSlot         *kvstore.TypedValue[iotago.SlotIndex]
+	storeLatestProcessedSlot         *kvstore.TypedValue[iotago.SlotIndex]
 	storeLatestIssuedValidationBlock *kvstore.TypedValue[*model.Block]
 	storeProtocolVersionEpochMapping *kvstore.TypedStore[iotago.Version, iotago.EpochIndex]
 	storeFutureProtocolParameters    *kvstore.TypedStore[iotago.Version, *types.Tuple[iotago.EpochIndex, iotago.Identifier]]
@@ -75,6 +77,12 @@ func NewSettings(store kvstore.KVStore, opts ...options.Option[api.EpochBasedPro
 			iotago.SlotIndex.Bytes,
 			iotago.SlotIndexFromBytes,
 		),
+		storeLatestProcessedSlot: kvstore.NewTypedValue(
+			store,
+			[]byte{latestStoredSlotKey},
+			iotago.SlotIndex.Bytes,
+			iotago.SlotIndexFromBytes,
+		),
 		storeLatestIssuedValidationBlock: kvstore.NewTypedValue(
 			store,
 			[]byte{latestIssuedValidationBlock},
@@ -299,30 +307,49 @@ func (s *Settings) latestFinalizedSlot() iotago.SlotIndex {
 	return latestFinalizedSlot
 }
 
-func (s *Settings) LatestIssuedValidationBlock() *model.Block {
+func (s *Settings) LatestStoredSlot() iotago.SlotIndex {
 	s.mutex.RLock()
 	defer s.mutex.RUnlock()
 
-	return s.latestIssuedValidationBlock()
+	return read(s.storeLatestProcessedSlot)
 }
 
-func (s *Settings) SetLatestIssuedValidationBlock(block *model.Block) (err error) {
+func (s *Settings) SetLatestStoredSlot(slot iotago.SlotIndex) (err error) {
 	s.mutex.Lock()
 	defer s.mutex.Unlock()
 
-	return s.storeLatestIssuedValidationBlock.Set(block)
+	return s.storeLatestProcessedSlot.Set(slot)
 }
 
-func (s *Settings) latestIssuedValidationBlock() *model.Block {
-	block, err := s.storeLatestIssuedValidationBlock.Get()
-	if err != nil {
-		if ierrors.Is(err, kvstore.ErrKeyNotFound) {
-			return nil
+func (s *Settings) AdvanceLatestStoredSlot(slot iotago.SlotIndex) (err error) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+
+	if _, err = s.storeLatestProcessedSlot.Compute(func(latestStoredSlot iotago.SlotIndex, _ bool) (newValue iotago.SlotIndex, err error) {
+		if latestStoredSlot >= slot {
+			return latestStoredSlot, kvstore.ErrTypedValueNotChanged
 		}
-		panic(err)
+
+		return slot, nil
+	}); err != nil {
+		return ierrors.Wrap(err, "failed to advance latest stored slot")
 	}
 
-	return block
+	return nil
+}
+
+func (s *Settings) LatestIssuedValidationBlock() *model.Block {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+
+	return read(s.storeLatestIssuedValidationBlock)
+}
+
+func (s *Settings) SetLatestIssuedValidationBlock(block *model.Block) (err error) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+
+	return s.storeLatestIssuedValidationBlock.Set(block)
 }
 
 func (s *Settings) Export(writer io.WriteSeeker, targetCommitment *iotago.Commitment) error {
@@ -571,7 +598,7 @@ func (s *Settings) String() string {
 	builder := stringify.NewStructBuilder("Settings")
 	builder.AddField(stringify.NewStructField("IsSnapshotImported", lo.PanicOnErr(s.storeSnapshotImported.Has())))
 	builder.AddField(stringify.NewStructField("LatestCommitment", s.latestCommitment()))
-	builder.AddField(stringify.NewStructField("LatestFinalizedSlot", s.latestFinalizedSlot()))
+	builder.AddField(stringify.NewStructField("LatestFinalizedSlot", read(s.storeLatestFinalizedSlot)))
 	if err := s.storeProtocolParameters.Iterate(kvstore.EmptyPrefix, func(version iotago.Version, protocolParams iotago.ProtocolParameters) bool {
 		builder.AddField(stringify.NewStructField(fmt.Sprintf("ProtocolParameters(%d)", version), protocolParams))
 
@@ -582,3 +609,12 @@ func (s *Settings) String() string {
 
 	return builder.String()
 }
+
+func read[T any](typedValue *kvstore.TypedValue[T]) (value T) {
+	value, err := typedValue.Get()
+	if err != nil && !ierrors.Is(err, kvstore.ErrKeyNotFound) {
+		panic(err)
+	}
+
+	return value
+}
diff --git a/pkg/storage/prunable/bucket_manager.go b/pkg/storage/prunable/bucket_manager.go
index 44737aed..907fd054 100644
--- a/pkg/storage/prunable/bucket_manager.go
+++ b/pkg/storage/prunable/bucket_manager.go
@@ -236,18 +236,22 @@ func (b *BucketManager) DeleteBucket(epoch iotago.EpochIndex) (deleted bool) {
 	return true
 }
 
-// RollbackBucket removes data in the bucket in slots [targetSlotIndex+1; epochEndSlot].
-func (b *BucketManager) RollbackBucket(epoch iotago.EpochIndex, targetSlot iotago.SlotIndex, epochEndSlot iotago.SlotIndex) error {
-	oldBucketKvStore := b.getDBInstance(epoch).KVStore()
-	for clearSlot := targetSlot + 1; clearSlot <= epochEndSlot; clearSlot++ {
-		// delete slot prefix from forkedPrunable storage that will be eventually copied into the new engine
-		if err := oldBucketKvStore.DeletePrefix(clearSlot.MustBytes()); err != nil {
-			return ierrors.Wrapf(err, "error while clearing slot %d in bucket for epoch %d", clearSlot, epoch)
+// PruneSlots prunes the data of all slots in the range [from, to] in the given epoch.
+func (b *BucketManager) PruneSlots(epoch iotago.EpochIndex, pruningRange [2]iotago.SlotIndex) error {
+	epochStore := b.getDBInstance(epoch).KVStore()
+
+	for slot := pruningRange[0]; slot <= pruningRange[1]; slot++ {
+		if err := epochStore.DeletePrefix(slot.MustBytes()); err != nil {
+			return ierrors.Wrapf(err, "error while clearing slot %d in bucket for epoch %d", slot, epoch)
 		}
 	}
 
+	// shutting down the storage does not prevent this storage from being used again and only forces a flush.
+	b.Shutdown()
+
 	return nil
 }
+
 func (b *BucketManager) Flush() error {
 	b.openDBsMutex.RLock()
 	defer b.openDBsMutex.RUnlock()
diff --git a/pkg/storage/prunable/epochstore/constants.go b/pkg/storage/prunable/epochstore/constants.go
new file mode 100644
index 00000000..f27e0f4a
--- /dev/null
+++ b/pkg/storage/prunable/epochstore/constants.go
@@ -0,0 +1,7 @@
+package epochstore
+
+const (
+	entriesKey byte = iota
+	lastAccessedEpochKey
+	lastPrunedEpochKey
+)
diff --git a/pkg/storage/prunable/epochstore/epoch_kv.go b/pkg/storage/prunable/epochstore/epoch_kv.go
index 91a84143..1a1dff4d 100644
--- a/pkg/storage/prunable/epochstore/epoch_kv.go
+++ b/pkg/storage/prunable/epochstore/epoch_kv.go
@@ -14,16 +14,17 @@ type EpochKVStore struct {
 	kv           kvstore.KVStore
 	pruningDelay iotago.EpochIndex
 
-	lastPrunedEpoch *model.PruningIndex
+	lastAccessedEpoch *kvstore.TypedValue[iotago.EpochIndex]
+	lastPrunedEpoch   *model.PruningIndex
 }
 
-func NewEpochKVStore(storeRealm kvstore.Realm, pruningRealm kvstore.Realm, kv kvstore.KVStore, pruningDelay iotago.EpochIndex) *EpochKVStore {
-
+func NewEpochKVStore(storeRealm kvstore.Realm, kv kvstore.KVStore, pruningDelay iotago.EpochIndex) *EpochKVStore {
 	return &EpochKVStore{
-		realm:           storeRealm,
-		kv:              lo.PanicOnErr(kv.WithExtendedRealm(storeRealm)),
-		pruningDelay:    pruningDelay,
-		lastPrunedEpoch: model.NewPruningIndex(lo.PanicOnErr(kv.WithExtendedRealm(pruningRealm)), storeRealm),
+		realm:             storeRealm,
+		kv:                lo.PanicOnErr(kv.WithExtendedRealm(append(storeRealm, entriesKey))),
+		pruningDelay:      pruningDelay,
+		lastAccessedEpoch: kvstore.NewTypedValue(kv, append(storeRealm, lastAccessedEpochKey), iotago.EpochIndex.Bytes, iotago.EpochIndexFromBytes),
+		lastPrunedEpoch:   model.NewPruningIndex(lo.PanicOnErr(kv.WithExtendedRealm(storeRealm)), kvstore.Realm{lastPrunedEpochKey}),
 	}
 }
 
@@ -37,11 +38,31 @@ func (e *EpochKVStore) RestoreLastPrunedEpoch() error {
 	return e.lastPrunedEpoch.RestoreFromDisk()
 }
 
+func (e *EpochKVStore) LastAccessedEpoch() (lastAccessedEpoch iotago.EpochIndex, err error) {
+	if lastAccessedEpoch, err = e.lastAccessedEpoch.Get(); err != nil {
+		if !ierrors.Is(err, kvstore.ErrKeyNotFound) {
+			err = ierrors.Wrap(err, "failed to get last accessed epoch")
+		} else {
+			err = nil
+		}
+	}
+
+	return lastAccessedEpoch, err
+}
+
 func (e *EpochKVStore) LastPrunedEpoch() (iotago.EpochIndex, bool) {
 	return e.lastPrunedEpoch.Index()
 }
 
 func (e *EpochKVStore) GetEpoch(epoch iotago.EpochIndex) (kvstore.KVStore, error) {
+	_, _ = e.lastAccessedEpoch.Compute(func(lastAccessedEpoch iotago.EpochIndex, exists bool) (newValue iotago.EpochIndex, err error) {
+		if lastAccessedEpoch >= epoch {
+			return lastAccessedEpoch, kvstore.ErrTypedValueNotChanged
+		}
+
+		return epoch, nil
+	})
+
 	if e.isTooOld(epoch) {
 		return nil, ierrors.Wrapf(database.ErrEpochPruned, "epoch %d is too old", epoch)
 	}
@@ -81,3 +102,18 @@ func (e *EpochKVStore) Prune(epoch iotago.EpochIndex, defaultPruningDelay iotago
 
 	return nil
 }
+
+func (e *EpochKVStore) RollbackEpochs(epoch iotago.EpochIndex) (lastPrunedEpoch iotago.EpochIndex, err error) {
+	lastAccessedEpoch, err := e.LastAccessedEpoch()
+	if err != nil {
+		return lastAccessedEpoch, ierrors.Wrap(err, "failed to get last accessed epoch")
+	}
+
+	for epochToPrune := epoch; epochToPrune <= lastAccessedEpoch; epochToPrune++ {
+		if err = e.DeleteEpoch(epochToPrune); err != nil {
+			return epochToPrune, ierrors.Wrapf(err, "error while deleting epoch %d", epochToPrune)
+		}
+	}
+
+	return lastAccessedEpoch, nil
+}
diff --git a/pkg/storage/prunable/epochstore/store.go b/pkg/storage/prunable/epochstore/store.go
index 35047cf8..e2fbc32c 100644
--- a/pkg/storage/prunable/epochstore/store.go
+++ b/pkg/storage/prunable/epochstore/store.go
@@ -14,28 +14,36 @@ type Store[V any] struct {
 	kv           *kvstore.TypedStore[iotago.EpochIndex, V]
 	pruningDelay iotago.EpochIndex
 
-	lastPrunedEpoch *model.PruningIndex
+	lastAccessedEpoch *kvstore.TypedValue[iotago.EpochIndex]
+	lastPrunedEpoch   *model.PruningIndex
 }
 
-func NewStore[V any](storeRealm kvstore.Realm, pruningRealm kvstore.Realm, kv kvstore.KVStore, pruningDelay iotago.EpochIndex, vToBytes kvstore.ObjectToBytes[V], bytesToV kvstore.BytesToObject[V]) *Store[V] {
+func NewStore[V any](storeRealm kvstore.Realm, kv kvstore.KVStore, pruningDelay iotago.EpochIndex, vToBytes kvstore.ObjectToBytes[V], bytesToV kvstore.BytesToObject[V]) *Store[V] {
 	return &Store[V]{
-		realm:           storeRealm,
-		kv:              kvstore.NewTypedStore(lo.PanicOnErr(kv.WithExtendedRealm(storeRealm)), iotago.EpochIndex.Bytes, iotago.EpochIndexFromBytes, vToBytes, bytesToV),
-		pruningDelay:    pruningDelay,
-		lastPrunedEpoch: model.NewPruningIndex(lo.PanicOnErr(kv.WithExtendedRealm(pruningRealm)), storeRealm),
+		realm:             storeRealm,
+		kv:                kvstore.NewTypedStore(lo.PanicOnErr(kv.WithExtendedRealm(append(storeRealm, entriesKey))), iotago.EpochIndex.Bytes, iotago.EpochIndexFromBytes, vToBytes, bytesToV),
+		pruningDelay:      pruningDelay,
+		lastAccessedEpoch: kvstore.NewTypedValue(kv, append(storeRealm, lastAccessedEpochKey), iotago.EpochIndex.Bytes, iotago.EpochIndexFromBytes),
+		lastPrunedEpoch:   model.NewPruningIndex(lo.PanicOnErr(kv.WithExtendedRealm(storeRealm)), kvstore.Realm{lastPrunedEpochKey}),
 	}
 }
 
-func (s *Store[V]) isTooOld(epoch iotago.EpochIndex) bool {
-	prunedEpoch, hasPruned := s.lastPrunedEpoch.Index()
-
-	return hasPruned && epoch <= prunedEpoch
-}
-
 func (s *Store[V]) RestoreLastPrunedEpoch() error {
 	return s.lastPrunedEpoch.RestoreFromDisk()
 }
 
+func (s *Store[V]) LastAccessedEpoch() (lastAccessedEpoch iotago.EpochIndex, err error) {
+	if lastAccessedEpoch, err = s.lastAccessedEpoch.Get(); err != nil {
+		if !ierrors.Is(err, kvstore.ErrKeyNotFound) {
+			err = ierrors.Wrap(err, "failed to get last accessed epoch")
+		} else {
+			err = nil
+		}
+	}
+
+	return lastAccessedEpoch, err
+}
+
 func (s *Store[V]) LastPrunedEpoch() (iotago.EpochIndex, bool) {
 	return s.lastPrunedEpoch.Index()
 }
@@ -61,6 +69,14 @@ func (s *Store[V]) Load(epoch iotago.EpochIndex) (V, error) {
 }
 
 func (s *Store[V]) Store(epoch iotago.EpochIndex, value V) error {
+	_, _ = s.lastAccessedEpoch.Compute(func(lastAccessedEpoch iotago.EpochIndex, exists bool) (newValue iotago.EpochIndex, err error) {
+		if lastAccessedEpoch >= epoch {
+			return lastAccessedEpoch, kvstore.ErrTypedValueNotChanged
+		}
+
+		return epoch, nil
+	})
+
 	if s.isTooOld(epoch) {
 		return ierrors.Wrapf(database.ErrEpochPruned, "epoch %d is too old", epoch)
 	}
@@ -133,3 +149,24 @@ func (s *Store[V]) Prune(epoch iotago.EpochIndex, defaultPruningDelay iotago.Epo
 
 	return nil
 }
+
+func (s *Store[V]) RollbackEpochs(epoch iotago.EpochIndex) (lastPrunedEpoch iotago.EpochIndex, err error) {
+	lastAccessedEpoch, err := s.LastAccessedEpoch()
+	if err != nil {
+		return lastAccessedEpoch, ierrors.Wrap(err, "failed to get last accessed epoch")
+	}
+
+	for epochToPrune := epoch; epochToPrune <= lastAccessedEpoch; epochToPrune++ {
+		if err = s.DeleteEpoch(epochToPrune); err != nil {
+			return epochToPrune, ierrors.Wrapf(err, "error while deleting epoch %d", epochToPrune)
+		}
+	}
+
+	return lastAccessedEpoch, nil
+}
+
+func (s *Store[V]) isTooOld(epoch iotago.EpochIndex) bool {
+	prunedEpoch, hasPruned := s.lastPrunedEpoch.Index()
+
+	return hasPruned && epoch <= prunedEpoch
+}
diff --git a/pkg/storage/prunable/prunable.go b/pkg/storage/prunable/prunable.go
index 9e439653..680c1f0e 100644
--- a/pkg/storage/prunable/prunable.go
+++ b/pkg/storage/prunable/prunable.go
@@ -41,10 +41,10 @@ func New(dbConfig database.Config, apiProvider iotago.APIProvider, errorHandler
 
 		semiPermanentDBConfig: semiPermanentDBConfig,
 		semiPermanentDB:       semiPermanentDB,
-		decidedUpgradeSignals: epochstore.NewStore(kvstore.Realm{epochPrefixDecidedUpgradeSignals}, kvstore.Realm{lastPrunedEpochKey}, semiPermanentDB.KVStore(), pruningDelayDecidedUpgradeSignals, model.VersionAndHash.Bytes, model.VersionAndHashFromBytes),
-		poolRewards:           epochstore.NewEpochKVStore(kvstore.Realm{epochPrefixPoolRewards}, kvstore.Realm{lastPrunedEpochKey}, semiPermanentDB.KVStore(), pruningDelayPoolRewards),
-		poolStats:             epochstore.NewStore(kvstore.Realm{epochPrefixPoolStats}, kvstore.Realm{lastPrunedEpochKey}, semiPermanentDB.KVStore(), pruningDelayPoolStats, (*model.PoolsStats).Bytes, model.PoolsStatsFromBytes),
-		committee:             epochstore.NewStore(kvstore.Realm{epochPrefixCommittee}, kvstore.Realm{lastPrunedEpochKey}, semiPermanentDB.KVStore(), pruningDelayCommittee, (*account.Accounts).Bytes, account.AccountsFromBytes),
+		decidedUpgradeSignals: epochstore.NewStore(kvstore.Realm{epochPrefixDecidedUpgradeSignals}, semiPermanentDB.KVStore(), pruningDelayDecidedUpgradeSignals, model.VersionAndHash.Bytes, model.VersionAndHashFromBytes),
+		poolRewards:           epochstore.NewEpochKVStore(kvstore.Realm{epochPrefixPoolRewards}, semiPermanentDB.KVStore(), pruningDelayPoolRewards),
+		poolStats:             epochstore.NewStore(kvstore.Realm{epochPrefixPoolStats}, semiPermanentDB.KVStore(), pruningDelayPoolStats, (*model.PoolsStats).Bytes, model.PoolsStatsFromBytes),
+		committee:             epochstore.NewStore(kvstore.Realm{epochPrefixCommittee}, semiPermanentDB.KVStore(), pruningDelayCommittee, (*account.Accounts).Bytes, account.AccountsFromBytes),
 	}
 }
 
@@ -142,58 +142,64 @@ func (p *Prunable) Flush() {
 	}
 }
 
-func (p *Prunable) Rollback(targetSlot iotago.SlotIndex) error {
-	timeProvider := p.apiProvider.APIForSlot(targetSlot).TimeProvider()
-	targetSlotEpoch := timeProvider.EpochFromSlot(targetSlot)
-	lastCommittedEpoch := targetSlotEpoch
-	// if the target index is the last slot of the epoch, the epoch was committed
-	if timeProvider.EpochEnd(targetSlotEpoch) != targetSlot {
-		lastCommittedEpoch--
+func (p *Prunable) Rollback(targetEpoch iotago.EpochIndex, pruningRange [2]iotago.SlotIndex) error {
+	if err := p.prunableSlotStore.PruneSlots(targetEpoch, pruningRange); err != nil {
+		return ierrors.Wrapf(err, "failed to prune slots in range [%d, %d] from target epoch %d", pruningRange[0], pruningRange[1], targetEpoch)
 	}
 
-	if err := p.prunableSlotStore.RollbackBucket(targetSlotEpoch, targetSlot, timeProvider.EpochEnd(targetSlotEpoch)); err != nil {
-		return ierrors.Wrapf(err, "error while rolling back slots in a bucket for epoch %d", targetSlotEpoch)
+	if err := p.rollbackCommitteesCandidates(targetEpoch, pruningRange[0]-1); err != nil {
+		return ierrors.Wrapf(err, "failed to rollback committee candidates to target epoch %d", targetEpoch)
 	}
 
-	if err := p.rollbackCommitteesCandidates(targetSlotEpoch, targetSlot); err != nil {
-		return ierrors.Wrapf(err, "error while rolling back committee for epoch %d", targetSlotEpoch)
+	lastPrunedCommitteeEpoch, err := p.rollbackCommitteeEpochs(targetEpoch+1, pruningRange[0]-1)
+	if err != nil {
+		return ierrors.Wrapf(err, "failed to rollback committee epochs to target epoch %d", targetEpoch)
 	}
 
-	// Shut down the prunableSlotStore in order to flush and get consistent state on disk after reopening.
-	p.prunableSlotStore.Shutdown()
+	lastPrunedPoolStatsEpoch, err := p.poolStats.RollbackEpochs(targetEpoch)
+	if err != nil {
+		return ierrors.Wrapf(err, "failed to rollback pool stats epochs to target epoch %d", targetEpoch)
+	}
 
-	// Removed entries that belong to the old fork and cannot be re-used.
-	for epoch := lastCommittedEpoch + 1; ; epoch++ {
-		if epoch > targetSlotEpoch {
-			shouldRollback, err := p.shouldRollbackCommittee(epoch, targetSlot)
-			if err != nil {
-				return ierrors.Wrapf(err, "error while checking if committee for epoch %d should be rolled back", epoch)
-			}
+	lastPrunedDecidedUpgradeSignalsEpoch, err := p.decidedUpgradeSignals.RollbackEpochs(targetEpoch)
+	if err != nil {
+		return ierrors.Wrapf(err, "failed to rollback decided upgrade signals epochs to target epoch %d", targetEpoch)
+	}
 
-			if shouldRollback {
-				if err := p.committee.DeleteEpoch(epoch); err != nil {
-					return ierrors.Wrapf(err, "error while deleting committee for epoch %d", epoch)
-				}
-			}
+	lastPrunedPoolRewardsEpoch, err := p.poolRewards.RollbackEpochs(targetEpoch)
+	if err != nil {
+		return ierrors.Wrapf(err, "failed to rollback pool rewards epochs to target epoch %d", targetEpoch)
+	}
 
-			if deleted := p.prunableSlotStore.DeleteBucket(epoch); !deleted {
-				break
-			}
-		}
+	for epochToPrune := targetEpoch + 1; epochToPrune <= max(
+		lastPrunedCommitteeEpoch,
+		lastPrunedPoolStatsEpoch,
+		lastPrunedDecidedUpgradeSignalsEpoch,
+		lastPrunedPoolRewardsEpoch,
+	); epochToPrune++ {
+		p.prunableSlotStore.DeleteBucket(epochToPrune)
+	}
 
-		if err := p.poolRewards.DeleteEpoch(epoch); err != nil {
-			return ierrors.Wrapf(err, "error while deleting pool rewards for epoch %d", epoch)
-		}
-		if err := p.poolStats.DeleteEpoch(epoch); err != nil {
-			return ierrors.Wrapf(err, "error while deleting pool stats for epoch %d", epoch)
-		}
+	return nil
+}
+
+func (p *Prunable) rollbackCommitteeEpochs(epoch iotago.EpochIndex, targetSlot iotago.SlotIndex) (lastPrunedEpoch iotago.EpochIndex, err error) {
+	lastAccessedEpoch, err := p.committee.LastAccessedEpoch()
+	if err != nil {
+		return lastAccessedEpoch, ierrors.Wrap(err, "failed to get last accessed committee epoch")
+	}
 
-		if err := p.decidedUpgradeSignals.DeleteEpoch(epoch); err != nil {
-			return ierrors.Wrapf(err, "error while deleting decided upgrade signals for epoch %d", epoch)
+	for epochToPrune := epoch; epochToPrune <= lastAccessedEpoch; epochToPrune++ {
+		if shouldRollback, rollbackErr := p.shouldRollbackCommittee(epochToPrune, targetSlot); rollbackErr != nil {
+			return epochToPrune, ierrors.Wrapf(rollbackErr, "error while checking if committee for epoch %d should be rolled back", epochToPrune)
+		} else if shouldRollback {
+			if err = p.committee.DeleteEpoch(epochToPrune); err != nil {
+				return epochToPrune, ierrors.Wrapf(err, "error while deleting committee for epoch %d", epochToPrune)
+			}
 		}
 	}
 
-	return nil
+	return lastAccessedEpoch, nil
 }
 
 // Remove committee for the next epoch only if forking point is before point of no return and committee is reused.
diff --git a/pkg/storage/prunable/prunable_epoch.go b/pkg/storage/prunable/prunable_epoch.go
index f09d3d07..0c2186dd 100644
--- a/pkg/storage/prunable/prunable_epoch.go
+++ b/pkg/storage/prunable/prunable_epoch.go
@@ -13,7 +13,6 @@ const (
 	epochPrefixPoolRewards
 	epochPrefixPoolStats
 	epochPrefixCommittee
-	lastPrunedEpochKey
 )
 
 const (
diff --git a/pkg/storage/prunable/slotstore/blocks.go b/pkg/storage/prunable/slotstore/blocks.go
index caa53218..176c1f1b 100644
--- a/pkg/storage/prunable/slotstore/blocks.go
+++ b/pkg/storage/prunable/slotstore/blocks.go
@@ -89,3 +89,8 @@ func (b *Blocks) ForEachBlockInSlot(consumer func(block *model.Block) error) err
 
 	return nil
 }
+
+// Clear clears the storage.
+func (b *Blocks) Clear() error {
+	return b.store.Clear()
+}
diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go
index 85fc0eb3..bd13e94c 100644
--- a/pkg/storage/storage.go
+++ b/pkg/storage/storage.go
@@ -4,6 +4,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/iotaledger/hive.go/ds/reactive"
 	"github.com/iotaledger/hive.go/ierrors"
 	hivedb "github.com/iotaledger/hive.go/kvstore/database"
 	"github.com/iotaledger/hive.go/runtime/options"
@@ -40,6 +41,7 @@ type Storage struct {
 	pruningLock        sync.RWMutex
 	lastPrunedEpoch    *model.EvictionIndex[iotago.EpochIndex]
 	lastPrunedSizeTime time.Time
+	lastAccessedBlocks reactive.Variable[iotago.SlotIndex]
 
 	optsDBEngine                       hivedb.Engine
 	optsAllowedDBEngines               []hivedb.Engine
@@ -58,6 +60,7 @@ func New(directory string, errorHandler func(error), opts ...options.Option[Stor
 		dir:                                utils.NewDirectory(directory, true),
 		errorHandler:                       errorHandler,
 		lastPrunedEpoch:                    model.NewEvictionIndex[iotago.EpochIndex](),
+		lastAccessedBlocks:                 reactive.NewVariable[iotago.SlotIndex](),
 		optsDBEngine:                       hivedb.EngineRocksDB,
 		optsPruningDelay:                   30,
 		optPruningSizeEnabled:              false,
diff --git a/pkg/storage/storage_prunable.go b/pkg/storage/storage_prunable.go
index 65758290..646fb3a8 100644
--- a/pkg/storage/storage_prunable.go
+++ b/pkg/storage/storage_prunable.go
@@ -1,6 +1,7 @@
 package storage
 
 import (
+	"github.com/iotaledger/hive.go/ierrors"
 	"github.com/iotaledger/hive.go/kvstore"
 	"github.com/iotaledger/iota-core/pkg/core/account"
 	"github.com/iotaledger/iota-core/pkg/model"
@@ -34,10 +35,25 @@ func (s *Storage) CommitteeCandidates(epoch iotago.EpochIndex) (kvstore.KVStore,
 }
 
 func (s *Storage) Blocks(slot iotago.SlotIndex) (*slotstore.Blocks, error) {
+	s.lastAccessedBlocks.Compute(func(lastAccessedBlocks iotago.SlotIndex) iotago.SlotIndex {
+		return max(lastAccessedBlocks, slot)
+	})
+
 	return s.prunable.Blocks(slot)
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (s *Storage) Reset() {
+	if err := s.Rollback(s.Settings().LatestCommitment().Slot()); err != nil {
+		s.errorHandler(ierrors.Wrap(err, "failed to reset prunable storage"))
+	}
+}
+
 func (s *Storage) RootBlocks(slot iotago.SlotIndex) (*slotstore.Store[iotago.BlockID, iotago.CommitmentID], error) {
+	if err := s.permanent.Settings().AdvanceLatestStoredSlot(slot); err != nil {
+		return nil, ierrors.Wrap(err, "failed to advance latest stored slot when accessing root blocks")
+	}
+
 	return s.prunable.RootBlocks(slot)
 }
 
@@ -46,30 +62,58 @@ func (s *Storage) GenesisRootBlockID() iotago.BlockID {
 }
 
 func (s *Storage) Mutations(slot iotago.SlotIndex) (kvstore.KVStore, error) {
+	if err := s.permanent.Settings().AdvanceLatestStoredSlot(slot); err != nil {
+		return nil, ierrors.Wrap(err, "failed to advance latest stored slot when accessing mutations")
+	}
+
 	return s.prunable.Mutations(slot)
 }
 
 func (s *Storage) Attestations(slot iotago.SlotIndex) (kvstore.KVStore, error) {
+	if err := s.permanent.Settings().AdvanceLatestStoredSlot(slot); err != nil {
+		return nil, ierrors.Wrap(err, "failed to advance latest stored slot when accessing attestations")
+	}
+
 	return s.prunable.Attestations(slot)
 }
 
 func (s *Storage) AccountDiffs(slot iotago.SlotIndex) (*slotstore.AccountDiffs, error) {
+	if err := s.permanent.Settings().AdvanceLatestStoredSlot(slot); err != nil {
+		return nil, ierrors.Wrap(err, "failed to advance latest stored slot when accessing account diffs")
+	}
+
 	return s.prunable.AccountDiffs(slot)
 }
 
 func (s *Storage) ValidatorPerformances(slot iotago.SlotIndex) (*slotstore.Store[iotago.AccountID, *model.ValidatorPerformance], error) {
+	if err := s.permanent.Settings().AdvanceLatestStoredSlot(slot); err != nil {
+		return nil, ierrors.Wrap(err, "failed to advance latest stored slot when accessing validator performances")
+	}
+
 	return s.prunable.ValidatorPerformances(slot)
 }
 
 func (s *Storage) UpgradeSignals(slot iotago.SlotIndex) (*slotstore.Store[account.SeatIndex, *model.SignaledBlock], error) {
+	if err := s.permanent.Settings().AdvanceLatestStoredSlot(slot); err != nil {
+		return nil, ierrors.Wrap(err, "failed to advance latest stored slot when accessing upgrade signals")
+	}
+
 	return s.prunable.UpgradeSignals(slot)
 }
 
 func (s *Storage) Roots(slot iotago.SlotIndex) (*slotstore.Store[iotago.CommitmentID, *iotago.Roots], error) {
+	if err := s.permanent.Settings().AdvanceLatestStoredSlot(slot); err != nil {
+		return nil, ierrors.Wrap(err, "failed to advance latest stored slot when accessing roots")
+	}
+
 	return s.prunable.Roots(slot)
 }
 
 func (s *Storage) Retainer(slot iotago.SlotIndex) (*slotstore.Retainer, error) {
+	if err := s.permanent.Settings().AdvanceLatestStoredSlot(slot); err != nil {
+		return nil, ierrors.Wrap(err, "failed to advance latest stored slot when accessing retainer")
+	}
+
 	return s.prunable.Retainer(slot)
 }
 
@@ -86,6 +130,18 @@ func (s *Storage) RestoreFromDisk() {
 	s.lastPrunedEpoch.MarkEvicted(lastPrunedEpoch)
 }
 
-func (s *Storage) RollbackPrunable(targetIndex iotago.SlotIndex) error {
-	return s.prunable.Rollback(targetIndex)
+func (s *Storage) Rollback(targetSlot iotago.SlotIndex) error {
+	return s.prunable.Rollback(s.pruningRange(targetSlot))
+}
+
+func (s *Storage) pruningRange(targetSlot iotago.SlotIndex) (targetEpoch iotago.EpochIndex, pruneRange [2]iotago.SlotIndex) {
+	epochOfSlot := func(slot iotago.SlotIndex) iotago.EpochIndex {
+		return s.Settings().APIProvider().APIForSlot(slot).TimeProvider().EpochFromSlot(slot)
+	}
+
+	if targetEpoch, pruneRange = epochOfSlot(targetSlot), [2]iotago.SlotIndex{targetSlot + 1, s.Settings().LatestStoredSlot()}; epochOfSlot(pruneRange[0]) > targetEpoch {
+		pruneRange[1] = s.Settings().APIProvider().APIForEpoch(targetEpoch).TimeProvider().EpochEnd(targetEpoch)
+	}
+
+	return targetEpoch, pruneRange
 }
diff --git a/tools/gendoc/go.mod b/tools/gendoc/go.mod
index 6f16dc8c..8e346d66 100644
--- a/tools/gendoc/go.mod
+++ b/tools/gendoc/go.mod
@@ -5,7 +5,7 @@ go 1.21
 replace github.com/iotaledger/iota-core => ../../
 
 require (
-	github.com/iotaledger/hive.go/app v0.0.0-20231110120127-f90b276b7c05
+	github.com/iotaledger/hive.go/app v0.0.0-20231110191152-7135670285dc
 	github.com/iotaledger/hive.go/apputils v0.0.0-20230829152614-7afc7a4d89b3
 	github.com/iotaledger/iota-core v0.0.0-00010101000000-000000000000
 )
@@ -57,19 +57,19 @@ require (
 	github.com/huin/goupnp v1.3.0 // indirect
 	github.com/iancoleman/orderedmap v0.3.0 // indirect
 	github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7 // indirect
-	github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/logger v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05 // indirect
+	github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/logger v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc // indirect
 	github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231110132801-e38d9fbdd467 // indirect
 	github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231110132251-8abdb05cce43 // indirect
 	github.com/iotaledger/iota.go/v4 v4.0.0-20231110131407-263d0662856b // indirect
diff --git a/tools/gendoc/go.sum b/tools/gendoc/go.sum
index d8567671..691e76fe 100644
--- a/tools/gendoc/go.sum
+++ b/tools/gendoc/go.sum
@@ -277,36 +277,36 @@ github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJ
 github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
 github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7 h1:dTrD7X2PTNgli6EbS4tV9qu3QAm/kBU3XaYZV2xdzys=
 github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7/go.mod h1:ZRdPu684P0fQ1z8sXz4dj9H5LWHhz4a9oCtvjunkSrw=
-github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05 h1:9iULukNrHusPXVUrRuv2vZef+rQvEPYKusssfsKFlqY=
-github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05/go.mod h1:gbUvr01B5ha530GnNm8K2OsHXOd2BtzBYOMxyTX3iDg=
-github.com/iotaledger/hive.go/app v0.0.0-20231110120127-f90b276b7c05 h1:6aowDCuwor/e9KK0ZSKrlbGlDmpDrXZ9N+Xgrfnlfks=
-github.com/iotaledger/hive.go/app v0.0.0-20231110120127-f90b276b7c05/go.mod h1:+riYmeLApkLlj4+EpuJpEJAsj/KGfD7cqLGy7oTsPOM=
+github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc h1:PsArE43UkLymmDy9r7n42Yd1pv1iq4FwSx3iv2Mo+vc=
+github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc/go.mod h1:gbUvr01B5ha530GnNm8K2OsHXOd2BtzBYOMxyTX3iDg=
+github.com/iotaledger/hive.go/app v0.0.0-20231110191152-7135670285dc h1:jMbElktKULtS8pA8MK5i5BTbOy+dtwAOGmVSZ5x6J2s=
+github.com/iotaledger/hive.go/app v0.0.0-20231110191152-7135670285dc/go.mod h1:+riYmeLApkLlj4+EpuJpEJAsj/KGfD7cqLGy7oTsPOM=
 github.com/iotaledger/hive.go/apputils v0.0.0-20230829152614-7afc7a4d89b3 h1:4aVJTc0KS77uEw0Tny4r0n1ORwcbAQDECaCclgf/6lE=
 github.com/iotaledger/hive.go/apputils v0.0.0-20230829152614-7afc7a4d89b3/go.mod h1:TZeAqieDu+xDOZp2e9+S+8pZp1PrfgcwLUnxmd8IgLU=
-github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05 h1:ZmAyqGmFNFH53vF7JYovt1ggwXOxrKx2byY9MH0mBmM=
-github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05 h1:sB8LMhiD9be6FRQ5ZHg7KuvhSN9rCqRzn3+AWy93F1k=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05/go.mod h1:CdixkrB7VdQzEDlVuwsxPtsiJL/WXrQgz3PELIqlLko=
-github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05 h1:DLXHQHB2FlhjNxI4URfTzDKPbVnQX8GXP56FIaENsv4=
-github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05/go.mod h1:OQ9EVTTQT1mkO/16BgwSIyQlAhEg+Cptud/yutevWsI=
-github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05 h1:h5/SlErbJXVedzehbECXtaHfH3u4IR9Neo8jpgxElSw=
-github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05/go.mod h1:JE8cbZSvzbB5TrwXibg6M0B7ck35YxF30ItHBzQRlgc=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05 h1:ZhbitKTZSFch1STCJSA6/2q4141ep1s+yd3i9frcNf0=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05 h1:BYfe6r0+hEBzLHxwh43u36tWUQlFv+zRFBwBItZvjGo=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05/go.mod h1:ytfKoHr/nF8u0y0G4mamfG0yjFtJiJVk0kgjnPOtsSY=
-github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05 h1:08CMaSb7xkgjj/vzxXHmPf+0eFqvX6TtqRBYFiwlrZY=
-github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05/go.mod h1:6Ee7i6b4tuTHuRYnPP8VUb0wr9XFI5qlqtnttBd9jRg=
-github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05 h1:ik4psDJWl8oQNh1W1jGgIY4KSh6Ba1R1tz17bWWR9bU=
-github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05/go.mod h1:vzO4/wRkEJDEZb/9fD10oKU9k1bj4qLir2Uhl5U1FkM=
-github.com/iotaledger/hive.go/logger v0.0.0-20231110120127-f90b276b7c05 h1:kAJRWFRCccdE3NXnl6NmYGNoYILUoRHSb2ryc8r1tBM=
-github.com/iotaledger/hive.go/logger v0.0.0-20231110120127-f90b276b7c05/go.mod h1:w1psHM2MuKsen1WdsPKrpqElYH7ZOQ+YdQIgJZg4HTo=
-github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05 h1:54OAQkw65vchnMDihq45Efy/rv2PD4UXx7UBxn6GUOk=
-github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05/go.mod h1:DrZPvUvLarK8C2qb+3H2vdypp/MuhpQmB3iMJbDCr/Q=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05 h1:FfTVlEMXz2yCFnyjGkOoihNMNwWtrd2S8ADsZvQJNLk=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05/go.mod h1:FoH3T6yKlZJp8xm8K+zsQiibSynp32v21CpWx8xkek8=
-github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05 h1:iMuBtSS/LWZsWu0LBB3/mtMMN4a/GkLmGbNPYaEjVGM=
-github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs=
+github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc h1:qeE5T8LXGjKaFduWCt06CXsUTkhfHNx6hOD5xYP31QU=
+github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc h1:dyguf5k/eVGyv94ISm/FDtInOktce6koo+QtJvAPUT8=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc/go.mod h1:CdixkrB7VdQzEDlVuwsxPtsiJL/WXrQgz3PELIqlLko=
+github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc h1:3wT7e5fRdDnnomkM6xPD110BCFz66MaXKxYUvLFuYkc=
+github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc/go.mod h1:OQ9EVTTQT1mkO/16BgwSIyQlAhEg+Cptud/yutevWsI=
+github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc h1:YQUKGFcOBGKSrok++Er5SZTtQx0UHTRgH4cvlHVOiwc=
+github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc/go.mod h1:JE8cbZSvzbB5TrwXibg6M0B7ck35YxF30ItHBzQRlgc=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc h1:sNFIiT+gEE6UlftfiBdrsUBIJtnhV6EpwVRw2YpbhUc=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc h1:3fsqfM2NqfhrewVdlKT3MHcXxVNvUCSP7P32il1ypa0=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc/go.mod h1:ytfKoHr/nF8u0y0G4mamfG0yjFtJiJVk0kgjnPOtsSY=
+github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc h1:OrQBscQTsAzAJGwVs7qlPgczbvufsbENkOYRmyM+CF4=
+github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc/go.mod h1:6Ee7i6b4tuTHuRYnPP8VUb0wr9XFI5qlqtnttBd9jRg=
+github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc h1:joYrsSZuVG3DfAQR9iS3qjnMExJ0qNp2+369sxb1Y4g=
+github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc/go.mod h1:vzO4/wRkEJDEZb/9fD10oKU9k1bj4qLir2Uhl5U1FkM=
+github.com/iotaledger/hive.go/logger v0.0.0-20231110191152-7135670285dc h1:p4K5bCNRVmbzVXZUa53Hg8s6gCW+tYjhG1f3C+1F044=
+github.com/iotaledger/hive.go/logger v0.0.0-20231110191152-7135670285dc/go.mod h1:w1psHM2MuKsen1WdsPKrpqElYH7ZOQ+YdQIgJZg4HTo=
+github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc h1:dN9VYzV53oz2TlHHGtRtqaGvMDvFRW0Uh433z13k6+E=
+github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc/go.mod h1:DrZPvUvLarK8C2qb+3H2vdypp/MuhpQmB3iMJbDCr/Q=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc h1:/DIsAs3PWCNkHoLXR2+uW34VAvZvfiCCJYA/rczfnmw=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc/go.mod h1:FoH3T6yKlZJp8xm8K+zsQiibSynp32v21CpWx8xkek8=
+github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc h1:Dp9sOvU2B7xoyX28bYZgUUDAIqMCBhsmK2vWhIgDyWE=
+github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs=
 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231110132801-e38d9fbdd467 h1:2FNiPAUbHOJ+mLI1aU81QaoitbkebxJWUEylPdnC2Lc=
 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231110132801-e38d9fbdd467/go.mod h1:bXOm6f+0zP19Ku/ozcSWZQiJb9ge9X7gg1TEcpRexUQ=
 github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231110132251-8abdb05cce43 h1:Rs1vQypwaWvs+BqQWoGu6ToVl2F8eSErJabd5lmO4Pw=
diff --git a/tools/genesis-snapshot/go.mod b/tools/genesis-snapshot/go.mod
index 2cc54a54..4c1d8b22 100644
--- a/tools/genesis-snapshot/go.mod
+++ b/tools/genesis-snapshot/go.mod
@@ -5,10 +5,10 @@ go 1.21
 replace github.com/iotaledger/iota-core => ../../
 
 require (
-	github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05
-	github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05
+	github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc
+	github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc
 	github.com/iotaledger/iota-core v0.0.0-00010101000000-000000000000
 	github.com/iotaledger/iota.go/v4 v4.0.0-20231110131407-263d0662856b
 	github.com/mr-tron/base58 v1.2.0
@@ -26,14 +26,14 @@ require (
 	github.com/holiman/uint256 v1.2.3 // indirect
 	github.com/iancoleman/orderedmap v0.3.0 // indirect
 	github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7 // indirect
-	github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05 // indirect
-	github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05 // indirect
+	github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc // indirect
+	github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc // indirect
 	github.com/ipfs/go-cid v0.4.1 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/kr/text v0.2.0 // indirect
diff --git a/tools/genesis-snapshot/go.sum b/tools/genesis-snapshot/go.sum
index 534734e1..38eaf1b6 100644
--- a/tools/genesis-snapshot/go.sum
+++ b/tools/genesis-snapshot/go.sum
@@ -28,30 +28,30 @@ github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJ
 github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
 github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7 h1:dTrD7X2PTNgli6EbS4tV9qu3QAm/kBU3XaYZV2xdzys=
 github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7/go.mod h1:ZRdPu684P0fQ1z8sXz4dj9H5LWHhz4a9oCtvjunkSrw=
-github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05 h1:9iULukNrHusPXVUrRuv2vZef+rQvEPYKusssfsKFlqY=
-github.com/iotaledger/hive.go/ads v0.0.0-20231110120127-f90b276b7c05/go.mod h1:gbUvr01B5ha530GnNm8K2OsHXOd2BtzBYOMxyTX3iDg=
-github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05 h1:ZmAyqGmFNFH53vF7JYovt1ggwXOxrKx2byY9MH0mBmM=
-github.com/iotaledger/hive.go/constraints v0.0.0-20231110120127-f90b276b7c05/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05 h1:sB8LMhiD9be6FRQ5ZHg7KuvhSN9rCqRzn3+AWy93F1k=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110120127-f90b276b7c05/go.mod h1:CdixkrB7VdQzEDlVuwsxPtsiJL/WXrQgz3PELIqlLko=
-github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05 h1:DLXHQHB2FlhjNxI4URfTzDKPbVnQX8GXP56FIaENsv4=
-github.com/iotaledger/hive.go/crypto v0.0.0-20231110120127-f90b276b7c05/go.mod h1:OQ9EVTTQT1mkO/16BgwSIyQlAhEg+Cptud/yutevWsI=
-github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05 h1:h5/SlErbJXVedzehbECXtaHfH3u4IR9Neo8jpgxElSw=
-github.com/iotaledger/hive.go/ds v0.0.0-20231110120127-f90b276b7c05/go.mod h1:JE8cbZSvzbB5TrwXibg6M0B7ck35YxF30ItHBzQRlgc=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05 h1:ZhbitKTZSFch1STCJSA6/2q4141ep1s+yd3i9frcNf0=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20231110120127-f90b276b7c05/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05 h1:BYfe6r0+hEBzLHxwh43u36tWUQlFv+zRFBwBItZvjGo=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20231110120127-f90b276b7c05/go.mod h1:ytfKoHr/nF8u0y0G4mamfG0yjFtJiJVk0kgjnPOtsSY=
-github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05 h1:08CMaSb7xkgjj/vzxXHmPf+0eFqvX6TtqRBYFiwlrZY=
-github.com/iotaledger/hive.go/lo v0.0.0-20231110120127-f90b276b7c05/go.mod h1:6Ee7i6b4tuTHuRYnPP8VUb0wr9XFI5qlqtnttBd9jRg=
-github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05 h1:ik4psDJWl8oQNh1W1jGgIY4KSh6Ba1R1tz17bWWR9bU=
-github.com/iotaledger/hive.go/log v0.0.0-20231110120127-f90b276b7c05/go.mod h1:vzO4/wRkEJDEZb/9fD10oKU9k1bj4qLir2Uhl5U1FkM=
-github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05 h1:54OAQkw65vchnMDihq45Efy/rv2PD4UXx7UBxn6GUOk=
-github.com/iotaledger/hive.go/runtime v0.0.0-20231110120127-f90b276b7c05/go.mod h1:DrZPvUvLarK8C2qb+3H2vdypp/MuhpQmB3iMJbDCr/Q=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05 h1:FfTVlEMXz2yCFnyjGkOoihNMNwWtrd2S8ADsZvQJNLk=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110120127-f90b276b7c05/go.mod h1:FoH3T6yKlZJp8xm8K+zsQiibSynp32v21CpWx8xkek8=
-github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05 h1:iMuBtSS/LWZsWu0LBB3/mtMMN4a/GkLmGbNPYaEjVGM=
-github.com/iotaledger/hive.go/stringify v0.0.0-20231110120127-f90b276b7c05/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs=
+github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc h1:PsArE43UkLymmDy9r7n42Yd1pv1iq4FwSx3iv2Mo+vc=
+github.com/iotaledger/hive.go/ads v0.0.0-20231110191152-7135670285dc/go.mod h1:gbUvr01B5ha530GnNm8K2OsHXOd2BtzBYOMxyTX3iDg=
+github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc h1:qeE5T8LXGjKaFduWCt06CXsUTkhfHNx6hOD5xYP31QU=
+github.com/iotaledger/hive.go/constraints v0.0.0-20231110191152-7135670285dc/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc h1:dyguf5k/eVGyv94ISm/FDtInOktce6koo+QtJvAPUT8=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231110191152-7135670285dc/go.mod h1:CdixkrB7VdQzEDlVuwsxPtsiJL/WXrQgz3PELIqlLko=
+github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc h1:3wT7e5fRdDnnomkM6xPD110BCFz66MaXKxYUvLFuYkc=
+github.com/iotaledger/hive.go/crypto v0.0.0-20231110191152-7135670285dc/go.mod h1:OQ9EVTTQT1mkO/16BgwSIyQlAhEg+Cptud/yutevWsI=
+github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc h1:YQUKGFcOBGKSrok++Er5SZTtQx0UHTRgH4cvlHVOiwc=
+github.com/iotaledger/hive.go/ds v0.0.0-20231110191152-7135670285dc/go.mod h1:JE8cbZSvzbB5TrwXibg6M0B7ck35YxF30ItHBzQRlgc=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc h1:sNFIiT+gEE6UlftfiBdrsUBIJtnhV6EpwVRw2YpbhUc=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20231110191152-7135670285dc/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc h1:3fsqfM2NqfhrewVdlKT3MHcXxVNvUCSP7P32il1ypa0=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20231110191152-7135670285dc/go.mod h1:ytfKoHr/nF8u0y0G4mamfG0yjFtJiJVk0kgjnPOtsSY=
+github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc h1:OrQBscQTsAzAJGwVs7qlPgczbvufsbENkOYRmyM+CF4=
+github.com/iotaledger/hive.go/lo v0.0.0-20231110191152-7135670285dc/go.mod h1:6Ee7i6b4tuTHuRYnPP8VUb0wr9XFI5qlqtnttBd9jRg=
+github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc h1:joYrsSZuVG3DfAQR9iS3qjnMExJ0qNp2+369sxb1Y4g=
+github.com/iotaledger/hive.go/log v0.0.0-20231110191152-7135670285dc/go.mod h1:vzO4/wRkEJDEZb/9fD10oKU9k1bj4qLir2Uhl5U1FkM=
+github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc h1:dN9VYzV53oz2TlHHGtRtqaGvMDvFRW0Uh433z13k6+E=
+github.com/iotaledger/hive.go/runtime v0.0.0-20231110191152-7135670285dc/go.mod h1:DrZPvUvLarK8C2qb+3H2vdypp/MuhpQmB3iMJbDCr/Q=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc h1:/DIsAs3PWCNkHoLXR2+uW34VAvZvfiCCJYA/rczfnmw=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231110191152-7135670285dc/go.mod h1:FoH3T6yKlZJp8xm8K+zsQiibSynp32v21CpWx8xkek8=
+github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc h1:Dp9sOvU2B7xoyX28bYZgUUDAIqMCBhsmK2vWhIgDyWE=
+github.com/iotaledger/hive.go/stringify v0.0.0-20231110191152-7135670285dc/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs=
 github.com/iotaledger/iota.go/v4 v4.0.0-20231110131407-263d0662856b h1:eU9vrxmXr1rMs67BsIWrfmEK+IjIsOnbl2XTlTtNIls=
 github.com/iotaledger/iota.go/v4 v4.0.0-20231110131407-263d0662856b/go.mod h1:1CUJKGvkOUGXakxFZGAagEQDX9qYyhzIElmUHCHo9RM=
 github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=


Test patch:
diff --git a/pkg/protocol/engine/attestation/attestations.go b/pkg/protocol/engine/attestation/attestations.go
index 1803a4b0..859d1648 100644
--- a/pkg/protocol/engine/attestation/attestations.go
+++ b/pkg/protocol/engine/attestation/attestations.go
@@ -24,6 +24,9 @@ type Attestations interface {
 	Export(writer io.WriteSeeker, targetSlot iotago.SlotIndex) (err error)
 	Rollback(index iotago.SlotIndex) (err error)
 
+	// Reset resets the component to a clean state as if it was created at the last commitment.
+	Reset()
+
 	RestoreFromDisk() (err error)
 
 	module.Interface
diff --git a/pkg/protocol/engine/attestation/slotattestation/manager.go b/pkg/protocol/engine/attestation/slotattestation/manager.go
index 437a0063..ad73fde4 100644
--- a/pkg/protocol/engine/attestation/slotattestation/manager.go
+++ b/pkg/protocol/engine/attestation/slotattestation/manager.go
@@ -319,6 +319,15 @@ func (m *Manager) Rollback(targetSlot iotago.SlotIndex) error {
 	return nil
 }
 
+// Reset resets the component to a clean state as if it was created at the last commitment.
+func (m *Manager) Reset() {
+	m.commitmentMutex.Lock()
+	defer m.commitmentMutex.Unlock()
+
+	// only reset future attestations as pending ones are only updated on commitment and accordingly in a "clean state".
+	m.futureAttestations.Clear()
+}
+
 func (m *Manager) computeAttestationCommitmentOffset(slot iotago.SlotIndex) (cutoffSlot iotago.SlotIndex, isValid bool) {
 	if slot < m.apiProvider.APIForSlot(slot).ProtocolParameters().MaxCommittableAge() {
 		return 0, false
diff --git a/pkg/protocol/engine/consensus/blockgadget/testframework_test.go b/pkg/protocol/engine/consensus/blockgadget/testframework_test.go
index c8b17ebe..aa7466f6 100644
--- a/pkg/protocol/engine/consensus/blockgadget/testframework_test.go
+++ b/pkg/protocol/engine/consensus/blockgadget/testframework_test.go
@@ -43,7 +43,7 @@ func NewTestFramework(test *testing.T) *TestFramework {
 		T:      test,
 		blocks: shrinkingmap.New[string, *blocks.Block](),
 
-		SeatManager: mock.NewManualPOA(api.SingleVersionProvider(tpkg.TestAPI), epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)),
+		SeatManager: mock.NewManualPOA(api.SingleVersionProvider(tpkg.TestAPI), epochstore.NewStore(kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)),
 	}
 
 	evictionState := eviction.NewState(mapdb.NewMapDB(), func(slot iotago.SlotIndex) (*slotstore.Store[iotago.BlockID, iotago.CommitmentID], error) {
diff --git a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go
index 54790e26..dded9061 100644
--- a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go
+++ b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go
@@ -21,7 +21,7 @@ import (
 )
 
 func TestTopStakers_InitializeCommittee(t *testing.T) {
-	committeeStore := epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)
+	committeeStore := epochstore.NewStore(kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)
 
 	topStakersSeatManager := &SeatManager{
 		apiProvider:     api.SingleVersionProvider(tpkg.TestAPI),
@@ -58,7 +58,7 @@ func TestTopStakers_InitializeCommittee(t *testing.T) {
 }
 
 func TestTopStakers_RotateCommittee(t *testing.T) {
-	committeeStore := epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)
+	committeeStore := epochstore.NewStore(kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)
 
 	topStakersSeatManager := &SeatManager{
 		apiProvider:     api.SingleVersionProvider(tpkg.TestAPI),
diff --git a/pkg/protocol/sybilprotection/sybilprotectionv1/performance/testsuite_test.go b/pkg/protocol/sybilprotection/sybilprotectionv1/performance/testsuite_test.go
index 9c535a13..50734ac0 100644
--- a/pkg/protocol/sybilprotection/sybilprotectionv1/performance/testsuite_test.go
+++ b/pkg/protocol/sybilprotection/sybilprotectionv1/performance/testsuite_test.go
@@ -68,10 +68,10 @@ func (t *TestSuite) InitPerformanceTracker() {
 		return p, nil
 	}
 
-	rewardsStore := epochstore.NewEpochKVStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0)
-	poolStatsStore := epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*model.PoolsStats).Bytes, model.PoolsStatsFromBytes)
-	committeeStore := epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)
-	committeeCandidatesStore := epochstore.NewEpochKVStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0)
+	rewardsStore := epochstore.NewEpochKVStore(kvstore.Realm{}, mapdb.NewMapDB(), 0)
+	poolStatsStore := epochstore.NewStore(kvstore.Realm{}, mapdb.NewMapDB(), 0, (*model.PoolsStats).Bytes, model.PoolsStatsFromBytes)
+	committeeStore := epochstore.NewStore(kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)
+	committeeCandidatesStore := epochstore.NewEpochKVStore(kvstore.Realm{}, mapdb.NewMapDB(), 0)
 
 	t.Instance = NewTracker(
 		rewardsStore.GetEpoch,