Repository: iotaledger/iota-core
Pull request #824 (2024-03-08 20:28:12)

Feat: Use new reactive logger and module in the engine
This PR adds the new Module into all of the engine components to support the new logging capabilities.

It is the iora-core counterpart to this hive.go PR: https://github.com/iotaledger/hive.go/pull/652


Patch:
diff --git a/go.mod b/go.mod
index f5d7c920..76e769a9 100644
--- a/go.mod
+++ b/go.mod
@@ -12,21 +12,21 @@ require (
 	github.com/google/uuid v1.6.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-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/app v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/constraints v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/crypto v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/db v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/ds v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/ierrors v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/kvstore v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/lo v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/log v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/runtime v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/sql v0.0.0-20240307102857-7e23a3c59bd2
-	github.com/iotaledger/hive.go/stringify v0.0.0-20240307102857-7e23a3c59bd2
+	github.com/iotaledger/hive.go/ads v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/app v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/constraints v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/crypto v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/db v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/ds v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/ierrors v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/kvstore v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/lo v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/log v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/runtime v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/sql v0.0.0-20240312125539-658ef8b73c89
+	github.com/iotaledger/hive.go/stringify v0.0.0-20240312125539-658ef8b73c89
 	github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240307101848-db58eb9353ec
 	github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240307100839-48553e1d2022
 	github.com/iotaledger/iota.go/v4 v4.0.0-20240313065735-74f8cf10c361
diff --git a/go.sum b/go.sum
index ae424462..d12a5e44 100644
--- a/go.sum
+++ b/go.sum
@@ -289,36 +289,36 @@ github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJ
 github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 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-20240307102857-7e23a3c59bd2 h1:Las7yFtYbyILe5rvkbgwp0COIb0TEGUOfwru1DPpRCE=
-github.com/iotaledger/hive.go/ads v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:tkWq2sJR/vWkbht1BJNIvNP6hJAaRye99wid0A2oU9I=
-github.com/iotaledger/hive.go/app v0.0.0-20240307102857-7e23a3c59bd2 h1:Hh3psyc4XfZVPEV5E7oBE14C+g0rRwBa4Ig3WBwPnVw=
-github.com/iotaledger/hive.go/app v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:EMUpj6oDb/QSiVpIe5nYwyDKSpO6ue7mdkzmK/VoBn8=
-github.com/iotaledger/hive.go/constraints v0.0.0-20240307102857-7e23a3c59bd2 h1:prG9e7LqHljPuHGa0dgNhtydMXgt+k0KmgGlQkCVblM=
-github.com/iotaledger/hive.go/constraints v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:JF7jjkL6tSUOXm23SWadBzBrl7eJk1DQRLc/fNoVZ+o=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240307102857-7e23a3c59bd2 h1:CSKLuAkFWA+1tBj8fAlzP/aYVskBXcuLGlx/Vxhe1iU=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240307102857-7e23a3c59bd2/go.mod h1:73ODTHUJxAoGUN0InOtuQ29LVq85pDf7GckM9d0ziqo=
-github.com/iotaledger/hive.go/crypto v0.0.0-20240307102857-7e23a3c59bd2 h1:Mu+5cOiB1AoaBULv+ibYn98+lQpW2yFX9CxMsOLtQEc=
-github.com/iotaledger/hive.go/crypto v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:aREIB19gIhSYdY0Hl/37sg1JRH7+j3ajeJxBIQf6mig=
-github.com/iotaledger/hive.go/db v0.0.0-20240307102857-7e23a3c59bd2 h1:Q+abZBAJV3cqy8yZeFwnI+1uIyVJdFaPHqwEih7EYMc=
-github.com/iotaledger/hive.go/db v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:8Y5vqE5NK5zG4Af0JV2UQeW67iLlymPLsT1KkOn4qY8=
-github.com/iotaledger/hive.go/ds v0.0.0-20240307102857-7e23a3c59bd2 h1:ezUzO0JC+frUaRsTPgpwSc0k4ilbZLCO7j4wRVqr0Xo=
-github.com/iotaledger/hive.go/ds v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:wfjeJj9B+MM/3yeUHfvT8Gj8bRsdl9utyh2dZg+1+B0=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20240307102857-7e23a3c59bd2 h1:NrM+NjA0MFUhr8wtTW6xUrUZYLYL4onbt4vXE3UpM1o=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:GQY0/35sjgT9Poi1Vrs9kFVvAkuKzGXfVh4j6CBXsAA=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20240307102857-7e23a3c59bd2 h1:pUmtxr0urvJ9JK5jWLHFjJXwe5qb2P3RZ/F361Q/lk8=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:dCgv8YMOihhGNxQu37Vh5XqT/7wLbIJst2WePqo1z8Y=
-github.com/iotaledger/hive.go/lo v0.0.0-20240307102857-7e23a3c59bd2 h1:vKWfTrBrZdWhvbZk80HZA9HjT6xqp6S06qHug22QY8Q=
-github.com/iotaledger/hive.go/lo v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:67oLzWYiBLGt5PN7IBVHdbt9P6oBYCx9UvMEL8ExDAc=
-github.com/iotaledger/hive.go/log v0.0.0-20240307102857-7e23a3c59bd2 h1:QPXAfitKRr/ssVoXcGDoXgKxRdH1nfFHAzAnzii/Dhc=
-github.com/iotaledger/hive.go/log v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:H5tmswUbT3o5+QiM6UPtBv7VnPf+lJtlantgpp2lzUI=
-github.com/iotaledger/hive.go/runtime v0.0.0-20240307102857-7e23a3c59bd2 h1:p2hxIuWG1gclXQjUAaiDtATIAFsFPMGNCUmfNSlYbBQ=
-github.com/iotaledger/hive.go/runtime v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:pueoYXud+HmTY2x9j/S6+ZX3M5ZyENFKPDrx3EtcwWs=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240307102857-7e23a3c59bd2 h1:CLqKvuxX7uXZew3O/fOZfbsyo10+ZNVKpPzqcWuMAIs=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240307102857-7e23a3c59bd2/go.mod h1:NK05G4PxwZF1m4jGANJWLhAQ2hP1Nt0L8mgCTFLsSCw=
-github.com/iotaledger/hive.go/sql v0.0.0-20240307102857-7e23a3c59bd2 h1:4c4KXZrs5HzbNjtxQMCF5FL0j2DMrJrP9rSjrZgqrjc=
-github.com/iotaledger/hive.go/sql v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:O9R4vLdoSBhEO+WyEEmBHKChlD80oz9EVi7vjuxE+J8=
-github.com/iotaledger/hive.go/stringify v0.0.0-20240307102857-7e23a3c59bd2 h1:rCOeAsmuLyOrY5EUH+5Y2tQTe1Cg1DffvL/8H7FK5Jo=
-github.com/iotaledger/hive.go/stringify v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:O4p7UmsfoeLqtAUwrKbq0lXMxjY/MLQSpZSavvvvGig=
+github.com/iotaledger/hive.go/ads v0.0.0-20240312125539-658ef8b73c89 h1:/NOniblB4wAKC3QRYx+d//mJhD0XmHMBErKa3zOnvRM=
+github.com/iotaledger/hive.go/ads v0.0.0-20240312125539-658ef8b73c89/go.mod h1:tkWq2sJR/vWkbht1BJNIvNP6hJAaRye99wid0A2oU9I=
+github.com/iotaledger/hive.go/app v0.0.0-20240312125539-658ef8b73c89 h1:RIbhfIY2o5iz2kk/B0hYKDJ8hTQPThb6mzq8FsffYrs=
+github.com/iotaledger/hive.go/app v0.0.0-20240312125539-658ef8b73c89/go.mod h1:EMUpj6oDb/QSiVpIe5nYwyDKSpO6ue7mdkzmK/VoBn8=
+github.com/iotaledger/hive.go/constraints v0.0.0-20240312125539-658ef8b73c89 h1:CyhKeBrhnZZ6iJO4bKuKQxjRqt6u/7nIAFGTw/vquKE=
+github.com/iotaledger/hive.go/constraints v0.0.0-20240312125539-658ef8b73c89/go.mod h1:JF7jjkL6tSUOXm23SWadBzBrl7eJk1DQRLc/fNoVZ+o=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240312125539-658ef8b73c89 h1:O80716g+R2vNwgbNIye2An9M1W9Gh8iNp5l6bIh6DTs=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240312125539-658ef8b73c89/go.mod h1:73ODTHUJxAoGUN0InOtuQ29LVq85pDf7GckM9d0ziqo=
+github.com/iotaledger/hive.go/crypto v0.0.0-20240312125539-658ef8b73c89 h1:akL3mWtCHx6GMNTv7xxV8c1YWtfKHLCyC/d6NywHmjM=
+github.com/iotaledger/hive.go/crypto v0.0.0-20240312125539-658ef8b73c89/go.mod h1:aREIB19gIhSYdY0Hl/37sg1JRH7+j3ajeJxBIQf6mig=
+github.com/iotaledger/hive.go/db v0.0.0-20240312125539-658ef8b73c89 h1:5QhyTJXhaiNOiFDsHMIv8aTbkuVxPEtKVgt+pCe3gAE=
+github.com/iotaledger/hive.go/db v0.0.0-20240312125539-658ef8b73c89/go.mod h1:8Y5vqE5NK5zG4Af0JV2UQeW67iLlymPLsT1KkOn4qY8=
+github.com/iotaledger/hive.go/ds v0.0.0-20240312125539-658ef8b73c89 h1:cnRq/Nnd7hClvx5tIVEVcC0oyggTEFKN4+t3+R8Rz14=
+github.com/iotaledger/hive.go/ds v0.0.0-20240312125539-658ef8b73c89/go.mod h1:wfjeJj9B+MM/3yeUHfvT8Gj8bRsdl9utyh2dZg+1+B0=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20240312125539-658ef8b73c89 h1:+oBtd3D7TjW8nt7kdvzYnox5T1aBgiCdJywWOnyjUgg=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20240312125539-658ef8b73c89/go.mod h1:GQY0/35sjgT9Poi1Vrs9kFVvAkuKzGXfVh4j6CBXsAA=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20240312125539-658ef8b73c89 h1:Z1zboF32gixXktGpiHmqIbu61GaT2Jazr/PCKaAKXt4=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20240312125539-658ef8b73c89/go.mod h1:dCgv8YMOihhGNxQu37Vh5XqT/7wLbIJst2WePqo1z8Y=
+github.com/iotaledger/hive.go/lo v0.0.0-20240312125539-658ef8b73c89 h1:JybC8QefExnKXp08ETO9s5QS0jMcnopqkFXp8jsgATY=
+github.com/iotaledger/hive.go/lo v0.0.0-20240312125539-658ef8b73c89/go.mod h1:67oLzWYiBLGt5PN7IBVHdbt9P6oBYCx9UvMEL8ExDAc=
+github.com/iotaledger/hive.go/log v0.0.0-20240312125539-658ef8b73c89 h1:Rk1bWnm/CMW9Xx158p0uc5vy1U3UZYopXsmuMbUkFsc=
+github.com/iotaledger/hive.go/log v0.0.0-20240312125539-658ef8b73c89/go.mod h1:H5tmswUbT3o5+QiM6UPtBv7VnPf+lJtlantgpp2lzUI=
+github.com/iotaledger/hive.go/runtime v0.0.0-20240312125539-658ef8b73c89 h1:pDrD8FZSobQ49OKdE2HtaySa8FTzyUkE4CPZB3CCg5M=
+github.com/iotaledger/hive.go/runtime v0.0.0-20240312125539-658ef8b73c89/go.mod h1:OKoOmZd+qDjm0WsisIB5FYbKhMm5iPx4/mDJL/8SjsU=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240312125539-658ef8b73c89 h1:/0aRg/v9CrurFtzoCIPREzHRbFCD6GEGFHKdNw0ePGk=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240312125539-658ef8b73c89/go.mod h1:NK05G4PxwZF1m4jGANJWLhAQ2hP1Nt0L8mgCTFLsSCw=
+github.com/iotaledger/hive.go/sql v0.0.0-20240312125539-658ef8b73c89 h1:/8BpaYldXDEUwsPw98T9DNLfQxfvLi1AAv7Owiz82us=
+github.com/iotaledger/hive.go/sql v0.0.0-20240312125539-658ef8b73c89/go.mod h1:O9R4vLdoSBhEO+WyEEmBHKChlD80oz9EVi7vjuxE+J8=
+github.com/iotaledger/hive.go/stringify v0.0.0-20240312125539-658ef8b73c89 h1:/gCkJ/6Pgk1nw5FCqbA50wCCbVcXDBT9JKQ3pPJ5PME=
+github.com/iotaledger/hive.go/stringify v0.0.0-20240312125539-658ef8b73c89/go.mod h1:O4p7UmsfoeLqtAUwrKbq0lXMxjY/MLQSpZSavvvvGig=
 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240307101848-db58eb9353ec h1:8pasR6AxVTr1AqTbyAgPmrgmEE+Z5Tn4DOQy/Eu8jN8=
 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240307101848-db58eb9353ec/go.mod h1:LrPLgC2QeGvsgyhS7SYoqH449z4C4ro4HOByg1PXqHw=
 github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240307100839-48553e1d2022 h1:I178SaWPhSxTvntKKvUVIdeogXIiLDALTbstKvbCuVc=
diff --git a/pkg/protocol/blocks.go b/pkg/protocol/blocks.go
index 5f27bcbf..dcdbe0b9 100644
--- a/pkg/protocol/blocks.go
+++ b/pkg/protocol/blocks.go
@@ -34,13 +34,13 @@ type Blocks struct {
 // newBlocks creates a new blocks protocol instance for the given protocol.
 func newBlocks(protocol *Protocol) *Blocks {
 	b := &Blocks{
-		Logger:              lo.Return1(protocol.Logger.NewChildLogger("Blocks")),
+		Logger:              protocol.NewChildLogger("Blocks"),
 		protocol:            protocol,
 		workerPool:          protocol.Workers.CreatePool("Blocks"),
 		droppedBlocksBuffer: buffer.NewUnsolidCommitmentBuffer[*types.Tuple[*model.Block, peer.ID]](20, 100),
 	}
 
-	protocol.Constructed.OnTrigger(func() {
+	protocol.ConstructedEvent().OnTrigger(func() {
 		protocol.Commitments.WithElements(func(commitment *Commitment) (shutdown func()) {
 			return commitment.ReplayDroppedBlocks.OnUpdate(func(_ bool, replayBlocks bool) {
 				if replayBlocks {
diff --git a/pkg/protocol/chain.go b/pkg/protocol/chain.go
index 64af9b97..b8293a15 100644
--- a/pkg/protocol/chain.go
+++ b/pkg/protocol/chain.go
@@ -106,7 +106,7 @@ func newChain(chains *Chains) *Chain {
 // initialized.
 func (c *Chain) WithInitializedEngine(callback func(engineInstance *engine.Engine) (shutdown func())) (shutdown func()) {
 	return c.Engine.WithNonEmptyValue(func(engineInstance *engine.Engine) (shutdown func()) {
-		return engineInstance.Initialized.WithNonEmptyValue(func(_ bool) (shutdown func()) {
+		return engineInstance.InitializedEvent().WithNonEmptyValue(func(_ bool) (shutdown func()) {
 			return callback(engineInstance)
 		})
 	})
diff --git a/pkg/protocol/chains.go b/pkg/protocol/chains.go
index 13c2bca9..b5f90412 100644
--- a/pkg/protocol/chains.go
+++ b/pkg/protocol/chains.go
@@ -67,7 +67,7 @@ func newChains(protocol *Protocol) *Chains {
 	c.WithElements(func(chain *Chain) (teardown func()) {
 		return chain.Engine.OnUpdate(func(_ *engine.Engine, newEngine *engine.Engine) {
 			if newEngine != nil {
-				newEngine.Logger.OnLogLevelActive(log.LevelTrace, func() (shutdown func()) {
+				newEngine.OnLogLevelActive(log.LevelTrace, func() (shutdown func()) {
 					return attachEngineLogs(newEngine)
 				})
 			}
@@ -78,12 +78,12 @@ func newChains(protocol *Protocol) *Chains {
 		c.initLogger(protocol.NewChildLogger("Chains")),
 		c.initChainSwitching(),
 
-		protocol.Constructed.WithNonEmptyValue(func(_ bool) (shutdown func()) {
+		protocol.ConstructedEvent().WithNonEmptyValue(func(_ bool) (shutdown func()) {
 			return c.deriveLatestSeenSlot(protocol)
 		}),
 	)
 
-	protocol.Shutdown.OnTrigger(shutdown)
+	protocol.ShutdownEvent().OnTrigger(shutdown)
 
 	return c
 }
diff --git a/pkg/protocol/commitments.go b/pkg/protocol/commitments.go
index 74f8f20c..f391d130 100644
--- a/pkg/protocol/commitments.go
+++ b/pkg/protocol/commitments.go
@@ -59,7 +59,7 @@ func newCommitments(protocol *Protocol) *Commitments {
 		c.initRequester(),
 	)
 
-	protocol.Shutdown.OnTrigger(shutdown)
+	protocol.ShutdownEvent().OnTrigger(shutdown)
 
 	return c
 }
@@ -110,7 +110,7 @@ func (c *Commitments) initLogger() (shutdown func()) {
 
 // initEngineCommitmentSynchronization initializes the synchronization of commitments that are published by the engines.
 func (c *Commitments) initEngineCommitmentSynchronization() func() {
-	return c.protocol.Constructed.WithNonEmptyValue(func(_ bool) (shutdown func()) {
+	return c.protocol.ConstructedEvent().WithNonEmptyValue(func(_ bool) (shutdown func()) {
 		return lo.BatchReverse(
 			// advance the root commitment of the main chain
 			c.protocol.Chains.Main.WithNonEmptyValue(func(mainChain *Chain) (shutdown func()) {
diff --git a/pkg/protocol/engine/accounts/accountsledger/manager.go b/pkg/protocol/engine/accounts/accountsledger/manager.go
index 8e9f031c..36e3adbd 100644
--- a/pkg/protocol/engine/accounts/accountsledger/manager.go
+++ b/pkg/protocol/engine/accounts/accountsledger/manager.go
@@ -50,12 +50,14 @@ type Manager struct {
 }
 
 func New(
+	subModule module.Module,
 	apiProvider iotago.APIProvider,
 	blockFunc func(id iotago.BlockID) (*blocks.Block, bool),
 	slotDiffFunc func(iotago.SlotIndex) (*slotstore.AccountDiffs, error),
 	accountsStore kvstore.KVStore,
 ) *Manager {
-	return &Manager{
+	return module.InitSimpleLifecycle(&Manager{
+		Module:                        subModule,
 		apiProvider:                   apiProvider,
 		blockBurns:                    shrinkingmap.New[iotago.SlotIndex, ds.Set[iotago.BlockID]](),
 		latestSupportedVersionSignals: memstorage.NewIndexedStorage[iotago.SlotIndex, iotago.AccountID, *model.SignaledBlock](),
@@ -69,11 +71,7 @@ func New(
 		),
 		block:    blockFunc,
 		slotDiff: slotDiffFunc,
-	}
-}
-
-func (m *Manager) Shutdown() {
-	m.TriggerStopped()
+	})
 }
 
 func (m *Manager) SetLatestCommittedSlot(slot iotago.SlotIndex) {
diff --git a/pkg/protocol/engine/blockdag/blockdag.go b/pkg/protocol/engine/blockdag/blockdag.go
index d5ff9127..9d5ef4b3 100644
--- a/pkg/protocol/engine/blockdag/blockdag.go
+++ b/pkg/protocol/engine/blockdag/blockdag.go
@@ -1,7 +1,6 @@
 package blockdag
 
 import (
-	"github.com/iotaledger/hive.go/log"
 	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/iota-core/pkg/model"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
@@ -20,7 +19,5 @@ type BlockDAG interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	log.Logger
-
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/blockdag/inmemoryblockdag/blockdag.go b/pkg/protocol/engine/blockdag/inmemoryblockdag/blockdag.go
index ac1e9a6a..e0ff4c34 100644
--- a/pkg/protocol/engine/blockdag/inmemoryblockdag/blockdag.go
+++ b/pkg/protocol/engine/blockdag/inmemoryblockdag/blockdag.go
@@ -4,7 +4,6 @@ import (
 	"sync/atomic"
 
 	"github.com/iotaledger/hive.go/ierrors"
-	"github.com/iotaledger/hive.go/log"
 	"github.com/iotaledger/hive.go/runtime/event"
 	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/options"
@@ -35,15 +34,15 @@ type BlockDAG struct {
 	errorHandler func(error)
 
 	module.Module
-
-	log.Logger
 }
 
 func NewProvider(opts ...options.Option[BlockDAG]) module.Provider[*engine.Engine, blockdag.BlockDAG] {
 	return module.Provide(func(e *engine.Engine) blockdag.BlockDAG {
-		b := New(e.Logger.NewChildLogger("BlockDAG"), e.Workers.CreateGroup("BlockDAG"), int(e.Storage.Settings().APIProvider().CommittedAPI().ProtocolParameters().MaxCommittableAge())*2, e.EvictionState, e.BlockCache, e.ErrorHandler("blockdag"), opts...)
+		b := New(e.NewSubModule("BlockDAG"), e.Workers.CreateGroup("BlockDAG"), int(e.Storage.Settings().APIProvider().CommittedAPI().ProtocolParameters().MaxCommittableAge())*2, e.EvictionState, e.BlockCache, e.ErrorHandler("blockdag"), opts...)
+
+		e.ConstructedEvent().OnTrigger(func() {
+			b.Init(e.SyncManager.LatestCommitment)
 
-		e.Constructed.OnTrigger(func() {
 			wp := b.workers.CreatePool("BlockDAG.Append", workerpool.WithWorkerCount(2))
 
 			e.Events.PreSolidFilter.BlockPreAllowed.Hook(func(block *model.Block) {
@@ -59,59 +58,35 @@ func NewProvider(opts ...options.Option[BlockDAG]) module.Provider[*engine.Engin
 				}
 			}, event.WithWorkerPool(wp))
 
-			b.latestCommitmentFunc = e.SyncManager.LatestCommitment
-
 			e.Events.BlockDAG.LinkTo(b.events)
-
-			b.TriggerInitialized()
 		})
 
 		return b
 	})
 }
 
-func (b *BlockDAG) setupBlock(block *blocks.Block) {
-	var unsolidParentsCount atomic.Int32
-	unsolidParentsCount.Store(int32(len(block.Parents())))
-
-	block.ForEachParent(func(parent iotago.Parent) {
-		parentBlock, exists := b.blockCache.Block(parent.ID)
-		if !exists {
-			b.errorHandler(ierrors.Errorf("failed to setup block %s, parent %s is missing", block.ID(), parent.ID))
-
-			return
-		}
-
-		parentBlock.Solid().OnUpdateOnce(func(_ bool, _ bool) {
-			if unsolidParentsCount.Add(-1) == 0 {
-				if block.SetSolid() {
-					b.events.BlockSolid.Trigger(block)
-				}
-			}
-		})
-
-		parentBlock.Invalid().OnUpdateOnce(func(_ bool, _ bool) {
-			if block.SetInvalid() {
-				b.events.BlockInvalid.Trigger(block, ierrors.Errorf("parent block %s is marked as invalid", parent.ID))
-			}
-		})
-	})
-}
-
 // New is the constructor for the BlockDAG and creates a new BlockDAG instance.
-func New(logger log.Logger, workers *workerpool.Group, unsolidCommitmentBufferSize int, evictionState *eviction.State, blockCache *blocks.Blocks, errorHandler func(error), opts ...options.Option[BlockDAG]) (newBlockDAG *BlockDAG) {
+func New(subModule module.Module, workers *workerpool.Group, unsolidCommitmentBufferSize int, evictionState *eviction.State, blockCache *blocks.Blocks, errorHandler func(error), opts ...options.Option[BlockDAG]) (newBlockDAG *BlockDAG) {
 	return options.Apply(&BlockDAG{
-		Logger:                logger,
+		Module:                subModule,
 		events:                blockdag.NewEvents(),
 		evictionState:         evictionState,
 		blockCache:            blockCache,
 		workers:               workers,
 		errorHandler:          errorHandler,
 		uncommittedSlotBlocks: buffer.NewUnsolidCommitmentBuffer[*blocks.Block](unsolidCommitmentBufferSize),
-	}, opts, (*BlockDAG).TriggerConstructed, (*BlockDAG).TriggerInitialized)
+	}, opts, func(b *BlockDAG) {
+		b.ShutdownEvent().OnTrigger(b.shutdown)
+
+		b.ConstructedEvent().Trigger()
+	})
 }
 
-var _ blockdag.BlockDAG = new(BlockDAG)
+func (b *BlockDAG) Init(latestCommitmentFunc func() *model.Commitment) {
+	b.latestCommitmentFunc = latestCommitmentFunc
+
+	b.InitializedEvent().Trigger()
+}
 
 // Append is used to append new Blocks to the BlockDAG. It is the main function of the BlockDAG that triggers Events.
 func (b *BlockDAG) Append(modelBlock *model.Block) (block *blocks.Block, wasAppended bool, err error) {
@@ -158,9 +133,32 @@ func (b *BlockDAG) Reset() {
 	b.uncommittedSlotBlocks.Reset()
 }
 
-func (b *BlockDAG) Shutdown() {
-	b.TriggerStopped()
-	b.workers.Shutdown()
+func (b *BlockDAG) setupBlock(block *blocks.Block) {
+	var unsolidParentsCount atomic.Int32
+	unsolidParentsCount.Store(int32(len(block.Parents())))
+
+	block.ForEachParent(func(parent iotago.Parent) {
+		parentBlock, exists := b.blockCache.Block(parent.ID)
+		if !exists {
+			b.errorHandler(ierrors.Errorf("failed to setup block %s, parent %s is missing", block.ID(), parent.ID))
+
+			return
+		}
+
+		parentBlock.Solid().OnUpdateOnce(func(_ bool, _ bool) {
+			if unsolidParentsCount.Add(-1) == 0 {
+				if block.SetSolid() {
+					b.events.BlockSolid.Trigger(block)
+				}
+			}
+		})
+
+		parentBlock.Invalid().OnUpdateOnce(func(_ bool, _ bool) {
+			if block.SetInvalid() {
+				b.events.BlockInvalid.Trigger(block, ierrors.Errorf("parent block %s is marked as invalid", parent.ID))
+			}
+		})
+	})
 }
 
 // append tries to append the given Block to the BlockDAG.
@@ -237,3 +235,9 @@ func (b *BlockDAG) registerChild(child *blocks.Block, parent iotago.Parent) {
 		parentBlock.AppendChild(child, parent.Type)
 	}
 }
+
+func (b *BlockDAG) shutdown() {
+	b.workers.Shutdown()
+
+	b.StoppedEvent().Trigger()
+}
diff --git a/pkg/protocol/engine/booker/booker.go b/pkg/protocol/engine/booker/booker.go
index f8a4ff14..45cf8900 100644
--- a/pkg/protocol/engine/booker/booker.go
+++ b/pkg/protocol/engine/booker/booker.go
@@ -8,5 +8,5 @@ type Booker interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/booker/inmemorybooker/booker.go b/pkg/protocol/engine/booker/inmemorybooker/booker.go
index 56ca12b2..888fe5a3 100644
--- a/pkg/protocol/engine/booker/inmemorybooker/booker.go
+++ b/pkg/protocol/engine/booker/inmemorybooker/booker.go
@@ -36,21 +36,10 @@ type Booker struct {
 
 func NewProvider(opts ...options.Option[Booker]) module.Provider[*engine.Engine, booker.Booker] {
 	return module.Provide(func(e *engine.Engine) booker.Booker {
-		b := New(e, e.BlockCache, e.ErrorHandler("booker"), opts...)
-		e.Constructed.OnTrigger(func() {
-			b.ledger = e.Ledger
-			b.ledger.HookConstructed(func() {
-				b.spendDAG = b.ledger.SpendDAG()
-				b.loadBlockFromStorage = e.Block
-				b.ledger.MemPool().OnTransactionAttached(func(transaction mempool.TransactionMetadata) {
-					transaction.OnAccepted(func() {
-						b.events.TransactionAccepted.Trigger(transaction)
-					})
-					transaction.OnInvalid(func(err error) {
-						b.events.TransactionInvalid.Trigger(transaction, err)
-					})
-				})
-			})
+		b := New(e.NewSubModule("Booker"), e, e.BlockCache, e.ErrorHandler("booker"), opts...)
+
+		e.ConstructedEvent().OnTrigger(func() {
+			b.Init(e.Ledger, e.Block)
 
 			e.Events.SeatManager.BlockProcessed.Hook(func(block *blocks.Block) {
 				if err := b.Queue(block); err != nil {
@@ -59,25 +48,48 @@ func NewProvider(opts ...options.Option[Booker]) module.Provider[*engine.Engine,
 			})
 
 			e.Events.Booker.LinkTo(b.events)
-
-			b.TriggerInitialized()
 		})
 
 		return b
 	})
 }
 
-func New(apiProvider iotago.APIProvider, blockCache *blocks.Blocks, errorHandler func(error), opts ...options.Option[Booker]) *Booker {
+func New(subModule module.Module, apiProvider iotago.APIProvider, blockCache *blocks.Blocks, errorHandler func(error), opts ...options.Option[Booker]) *Booker {
 	return options.Apply(&Booker{
-		events:      booker.NewEvents(),
-		apiProvider: apiProvider,
-
+		Module:       subModule,
+		events:       booker.NewEvents(),
+		apiProvider:  apiProvider,
 		blockCache:   blockCache,
 		errorHandler: errorHandler,
-	}, opts, (*Booker).TriggerConstructed)
+	}, opts, func(b *Booker) {
+		b.ShutdownEvent().OnTrigger(func() {
+			b.StoppedEvent().Trigger()
+		})
+
+		b.ConstructedEvent().Trigger()
+	})
 }
 
-var _ booker.Booker = new(Booker)
+func (b *Booker) Init(ledger ledger.Ledger, loadBlockFromStorage func(iotago.BlockID) (*model.Block, bool)) {
+	b.ledger = ledger
+	b.loadBlockFromStorage = loadBlockFromStorage
+
+	ledger.InitializedEvent().OnTrigger(func() {
+		b.spendDAG = ledger.SpendDAG()
+
+		ledger.MemPool().OnTransactionAttached(func(transaction mempool.TransactionMetadata) {
+			transaction.OnAccepted(func() {
+				b.events.TransactionAccepted.Trigger(transaction)
+			})
+
+			transaction.OnInvalid(func(err error) {
+				b.events.TransactionInvalid.Trigger(transaction, err)
+			})
+		})
+
+		b.InitializedEvent().Trigger()
+	})
+}
 
 // Queue checks if payload is solid and then sets up the block to react to its parents.
 func (b *Booker) Queue(block *blocks.Block) error {
@@ -122,10 +134,6 @@ func (b *Booker) Queue(block *blocks.Block) error {
 // 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()
-}
-
 func (b *Booker) setupBlock(block *blocks.Block) {
 	var unbookedParentsCount atomic.Int32
 	unbookedParentsCount.Store(int32(len(block.Parents())))
diff --git a/pkg/protocol/engine/clock/blocktime/clock.go b/pkg/protocol/engine/clock/blocktime/clock.go
index 506387df..d009dcb5 100644
--- a/pkg/protocol/engine/clock/blocktime/clock.go
+++ b/pkg/protocol/engine/clock/blocktime/clock.go
@@ -28,7 +28,7 @@ type Clock struct {
 
 	syncutils.RWMutex
 
-	// Module embeds the required methods of the module.Interface.
+	// Module embeds the required methods of the modular framework.
 	module.Module
 }
 
@@ -36,24 +36,23 @@ type Clock struct {
 func NewProvider(opts ...options.Option[Clock]) module.Provider[*engine.Engine, clock.Clock] {
 	return module.Provide(func(e *engine.Engine) clock.Clock {
 		return options.Apply(&Clock{
+			Module:        e.NewSubModule("Clock"),
 			acceptedTime:  NewRelativeTime(),
 			confirmedTime: NewRelativeTime(),
 			workerPool:    e.Workers.CreatePool("Clock", workerpool.WithWorkerCount(1), workerpool.WithCancelPendingTasksOnShutdown(true), workerpool.WithPanicOnSubmitAfterShutdown(true)),
 		}, opts, func(c *Clock) {
-			e.Constructed.OnTrigger(func() {
+			e.ConstructedEvent().OnTrigger(func() {
 				latestCommitmentIndex := e.Storage.Settings().LatestCommitment().Slot()
 				c.acceptedTime.Set(e.APIForSlot(latestCommitmentIndex).TimeProvider().SlotEndTime(latestCommitmentIndex))
 
 				latestFinalizedSlotIndex := e.Storage.Settings().LatestFinalizedSlot()
 				c.confirmedTime.Set(e.APIForSlot(latestFinalizedSlotIndex).TimeProvider().SlotEndTime(latestFinalizedSlotIndex))
 
-				c.TriggerInitialized()
-
 				e.Events.Clock.AcceptedTimeUpdated.LinkTo(c.acceptedTime.OnUpdated)
 				e.Events.Clock.ConfirmedTimeUpdated.LinkTo(c.confirmedTime.OnUpdated)
 
 				asyncOpt := event.WithWorkerPool(c.workerPool)
-				c.HookStopped(lo.Batch(
+				c.ShutdownEvent().OnTrigger(lo.Batch(
 					e.Events.BlockGadget.BlockAccepted.Hook(func(block *blocks.Block) {
 						c.acceptedTime.Advance(block.IssuingTime())
 					}, asyncOpt).Unhook,
@@ -69,11 +68,19 @@ func NewProvider(opts ...options.Option[Clock]) module.Provider[*engine.Engine,
 						c.acceptedTime.Advance(slotEndTime)
 						c.confirmedTime.Advance(slotEndTime)
 					}, asyncOpt).Unhook,
+
+					func() {
+						c.workerPool.Shutdown()
+
+						c.StoppedEvent().Trigger()
+					},
 				))
+
+				c.InitializedEvent().Trigger()
 			})
 
-			e.Stopped.OnTrigger(c.TriggerStopped)
-		}, (*Clock).TriggerConstructed)
+			c.ConstructedEvent().Trigger()
+		})
 	})
 }
 
@@ -104,8 +111,3 @@ func (c *Clock) Reset(newTime time.Time) {
 	c.acceptedTime.Reset(newTime)
 	c.confirmedTime.Reset(newTime)
 }
-
-func (c *Clock) Shutdown() {
-	c.workerPool.Shutdown()
-	c.TriggerStopped()
-}
diff --git a/pkg/protocol/engine/clock/clock.go b/pkg/protocol/engine/clock/clock.go
index 1d1f4869..78509c10 100644
--- a/pkg/protocol/engine/clock/clock.go
+++ b/pkg/protocol/engine/clock/clock.go
@@ -19,8 +19,8 @@ type Clock interface {
 	// 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
+	// Module embeds the required methods of the modular framework.
+	module.Module
 }
 
 // Snapshot contains the snapshot of all time values tracked in the clock.
diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go b/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go
index 85a8bc2e..5a205267 100644
--- a/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go
+++ b/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go
@@ -53,17 +53,17 @@ type Scheduler struct {
 
 func NewProvider(opts ...options.Option[Scheduler]) module.Provider[*engine.Engine, scheduler.Scheduler] {
 	return module.Provide(func(e *engine.Engine) scheduler.Scheduler {
-		s := New(e, opts...)
+		s := New(e.NewSubModule("Scheduler"), e, opts...)
 		s.errorHandler = e.ErrorHandler("scheduler")
 		s.basicBuffer = NewBufferQueue()
 
-		e.Constructed.OnTrigger(func() {
+		e.ConstructedEvent().OnTrigger(func() {
 			s.latestCommittedSlot = func() iotago.SlotIndex {
 				return e.SyncManager.LatestCommitment().Slot()
 			}
 			s.blockCache = e.BlockCache
 			e.Events.Scheduler.LinkTo(s.events)
-			e.SybilProtection.HookInitialized(func() {
+			e.SybilProtection.InitializedEvent().OnTrigger(func() {
 				s.seatManager = e.SybilProtection.SeatManager()
 			})
 			e.Events.Notarization.LatestCommitmentUpdated.Hook(func(commitment *model.Commitment) {
@@ -90,7 +90,7 @@ func NewProvider(opts ...options.Option[Scheduler]) module.Provider[*engine.Engi
 					s.validatorBuffer.Clear()
 				}
 			})
-			e.Ledger.HookInitialized(func() {
+			e.Ledger.InitializedEvent().OnTrigger(func() {
 				// quantum retrieve function gets the account's Mana and returns the quantum for that account
 				s.quantumFunc = func(accountID iotago.AccountID, manaSlot iotago.SlotIndex) (Deficit, error) {
 					mana, err := e.Ledger.ManaManager().GetManaOnAccount(accountID, manaSlot)
@@ -103,7 +103,7 @@ func NewProvider(opts ...options.Option[Scheduler]) module.Provider[*engine.Engi
 					return 1 + Deficit(mana), nil
 				}
 			})
-			s.TriggerConstructed()
+			s.ConstructedEvent().Trigger()
 			e.Events.Booker.BlockBooked.Hook(func(block *blocks.Block) {
 				s.AddBlock(block)
 				s.selectBlockToScheduleWithLocking()
@@ -121,30 +121,31 @@ func NewProvider(opts ...options.Option[Scheduler]) module.Provider[*engine.Engi
 				s.removeIssuer(accountID, ierrors.New("account destroyed"))
 			})
 
-			e.Initialized.OnTrigger(s.Start)
+			e.InitializedEvent().OnTrigger(s.Start)
 		})
 
 		return s
 	})
 }
 
-func New(apiProvider iotago.APIProvider, opts ...options.Option[Scheduler]) *Scheduler {
+func New(module module.Module, apiProvider iotago.APIProvider, opts ...options.Option[Scheduler]) *Scheduler {
 	return options.Apply(
 		&Scheduler{
+			Module:          module,
 			events:          scheduler.NewEvents(),
 			deficits:        shrinkingmap.New[iotago.AccountID, Deficit](),
 			apiProvider:     apiProvider,
 			validatorBuffer: NewValidatorBuffer(),
-		}, opts,
+		}, opts, func(s *Scheduler) {
+			s.ShutdownEvent().OnTrigger(s.shutdown)
+		},
 	)
 }
 
-func (s *Scheduler) Shutdown() {
+func (s *Scheduler) shutdown() {
 	s.bufferMutex.Lock()
 	defer s.bufferMutex.Unlock()
 
-	s.TriggerShutdown()
-
 	// validator workers need to be shut down first, otherwise they will hang on the shutdown channel.
 	s.validatorBuffer.buffer.ForEach(func(_ iotago.AccountID, validatorQueue *ValidatorQueue) bool {
 		s.shutdownValidatorQueue(validatorQueue)
@@ -157,7 +158,7 @@ func (s *Scheduler) Shutdown() {
 
 	s.workersWg.Wait()
 
-	s.TriggerStopped()
+	s.StoppedEvent().Trigger()
 }
 
 // Start starts the scheduler.
@@ -166,7 +167,7 @@ func (s *Scheduler) Start() {
 	s.workersWg.Add(1)
 	go s.basicBlockLoop()
 
-	s.TriggerInitialized()
+	s.InitializedEvent().Trigger()
 }
 
 // IssuerQueueBlockCount returns the number of blocks in the queue of the given issuer.
diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/passthrough/scheduler.go b/pkg/protocol/engine/congestioncontrol/scheduler/passthrough/scheduler.go
index 3df7a72e..44f06bca 100644
--- a/pkg/protocol/engine/congestioncontrol/scheduler/passthrough/scheduler.go
+++ b/pkg/protocol/engine/congestioncontrol/scheduler/passthrough/scheduler.go
@@ -16,10 +16,11 @@ type Scheduler struct {
 
 func NewProvider() module.Provider[*engine.Engine, scheduler.Scheduler] {
 	return module.Provide(func(e *engine.Engine) scheduler.Scheduler {
-		s := New()
-		e.Constructed.OnTrigger(func() {
+		s := New(e.NewSubModule("Scheduler"))
+
+		e.ConstructedEvent().OnTrigger(func() {
 			e.Events.Scheduler.LinkTo(s.events)
-			s.TriggerConstructed()
+
 			e.Events.Booker.BlockBooked.Hook(func(block *blocks.Block) {
 				s.AddBlock(block)
 			})
@@ -29,15 +30,11 @@ func NewProvider() module.Provider[*engine.Engine, scheduler.Scheduler] {
 	})
 }
 
-func New() *Scheduler {
-	return &Scheduler{
+func New(subModule module.Module) *Scheduler {
+	return module.InitSimpleLifecycle(&Scheduler{
+		Module: subModule,
 		events: scheduler.NewEvents(),
-	}
-}
-
-func (s *Scheduler) Shutdown() {
-	s.TriggerShutdown()
-	s.TriggerStopped()
+	})
 }
 
 func (s *Scheduler) IsBlockIssuerReady(_ iotago.AccountID, _ ...iotago.WorkScore) bool {
diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/scheduler.go b/pkg/protocol/engine/congestioncontrol/scheduler/scheduler.go
index 59709fec..f7fd2713 100644
--- a/pkg/protocol/engine/congestioncontrol/scheduler/scheduler.go
+++ b/pkg/protocol/engine/congestioncontrol/scheduler/scheduler.go
@@ -26,5 +26,5 @@ type Scheduler interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/consensus/blockgadget/gadget.go b/pkg/protocol/engine/consensus/blockgadget/gadget.go
index 32d82770..66515e7c 100644
--- a/pkg/protocol/engine/consensus/blockgadget/gadget.go
+++ b/pkg/protocol/engine/consensus/blockgadget/gadget.go
@@ -6,7 +6,7 @@ import (
 )
 
 type Gadget interface {
-	module.Interface
+	module.Module
 
 	TrackWitnessWeight(votingBlock *blocks.Block)
 	SetAccepted(block *blocks.Block) bool
diff --git a/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go b/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go
index e55a639b..5d497c9c 100644
--- a/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go
+++ b/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go
@@ -36,7 +36,7 @@ type Gadget struct {
 
 func NewProvider(opts ...options.Option[Gadget]) module.Provider[*engine.Engine, blockgadget.Gadget] {
 	return module.Provide(func(e *engine.Engine) blockgadget.Gadget {
-		g := New(e.BlockCache, e.SybilProtection.SeatManager(), e.ErrorHandler("gadget"), opts...)
+		g := New(e.NewSubModule("ThresholdBlockGadget"), e.BlockCache, e.SybilProtection.SeatManager(), e.ErrorHandler("gadget"), opts...)
 
 		wp := e.Workers.CreatePool("ThresholdBlockGadget", workerpool.WithWorkerCount(1))
 		e.Events.Booker.BlockBooked.Hook(g.TrackWitnessWeight, event.WithWorkerPool(wp))
@@ -47,8 +47,9 @@ func NewProvider(opts ...options.Option[Gadget]) module.Provider[*engine.Engine,
 	})
 }
 
-func New(blockCache *blocks.Blocks, seatManager seatmanager.SeatManager, errorHandler func(error), opts ...options.Option[Gadget]) *Gadget {
-	return options.Apply(&Gadget{
+func New(subModule module.Module, blockCache *blocks.Blocks, seatManager seatmanager.SeatManager, errorHandler func(error), opts ...options.Option[Gadget]) *Gadget {
+	return module.InitSimpleLifecycle(options.Apply(&Gadget{
+		Module:       subModule,
 		events:       blockgadget.NewEvents(),
 		seatManager:  seatManager,
 		blockCache:   blockCache,
@@ -57,19 +58,13 @@ func New(blockCache *blocks.Blocks, seatManager seatmanager.SeatManager, errorHa
 		optsAcceptanceThreshold:               0.67,
 		optsConfirmationThreshold:             0.67,
 		optsConfirmationRatificationThreshold: 2,
-	}, opts,
-		(*Gadget).TriggerConstructed,
-	)
+	}, opts))
 }
 
 func (g *Gadget) Events() *blockgadget.Events {
 	return g.events
 }
 
-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() {}
 
diff --git a/pkg/protocol/engine/consensus/slotgadget/slotgadget.go b/pkg/protocol/engine/consensus/slotgadget/slotgadget.go
index e9d0e961..65a2e86a 100644
--- a/pkg/protocol/engine/consensus/slotgadget/slotgadget.go
+++ b/pkg/protocol/engine/consensus/slotgadget/slotgadget.go
@@ -9,5 +9,5 @@ type Gadget interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset(targetSlot iotago.SlotIndex)
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/consensus/slotgadget/totalweightslotgadget/gadget.go b/pkg/protocol/engine/consensus/slotgadget/totalweightslotgadget/gadget.go
index 6b9b9eb8..f3bee366 100644
--- a/pkg/protocol/engine/consensus/slotgadget/totalweightslotgadget/gadget.go
+++ b/pkg/protocol/engine/consensus/slotgadget/totalweightslotgadget/gadget.go
@@ -38,18 +38,16 @@ type Gadget struct {
 func NewProvider(opts ...options.Option[Gadget]) module.Provider[*engine.Engine, slotgadget.Gadget] {
 	return module.Provide(func(e *engine.Engine) slotgadget.Gadget {
 		return options.Apply(&Gadget{
+			Module:                        e.NewSubModule("TotalWeightSlotGadget"),
 			events:                        slotgadget.NewEvents(),
+			slotTrackers:                  shrinkingmap.New[iotago.SlotIndex, *slottracker.SlotTracker](),
 			optsSlotFinalizationThreshold: 0.67,
 			errorHandler:                  e.ErrorHandler("slotgadget"),
 		}, opts, func(g *Gadget) {
-
-			g.slotTrackers = shrinkingmap.New[iotago.SlotIndex, *slottracker.SlotTracker]()
-
 			e.Events.SlotGadget.LinkTo(g.events)
 
-			e.Constructed.OnTrigger(func() {
+			e.ConstructedEvent().OnTrigger(func() {
 				g.seatManager = e.SybilProtection.SeatManager()
-				g.TriggerConstructed()
 
 				e.Events.BlockGadget.BlockConfirmed.Hook(g.trackVotes)
 			})
@@ -60,20 +58,21 @@ func NewProvider(opts ...options.Option[Gadget]) module.Provider[*engine.Engine,
 				}
 			}
 
-			e.Initialized.OnTrigger(func() {
+			e.InitializedEvent().OnTrigger(func() {
 				// Can't use setter here as it has a side effect.
-				func() {
-					g.mutex.Lock()
-					defer g.mutex.Unlock()
+				g.mutex.Lock()
+				g.lastFinalizedSlot = e.Storage.Settings().LatestFinalizedSlot()
+				g.mutex.Unlock()
 
-					g.lastFinalizedSlot = e.Storage.Settings().LatestFinalizedSlot()
-				}()
+				g.InitializedEvent().Trigger()
+			})
 
-				g.TriggerInitialized()
+			g.ShutdownEvent().OnTrigger(func() {
+				g.StoppedEvent().Trigger()
 			})
-		},
-			(*Gadget).TriggerConstructed,
-		)
+
+			g.ConstructedEvent().Trigger()
+		})
 	})
 }
 
@@ -91,10 +90,6 @@ func (g *Gadget) Reset(targetSlot iotago.SlotIndex) {
 	})
 }
 
-func (g *Gadget) Shutdown() {
-	g.TriggerStopped()
-}
-
 func (g *Gadget) setLastFinalizedSlot(i iotago.SlotIndex) {
 	g.lastFinalizedSlot = i
 	g.storeLastFinalizedSlotFunc(i)
diff --git a/pkg/protocol/engine/engine.go b/pkg/protocol/engine/engine.go
index db54673a..55ac6fa2 100644
--- a/pkg/protocol/engine/engine.go
+++ b/pkg/protocol/engine/engine.go
@@ -90,7 +90,7 @@ type Engine struct {
 	optsCheckCommitment  bool
 	optsBlockRequester   []options.Option[eventticker.EventTicker[iotago.SlotIndex, iotago.BlockID]]
 
-	*module.ReactiveModule
+	module.Module
 }
 
 func New(
@@ -134,7 +134,7 @@ func New(
 			optsSnapshotDepth:   5,
 			optsCheckCommitment: true,
 		}, opts, func(e *Engine) {
-			e.ReactiveModule = e.initReactiveModule(logger)
+			e.Module = e.initReactiveModule(logger)
 
 			e.errorHandler = func(err error) {
 				e.LogError("engine error", "err", err)
@@ -185,7 +185,7 @@ func New(
 		(*Engine).setupPruning,
 		(*Engine).acceptanceHandler,
 		func(e *Engine) {
-			e.Constructed.Trigger()
+			e.ConstructedEvent().Trigger()
 
 			// Make sure that we have the protocol parameters for the latest supported iota.go protocol version of the software.
 			// If not the user needs to update the protocol parameters file.
@@ -239,7 +239,7 @@ func New(
 				}
 			}
 
-			e.Initialized.Trigger()
+			e.InitializedEvent().Trigger()
 
 			e.LogTrace("initialized", "settings", e.Storage.Settings().String())
 		},
@@ -553,14 +553,14 @@ func (e *Engine) initRootCommitment() {
 		})
 	}
 
-	e.Constructed.OnTrigger(func() {
+	e.ConstructedEvent().OnTrigger(func() {
 		unsubscribe := e.Events.SlotGadget.SlotFinalized.Hook(updateRootCommitment).Unhook
 
-		e.Initialized.OnTrigger(func() {
+		e.InitializedEvent().OnTrigger(func() {
 			updateRootCommitment(e.Storage.Settings().LatestFinalizedSlot())
 		})
 
-		e.Shutdown.OnTrigger(unsubscribe)
+		e.ShutdownEvent().OnTrigger(unsubscribe)
 	})
 }
 
@@ -571,60 +571,68 @@ func (e *Engine) initLatestCommitment() {
 		})
 	}
 
-	e.Constructed.OnTrigger(func() {
+	e.ConstructedEvent().OnTrigger(func() {
 		unsubscribe := e.Events.Notarization.LatestCommitmentUpdated.Hook(updateLatestCommitment).Unhook
 
-		e.Initialized.OnTrigger(func() {
+		e.InitializedEvent().OnTrigger(func() {
 			updateLatestCommitment(e.Storage.Settings().LatestCommitment())
 		})
 
-		e.Shutdown.OnTrigger(unsubscribe)
+		e.ShutdownEvent().OnTrigger(unsubscribe)
 	})
 }
 
-func (e *Engine) initReactiveModule(parentLogger log.Logger) (reactiveModule *module.ReactiveModule) {
-	logger := parentLogger.NewChildLogger("Engine", true)
-	reactiveModule = module.NewReactiveModule(logger)
+func (e *Engine) initReactiveModule(parentLogger log.Logger) (reactiveModule module.Module) {
+	reactiveModule = module.New(parentLogger.NewChildLogger("Engine", true))
 
 	e.RootCommitment.LogUpdates(reactiveModule, log.LevelTrace, "RootCommitment")
 	e.LatestCommitment.LogUpdates(reactiveModule, log.LevelTrace, "LatestCommitment")
 
-	reactiveModule.Shutdown.OnTrigger(func() {
+	reactiveModule.ShutdownEvent().OnTrigger(func() {
 		reactiveModule.LogDebug("shutting down")
 
-		logger.UnsubscribeFromParentLogger()
-
-		// Shutdown should be performed in the reverse dataflow order.
 		e.BlockRequester.Shutdown()
-		e.Scheduler.Shutdown()
-		e.TipSelection.Shutdown()
-		e.TipManager.Shutdown()
-		e.Attestations.Shutdown()
-		e.SyncManager.Shutdown()
-		e.Notarization.Shutdown()
-		e.Clock.Shutdown()
-		e.SlotGadget.Shutdown()
-		e.BlockGadget.Shutdown()
-		e.UpgradeOrchestrator.Shutdown()
-		e.SybilProtection.Shutdown()
-		e.Booker.Shutdown()
-		e.Ledger.Shutdown()
-		e.PostSolidFilter.Shutdown()
-		e.BlockDAG.Shutdown()
-		e.PreSolidFilter.Shutdown()
-		e.BlockRetainer.Shutdown()
-		e.TxRetainer.Shutdown()
+
+		e.shutdownSubModules()
+
 		e.Workers.Shutdown()
 		e.Storage.Shutdown()
 
-		reactiveModule.LogDebug("stopped")
+		e.StoppedEvent().Trigger()
 
-		e.Stopped.Trigger()
+		reactiveModule.LogDebug("stopped")
 	})
 
 	return reactiveModule
 }
 
+func (e *Engine) shutdownSubModules() {
+	// shutdown should be performed in the reverse dataflow order.
+	shutdownOrder := []module.Module{
+		e.Scheduler,
+		e.TipSelection,
+		e.TipManager,
+		e.Attestations,
+		e.SyncManager,
+		e.Notarization,
+		e.Clock,
+		e.SlotGadget,
+		e.BlockGadget,
+		e.UpgradeOrchestrator,
+		e.SybilProtection,
+		e.Booker,
+		e.Ledger,
+		e.PostSolidFilter,
+		e.BlockDAG,
+		e.PreSolidFilter,
+		e.BlockRetainer,
+		e.TxRetainer,
+	}
+
+	module.TriggerAll(module.Module.ShutdownEvent, shutdownOrder...)
+	module.WaitAll(module.Module.StoppedEvent, shutdownOrder...).Wait()
+}
+
 // endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 // region Options //////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/pkg/protocol/engine/filter/postsolidfilter/post_solid_filter.go b/pkg/protocol/engine/filter/postsolidfilter/post_solid_filter.go
index 3f62b262..fc0815c1 100644
--- a/pkg/protocol/engine/filter/postsolidfilter/post_solid_filter.go
+++ b/pkg/protocol/engine/filter/postsolidfilter/post_solid_filter.go
@@ -12,5 +12,5 @@ type PostSolidFilter interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/filter/postsolidfilter/postsolidblockfilter/post_solid_block_filter.go b/pkg/protocol/engine/filter/postsolidfilter/postsolidblockfilter/post_solid_block_filter.go
index cc5a6633..9cdb97f2 100644
--- a/pkg/protocol/engine/filter/postsolidfilter/postsolidblockfilter/post_solid_block_filter.go
+++ b/pkg/protocol/engine/filter/postsolidfilter/postsolidblockfilter/post_solid_block_filter.go
@@ -28,30 +28,41 @@ type PostSolidBlockFilter struct {
 
 func NewProvider(opts ...options.Option[PostSolidBlockFilter]) module.Provider[*engine.Engine, postsolidfilter.PostSolidFilter] {
 	return module.Provide(func(e *engine.Engine) postsolidfilter.PostSolidFilter {
-		c := New(opts...)
-		e.Constructed.OnTrigger(func() {
-			c.accountRetrieveFunc = e.Ledger.Account
-			c.blockCacheRetrieveFunc = e.BlockCache.Block
+		c := New(e.NewSubModule("PostSolidFilter"), opts...)
 
-			e.Ledger.HookConstructed(func() {
-				c.rmcRetrieveFunc = e.Ledger.RMCManager().RMC
-			})
+		e.ConstructedEvent().OnTrigger(func() {
+			e.Events.PostSolidFilter.LinkTo(c.events)
 
 			e.Events.BlockDAG.BlockSolid.Hook(c.ProcessSolidBlock)
-			e.Events.PostSolidFilter.LinkTo(c.events)
 
-			c.TriggerInitialized()
+			e.Ledger.InitializedEvent().OnTrigger(func() {
+				c.Init(e.Ledger.Account, e.BlockCache.Block, e.Ledger.RMCManager().RMC)
+			})
 		})
 
 		return c
 	})
 }
 
-func New(opts ...options.Option[PostSolidBlockFilter]) *PostSolidBlockFilter {
+func New(module module.Module, opts ...options.Option[PostSolidBlockFilter]) *PostSolidBlockFilter {
 	return options.Apply(&PostSolidBlockFilter{
+		Module: module,
 		events: postsolidfilter.NewEvents(),
-	}, opts,
-	)
+	}, opts, func(p *PostSolidBlockFilter) {
+		p.ShutdownEvent().OnTrigger(func() {
+			p.StoppedEvent().Trigger()
+		})
+
+		p.ConstructedEvent().Trigger()
+	})
+}
+
+func (c *PostSolidBlockFilter) Init(accountRetrieveFunc func(accountID iotago.AccountID, targetIndex iotago.SlotIndex) (*accounts.AccountData, bool, error), blockCacheRetrieveFunc func(iotago.BlockID) (*blocks.Block, bool), rmcRetrieveFunc func(iotago.SlotIndex) (iotago.Mana, error)) {
+	c.accountRetrieveFunc = accountRetrieveFunc
+	c.blockCacheRetrieveFunc = blockCacheRetrieveFunc
+	c.rmcRetrieveFunc = rmcRetrieveFunc
+
+	c.InitializedEvent().Trigger()
 }
 
 func (c *PostSolidBlockFilter) ProcessSolidBlock(block *blocks.Block) {
@@ -204,10 +215,6 @@ func (c *PostSolidBlockFilter) ProcessSolidBlock(block *blocks.Block) {
 // Reset resets the component to a clean state as if it was created at the last commitment.
 func (c *PostSolidBlockFilter) Reset() { /* nothing to reset but comply with interface */ }
 
-func (c *PostSolidBlockFilter) Shutdown() {
-	c.TriggerStopped()
-}
-
 func (c *PostSolidBlockFilter) filterBlock(block *blocks.Block, reason error) {
 	block.SetInvalid()
 
diff --git a/pkg/protocol/engine/filter/presolidfilter/pre_solid_filter.go b/pkg/protocol/engine/filter/presolidfilter/pre_solid_filter.go
index fb8c2320..1803b4ee 100644
--- a/pkg/protocol/engine/filter/presolidfilter/pre_solid_filter.go
+++ b/pkg/protocol/engine/filter/presolidfilter/pre_solid_filter.go
@@ -14,5 +14,5 @@ type PreSolidFilter interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/filter/presolidfilter/presolidblockfilter/pre_solid_block_filter.go b/pkg/protocol/engine/filter/presolidfilter/presolidblockfilter/pre_solid_block_filter.go
index ee1c0edc..adb0f50a 100644
--- a/pkg/protocol/engine/filter/presolidfilter/presolidblockfilter/pre_solid_block_filter.go
+++ b/pkg/protocol/engine/filter/presolidfilter/presolidblockfilter/pre_solid_block_filter.go
@@ -31,32 +31,40 @@ type PreSolidBlockFilter struct {
 
 func NewProvider(opts ...options.Option[PreSolidBlockFilter]) module.Provider[*engine.Engine, presolidfilter.PreSolidFilter] {
 	return module.Provide(func(e *engine.Engine) presolidfilter.PreSolidFilter {
-		f := New(e, opts...)
-		f.TriggerConstructed()
+		f := New(e.NewSubModule("PreSolidBlockFilter"), e, opts...)
 
-		e.Constructed.OnTrigger(func() {
-			e.Events.PreSolidFilter.LinkTo(f.events)
-			e.SybilProtection.HookInitialized(func() {
-				f.committeeFunc = e.SybilProtection.SeatManager().CommitteeInSlot
+		e.ConstructedEvent().OnTrigger(func() {
+			e.SybilProtection.InitializedEvent().OnTrigger(func() {
+				f.Init(e.SybilProtection.SeatManager().CommitteeInSlot)
 			})
-			f.TriggerInitialized()
+
+			e.Events.PreSolidFilter.LinkTo(f.events)
 		})
 
 		return f
 	})
 }
 
-var _ presolidfilter.PreSolidFilter = new(PreSolidBlockFilter)
-
 // New creates a new PreSolidBlockFilter.
-func New(apiProvider iotago.APIProvider, opts ...options.Option[PreSolidBlockFilter]) *PreSolidBlockFilter {
+func New(module module.Module, apiProvider iotago.APIProvider, opts ...options.Option[PreSolidBlockFilter]) *PreSolidBlockFilter {
 	return options.Apply(&PreSolidBlockFilter{
+		Module:      module,
 		events:      presolidfilter.NewEvents(),
 		apiProvider: apiProvider,
-	}, opts,
-		(*PreSolidBlockFilter).TriggerConstructed,
-		(*PreSolidBlockFilter).TriggerInitialized,
-	)
+	}, opts, func(p *PreSolidBlockFilter) {
+		p.ShutdownEvent().OnTrigger(func() {
+			p.StoppedEvent().Trigger()
+		})
+
+		p.ConstructedEvent().Trigger()
+	})
+}
+
+// Init initializes the PreSolidBlockFilter.
+func (f *PreSolidBlockFilter) Init(committeeFunc func(iotago.SlotIndex) (*account.SeatedAccounts, bool)) {
+	f.committeeFunc = committeeFunc
+
+	f.InitializedEvent().Trigger()
 }
 
 // ProcessReceivedBlock processes block from the given source.
@@ -102,7 +110,3 @@ func (f *PreSolidBlockFilter) ProcessReceivedBlock(block *model.Block, source pe
 
 // Reset resets the component to a clean state as if it was created at the last commitment.
 func (f *PreSolidBlockFilter) Reset() { /* nothing to reset but comply with interface */ }
-
-func (f *PreSolidBlockFilter) Shutdown() {
-	f.TriggerStopped()
-}
diff --git a/pkg/protocol/engine/ledger/ledger.go b/pkg/protocol/engine/ledger/ledger.go
index 38e45ad3..9a4b724a 100644
--- a/pkg/protocol/engine/ledger/ledger.go
+++ b/pkg/protocol/engine/ledger/ledger.go
@@ -46,5 +46,5 @@ type Ledger interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/ledger/ledger/ledger.go b/pkg/protocol/engine/ledger/ledger/ledger.go
index 22fc3b20..46cfc623 100644
--- a/pkg/protocol/engine/ledger/ledger/ledger.go
+++ b/pkg/protocol/engine/ledger/ledger/ledger.go
@@ -51,6 +51,7 @@ type Ledger struct {
 func NewProvider() module.Provider[*engine.Engine, ledger.Ledger] {
 	return module.Provide(func(e *engine.Engine) ledger.Ledger {
 		l := New(
+			e.NewSubModule("Ledger"),
 			e.Storage.Ledger(),
 			e.Storage.Accounts(),
 			e.Storage.Commitments().Load,
@@ -61,7 +62,7 @@ func NewProvider() module.Provider[*engine.Engine, ledger.Ledger] {
 			e.ErrorHandler("ledger"),
 		)
 
-		e.Constructed.OnTrigger(func() {
+		e.ConstructedEvent().OnTrigger(func() {
 			e.Events.Ledger.LinkTo(l.events)
 			l.spendDAG = spenddagv1.New[iotago.TransactionID, mempool.StateID, ledger.BlockVoteRank](func() int {
 				return l.sybilProtection.SeatManager().OnlineCommittee().Size()
@@ -82,8 +83,7 @@ func NewProvider() module.Provider[*engine.Engine, ledger.Ledger] {
 			//	l.memPool.PublishRequestedState(scd.Commitment.Commitment())
 			// })
 
-			l.TriggerConstructed()
-			l.TriggerInitialized()
+			l.InitializedEvent().Trigger()
 		})
 
 		return l
@@ -91,6 +91,7 @@ func NewProvider() module.Provider[*engine.Engine, ledger.Ledger] {
 }
 
 func New(
+	subModule module.Module,
 	utxoLedger *utxoledger.Manager,
 	accountsStore kvstore.KVStore,
 	commitmentLoader func(iotago.SlotIndex) (*model.Commitment, error),
@@ -100,10 +101,11 @@ func New(
 	sybilProtection sybilprotection.SybilProtection,
 	errorHandler func(error),
 ) *Ledger {
-	return &Ledger{
+	l := &Ledger{
+		Module:           subModule,
 		events:           ledger.NewEvents(),
 		apiProvider:      apiProvider,
-		accountsLedger:   accountsledger.New(apiProvider, blocksFunc, slotDiffFunc, accountsStore),
+		accountsLedger:   accountsledger.New(subModule.NewSubModule("AccountsLedger"), apiProvider, blocksFunc, slotDiffFunc, accountsStore),
 		rmcManager:       rmc.NewManager(apiProvider, commitmentLoader),
 		utxoLedger:       utxoLedger,
 		commitmentLoader: commitmentLoader,
@@ -113,6 +115,12 @@ func New(
 			return sybilProtection.SeatManager().OnlineCommittee().Size()
 		}),
 	}
+
+	l.ShutdownEvent().OnTrigger(l.shutdown)
+
+	l.ConstructedEvent().Trigger()
+
+	return l
 }
 
 func (l *Ledger) OnTransactionAttached(handler func(transaction mempool.TransactionMetadata), opts ...event.Option) *event.Hook[func(metadata mempool.TransactionMetadata)] {
@@ -361,9 +369,10 @@ func (l *Ledger) Reset() {
 	l.rmcManager.Reset()
 }
 
-func (l *Ledger) Shutdown() {
-	l.TriggerStopped()
+func (l *Ledger) shutdown() {
 	l.spendDAG.Shutdown()
+
+	l.StoppedEvent().Trigger()
 }
 
 // Process the collected account changes. The consumedAccounts and createdAccounts maps only contain outputs with a
diff --git a/pkg/protocol/engine/notarization/notarization.go b/pkg/protocol/engine/notarization/notarization.go
index 1c3e35a5..c943f37a 100644
--- a/pkg/protocol/engine/notarization/notarization.go
+++ b/pkg/protocol/engine/notarization/notarization.go
@@ -18,5 +18,5 @@ type Notarization interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/notarization/slotnotarization/manager.go b/pkg/protocol/engine/notarization/slotnotarization/manager.go
index bb17d6a6..0366bf36 100644
--- a/pkg/protocol/engine/notarization/slotnotarization/manager.go
+++ b/pkg/protocol/engine/notarization/slotnotarization/manager.go
@@ -4,7 +4,6 @@ import (
 	"time"
 
 	"github.com/iotaledger/hive.go/ierrors"
-	"github.com/iotaledger/hive.go/log"
 	"github.com/iotaledger/hive.go/runtime/event"
 	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/syncutils"
@@ -45,8 +44,6 @@ type Manager struct {
 
 	commitmentMutex syncutils.RWMutex
 
-	log.Logger
-
 	module.Module
 }
 
@@ -54,12 +51,12 @@ func NewProvider() module.Provider[*engine.Engine, notarization.Notarization] {
 	return module.Provide(func(e *engine.Engine) notarization.Notarization {
 		logger := e.NewChildLogger("NotarizationManager")
 
-		m := NewManager(logger, e.Workers.CreateGroup("NotarizationManager"), e.ErrorHandler("notarization"))
-		m.HookShutdown(logger.UnsubscribeFromParentLogger)
+		m := NewManager(e.NewSubModule("NotarizationManager"), e.Workers.CreateGroup("NotarizationManager"), e.ErrorHandler("notarization"))
+		m.ShutdownEvent().OnTrigger(logger.UnsubscribeFromParentLogger)
 
 		m.apiProvider = e
 
-		e.Constructed.OnTrigger(func() {
+		e.ConstructedEvent().OnTrigger(func() {
 			m.storage = e.Storage
 			m.acceptedTimeFunc = e.Clock.Accepted().Time
 
@@ -82,36 +79,37 @@ func NewProvider() module.Provider[*engine.Engine, notarization.Notarization] {
 
 			e.Events.Notarization.LinkTo(m.events)
 
-			m.TriggerInitialized()
 			m.slotMutations = NewSlotMutations(e.Storage.Settings().LatestCommitment().Slot())
-			m.TriggerConstructed()
+			m.InitializedEvent().Trigger()
 		})
 
-		e.Shutdown.OnTrigger(m.Shutdown)
-
 		return m
 	})
 }
 
-func NewManager(logger log.Logger, workers *workerpool.Group, errorHandler func(error)) *Manager {
-	return &Manager{
-		Logger:       logger,
+func NewManager(subModule module.Module, workers *workerpool.Group, errorHandler func(error)) *Manager {
+	m := &Manager{
+		Module:       subModule,
 		events:       notarization.NewEvents(),
 		workers:      workers,
 		errorHandler: errorHandler,
 	}
+
+	m.ShutdownEvent().OnTrigger(m.Shutdown)
+
+	m.ConstructedEvent().Trigger()
+
+	return m
 }
 
 func (m *Manager) Shutdown() {
-	m.TriggerShutdown()
-
 	// Alternative 2
 	if m.acceptedBlockProcessedDetach != nil {
 		m.acceptedBlockProcessedDetach()
 	}
 	m.workers.Shutdown()
 
-	m.TriggerStopped()
+	m.StoppedEvent().Trigger()
 }
 
 // tryCommitUntil tries to create slot commitments until the new provided acceptance time.
@@ -124,7 +122,7 @@ func (m *Manager) tryCommitUntil(commitUntilSlot iotago.SlotIndex) {
 func (m *Manager) ForceCommit(slot iotago.SlotIndex) (*model.Commitment, error) {
 	m.LogInfof("Force commit slot %d", slot)
 
-	if m.WasStopped() {
+	if m.ShutdownEvent().WasTriggered() {
 		return nil, ierrors.New("notarization manager was stopped")
 	}
 
@@ -184,7 +182,7 @@ func (m *Manager) notarizeAcceptedBlock(block *blocks.Block) (err error) {
 
 func (m *Manager) tryCommitSlotUntil(acceptedBlockIndex iotago.SlotIndex) {
 	for i := m.storage.Settings().LatestCommitment().Slot() + 1; i <= acceptedBlockIndex; i++ {
-		if m.WasStopped() {
+		if m.ShutdownEvent().WasTriggered() {
 			break
 		}
 
diff --git a/pkg/protocol/engine/syncmanager/syncmanager.go b/pkg/protocol/engine/syncmanager/syncmanager.go
index 82b1b5ff..46143bbd 100644
--- a/pkg/protocol/engine/syncmanager/syncmanager.go
+++ b/pkg/protocol/engine/syncmanager/syncmanager.go
@@ -34,10 +34,7 @@ type SyncManager interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	// Shutdown shuts down the SyncManager.
-	Shutdown()
-
-	module.Interface
+	module.Module
 }
 
 type SyncStatus struct {
diff --git a/pkg/protocol/engine/syncmanager/trivialsyncmanager/syncmanager.go b/pkg/protocol/engine/syncmanager/trivialsyncmanager/syncmanager.go
index cdb91e81..0da58dc7 100644
--- a/pkg/protocol/engine/syncmanager/trivialsyncmanager/syncmanager.go
+++ b/pkg/protocol/engine/syncmanager/trivialsyncmanager/syncmanager.go
@@ -55,7 +55,7 @@ type SyncManager struct {
 // NewProvider creates a new SyncManager provider.
 func NewProvider(opts ...options.Option[SyncManager]) module.Provider[*engine.Engine, syncmanager.SyncManager] {
 	return module.Provide(func(e *engine.Engine) syncmanager.SyncManager {
-		s := New(e, e.Storage.Settings().LatestCommitment(), e.Storage.Settings().LatestFinalizedSlot(), opts...)
+		s := New(e.NewSubModule("SyncManager"), e, e.Storage.Settings().LatestCommitment(), e.Storage.Settings().LatestFinalizedSlot(), opts...)
 		asyncOpt := event.WithWorkerPool(e.Workers.CreatePool("SyncManager", workerpool.WithWorkerCount(1)))
 
 		e.Events.BlockGadget.BlockAccepted.Hook(func(b *blocks.Block) {
@@ -75,10 +75,10 @@ func NewProvider(opts ...options.Option[SyncManager]) module.Provider[*engine.En
 				s.updateBootstrappedStatus()
 			}
 
-			syncChaged := s.updateSyncStatus()
+			syncChanged := s.updateSyncStatus()
 			commitmentChanged := s.updateLatestCommitment(commitment)
 
-			if syncChaged || commitmentChanged {
+			if syncChanged || commitmentChanged {
 				s.triggerUpdate()
 			}
 		}, asyncOpt)
@@ -96,14 +96,14 @@ func NewProvider(opts ...options.Option[SyncManager]) module.Provider[*engine.En
 		}, asyncOpt)
 
 		e.Events.SyncManager.LinkTo(s.events)
-		s.TriggerInitialized()
 
 		return s
 	})
 }
 
-func New(e *engine.Engine, latestCommitment *model.Commitment, finalizedSlot iotago.SlotIndex, opts ...options.Option[SyncManager]) *SyncManager {
-	return options.Apply(&SyncManager{
+func New(subModule module.Module, e *engine.Engine, latestCommitment *model.Commitment, finalizedSlot iotago.SlotIndex, opts ...options.Option[SyncManager]) *SyncManager {
+	return module.InitSimpleLifecycle(options.Apply(&SyncManager{
+		Module:                 subModule,
 		events:                 syncmanager.NewEvents(),
 		engine:                 e,
 		syncThreshold:          10 * time.Second,
@@ -122,7 +122,7 @@ func New(e *engine.Engine, latestCommitment *model.Commitment, finalizedSlot iot
 				return time.Since(e.Clock.Accepted().RelativeTime()) < s.optsBootstrappedThreshold && e.Notarization.IsBootstrapped()
 			}
 		}
-	})
+	}))
 }
 
 func (s *SyncManager) SyncStatus() *syncmanager.SyncStatus {
@@ -167,10 +167,6 @@ func (s *SyncManager) Reset() {
 	s.isSynced = false
 }
 
-func (s *SyncManager) Shutdown() {
-	s.TriggerStopped()
-}
-
 func (s *SyncManager) updateLastAcceptedBlock(id iotago.BlockID) (changed bool) {
 	s.lastAcceptedBlockSlotLock.Lock()
 	defer s.lastAcceptedBlockSlotLock.Unlock()
diff --git a/pkg/protocol/engine/tipmanager/tipmanager.go b/pkg/protocol/engine/tipmanager/tipmanager.go
index d3aa6ae4..f49ddba8 100644
--- a/pkg/protocol/engine/tipmanager/tipmanager.go
+++ b/pkg/protocol/engine/tipmanager/tipmanager.go
@@ -49,6 +49,6 @@ type TipManager interface {
 	// 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
+	// Interface embeds the required methods of the module.Module.
+	module.Module
 }
diff --git a/pkg/protocol/engine/tipmanager/v1/provider.go b/pkg/protocol/engine/tipmanager/v1/provider.go
index e1c8a84a..8debf631 100644
--- a/pkg/protocol/engine/tipmanager/v1/provider.go
+++ b/pkg/protocol/engine/tipmanager/v1/provider.go
@@ -14,9 +14,9 @@ import (
 // NewProvider creates a new TipManager provider, that can be used to inject the component into an engine.
 func NewProvider() module.Provider[*engine.Engine, tipmanager.TipManager] {
 	return module.Provide(func(e *engine.Engine) tipmanager.TipManager {
-		t := New(e.BlockCache.Block, e.SybilProtection.SeatManager().CommitteeInSlot)
+		t := New(e.NewSubModule("TipManager"), e.BlockCache.Block, e.SybilProtection.SeatManager().CommitteeInSlot)
 
-		e.Constructed.OnTrigger(func() {
+		e.ConstructedEvent().OnTrigger(func() {
 			tipWorker := e.Workers.CreatePool("AddTip", workerpool.WithWorkerCount(2))
 
 			e.Events.Scheduler.BlockScheduled.Hook(lo.Void(t.AddBlock), event.WithWorkerPool(tipWorker))
@@ -29,13 +29,10 @@ func NewProvider() module.Provider[*engine.Engine, tipmanager.TipManager] {
 			e.Events.SeatManager.OnlineCommitteeSeatRemoved.Hook(t.RemoveSeat)
 
 			e.Events.TipManager.BlockAdded.LinkTo(t.blockAdded)
-
-			t.TriggerInitialized()
 		})
 
-		e.Shutdown.OnTrigger(func() {
-			t.TriggerShutdown()
-			t.TriggerStopped()
+		e.ShutdownEvent().OnTrigger(func() {
+			t.ShutdownEvent().Trigger()
 		})
 
 		return t
diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tipmanager.go
index 6c08a30d..26282b08 100644
--- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go
+++ b/pkg/protocol/engine/tipmanager/v1/tipmanager.go
@@ -54,10 +54,12 @@ type TipManager struct {
 
 // New creates a new TipManager.
 func New(
+	subModule module.Module,
 	blockRetriever func(blockID iotago.BlockID) (block *blocks.Block, exists bool),
 	retrieveCommitteeInSlot func(slot iotago.SlotIndex) (*account.SeatedAccounts, bool),
 ) *TipManager {
-	t := &TipManager{
+	return module.InitSimpleLifecycle(&TipManager{
+		Module:                  subModule,
 		retrieveBlock:           blockRetriever,
 		retrieveCommitteeInSlot: retrieveCommitteeInSlot,
 		tipMetadataStorage:      shrinkingmap.New[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]](),
@@ -66,12 +68,7 @@ func New(
 		strongTipSet:            randommap.New[iotago.BlockID, *TipMetadata](),
 		weakTipSet:              randommap.New[iotago.BlockID, *TipMetadata](),
 		blockAdded:              event.New1[tipmanager.TipMetadata](),
-	}
-
-	t.TriggerConstructed()
-	t.TriggerInitialized()
-
-	return t
+	})
 }
 
 // AddBlock adds a Block to the TipManager and returns the TipMetadata if the Block was added successfully.
@@ -157,12 +154,6 @@ func (t *TipManager) Reset() {
 	lo.ForEach(t.weakTipSet.Keys(), func(id iotago.BlockID) { t.weakTipSet.Delete(id) })
 }
 
-// Shutdown marks the TipManager as shutdown.
-func (t *TipManager) Shutdown() {
-	t.TriggerShutdown()
-	t.TriggerStopped()
-}
-
 // setupBlockMetadata sets up the behavior of the given Block.
 func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) {
 	tipMetadata.isStrongTipPoolMember.WithNonEmptyValue(func(_ bool) func() {
diff --git a/pkg/protocol/engine/tipselection/tipselection.go b/pkg/protocol/engine/tipselection/tipselection.go
index 9361b64b..59b76431 100644
--- a/pkg/protocol/engine/tipselection/tipselection.go
+++ b/pkg/protocol/engine/tipselection/tipselection.go
@@ -18,6 +18,6 @@ type TipSelection interface {
 	// 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
+	// Interface embeds the required methods of the module.Module.
+	module.Module
 }
diff --git a/pkg/protocol/engine/tipselection/v1/provider.go b/pkg/protocol/engine/tipselection/v1/provider.go
index d3fe8cd6..336fe18f 100644
--- a/pkg/protocol/engine/tipselection/v1/provider.go
+++ b/pkg/protocol/engine/tipselection/v1/provider.go
@@ -16,18 +16,20 @@ import (
 // NewProvider creates a new TipSelection provider, that can be used to inject the component into an engine.
 func NewProvider(opts ...options.Option[TipSelection]) module.Provider[*engine.Engine, tipselection.TipSelection] {
 	return module.Provide(func(e *engine.Engine) tipselection.TipSelection {
-		t := New(opts...)
+		t := New(e.NewSubModule("TipSelection"), opts...)
 
-		e.Constructed.OnTrigger(func() {
+		e.ConstructedEvent().OnTrigger(func() {
 			// wait for submodules to be constructed (so all of their properties are available)
-			module.OnAllConstructed(func() {
+			module.WaitAll(module.Module.ConstructedEvent, e.TipManager, e.Ledger, e.SybilProtection).OnTrigger(func() {
 				t.Construct(e.TipManager, e.Ledger.SpendDAG(), e.Ledger.MemPool().TransactionMetadata, func() iotago.BlockID { return lo.Return1(e.EvictionState.LatestActiveRootBlock()) }, DynamicLivenessThreshold(func() int {
 					return e.SybilProtection.SeatManager().OnlineCommittee().Size()
 				}))
-			}, e.TipManager, e.Ledger, e.SybilProtection)
+			})
 		})
 
-		e.Shutdown.OnTrigger(t.Shutdown)
+		e.ShutdownEvent().OnTrigger(func() {
+			t.ShutdownEvent().Trigger()
+		})
 
 		return t
 	})
diff --git a/pkg/protocol/engine/tipselection/v1/tip_selection.go b/pkg/protocol/engine/tipselection/v1/tip_selection.go
index 88db0799..e9a71d1f 100644
--- a/pkg/protocol/engine/tipselection/v1/tip_selection.go
+++ b/pkg/protocol/engine/tipselection/v1/tip_selection.go
@@ -63,13 +63,14 @@ type TipSelection struct {
 	// acceptanceTimeMutex is used to synchronize access to the acceptance time.
 	acceptanceTimeMutex syncutils.RWMutex
 
-	// Module embeds the required methods of the module.Interface.
+	// Module embeds the required methods of the modular framework.
 	module.Module
 }
 
 // New is the constructor for the TipSelection.
-func New(opts ...options.Option[TipSelection]) *TipSelection {
+func New(module module.Module, opts ...options.Option[TipSelection]) *TipSelection {
 	return options.Apply(&TipSelection{
+		Module:                                module,
 		livenessThresholdQueue:                timed.NewPriorityQueue[tipmanager.TipMetadata](true),
 		acceptanceTime:                        reactive.NewVariable[time.Time](monotonicallyIncreasing),
 		optMaxStrongParents:                   8,
@@ -91,17 +92,13 @@ func (t *TipSelection) Construct(tipManager tipmanager.TipManager, spendDAG spen
 	t.rootBlock = rootBlockRetriever
 	t.livenessThreshold = livenessThresholdFunc
 
-	t.TriggerConstructed()
-
 	t.acceptanceTime.OnUpdate(func(_ time.Time, acceptanceTime time.Time) {
 		t.triggerLivenessThreshold(acceptanceTime)
 	})
 
 	tipManager.OnBlockAdded(t.classifyTip)
 
-	t.TriggerInitialized()
-
-	return t
+	return module.InitSimpleLifecycle(t)
 }
 
 // SelectTips selects the tips that should be used as references for a new block.
@@ -166,12 +163,6 @@ func (t *TipSelection) Reset() {
 	t.resetLivenessThresholdQueue()
 }
 
-// Shutdown triggers the shutdown of the TipSelection.
-func (t *TipSelection) Shutdown() {
-	t.TriggerShutdown()
-	t.TriggerStopped()
-}
-
 // classifyTip determines the initial tip pool of the given tip.
 func (t *TipSelection) classifyTip(tipMetadata tipmanager.TipMetadata) {
 	if t.isValidStrongTip(tipMetadata.Block()) {
diff --git a/pkg/protocol/engine/upgrade/orchestrator.go b/pkg/protocol/engine/upgrade/orchestrator.go
index 33b8dced..889a2773 100644
--- a/pkg/protocol/engine/upgrade/orchestrator.go
+++ b/pkg/protocol/engine/upgrade/orchestrator.go
@@ -20,5 +20,5 @@ type Orchestrator interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go b/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go
index 77ffecac..e67c2716 100644
--- a/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go
+++ b/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go
@@ -75,6 +75,7 @@ type Orchestrator struct {
 func NewProvider(opts ...options.Option[Orchestrator]) module.Provider[*engine.Engine, upgrade.Orchestrator] {
 	return module.Provide(func(e *engine.Engine) upgrade.Orchestrator {
 		o := NewOrchestrator(
+			e.NewSubModule("UpgradeOrchestrator"),
 			e.ErrorHandler("upgradegadget"),
 			e.Storage.DecidedUpgradeSignals(),
 			e.Storage.UpgradeSignals,
@@ -103,13 +104,13 @@ func NewProvider(opts ...options.Option[Orchestrator]) module.Provider[*engine.E
 			}
 		}
 
-		o.TriggerInitialized()
+		o.InitializedEvent().Trigger()
 
 		return o
 	})
 }
 
-func NewOrchestrator(errorHandler func(error),
+func NewOrchestrator(module module.Module, errorHandler func(error),
 	decidedUpgradeSignals epochstore.Store[model.VersionAndHash],
 	upgradeSignalsFunc func(slot iotago.SlotIndex) (*slotstore.Store[account.SeatIndex, *model.SignaledBlock], error),
 	apiProvider iotago.APIProvider,
@@ -118,6 +119,7 @@ func NewOrchestrator(errorHandler func(error),
 	epochForVersionFunc func(iotago.Version) (iotago.EpochIndex, bool),
 	seatManager seatmanager.SeatManager, opts ...options.Option[Orchestrator]) *Orchestrator {
 	return options.Apply(&Orchestrator{
+		Module:                    module,
 		errorHandler:              errorHandler,
 		latestSignals:             memstorage.NewIndexedStorage[iotago.SlotIndex, account.SeatIndex, *model.SignaledBlock](),
 		decidedUpgradeSignals:     decidedUpgradeSignals,
@@ -129,9 +131,13 @@ func NewOrchestrator(errorHandler func(error),
 
 		apiProvider: apiProvider,
 		seatManager: seatManager,
-	}, opts,
-		(*Orchestrator).TriggerConstructed,
-	)
+	}, opts, func(o *Orchestrator) {
+		o.ShutdownEvent().OnTrigger(func() {
+			o.StoppedEvent().Trigger()
+		})
+
+		o.ConstructedEvent().Trigger()
+	})
 }
 
 // Reset resets the component to a clean state as if it was created at the last commitment.
@@ -141,10 +147,6 @@ func (o *Orchestrator) Reset() {
 	o.latestSignals.Clear()
 }
 
-func (o *Orchestrator) Shutdown() {
-	o.TriggerStopped()
-}
-
 func (o *Orchestrator) TrackValidationBlock(block *blocks.Block) {
 	// Not a validation block.
 	validationBlock, isValidationBlock := block.ValidationBlock()
diff --git a/pkg/protocol/engines.go b/pkg/protocol/engines.go
index 5a2b9ca4..c10de546 100644
--- a/pkg/protocol/engines.go
+++ b/pkg/protocol/engines.go
@@ -38,51 +38,45 @@ type Engines struct {
 	directory *utils.Directory
 
 	// ReactiveModule embeds a reactive module that provides default API for logging and lifecycle management.
-	*module.ReactiveModule
+	module.Module
 }
 
 // newEngines creates a new Engines instance.
 func newEngines(protocol *Protocol) *Engines {
 	e := &Engines{
-		Main:           reactive.NewVariable[*engine.Engine](),
-		ReactiveModule: protocol.NewReactiveSubModule("Engines"),
-		protocol:       protocol,
-		worker:         protocol.Workers.CreatePool("Engines", workerpool.WithWorkerCount(1)),
-		directory:      utils.NewDirectory(protocol.Options.BaseDirectory),
+		Main:      reactive.NewVariable[*engine.Engine](),
+		Module:    protocol.NewSubModule("Engines"),
+		protocol:  protocol,
+		worker:    protocol.Workers.CreatePool("Engines", workerpool.WithWorkerCount(1)),
+		directory: utils.NewDirectory(protocol.Options.BaseDirectory),
 	}
 
-	protocol.Constructed.OnTrigger(func() {
+	protocol.ConstructedEvent().OnTrigger(func() {
 		shutdown := lo.BatchReverse(
-			e.initLogger(protocol.NewChildLogger("Engines")),
+			e.initLogging(),
 
 			e.syncMainEngineFromMainChain(),
 			e.syncMainEngineInfoFile(),
 			e.injectEngineInstances(),
 		)
 
-		e.Shutdown.OnTrigger(func() {
+		e.ShutdownEvent().OnTrigger(func() {
 			shutdown()
 
-			e.Stopped.Trigger()
+			e.StoppedEvent().Trigger()
 		})
 
-		e.Initialized.Trigger()
+		e.InitializedEvent().Trigger()
 	})
 
-	e.Constructed.Trigger()
+	e.ConstructedEvent().Trigger()
 
 	return e
 }
 
-// initLogger initializes the logger for this component.
-func (e *Engines) initLogger(logger log.Logger) (shutdown func()) {
-	e.Logger = logger
-
-	return lo.BatchReverse(
-		e.Main.LogUpdates(e, log.LevelTrace, "Main", (*engine.Engine).LogName),
-
-		logger.UnsubscribeFromParentLogger,
-	)
+// initLogging initializes the logging for this component.
+func (e *Engines) initLogging() (shutdown func()) {
+	return e.Main.LogUpdates(e, log.LevelTrace, "Main", (*engine.Engine).LogName)
 }
 
 // ForkAtSlot creates a new engine instance that forks from the main engine at the given slot.
@@ -93,7 +87,7 @@ func (e *Engines) ForkAtSlot(slot iotago.SlotIndex) (*engine.Engine, error) {
 	}
 
 	// copy raw data on disk.
-	newStorage, err := storage.Clone(e.Logger, e.Main.Get().Storage, e.directory.Path(newEngineAlias), DatabaseVersion, errorHandler, e.protocol.Options.StorageOptions...)
+	newStorage, err := storage.Clone(e, e.Main.Get().Storage, e.directory.Path(newEngineAlias), DatabaseVersion, errorHandler, e.protocol.Options.StorageOptions...)
 	if err != nil {
 		return nil, ierrors.Wrapf(err, "failed to copy storage from active engine instance (%s) to new engine instance (%s)", e.Main.Get().Storage.Directory(), e.directory.Path(newEngineAlias))
 	}
@@ -108,7 +102,7 @@ func (e *Engines) ForkAtSlot(slot iotago.SlotIndex) (*engine.Engine, error) {
 	evictionState.Initialize(latestCommitment.Slot())
 
 	blockCache := blocks.New(evictionState, newStorage.Settings().APIProvider())
-	accountsManager := accountsledger.New(newStorage.Settings().APIProvider(), blockCache.Block, newStorage.AccountDiffs, newStorage.Accounts())
+	accountsManager := accountsledger.New(module.New(log.NewLogger(log.WithName("ForkedAccountsLedger"))), newStorage.Settings().APIProvider(), blockCache.Block, newStorage.AccountDiffs, newStorage.Accounts())
 
 	accountsManager.SetLatestCommittedSlot(latestCommitment.Slot())
 	if err = accountsManager.Rollback(slot); err != nil {
@@ -204,13 +198,13 @@ func (e *Engines) loadEngineInstanceFromSnapshot(engineAlias string, snapshotPat
 		e.protocol.LogError("engine error", "err", err, "name", engineAlias[0:8])
 	}
 
-	return e.loadEngineInstanceWithStorage(engineAlias, storage.Create(e.Logger, e.directory.Path(engineAlias), DatabaseVersion, errorHandler, e.protocol.Options.StorageOptions...), engine.WithSnapshotPath(snapshotPath), engine.WithCommitmentCheck(commitmentCheck))
+	return e.loadEngineInstanceWithStorage(engineAlias, storage.Create(e, e.directory.Path(engineAlias), DatabaseVersion, errorHandler, e.protocol.Options.StorageOptions...), engine.WithSnapshotPath(snapshotPath), engine.WithCommitmentCheck(commitmentCheck))
 }
 
 // loadEngineInstanceWithStorage loads an engine instance with the given storage.
 func (e *Engines) loadEngineInstanceWithStorage(engineAlias string, storage *storage.Storage, engineOptions ...options.Option[engine.Engine]) *engine.Engine {
 	return engine.New(
-		e.protocol.Logger,
+		e.protocol,
 		e.protocol.Workers.CreateGroup(engineAlias),
 		storage,
 		e.protocol.Options.PreSolidFilterProvider,
@@ -275,7 +269,7 @@ func (e *Engines) injectEngineInstances() (shutdown func()) {
 				}(); err != nil {
 					e.LogError("failed to create new engine instance", "err", err)
 				} else {
-					e.protocol.Network.OnShutdown(func() { newEngine.Shutdown.Trigger() })
+					e.protocol.Network.OnShutdown(func() { newEngine.ShutdownEvent().Trigger() })
 
 					chain.Engine.Set(newEngine)
 				}
diff --git a/pkg/protocol/network.go b/pkg/protocol/network.go
index faad36e0..ba419ab1 100644
--- a/pkg/protocol/network.go
+++ b/pkg/protocol/network.go
@@ -34,7 +34,7 @@ func newNetwork(protocol *Protocol, networkEndpoint network.Endpoint) *Network {
 		protocol: protocol,
 	}
 
-	protocol.Shutdown.OnTrigger(n.Logger.UnsubscribeFromParentLogger)
+	protocol.ShutdownEvent().OnTrigger(n.Logger.UnsubscribeFromParentLogger)
 
 	return n
 }
diff --git a/pkg/protocol/protocol.go b/pkg/protocol/protocol.go
index 53b11352..1caa047e 100644
--- a/pkg/protocol/protocol.go
+++ b/pkg/protocol/protocol.go
@@ -56,21 +56,21 @@ type Protocol struct {
 	reactive.EvictionState[iotago.SlotIndex]
 
 	// ReactiveModule embeds the reactive module logic of the protocol.
-	*module.ReactiveModule
+	module.Module
 }
 
 // New creates a new protocol instance from the given parameters.
 func New(logger log.Logger, workers *workerpool.Group, networkEndpoint network.Endpoint, opts ...options.Option[Protocol]) *Protocol {
 	return options.Apply(&Protocol{
-		Events:         NewEvents(),
-		Workers:        workers,
-		Options:        NewDefaultOptions(),
-		ReactiveModule: module.NewReactiveModule(logger),
-		EvictionState:  reactive.NewEvictionState[iotago.SlotIndex](),
+		Events:        NewEvents(),
+		Workers:       workers,
+		Options:       NewDefaultOptions(),
+		Module:        module.New(logger),
+		EvictionState: reactive.NewEvictionState[iotago.SlotIndex](),
 	}, opts, func(p *Protocol) {
 		shutdownSubComponents := p.initSubcomponents(networkEndpoint)
 
-		p.Initialized.OnTrigger(func() {
+		p.InitializedEvent().OnTrigger(func() {
 			shutdown := lo.BatchReverse(
 				p.initEviction(),
 				p.initGlobalEventsRedirection(),
@@ -79,10 +79,10 @@ func New(logger log.Logger, workers *workerpool.Group, networkEndpoint network.E
 				shutdownSubComponents,
 			)
 
-			p.Shutdown.OnTrigger(shutdown)
+			p.ShutdownEvent().OnTrigger(shutdown)
 		})
 
-		p.Constructed.Trigger()
+		p.ConstructedEvent().Trigger()
 
 		p.waitInitialized()
 	})
@@ -97,12 +97,12 @@ func (p *Protocol) IssueBlock(block *model.Block) error {
 
 // Run starts the protocol.
 func (p *Protocol) Run(ctx context.Context) error {
-	p.Initialized.Trigger()
+	p.InitializedEvent().Trigger()
 
 	<-ctx.Done()
 
-	p.Shutdown.Trigger()
-	p.Stopped.Trigger()
+	p.ShutdownEvent().Trigger()
+	p.StoppedEvent().Trigger()
 
 	return ctx.Err()
 }
@@ -156,7 +156,7 @@ func (p *Protocol) initSubcomponents(networkEndpoint network.Endpoint) (shutdown
 		p.WarpSync.Shutdown()
 		p.Network.Shutdown()
 		p.Workers.WaitChildren()
-		p.Engines.Shutdown.Trigger()
+		p.Engines.ShutdownEvent().Trigger()
 		p.Workers.Shutdown()
 	}
 }
diff --git a/pkg/protocol/sybilprotection/seatmanager/poa/poa.go b/pkg/protocol/sybilprotection/seatmanager/poa/poa.go
index 041ce823..531a4078 100644
--- a/pkg/protocol/sybilprotection/seatmanager/poa/poa.go
+++ b/pkg/protocol/sybilprotection/seatmanager/poa/poa.go
@@ -40,6 +40,7 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En
 	return module.Provide(func(e *engine.Engine) seatmanager.SeatManager {
 		return options.Apply(
 			&SeatManager{
+				Module:         e.NewSubModule("ProofOfAuthoritySeatManager"),
 				events:         seatmanager.NewEvents(),
 				apiProvider:    e,
 				committeeStore: e.Storage.Committee(),
@@ -51,9 +52,7 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En
 
 				e.Events.SeatManager.LinkTo(s.events)
 
-				e.Constructed.OnTrigger(func() {
-					s.TriggerConstructed()
-
+				e.ConstructedEvent().OnTrigger(func() {
 					// We need to mark validators as active upon solidity of blocks as otherwise we would not be able to
 					// recover if no node was part of the online committee anymore.
 					e.Events.PostSolidFilter.BlockAllowed.Hook(func(block *blocks.Block) {
@@ -70,7 +69,15 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En
 
 						s.events.BlockProcessed.Trigger(block)
 					})
+
+					s.InitializedEvent().Trigger()
+				})
+
+				s.ShutdownEvent().OnTrigger(func() {
+					s.StoppedEvent().Trigger()
 				})
+
+				s.ConstructedEvent().Trigger()
 			})
 	})
 }
@@ -155,10 +162,6 @@ func (s *SeatManager) SeatCountInEpoch(_ iotago.EpochIndex) int {
 	return s.committee.SeatCount()
 }
 
-func (s *SeatManager) Shutdown() {
-	s.TriggerStopped()
-}
-
 func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime time.Time) error {
 	s.committeeMutex.Lock()
 	defer s.committeeMutex.Unlock()
diff --git a/pkg/protocol/sybilprotection/seatmanager/seatmanager.go b/pkg/protocol/sybilprotection/seatmanager/seatmanager.go
index e6c8ea05..53a139fe 100644
--- a/pkg/protocol/sybilprotection/seatmanager/seatmanager.go
+++ b/pkg/protocol/sybilprotection/seatmanager/seatmanager.go
@@ -37,6 +37,6 @@ type SeatManager interface {
 	// SeatCountInEpoch returns the number of seats in the SeatManager for the given epoch.
 	SeatCountInEpoch(epoch iotago.EpochIndex) int
 
-	// Interface embeds the required methods of the module.Interface.
-	module.Interface
+	// Interface embeds the required methods of the module.Module.
+	module.Module
 }
diff --git a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go
index b76267ec..3729c8b2 100644
--- a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go
+++ b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go
@@ -41,6 +41,7 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En
 	return module.Provide(func(e *engine.Engine) seatmanager.SeatManager {
 		return options.Apply(
 			&SeatManager{
+				Module:         e.NewSubModule("SeatManager"),
 				apiProvider:    e,
 				events:         seatmanager.NewEvents(),
 				committeeStore: e.Storage.Committee(),
@@ -52,9 +53,7 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En
 
 				e.Events.SeatManager.LinkTo(s.events)
 
-				e.Constructed.OnTrigger(func() {
-					s.TriggerConstructed()
-
+				e.ConstructedEvent().OnTrigger(func() {
 					// We need to mark validators as active upon solidity of blocks as otherwise we would not be able to
 					// recover if no node was part of the online committee anymore.
 					e.Events.PostSolidFilter.BlockAllowed.Hook(func(block *blocks.Block) {
@@ -71,7 +70,15 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En
 
 						s.events.BlockProcessed.Trigger(block)
 					})
+
+					s.ShutdownEvent().OnTrigger(func() {
+						s.StoppedEvent().Trigger()
+					})
+
+					s.InitializedEvent().Trigger()
 				})
+
+				s.ConstructedEvent().Trigger()
 			})
 	})
 }
@@ -173,7 +180,7 @@ func (s *SeatManager) SeatCountInEpoch(epoch iotago.EpochIndex) int {
 }
 
 func (s *SeatManager) Shutdown() {
-	s.TriggerStopped()
+	s.StoppedEvent().Trigger()
 }
 
 func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime time.Time) error {
diff --git a/pkg/protocol/sybilprotection/sybilprotection.go b/pkg/protocol/sybilprotection/sybilprotection.go
index 52acd55c..004cd128 100644
--- a/pkg/protocol/sybilprotection/sybilprotection.go
+++ b/pkg/protocol/sybilprotection/sybilprotection.go
@@ -39,5 +39,5 @@ type SybilProtection interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset()
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go b/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
index ca653120..1d3742b5 100644
--- a/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
+++ b/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
@@ -47,55 +47,54 @@ type SybilProtection struct {
 func NewProvider(opts ...options.Option[SybilProtection]) module.Provider[*engine.Engine, sybilprotection.SybilProtection] {
 	return module.Provide(func(e *engine.Engine) sybilprotection.SybilProtection {
 		return options.Apply(&SybilProtection{
+			Module: e.NewSubModule("SybilProtection"),
 			events: sybilprotection.NewEvents(),
 
 			apiProvider:             e,
 			optsSeatManagerProvider: topstakers.NewProvider(),
-		}, opts,
-			func(o *SybilProtection) {
-				o.seatManager = o.optsSeatManagerProvider(e)
-
-				e.Constructed.OnTrigger(func() {
-					o.ledger = e.Ledger
-					o.errHandler = e.ErrorHandler("SybilProtection")
-					logger := e.NewChildLogger("PerformanceTracker")
-					latestCommittedSlot := e.Storage.Settings().LatestCommitment().Slot()
-					latestCommittedEpoch := o.apiProvider.APIForSlot(latestCommittedSlot).TimeProvider().EpochFromSlot(latestCommittedSlot)
-					o.performanceTracker = performance.NewTracker(e.Storage.RewardsForEpoch, e.Storage.PoolStats(), e.Storage.Committee(), e.Storage.CommitteeCandidates, e.Storage.ValidatorPerformances, latestCommittedEpoch, e, o.errHandler, logger)
-					o.lastCommittedSlot = latestCommittedSlot
-
-					if o.optsInitialCommittee != nil {
-						if _, err := o.seatManager.RotateCommittee(0, o.optsInitialCommittee); err != nil {
-							panic(ierrors.Wrap(err, "error while registering initial committee for epoch 0"))
-						}
+		}, opts, func(o *SybilProtection) {
+			o.seatManager = o.optsSeatManagerProvider(e)
+
+			e.ConstructedEvent().OnTrigger(func() {
+				o.ledger = e.Ledger
+				o.errHandler = e.ErrorHandler("SybilProtection")
+				logger := e.NewChildLogger("PerformanceTracker")
+				latestCommittedSlot := e.Storage.Settings().LatestCommitment().Slot()
+				latestCommittedEpoch := o.apiProvider.APIForSlot(latestCommittedSlot).TimeProvider().EpochFromSlot(latestCommittedSlot)
+				o.performanceTracker = performance.NewTracker(e.Storage.RewardsForEpoch, e.Storage.PoolStats(), e.Storage.Committee(), e.Storage.CommitteeCandidates, e.Storage.ValidatorPerformances, latestCommittedEpoch, e, o.errHandler, logger)
+				o.lastCommittedSlot = latestCommittedSlot
+
+				if o.optsInitialCommittee != nil {
+					if _, err := o.seatManager.RotateCommittee(0, o.optsInitialCommittee); err != nil {
+						panic(ierrors.Wrap(err, "error while registering initial committee for epoch 0"))
 					}
+				}
 
-					o.TriggerConstructed()
-
-					// When the engine is triggered initialized, snapshot has been read or database has been initialized properly,
-					// so the committee should be available in the performance manager.
-					e.Initialized.OnTrigger(func() {
-						// Mark the committee for the last committed slot as active.
-						currentEpoch := e.CommittedAPI().TimeProvider().EpochFromSlot(e.Storage.Settings().LatestCommitment().Slot())
-						err := o.seatManager.InitializeCommittee(currentEpoch, e.Clock.Accepted().RelativeTime())
-						if err != nil {
-							panic(ierrors.Wrap(err, "error while initializing committee"))
-						}
+				// When the engine is triggered initialized, snapshot has been read or database has been initialized properly,
+				// so the committee should be available in the performance manager.
+				e.InitializedEvent().OnTrigger(func() {
+					// Mark the committee for the last committed slot as active.
+					currentEpoch := e.CommittedAPI().TimeProvider().EpochFromSlot(e.Storage.Settings().LatestCommitment().Slot())
+					err := o.seatManager.InitializeCommittee(currentEpoch, e.Clock.Accepted().RelativeTime())
+					if err != nil {
+						panic(ierrors.Wrap(err, "error while initializing committee"))
+					}
 
-						o.TriggerInitialized()
-					})
+					o.InitializedEvent().Trigger()
 				})
+			})
 
-				e.Events.SlotGadget.SlotFinalized.Hook(o.slotFinalized)
+			e.Events.SlotGadget.SlotFinalized.Hook(o.slotFinalized)
 
-				e.Events.SybilProtection.LinkTo(o.events)
-			},
-		)
-	})
-}
+			e.Events.SybilProtection.LinkTo(o.events)
+
+			o.ShutdownEvent().OnTrigger(func() {
+				o.StoppedEvent().Trigger()
+			})
 
-func (o *SybilProtection) Shutdown() {
-	o.TriggerStopped()
+			o.ConstructedEvent().Trigger()
+		})
+	})
 }
 
 func (o *SybilProtection) TrackBlock(block *blocks.Block) {
diff --git a/pkg/protocol/warp_sync.go b/pkg/protocol/warp_sync.go
index 84f06fa5..ced1b1f3 100644
--- a/pkg/protocol/warp_sync.go
+++ b/pkg/protocol/warp_sync.go
@@ -11,7 +11,6 @@ import (
 	"github.com/iotaledger/hive.go/ds/reactive"
 	"github.com/iotaledger/hive.go/ierrors"
 	"github.com/iotaledger/hive.go/kvstore/mapdb"
-	"github.com/iotaledger/hive.go/lo"
 	"github.com/iotaledger/hive.go/log"
 	"github.com/iotaledger/hive.go/runtime/workerpool"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine"
@@ -37,7 +36,7 @@ type WarpSync struct {
 // newWarpSync creates a new warp sync protocol instance for the given protocol.
 func newWarpSync(protocol *Protocol) *WarpSync {
 	c := &WarpSync{
-		Logger:     lo.Return1(protocol.Logger.NewChildLogger("WarpSync")),
+		Logger:     protocol.NewChildLogger("WarpSync"),
 		protocol:   protocol,
 		workerPool: protocol.Workers.CreatePool("WarpSync", workerpool.WithWorkerCount(1)),
 		ticker:     eventticker.New[iotago.SlotIndex, iotago.CommitmentID](protocol.Options.WarpSyncRequesterOptions...),
@@ -45,7 +44,7 @@ func newWarpSync(protocol *Protocol) *WarpSync {
 
 	c.ticker.Events.Tick.Hook(c.SendRequest)
 
-	protocol.Constructed.OnTrigger(func() {
+	protocol.ConstructedEvent().OnTrigger(func() {
 		protocol.Chains.WithInitializedEngines(func(chain *Chain, engine *engine.Engine) (shutdown func()) {
 			return chain.WarpSyncMode.OnUpdate(func(_ bool, warpSyncModeEnabled bool) {
 				if warpSyncModeEnabled {
diff --git a/pkg/retainer/blockretainer/block_retainer.go b/pkg/retainer/blockretainer/block_retainer.go
index 27363bd0..51ba90f1 100644
--- a/pkg/retainer/blockretainer/block_retainer.go
+++ b/pkg/retainer/blockretainer/block_retainer.go
@@ -31,8 +31,9 @@ type BlockRetainer struct {
 	module.Module
 }
 
-func New(workersGroup *workerpool.Group, retainerStoreFunc StoreFunc, latestCommittedSlotFunc LatestCommittedSlotFunc, finalizedSlotFunc FinalizedSlotFunc, errorHandler func(error)) *BlockRetainer {
-	return &BlockRetainer{
+func New(module module.Module, workersGroup *workerpool.Group, retainerStoreFunc StoreFunc, latestCommittedSlotFunc LatestCommittedSlotFunc, finalizedSlotFunc FinalizedSlotFunc, errorHandler func(error)) *BlockRetainer {
+	b := &BlockRetainer{
+		Module:                  module,
 		events:                  retainer.NewEvents(),
 		workerPool:              workersGroup.CreatePool("Retainer", workerpool.WithWorkerCount(1)),
 		store:                   retainerStoreFunc,
@@ -40,12 +41,20 @@ func New(workersGroup *workerpool.Group, retainerStoreFunc StoreFunc, latestComm
 		finalizedSlotFunc:       finalizedSlotFunc,
 		errorHandler:            errorHandler,
 	}
+
+	b.ShutdownEvent().OnTrigger(func() {
+		b.StoppedEvent().Trigger()
+	})
+
+	b.ConstructedEvent().Trigger()
+
+	return b
 }
 
 // NewProvider creates a new BlockRetainer provider.
 func NewProvider() module.Provider[*engine.Engine, retainer.BlockRetainer] {
 	return module.Provide(func(e *engine.Engine) retainer.BlockRetainer {
-		r := New(e.Workers.CreateGroup("Retainer"),
+		r := New(e.NewSubModule("BlockRetainer"), e.Workers.CreateGroup("Retainer"),
 			e.Storage.BlockMetadata,
 			func() iotago.SlotIndex {
 				return e.SyncManager.LatestCommitment().Slot()
@@ -83,7 +92,7 @@ func NewProvider() module.Provider[*engine.Engine, retainer.BlockRetainer] {
 
 		e.Events.Retainer.BlockRetained.LinkTo(r.events.BlockRetained)
 
-		r.TriggerInitialized()
+		r.InitializedEvent().Trigger()
 
 		return r
 	})
@@ -95,10 +104,6 @@ func (r *BlockRetainer) Reset() {
 	// on chain switching reset everything up to the forking point
 }
 
-func (r *BlockRetainer) Shutdown() {
-	r.workerPool.Shutdown()
-}
-
 func (r *BlockRetainer) getBlockMetadata(blockID iotago.BlockID) (*slotstore.BlockMetadata, error) {
 	store, err := r.store(blockID.Slot())
 	if err != nil {
diff --git a/pkg/retainer/retainer.go b/pkg/retainer/retainer.go
index d00401c9..5a21763c 100644
--- a/pkg/retainer/retainer.go
+++ b/pkg/retainer/retainer.go
@@ -13,8 +13,8 @@ type BlockRetainer interface {
 	// 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
+	// Interface embeds the required methods of the module.Module.
+	module.Module
 }
 
 // TransactionRetainer keeps and resolves all the transaction-related metadata needed in the API and INX.
@@ -25,5 +25,5 @@ type TransactionRetainer interface {
 	// Reset resets the component to a clean state as if it was created at the last commitment.
 	Reset(targetSlot iotago.SlotIndex)
 
-	module.Interface
+	module.Module
 }
diff --git a/pkg/retainer/txretainer/tx_retainer.go b/pkg/retainer/txretainer/tx_retainer.go
index 7421c2d1..d35ade54 100644
--- a/pkg/retainer/txretainer/tx_retainer.go
+++ b/pkg/retainer/txretainer/tx_retainer.go
@@ -110,24 +110,22 @@ func WithDebugStoreErrorMessages(store bool) options.Option[TransactionRetainer]
 	}
 }
 
-func New(workersGroup *workerpool.Group, dbExecFunc storage.SQLDatabaseExecFunc, latestCommittedSlotFunc SlotFunc, finalizedSlotFunc SlotFunc, errorHandler func(error), opts ...options.Option[TransactionRetainer]) *TransactionRetainer {
-	return options.Apply(&TransactionRetainer{
+func New(parentModule module.Module, workersGroup *workerpool.Group, dbExecFunc storage.SQLDatabaseExecFunc, latestCommittedSlotFunc SlotFunc, finalizedSlotFunc SlotFunc, errorHandler func(error), opts ...options.Option[TransactionRetainer]) *TransactionRetainer {
+	return module.InitSimpleLifecycle(options.Apply(&TransactionRetainer{
+		Module:                  parentModule.NewSubModule("TransactionRetainer"),
 		workerPool:              workersGroup.CreatePool("TxRetainer", workerpool.WithWorkerCount(1)),
 		txRetainerCache:         NewTransactionRetainerCache(),
 		txRetainerDatabase:      NewTransactionRetainerDB(dbExecFunc),
 		latestCommittedSlotFunc: latestCommittedSlotFunc,
 		finalizedSlotFunc:       finalizedSlotFunc,
 		errorHandler:            errorHandler,
-	}, opts,
-		(*TransactionRetainer).TriggerConstructed,
-		(*TransactionRetainer).TriggerInitialized,
-	)
+	}, opts), (*TransactionRetainer).shutdown)
 }
 
 // NewProvider creates a new TransactionRetainer provider.
 func NewProvider(opts ...options.Option[TransactionRetainer]) module.Provider[*engine.Engine, retainer.TransactionRetainer] {
 	return module.Provide(func(e *engine.Engine) retainer.TransactionRetainer {
-		r := New(e.Workers.CreateGroup("TransactionRetainer"),
+		r := New(e, e.Workers.CreateGroup("TransactionRetainer"),
 			e.Storage.TransactionRetainerDatabaseExecFunc(),
 			func() iotago.SlotIndex {
 				return e.SyncManager.LatestCommitment().Slot()
@@ -141,7 +139,7 @@ func NewProvider(opts ...options.Option[TransactionRetainer]) module.Provider[*e
 
 		asyncOpt := event.WithWorkerPool(r.workerPool)
 
-		e.Initialized.OnTrigger(func() {
+		e.InitializedEvent().OnTrigger(func() {
 			// attaching the transaction failed for some reason => store the error
 			// HINT: we treat the transaction as unsigned here, because we don't know if it was signed or not.
 			// This should not be a problem, because the error reason will still be stored and visible to the user,
@@ -245,17 +243,10 @@ func NewProvider(opts ...options.Option[TransactionRetainer]) module.Provider[*e
 			}, asyncOpt)
 		})
 
-		r.TriggerInitialized()
-
 		return r
 	})
 }
 
-// Shutdown shuts down the TransactionRetainer.
-func (r *TransactionRetainer) Shutdown() {
-	r.workerPool.Shutdown()
-}
-
 // Reset resets the component to a clean state as if it was created at the last commitment.
 func (r *TransactionRetainer) Reset(targetSlot iotago.SlotIndex) {
 	// we need to rollback the transaction retainer to the target slot
@@ -375,3 +366,10 @@ func (r *TransactionRetainer) TransactionMetadata(txID iotago.TransactionID) (*a
 
 	return response, nil
 }
+
+// Shutdown shuts down the TransactionRetainer.
+func (r *TransactionRetainer) shutdown() {
+	r.workerPool.Shutdown()
+
+	r.StoppedEvent().Trigger()
+}
diff --git a/tools/gendoc/go.mod b/tools/gendoc/go.mod
index 2ceae959..b3fc2a15 100644
--- a/tools/gendoc/go.mod
+++ b/tools/gendoc/go.mod
@@ -5,7 +5,7 @@ go 1.22.0
 replace github.com/iotaledger/iota-core => ../../
 
 require (
-	github.com/iotaledger/hive.go/app v0.0.0-20240307102857-7e23a3c59bd2
+	github.com/iotaledger/hive.go/app v0.0.0-20240312125539-658ef8b73c89
 	github.com/iotaledger/hive.go/apputils v0.0.0-20230829152614-7afc7a4d89b3
 	github.com/iotaledger/iota-core v0.0.0-00010101000000-000000000000
 )
@@ -56,20 +56,20 @@ 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-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/constraints v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/crypto v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/db v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/ds v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/ierrors v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/kvstore v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/lo v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/log v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/runtime v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/sql v0.0.0-20240307102857-7e23a3c59bd2 // indirect
-	github.com/iotaledger/hive.go/stringify v0.0.0-20240307102857-7e23a3c59bd2 // indirect
+	github.com/iotaledger/hive.go/ads v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/constraints v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/crypto v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/db v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/ds v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/ierrors v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/kvstore v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/lo v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/log v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/runtime v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/sql v0.0.0-20240312125539-658ef8b73c89 // indirect
+	github.com/iotaledger/hive.go/stringify v0.0.0-20240312125539-658ef8b73c89 // indirect
 	github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240307101848-db58eb9353ec // indirect
 	github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240307100839-48553e1d2022 // indirect
 	github.com/iotaledger/iota-crypto-demo v0.0.0-20240216103559-27ca8dffd1e7 // indirect
diff --git a/tools/gendoc/go.sum b/tools/gendoc/go.sum
index 5be21d31..92ca24b8 100644
--- a/tools/gendoc/go.sum
+++ b/tools/gendoc/go.sum
@@ -284,38 +284,38 @@ github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJ
 github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 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-20240307102857-7e23a3c59bd2 h1:Las7yFtYbyILe5rvkbgwp0COIb0TEGUOfwru1DPpRCE=
-github.com/iotaledger/hive.go/ads v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:tkWq2sJR/vWkbht1BJNIvNP6hJAaRye99wid0A2oU9I=
-github.com/iotaledger/hive.go/app v0.0.0-20240307102857-7e23a3c59bd2 h1:Hh3psyc4XfZVPEV5E7oBE14C+g0rRwBa4Ig3WBwPnVw=
-github.com/iotaledger/hive.go/app v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:EMUpj6oDb/QSiVpIe5nYwyDKSpO6ue7mdkzmK/VoBn8=
+github.com/iotaledger/hive.go/ads v0.0.0-20240312125539-658ef8b73c89 h1:/NOniblB4wAKC3QRYx+d//mJhD0XmHMBErKa3zOnvRM=
+github.com/iotaledger/hive.go/ads v0.0.0-20240312125539-658ef8b73c89/go.mod h1:tkWq2sJR/vWkbht1BJNIvNP6hJAaRye99wid0A2oU9I=
+github.com/iotaledger/hive.go/app v0.0.0-20240312125539-658ef8b73c89 h1:RIbhfIY2o5iz2kk/B0hYKDJ8hTQPThb6mzq8FsffYrs=
+github.com/iotaledger/hive.go/app v0.0.0-20240312125539-658ef8b73c89/go.mod h1:EMUpj6oDb/QSiVpIe5nYwyDKSpO6ue7mdkzmK/VoBn8=
 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-20240307102857-7e23a3c59bd2 h1:prG9e7LqHljPuHGa0dgNhtydMXgt+k0KmgGlQkCVblM=
-github.com/iotaledger/hive.go/constraints v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:JF7jjkL6tSUOXm23SWadBzBrl7eJk1DQRLc/fNoVZ+o=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240307102857-7e23a3c59bd2 h1:CSKLuAkFWA+1tBj8fAlzP/aYVskBXcuLGlx/Vxhe1iU=
-github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240307102857-7e23a3c59bd2/go.mod h1:73ODTHUJxAoGUN0InOtuQ29LVq85pDf7GckM9d0ziqo=
-github.com/iotaledger/hive.go/crypto v0.0.0-20240307102857-7e23a3c59bd2 h1:Mu+5cOiB1AoaBULv+ibYn98+lQpW2yFX9CxMsOLtQEc=
-github.com/iotaledger/hive.go/crypto v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:aREIB19gIhSYdY0Hl/37sg1JRH7+j3ajeJxBIQf6mig=
-github.com/iotaledger/hive.go/db v0.0.0-20240307102857-7e23a3c59bd2 h1:Q+abZBAJV3cqy8yZeFwnI+1uIyVJdFaPHqwEih7EYMc=
-github.com/iotaledger/hive.go/db v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:8Y5vqE5NK5zG4Af0JV2UQeW67iLlymPLsT1KkOn4qY8=
-github.com/iotaledger/hive.go/ds v0.0.0-20240307102857-7e23a3c59bd2 h1:ezUzO0JC+frUaRsTPgpwSc0k4ilbZLCO7j4wRVqr0Xo=
-github.com/iotaledger/hive.go/ds v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:wfjeJj9B+MM/3yeUHfvT8Gj8bRsdl9utyh2dZg+1+B0=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20240307102857-7e23a3c59bd2 h1:NrM+NjA0MFUhr8wtTW6xUrUZYLYL4onbt4vXE3UpM1o=
-github.com/iotaledger/hive.go/ierrors v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:GQY0/35sjgT9Poi1Vrs9kFVvAkuKzGXfVh4j6CBXsAA=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20240307102857-7e23a3c59bd2 h1:pUmtxr0urvJ9JK5jWLHFjJXwe5qb2P3RZ/F361Q/lk8=
-github.com/iotaledger/hive.go/kvstore v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:dCgv8YMOihhGNxQu37Vh5XqT/7wLbIJst2WePqo1z8Y=
-github.com/iotaledger/hive.go/lo v0.0.0-20240307102857-7e23a3c59bd2 h1:vKWfTrBrZdWhvbZk80HZA9HjT6xqp6S06qHug22QY8Q=
-github.com/iotaledger/hive.go/lo v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:67oLzWYiBLGt5PN7IBVHdbt9P6oBYCx9UvMEL8ExDAc=
-github.com/iotaledger/hive.go/log v0.0.0-20240307102857-7e23a3c59bd2 h1:QPXAfitKRr/ssVoXcGDoXgKxRdH1nfFHAzAnzii/Dhc=
-github.com/iotaledger/hive.go/log v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:H5tmswUbT3o5+QiM6UPtBv7VnPf+lJtlantgpp2lzUI=
-github.com/iotaledger/hive.go/runtime v0.0.0-20240307102857-7e23a3c59bd2 h1:p2hxIuWG1gclXQjUAaiDtATIAFsFPMGNCUmfNSlYbBQ=
-github.com/iotaledger/hive.go/runtime v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:pueoYXud+HmTY2x9j/S6+ZX3M5ZyENFKPDrx3EtcwWs=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240307102857-7e23a3c59bd2 h1:CLqKvuxX7uXZew3O/fOZfbsyo10+ZNVKpPzqcWuMAIs=
-github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240307102857-7e23a3c59bd2/go.mod h1:NK05G4PxwZF1m4jGANJWLhAQ2hP1Nt0L8mgCTFLsSCw=
-github.com/iotaledger/hive.go/sql v0.0.0-20240307102857-7e23a3c59bd2 h1:4c4KXZrs5HzbNjtxQMCF5FL0j2DMrJrP9rSjrZgqrjc=
-github.com/iotaledger/hive.go/sql v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:O9R4vLdoSBhEO+WyEEmBHKChlD80oz9EVi7vjuxE+J8=
-github.com/iotaledger/hive.go/stringify v0.0.0-20240307102857-7e23a3c59bd2 h1:rCOeAsmuLyOrY5EUH+5Y2tQTe1Cg1DffvL/8H7FK5Jo=
-github.com/iotaledger/hive.go/stringify v0.0.0-20240307102857-7e23a3c59bd2/go.mod h1:O4p7UmsfoeLqtAUwrKbq0lXMxjY/MLQSpZSavvvvGig=
+github.com/iotaledger/hive.go/constraints v0.0.0-20240312125539-658ef8b73c89 h1:CyhKeBrhnZZ6iJO4bKuKQxjRqt6u/7nIAFGTw/vquKE=
+github.com/iotaledger/hive.go/constraints v0.0.0-20240312125539-658ef8b73c89/go.mod h1:JF7jjkL6tSUOXm23SWadBzBrl7eJk1DQRLc/fNoVZ+o=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240312125539-658ef8b73c89 h1:O80716g+R2vNwgbNIye2An9M1W9Gh8iNp5l6bIh6DTs=
+github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20240312125539-658ef8b73c89/go.mod h1:73ODTHUJxAoGUN0InOtuQ29LVq85pDf7GckM9d0ziqo=
+github.com/iotaledger/hive.go/crypto v0.0.0-20240312125539-658ef8b73c89 h1:akL3mWtCHx6GMNTv7xxV8c1YWtfKHLCyC/d6NywHmjM=
+github.com/iotaledger/hive.go/crypto v0.0.0-20240312125539-658ef8b73c89/go.mod h1:aREIB19gIhSYdY0Hl/37sg1JRH7+j3ajeJxBIQf6mig=
+github.com/iotaledger/hive.go/db v0.0.0-20240312125539-658ef8b73c89 h1:5QhyTJXhaiNOiFDsHMIv8aTbkuVxPEtKVgt+pCe3gAE=
+github.com/iotaledger/hive.go/db v0.0.0-20240312125539-658ef8b73c89/go.mod h1:8Y5vqE5NK5zG4Af0JV2UQeW67iLlymPLsT1KkOn4qY8=
+github.com/iotaledger/hive.go/ds v0.0.0-20240312125539-658ef8b73c89 h1:cnRq/Nnd7hClvx5tIVEVcC0oyggTEFKN4+t3+R8Rz14=
+github.com/iotaledger/hive.go/ds v0.0.0-20240312125539-658ef8b73c89/go.mod h1:wfjeJj9B+MM/3yeUHfvT8Gj8bRsdl9utyh2dZg+1+B0=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20240312125539-658ef8b73c89 h1:+oBtd3D7TjW8nt7kdvzYnox5T1aBgiCdJywWOnyjUgg=
+github.com/iotaledger/hive.go/ierrors v0.0.0-20240312125539-658ef8b73c89/go.mod h1:GQY0/35sjgT9Poi1Vrs9kFVvAkuKzGXfVh4j6CBXsAA=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20240312125539-658ef8b73c89 h1:Z1zboF32gixXktGpiHmqIbu61GaT2Jazr/PCKaAKXt4=
+github.com/iotaledger/hive.go/kvstore v0.0.0-20240312125539-658ef8b73c89/go.mod h1:dCgv8YMOihhGNxQu37Vh5XqT/7wLbIJst2WePqo1z8Y=
+github.com/iotaledger/hive.go/lo v0.0.0-20240312125539-658ef8b73c89 h1:JybC8QefExnKXp08ETO9s5QS0jMcnopqkFXp8jsgATY=
+github.com/iotaledger/hive.go/lo v0.0.0-20240312125539-658ef8b73c89/go.mod h1:67oLzWYiBLGt5PN7IBVHdbt9P6oBYCx9UvMEL8ExDAc=
+github.com/iotaledger/hive.go/log v0.0.0-20240312125539-658ef8b73c89 h1:Rk1bWnm/CMW9Xx158p0uc5vy1U3UZYopXsmuMbUkFsc=
+github.com/iotaledger/hive.go/log v0.0.0-20240312125539-658ef8b73c89/go.mod h1:H5tmswUbT3o5+QiM6UPtBv7VnPf+lJtlantgpp2lzUI=
+github.com/iotaledger/hive.go/runtime v0.0.0-20240312125539-658ef8b73c89 h1:pDrD8FZSobQ49OKdE2HtaySa8FTzyUkE4CPZB3CCg5M=
+github.com/iotaledger/hive.go/runtime v0.0.0-20240312125539-658ef8b73c89/go.mod h1:OKoOmZd+qDjm0WsisIB5FYbKhMm5iPx4/mDJL/8SjsU=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240312125539-658ef8b73c89 h1:/0aRg/v9CrurFtzoCIPREzHRbFCD6GEGFHKdNw0ePGk=
+github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20240312125539-658ef8b73c89/go.mod h1:NK05G4PxwZF1m4jGANJWLhAQ2hP1Nt0L8mgCTFLsSCw=
+github.com/iotaledger/hive.go/sql v0.0.0-20240312125539-658ef8b73c89 h1:/8BpaYldXDEUwsPw98T9DNLfQxfvLi1AAv7Owiz82us=
+github.com/iotaledger/hive.go/sql v0.0.0-20240312125539-658ef8b73c89/go.mod h1:O9R4vLdoSBhEO+WyEEmBHKChlD80oz9EVi7vjuxE+J8=
+github.com/iotaledger/hive.go/stringify v0.0.0-20240312125539-658ef8b73c89 h1:/gCkJ/6Pgk1nw5FCqbA50wCCbVcXDBT9JKQ3pPJ5PME=
+github.com/iotaledger/hive.go/stringify v0.0.0-20240312125539-658ef8b73c89/go.mod h1:O4p7UmsfoeLqtAUwrKbq0lXMxjY/MLQSpZSavvvvGig=
 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240307101848-db58eb9353ec h1:8pasR6AxVTr1AqTbyAgPmrgmEE+Z5Tn4DOQy/Eu8jN8=
 github.com/iotaledger/inx-app v1.0.0-rc.3.0.20240307101848-db58eb9353ec/go.mod h1:LrPLgC2QeGvsgyhS7SYoqH449z4C4ro4HOByg1PXqHw=
 github.com/iotaledger/inx/go v1.0.0-rc.2.0.20240307100839-48553e1d2022 h1:I178SaWPhSxTvntKKvUVIdeogXIiLDALTbstKvbCuVc=


Test patch:
diff --git a/pkg/protocol/attestations.go b/pkg/protocol/attestations.go
index e62e7268..235f7974 100644
--- a/pkg/protocol/attestations.go
+++ b/pkg/protocol/attestations.go
@@ -35,20 +35,20 @@ type Attestations struct {
 // newAttestations creates a new attestation protocol instance for the given protocol.
 func newAttestations(protocol *Protocol) *Attestations {
 	a := &Attestations{
-		Logger:              lo.Return1(protocol.Logger.NewChildLogger("Attestations")),
+		Logger:              protocol.NewChildLogger("Attestations"),
 		protocol:            protocol,
 		workerPool:          protocol.Workers.CreatePool("Attestations"),
 		requester:           eventticker.New[iotago.SlotIndex, iotago.CommitmentID](protocol.Options.AttestationRequesterOptions...),
 		commitmentVerifiers: shrinkingmap.New[iotago.CommitmentID, *CommitmentVerifier](),
 	}
 
-	protocol.Constructed.OnTrigger(func() {
+	protocol.ConstructedEvent().OnTrigger(func() {
 		shutdown := lo.BatchReverse(
 			a.initCommitmentVerifiers(),
 			a.initRequester(),
 		)
 
-		protocol.Shutdown.OnTrigger(shutdown)
+		protocol.ShutdownEvent().OnTrigger(shutdown)
 	})
 
 	return a
diff --git a/pkg/protocol/engine/accounts/accountsledger/testsuite_test.go b/pkg/protocol/engine/accounts/accountsledger/testsuite_test.go
index fecb0663..38d18b17 100644
--- a/pkg/protocol/engine/accounts/accountsledger/testsuite_test.go
+++ b/pkg/protocol/engine/accounts/accountsledger/testsuite_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/iotaledger/hive.go/kvstore"
 	"github.com/iotaledger/hive.go/kvstore/mapdb"
 	"github.com/iotaledger/hive.go/lo"
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/iota-core/pkg/model"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/accounts"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/accounts/accountsledger"
@@ -79,7 +80,7 @@ func (t *TestSuite) initAccountLedger() *accountsledger.Manager {
 		return storage.Get(id)
 	}
 
-	manager := accountsledger.New(t.apiProvider, blockFunc, slotDiffFunc, mapdb.NewMapDB())
+	manager := accountsledger.New(module.NewTestModule(t.T), t.apiProvider, blockFunc, slotDiffFunc, mapdb.NewMapDB())
 
 	return manager
 }
diff --git a/pkg/protocol/engine/attestation/attestations.go b/pkg/protocol/engine/attestation/attestations.go
index 5a01b127..be28f84c 100644
--- a/pkg/protocol/engine/attestation/attestations.go
+++ b/pkg/protocol/engine/attestation/attestations.go
@@ -4,7 +4,6 @@ import (
 	"io"
 
 	"github.com/iotaledger/hive.go/ads"
-	"github.com/iotaledger/hive.go/log"
 	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
 	iotago "github.com/iotaledger/iota.go/v4"
@@ -30,7 +29,5 @@ type Attestations interface {
 
 	RestoreFromDisk() (err error)
 
-	log.Logger
-
-	module.Interface
+	module.Module
 }
diff --git a/pkg/protocol/engine/attestation/slotattestation/manager.go b/pkg/protocol/engine/attestation/slotattestation/manager.go
index 4028ebd9..83c4e560 100644
--- a/pkg/protocol/engine/attestation/slotattestation/manager.go
+++ b/pkg/protocol/engine/attestation/slotattestation/manager.go
@@ -6,7 +6,6 @@ import (
 	"github.com/iotaledger/hive.go/ierrors"
 	"github.com/iotaledger/hive.go/kvstore"
 	"github.com/iotaledger/hive.go/lo"
-	"github.com/iotaledger/hive.go/log"
 	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/syncutils"
 	"github.com/iotaledger/iota-core/pkg/core/account"
@@ -65,8 +64,6 @@ type Manager struct {
 	apiProvider iotago.APIProvider
 
 	module.Module
-
-	log.Logger
 }
 
 func NewProvider() module.Provider[*engine.Engine, attestation.Attestations] {
@@ -74,7 +71,7 @@ func NewProvider() module.Provider[*engine.Engine, attestation.Attestations] {
 		latestCommitment := e.Storage.Settings().LatestCommitment()
 
 		return NewManager(
-			e.Logger.NewChildLogger("AttestationManager"),
+			e.NewSubModule("AttestationManager"),
 			latestCommitment.Slot(),
 			latestCommitment.CumulativeWeight(),
 			e.Storage.Attestations,
@@ -85,15 +82,15 @@ func NewProvider() module.Provider[*engine.Engine, attestation.Attestations] {
 }
 
 func NewManager(
-	logger log.Logger,
+	subModule module.Module,
 	lastCommittedSlot iotago.SlotIndex,
 	lastCumulativeWeight uint64,
 	bucketedStorage func(slot iotago.SlotIndex) (kvstore.KVStore, error),
 	committeeFunc func(slot iotago.SlotIndex) (*account.SeatedAccounts, bool),
 	apiProvider iotago.APIProvider,
 ) *Manager {
-	m := &Manager{
-		Logger:               logger,
+	return module.InitSimpleLifecycle(&Manager{
+		Module:               subModule,
 		lastCommittedSlot:    lastCommittedSlot,
 		lastCumulativeWeight: lastCumulativeWeight,
 		committeeFunc:        committeeFunc,
@@ -101,17 +98,7 @@ func NewManager(
 		futureAttestations:   memstorage.NewIndexedStorage[iotago.SlotIndex, iotago.AccountID, *iotago.Attestation](),
 		pendingAttestations:  memstorage.NewIndexedStorage[iotago.SlotIndex, iotago.AccountID, *iotago.Attestation](),
 		apiProvider:          apiProvider,
-	}
-	m.TriggerConstructed()
-
-	return m
-}
-
-func (m *Manager) Shutdown() {
-	if err := m.writeToDisk(); err != nil {
-		panic(err)
-	}
-	m.TriggerStopped()
+	}, (*Manager).shutdown)
 }
 
 // Get returns the attestations that are included in the commitment of the given slot as list.
@@ -357,3 +344,11 @@ func (m *Manager) computeAttestationCommitmentOffset(slot iotago.SlotIndex) (cut
 
 	return slot - m.apiProvider.APIForSlot(slot).ProtocolParameters().MaxCommittableAge(), true
 }
+
+func (m *Manager) shutdown() {
+	if err := m.writeToDisk(); err != nil {
+		panic(err)
+	}
+
+	m.StoppedEvent().Trigger()
+}
diff --git a/pkg/protocol/engine/attestation/slotattestation/snapshot.go b/pkg/protocol/engine/attestation/slotattestation/snapshot.go
index 1a80da03..79973651 100644
--- a/pkg/protocol/engine/attestation/slotattestation/snapshot.go
+++ b/pkg/protocol/engine/attestation/slotattestation/snapshot.go
@@ -36,7 +36,7 @@ func (m *Manager) Import(reader io.ReadSeeker) error {
 		m.applyToPendingAttestations(a, attestationSlotIndex)
 	}
 
-	m.TriggerInitialized()
+	m.InitializedEvent().Trigger()
 
 	return nil
 }
diff --git a/pkg/protocol/engine/attestation/slotattestation/storage.go b/pkg/protocol/engine/attestation/slotattestation/storage.go
index fcc5b394..cc5e306c 100644
--- a/pkg/protocol/engine/attestation/slotattestation/storage.go
+++ b/pkg/protocol/engine/attestation/slotattestation/storage.go
@@ -32,7 +32,7 @@ func (m *Manager) RestoreFromDisk() error {
 		}
 	}
 
-	m.TriggerInitialized()
+	m.InitializedEvent().Trigger()
 
 	return nil
 }
diff --git a/pkg/protocol/engine/attestation/slotattestation/testframework_test.go b/pkg/protocol/engine/attestation/slotattestation/testframework_test.go
index 7bb27407..850f808e 100644
--- a/pkg/protocol/engine/attestation/slotattestation/testframework_test.go
+++ b/pkg/protocol/engine/attestation/slotattestation/testframework_test.go
@@ -13,7 +13,7 @@ import (
 	"github.com/iotaledger/hive.go/kvstore"
 	"github.com/iotaledger/hive.go/kvstore/mapdb"
 	"github.com/iotaledger/hive.go/lo"
-	"github.com/iotaledger/hive.go/log"
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/syncutils"
 	"github.com/iotaledger/iota-core/pkg/core/account"
 	"github.com/iotaledger/iota-core/pkg/model"
@@ -77,7 +77,7 @@ func NewTestFramework(test *testing.T) *TestFramework {
 	t.apiProvider = iotago.SingleVersionProvider(t.testAPI)
 
 	t.Instance = slotattestation.NewManager(
-		log.NewLogger(),
+		module.NewTestModule(test),
 		0,
 		0,
 		bucketedStorage,
diff --git a/pkg/protocol/engine/consensus/blockgadget/testframework_test.go b/pkg/protocol/engine/consensus/blockgadget/testframework_test.go
index 34b185d3..9ebf59fb 100644
--- a/pkg/protocol/engine/consensus/blockgadget/testframework_test.go
+++ b/pkg/protocol/engine/consensus/blockgadget/testframework_test.go
@@ -13,6 +13,7 @@ import (
 	"github.com/iotaledger/hive.go/kvstore"
 	"github.com/iotaledger/hive.go/kvstore/mapdb"
 	"github.com/iotaledger/hive.go/lo"
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/iota-core/pkg/core/account"
 	"github.com/iotaledger/iota-core/pkg/model"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
@@ -59,7 +60,7 @@ func NewTestFramework(test *testing.T) *TestFramework {
 	evictionState := eviction.NewState(newSettings, prunableStorage.RootBlocks)
 
 	t.blockCache = blocks.New(evictionState, iotago.SingleVersionProvider(tpkg.ZeroCostTestAPI))
-	instance := thresholdblockgadget.New(t.blockCache, t.SeatManager, func(err error) {
+	instance := thresholdblockgadget.New(module.NewTestModule(test), t.blockCache, t.SeatManager, func(err error) {
 		fmt.Printf(">> Gadget.Error: %s\n", err)
 	})
 
diff --git a/pkg/protocol/engine/filter/postsolidfilter/postsolidblockfilter/post_solid_block_filter_test.go b/pkg/protocol/engine/filter/postsolidfilter/postsolidblockfilter/post_solid_block_filter_test.go
index 403eeb89..29d7f97b 100644
--- a/pkg/protocol/engine/filter/postsolidfilter/postsolidblockfilter/post_solid_block_filter_test.go
+++ b/pkg/protocol/engine/filter/postsolidfilter/postsolidblockfilter/post_solid_block_filter_test.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/iotaledger/hive.go/crypto/ed25519"
 	"github.com/iotaledger/hive.go/ierrors"
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/options"
 	"github.com/iotaledger/iota-core/pkg/model"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/accounts"
@@ -36,7 +37,7 @@ func NewTestFramework(t *testing.T, apiProvider iotago.APIProvider, optsFilter .
 		accountData: make(map[iotago.AccountID]*accounts.AccountData),
 		rmcData:     make(map[iotago.SlotIndex]iotago.Mana),
 	}
-	tf.PostSolidFilter = New(optsFilter...)
+	tf.PostSolidFilter = New(module.NewTestModule(t), optsFilter...)
 
 	tf.PostSolidFilter.accountRetrieveFunc = func(accountID iotago.AccountID, targetSlot iotago.SlotIndex) (*accounts.AccountData, bool, error) {
 		if accountData, ok := tf.accountData[accountID]; ok {
diff --git a/pkg/protocol/engine/filter/presolidfilter/presolidblockfilter/pre_solid_block_filter_test.go b/pkg/protocol/engine/filter/presolidfilter/presolidblockfilter/pre_solid_block_filter_test.go
index e2afb2c4..f3fcda66 100644
--- a/pkg/protocol/engine/filter/presolidfilter/presolidblockfilter/pre_solid_block_filter_test.go
+++ b/pkg/protocol/engine/filter/presolidfilter/presolidblockfilter/pre_solid_block_filter_test.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/iotaledger/hive.go/ds"
 	"github.com/iotaledger/hive.go/ierrors"
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/options"
 	"github.com/iotaledger/hive.go/serializer/v2/serix"
 	"github.com/iotaledger/iota-core/pkg/core/account"
@@ -30,7 +31,7 @@ func NewTestFramework(t *testing.T, apiProvider iotago.APIProvider, optsFilter .
 		Test:        t,
 		apiProvider: apiProvider,
 	}
-	tf.Filter = New(apiProvider, optsFilter...)
+	tf.Filter = New(module.NewTestModule(t), apiProvider, optsFilter...)
 
 	tf.Filter.events.BlockPreAllowed.Hook(func(block *model.Block) {
 		t.Logf("BlockPreAllowed: %s", block.ID())
diff --git a/pkg/protocol/engine/tipmanager/tests/testframework.go b/pkg/protocol/engine/tipmanager/tests/testframework.go
index a4503b99..4c8a312f 100644
--- a/pkg/protocol/engine/tipmanager/tests/testframework.go
+++ b/pkg/protocol/engine/tipmanager/tests/testframework.go
@@ -9,6 +9,7 @@ import (
 	"github.com/iotaledger/hive.go/ds"
 	"github.com/iotaledger/hive.go/kvstore/mapdb"
 	"github.com/iotaledger/hive.go/lo"
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/iota-core/pkg/core/account"
 	"github.com/iotaledger/iota-core/pkg/model"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
@@ -55,7 +56,7 @@ func NewTestFramework(test *testing.T) *TestFramework {
 
 	t.blockIDsByAlias["Genesis"] = iotago.EmptyBlockID
 
-	t.Instance = tipmanagerv1.New(func(blockID iotago.BlockID) (block *blocks.Block, exists bool) {
+	t.Instance = tipmanagerv1.New(module.NewTestModule(test), func(blockID iotago.BlockID) (block *blocks.Block, exists bool) {
 		block, exists = t.blocksByID[blockID]
 		return block, exists
 	}, t.manualPOA.CommitteeInSlot)
diff --git a/pkg/protocol/engine/tipselection/v1/test_framework_test.go b/pkg/protocol/engine/tipselection/v1/test_framework_test.go
index 4ccfc617..f5f111d1 100644
--- a/pkg/protocol/engine/tipselection/v1/test_framework_test.go
+++ b/pkg/protocol/engine/tipselection/v1/test_framework_test.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/stretchr/testify/require"
 
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/options"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/ledger"
@@ -47,7 +48,7 @@ func NewTestFramework(test *testing.T, opts ...options.Option[TestFramework]) *T
 
 		t.TipManager = tipmanagertests.NewTestFramework(test)
 
-		t.Instance = tipselectionv1.New().Construct(
+		t.Instance = tipselectionv1.New(module.NewTestModule(test)).Construct(
 			t.TipManager.Instance,
 			spenddagv1.New[iotago.TransactionID, mempool.StateID, ledger.BlockVoteRank](t.CommitteeSize),
 			transactionMetadataRetriever,
diff --git a/pkg/retainer/blockretainer/tests/testframework.go b/pkg/retainer/blockretainer/tests/testframework.go
index 05eb9b61..390e529c 100644
--- a/pkg/retainer/blockretainer/tests/testframework.go
+++ b/pkg/retainer/blockretainer/tests/testframework.go
@@ -7,6 +7,7 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/iotaledger/hive.go/kvstore/mapdb"
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/workerpool"
 	"github.com/iotaledger/iota-core/pkg/model"
 	"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
@@ -83,7 +84,7 @@ func NewTestFramework(test *testing.T) *TestFramework {
 		require.NoError(test, err)
 	}
 
-	tf.Instance = blockretainer.New(workers, storeFunc, latestCommittedSlotFunc, lastFinalizedSlotFunc, errorHandlerFunc)
+	tf.Instance = blockretainer.New(module.NewTestModule(test), workers, storeFunc, latestCommittedSlotFunc, lastFinalizedSlotFunc, errorHandlerFunc)
 
 	return tf
 }
diff --git a/pkg/retainer/txretainer/testsuite_test.go b/pkg/retainer/txretainer/testsuite_test.go
index 60db4b1e..c9ef5656 100644
--- a/pkg/retainer/txretainer/testsuite_test.go
+++ b/pkg/retainer/txretainer/testsuite_test.go
@@ -6,6 +6,7 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/iotaledger/hive.go/log"
+	"github.com/iotaledger/hive.go/runtime/module"
 	"github.com/iotaledger/hive.go/runtime/workerpool"
 	"github.com/iotaledger/iota-core/pkg/retainer/txretainer"
 	"github.com/iotaledger/iota-core/pkg/storage/clonablesql"
@@ -38,7 +39,7 @@ func newTestSuite(t *testing.T) *txRetainerTestsuite {
 		database: newClonableSQLiteDatabase(t),
 	}
 
-	testSuite.TxRetainer = txretainer.New(workers, testSuite.database.ExecDBFunc(),
+	testSuite.TxRetainer = txretainer.New(module.NewTestModule(t), workers, testSuite.database.ExecDBFunc(),
 		func() iotago.SlotIndex {
 			return testSuite.latestCommittedSlot
 		}, func() iotago.SlotIndex {
@@ -54,7 +55,7 @@ func newTestSuite(t *testing.T) *txRetainerTestsuite {
 
 func (ts *txRetainerTestsuite) Close() {
 	ts.database.Shutdown()
-	ts.TxRetainer.Shutdown()
+	ts.TxRetainer.ShutdownEvent().Trigger()
 }
 
 func (ts *txRetainerTestsuite) SetLatestCommittedSlot(slot iotago.SlotIndex) {
diff --git a/pkg/tests/protocol_engine_switching_test.go b/pkg/tests/protocol_engine_switching_test.go
index 9d4e82ed..b98155a8 100644
--- a/pkg/tests/protocol_engine_switching_test.go
+++ b/pkg/tests/protocol_engine_switching_test.go
@@ -1035,7 +1035,7 @@ func TestProtocol_EngineSwitching_Tie(t *testing.T) {
 
 					onlineValidators := ds.NewSet[string]()
 
-					e.Constructed.OnTrigger(func() {
+					e.ConstructedEvent().OnTrigger(func() {
 						e.Events.PostSolidFilter.BlockAllowed.Hook(func(block *blocks.Block) {
 							if node, exists := validatorsByAccountID[block.ModelBlock().ProtocolBlock().Header.IssuerID]; exists && onlineValidators.Add(node.Name) {
 								e.LogError("node online", "name", node.Name)
diff --git a/pkg/tests/protocol_eviction_test.go b/pkg/tests/protocol_eviction_test.go
index 9fe0d04c..17eb69a1 100644
--- a/pkg/tests/protocol_eviction_test.go
+++ b/pkg/tests/protocol_eviction_test.go
@@ -70,7 +70,7 @@ func TestProtocol_Eviction(t *testing.T) {
 
 						onlineValidators := ds.NewSet[string]()
 
-						e.Constructed.OnTrigger(func() {
+						e.ConstructedEvent().OnTrigger(func() {
 							e.Events.BlockDAG.BlockAppended.Hook(func(block *blocks.Block) {
 								if block.ModelBlock().ProtocolBlock().Header.IssuerID == node.Validator.AccountID && onlineValidators.Add(node.Name) {
 									e.LogError("node online", "name", node.Name)
diff --git a/pkg/testsuite/mock/node.go b/pkg/testsuite/mock/node.go
index 4e891db7..e12ac514 100644
--- a/pkg/testsuite/mock/node.go
+++ b/pkg/testsuite/mock/node.go
@@ -137,7 +137,7 @@ func (n *Node) Initialize(failOnBlockFiltered bool, opts ...options.Option[proto
 
 	started := make(chan struct{}, 1)
 
-	n.Protocol.Initialized.OnTrigger(func() {
+	n.Protocol.InitializedEvent().OnTrigger(func() {
 		close(started)
 	})
 
@@ -270,7 +270,7 @@ func (n *Node) Shutdown() {
 	stopped := make(chan struct{}, 1)
 
 	if n.Protocol != nil {
-		n.Protocol.Stopped.OnTrigger(func() {
+		n.Protocol.StoppedEvent().OnTrigger(func() {
 			close(stopped)
 		})
 	} else {
diff --git a/pkg/testsuite/snapshotcreator/snapshotcreator.go b/pkg/testsuite/snapshotcreator/snapshotcreator.go
index 650ef404..5aaf7fe3 100644
--- a/pkg/testsuite/snapshotcreator/snapshotcreator.go
+++ b/pkg/testsuite/snapshotcreator/snapshotcreator.go
@@ -120,7 +120,7 @@ func CreateSnapshot(opts ...options.Option[Options]) error {
 		engine.WithSnapshotPath(""),       // magic to disable loading snapshot
 		engine.WithCommitmentCheck(false), // to not check the commitment when creating a first snapshot
 	)
-	defer engineInstance.Shutdown.Trigger()
+	defer engineInstance.ShutdownEvent().Trigger()
 
 	if opt.AddGenesisRootBlock {
 		engineInstance.EvictionState.AddRootBlock(api.ProtocolParameters().GenesisBlockID(), genesisCommitment.ID())