Repository: iotaledger/iota-core (go)

Issue:
Make activity window a protocol parameter
The `ActivityTracker` was recently changed to work based on protocol time (solid blocks seen, and filtered) instead of real time.

```go
e.Events.CommitmentFilter.BlockAllowed.Hook(func(block *blocks.Block) {
	// Only track identities that are part of the committee.
	committee, exists := s.CommitteeInSlot(block.ID().Slot())
	if !exists {
		panic(ierrors.Errorf("committee not selected for slot %d, but received block in that slot", block.ID().Slot()))
	}

	seat, exists := committee.GetSeat(block.ProtocolBlock().Header.IssuerID)
	if exists {
		s.activityTracker.MarkSeatActive(seat, block.ProtocolBlock().Header.IssuerID, block.IssuingTime())
	}

	s.events.BlockProcessed.Trigger(block)
})
```

Therefore, having it as a local node configuration does not make much sense as different activity windows would lead to a different perception of online committee, thus leading to different commitments. Instead, it should be part of the protocol parameters.

Pull request description:
Activity Window from Protocol Parameters
The activity window can no longer be a locally defined parameter as this would lead to different perceptions of the online committee. Instead, this PR uses an existing protocol parameter, MinCommittableAge, as the activity window. The reason for this choice is that we have established that activity window must be less than MaxCommittableAge to prevent forcing commits even when there are online validators, because the activity window does not adapt fast enough. It is best to have it as small as possible to prevent this occuring, and the smallest sane value is MinCommittableAge according to https://github.com/iotaledger/iota-core/issues/617 although I am not sure why exactly we have this constraint @piotrm50 .

closes #519 

Patch:
diff --git a/pkg/core/account/accounts.go b/pkg/core/account/accounts.go
index 7cfdfa3b..e5237f6d 100644
--- a/pkg/core/account/accounts.go
+++ b/pkg/core/account/accounts.go
@@ -125,8 +125,8 @@ func (a *Accounts) ForEach(callback func(id iotago.AccountID, pool *Pool) bool)
 	a.accountPools.ForEach(callback)
 }
 
-// SelectCommittee creates a new SeatedAccounts instance, that maintains the seats of the given members.
-func (a *Accounts) SelectCommittee(members ...iotago.AccountID) *SeatedAccounts {
+// SeatedAccounts creates a new SeatedAccounts instance, that maintains the seats of the given members.
+func (a *Accounts) SeatedAccounts(members ...iotago.AccountID) *SeatedAccounts {
 	return NewSeatedAccounts(a, members...)
 }
 
diff --git a/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1/activitytracker.go b/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1/activitytracker.go
index 9a58b668..5ab90909 100644
--- a/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1/activitytracker.go
+++ b/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1/activitytracker.go
@@ -22,17 +22,17 @@ type ActivityTracker struct {
 	lastActivityTime time.Time
 	activityMutex    syncutils.RWMutex
 
-	activityWindow time.Duration
+	apiProvider iotago.APIProvider
 }
 
-func NewActivityTracker(activityWindow time.Duration) *ActivityTracker {
+func NewActivityTracker(apiProvider iotago.APIProvider) *ActivityTracker {
 	return &ActivityTracker{
 		Events:          activitytracker.NewEvents(),
 		onlineCommittee: ds.NewSet[account.SeatIndex](),
 		inactivityQueue: timed.NewPriorityQueue[account.SeatIndex](true),
 		lastActivities:  shrinkingmap.New[account.SeatIndex, time.Time](),
 
-		activityWindow: activityWindow,
+		apiProvider: apiProvider,
 	}
 }
 
@@ -48,7 +48,10 @@ func (a *ActivityTracker) MarkSeatActive(seat account.SeatIndex, id iotago.Accou
 	a.activityMutex.Lock()
 	defer a.activityMutex.Unlock()
 
-	if lastActivity, exists := a.lastActivities.Get(seat); (exists && lastActivity.After(seatActivityTime)) || seatActivityTime.Before(a.lastActivityTime.Add(-a.activityWindow)) {
+	// activity window is given by min committable age in seconds from the protocol parameters
+	protocolParams := a.apiProvider.APIForTime(seatActivityTime).ProtocolParameters()
+	activityWindow := time.Duration(protocolParams.MinCommittableAge()*iotago.SlotIndex(protocolParams.SlotDurationInSeconds())) * time.Second
+	if lastActivity, exists := a.lastActivities.Get(seat); (exists && lastActivity.After(seatActivityTime)) || seatActivityTime.Before(a.lastActivityTime.Add(-activityWindow)) {
 		return
 	} else if !exists {
 		a.onlineCommittee.Add(seat)
@@ -65,7 +68,7 @@ func (a *ActivityTracker) MarkSeatActive(seat account.SeatIndex, id iotago.Accou
 
 	a.lastActivityTime = seatActivityTime
 
-	activityThreshold := seatActivityTime.Add(-a.activityWindow)
+	activityThreshold := seatActivityTime.Add(-activityWindow)
 	for _, inactiveSeat := range a.inactivityQueue.PopUntil(activityThreshold) {
 		if lastActivityForInactiveSeat, exists := a.lastActivities.Get(inactiveSeat); exists && lastActivityForInactiveSeat.After(activityThreshold) {
 			continue
diff --git a/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go b/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go
index 4551ead1..edd0832b 100644
--- a/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go
+++ b/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go
@@ -40,7 +40,7 @@ func NewManualPOA(e iotago.APIProvider, committeeStore *epochstore.Store[*accoun
 		online:         ds.NewSet[account.SeatIndex](),
 		aliases:        shrinkingmap.New[string, iotago.AccountID](),
 	}
-	m.committee = m.accounts.SelectCommittee()
+	m.committee = m.accounts.SeatedAccounts()
 
 	return m
 }
@@ -71,7 +71,7 @@ func (m *ManualPOA) AddRandomAccount(alias string) iotago.AccountID {
 
 	m.aliases.Set(alias, id)
 
-	m.committee = m.accounts.SelectCommittee(m.accounts.IDs()...)
+	m.committee = m.accounts.SeatedAccounts(m.accounts.IDs()...)
 
 	if err := m.committeeStore.Store(0, m.accounts); err != nil {
 		panic(err)
@@ -90,7 +90,7 @@ func (m *ManualPOA) AddAccount(id iotago.AccountID, alias string) iotago.Account
 	}
 	m.aliases.Set(alias, id)
 
-	m.committee = m.accounts.SelectCommittee(m.accounts.IDs()...)
+	m.committee = m.accounts.SeatedAccounts(m.accounts.IDs()...)
 
 	if err := m.committeeStore.Store(0, m.accounts); err != nil {
 		panic(err)
@@ -152,7 +152,7 @@ func (m *ManualPOA) committeeInEpoch(epoch iotago.EpochIndex) (*account.SeatedAc
 		return nil, false
 	}
 
-	return c.SelectCommittee(c.IDs()...), true
+	return c.SeatedAccounts(c.IDs()...), true
 }
 
 func (m *ManualPOA) OnlineCommittee() ds.Set[account.SeatIndex] {
@@ -179,7 +179,7 @@ func (m *ManualPOA) RotateCommittee(epoch iotago.EpochIndex, validators accounts
 				return nil, ierrors.Wrapf(err, "error while setting pool for epoch %d for validator %s", epoch, validatorData.ID.String())
 			}
 		}
-		m.committee = m.accounts.SelectCommittee(m.accounts.IDs()...)
+		m.committee = m.accounts.SeatedAccounts(m.accounts.IDs()...)
 	}
 
 	if err := m.committeeStore.Store(epoch, m.accounts); err != nil {
@@ -189,10 +189,10 @@ func (m *ManualPOA) RotateCommittee(epoch iotago.EpochIndex, validators accounts
 	return m.committee, nil
 }
 
-func (m *ManualPOA) SetCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error {
+func (m *ManualPOA) ReuseCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error {
 	if m.committee == nil || m.accounts.Size() == 0 {
 		m.accounts = validators
-		m.committee = m.accounts.SelectCommittee(validators.IDs()...)
+		m.committee = m.accounts.SeatedAccounts(validators.IDs()...)
 	}
 
 	if err := m.committeeStore.Store(epoch, validators); err != nil {
diff --git a/pkg/protocol/sybilprotection/seatmanager/poa/options.go b/pkg/protocol/sybilprotection/seatmanager/poa/options.go
index f4df0547..dcd04712 100644
--- a/pkg/protocol/sybilprotection/seatmanager/poa/options.go
+++ b/pkg/protocol/sybilprotection/seatmanager/poa/options.go
@@ -1,19 +1,10 @@
 package poa
 
 import (
-	"time"
-
 	"github.com/iotaledger/hive.go/runtime/options"
 	iotago "github.com/iotaledger/iota.go/v4"
 )
 
-// WithActivityWindow sets the duration for which a validator is recognized as active after issuing a block.
-func WithActivityWindow(activityWindow time.Duration) options.Option[SeatManager] {
-	return func(p *SeatManager) {
-		p.optsActivityWindow = activityWindow
-	}
-}
-
 func WithOnlineCommitteeStartup(optsOnlineCommittee ...iotago.AccountID) options.Option[SeatManager] {
 	return func(p *SeatManager) {
 		p.optsOnlineCommitteeStartup = optsOnlineCommittee
diff --git a/pkg/protocol/sybilprotection/seatmanager/poa/poa.go b/pkg/protocol/sybilprotection/seatmanager/poa/poa.go
index d211750b..ba9d1de8 100644
--- a/pkg/protocol/sybilprotection/seatmanager/poa/poa.go
+++ b/pkg/protocol/sybilprotection/seatmanager/poa/poa.go
@@ -30,7 +30,6 @@ type SeatManager struct {
 
 	committeeMutex syncutils.RWMutex
 
-	optsActivityWindow         time.Duration
 	optsOnlineCommitteeStartup []iotago.AccountID
 
 	module.Module
@@ -44,10 +43,8 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En
 				events:         seatmanager.NewEvents(),
 				apiProvider:    e,
 				committeeStore: e.Storage.Committee(),
-
-				optsActivityWindow: time.Second * 30,
 			}, opts, func(s *SeatManager) {
-				activityTracker := activitytrackerv1.NewActivityTracker(s.optsActivityWindow)
+				activityTracker := activitytrackerv1.NewActivityTracker(e)
 				s.activityTracker = activityTracker
 				s.events.OnlineCommitteeSeatAdded.LinkTo(activityTracker.Events.OnlineCommitteeSeatAdded)
 				s.events.OnlineCommitteeSeatRemoved.LinkTo(activityTracker.Events.OnlineCommitteeSeatRemoved)
@@ -97,7 +94,7 @@ func (s *SeatManager) RotateCommittee(epoch iotago.EpochIndex, validators accoun
 				return nil, ierrors.Wrapf(err, "error while setting committee for epoch %d for validator %s", epoch, validatorData.ID.String())
 			}
 		}
-		s.committee = committeeAccounts.SelectCommittee(committeeAccounts.IDs()...)
+		s.committee = committeeAccounts.SeatedAccounts(committeeAccounts.IDs()...)
 	}
 
 	accounts, err := s.committee.Accounts()
@@ -138,7 +135,7 @@ func (s *SeatManager) committeeInEpoch(epoch iotago.EpochIndex) (*account.Seated
 		return nil, false
 	}
 
-	return c.SelectCommittee(c.IDs()...), true
+	return c.SeatedAccounts(c.IDs()...), true
 }
 
 // OnlineCommittee returns the set of validators selected to be part of the committee that has been seen recently.
@@ -174,7 +171,7 @@ func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime
 	}
 
 	committeeAccountsIDs := committeeAccounts.IDs()
-	s.committee = committeeAccounts.SelectCommittee(committeeAccountsIDs...)
+	s.committee = committeeAccounts.SeatedAccounts(committeeAccountsIDs...)
 
 	// Set validators that are part of the committee as active.
 	onlineValidators := committeeAccountsIDs
@@ -195,11 +192,11 @@ func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime
 	return nil
 }
 
-func (s *SeatManager) SetCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error {
+func (s *SeatManager) ReuseCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error {
 	s.committeeMutex.Lock()
 	defer s.committeeMutex.Unlock()
 
-	s.committee = validators.SelectCommittee(validators.IDs()...)
+	s.committee = validators.SeatedAccounts(validators.IDs()...)
 
 	accounts, err := s.committee.Accounts()
 	if err != nil {
diff --git a/pkg/protocol/sybilprotection/seatmanager/seatmanager.go b/pkg/protocol/sybilprotection/seatmanager/seatmanager.go
index 38c443e1..656076a8 100644
--- a/pkg/protocol/sybilprotection/seatmanager/seatmanager.go
+++ b/pkg/protocol/sybilprotection/seatmanager/seatmanager.go
@@ -15,9 +15,8 @@ type SeatManager interface {
 	// RotateCommittee rotates the committee evaluating the given set of candidates to produce the new committee.
 	RotateCommittee(epoch iotago.EpochIndex, candidates accounts.AccountsData) (*account.SeatedAccounts, error)
 
-	// SetCommittee sets the committee for a given slot.
-	// This is used when re-using the same committee for consecutive epochs.
-	SetCommittee(epoch iotago.EpochIndex, committee *account.Accounts) error
+	// ReuseCommittee reuses the committee from a previous epoch.
+	ReuseCommittee(epoch iotago.EpochIndex, committee *account.Accounts) error
 
 	// InitializeCommittee initializes the committee for the current slot by marking whole or a subset of the committee as active.
 	// This is used when initializing committee after node startup (loaded from snapshot or database).
diff --git a/pkg/protocol/sybilprotection/seatmanager/topstakers/options.go b/pkg/protocol/sybilprotection/seatmanager/topstakers/options.go
index b6d6fffe..25950815 100644
--- a/pkg/protocol/sybilprotection/seatmanager/topstakers/options.go
+++ b/pkg/protocol/sybilprotection/seatmanager/topstakers/options.go
@@ -1,19 +1,10 @@
 package topstakers
 
 import (
-	"time"
-
 	"github.com/iotaledger/hive.go/runtime/options"
 	iotago "github.com/iotaledger/iota.go/v4"
 )
 
-// WithActivityWindow sets the duration for which a validator is recognized as active after issuing a block.
-func WithActivityWindow(activityWindow time.Duration) options.Option[SeatManager] {
-	return func(p *SeatManager) {
-		p.optsActivityWindow = activityWindow
-	}
-}
-
 func WithOnlineCommitteeStartup(optsOnlineCommittee ...iotago.AccountID) options.Option[SeatManager] {
 	return func(p *SeatManager) {
 		p.optsOnlineCommitteeStartup = optsOnlineCommittee
diff --git a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go
index cc359e61..000bec5f 100644
--- a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go
+++ b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go
@@ -31,7 +31,6 @@ type SeatManager struct {
 	committeeMutex  syncutils.RWMutex
 	activityTracker activitytracker.ActivityTracker
 
-	optsActivityWindow         time.Duration
 	optsOnlineCommitteeStartup []iotago.AccountID
 
 	module.Module
@@ -45,10 +44,8 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En
 				apiProvider:    e,
 				events:         seatmanager.NewEvents(),
 				committeeStore: e.Storage.Committee(),
-
-				optsActivityWindow: time.Second * 30,
 			}, opts, func(s *SeatManager) {
-				activityTracker := activitytrackerv1.NewActivityTracker(s.optsActivityWindow)
+				activityTracker := activitytrackerv1.NewActivityTracker(e)
 				s.activityTracker = activityTracker
 				s.events.OnlineCommitteeSeatAdded.LinkTo(activityTracker.Events.OnlineCommitteeSeatAdded)
 				s.events.OnlineCommitteeSeatRemoved.LinkTo(activityTracker.Events.OnlineCommitteeSeatRemoved)
@@ -132,7 +129,7 @@ func (s *SeatManager) committeeInEpoch(epoch iotago.EpochIndex) (*account.Seated
 		return nil, false
 	}
 
-	return c.SelectCommittee(c.IDs()...), true
+	return c.SeatedAccounts(c.IDs()...), true
 }
 
 // OnlineCommittee returns the set of validators selected to be part of the committee that has been seen recently.
@@ -171,7 +168,7 @@ func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime
 		return ierrors.Wrapf(err, "failed to load PoA committee for epoch %d", epoch)
 	}
 
-	committee := committeeAccounts.SelectCommittee(committeeAccounts.IDs()...)
+	committee := committeeAccounts.SeatedAccounts(committeeAccounts.IDs()...)
 
 	onlineValidators := committeeAccounts.IDs()
 	if len(s.optsOnlineCommitteeStartup) > 0 {
@@ -191,7 +188,7 @@ func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime
 	return nil
 }
 
-func (s *SeatManager) SetCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error {
+func (s *SeatManager) ReuseCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error {
 	s.committeeMutex.Lock()
 	defer s.committeeMutex.Unlock()
 
@@ -249,7 +246,7 @@ func (s *SeatManager) selectNewCommittee(epoch iotago.EpochIndex, candidates acc
 			return nil, ierrors.Wrapf(err, "error while setting pool for committee candidate %s", candidateData.ID.String())
 		}
 	}
-	committee := newCommitteeAccounts.SelectCommittee(newCommitteeAccounts.IDs()...)
+	committee := newCommitteeAccounts.SeatedAccounts(newCommitteeAccounts.IDs()...)
 
 	return committee, nil
 }
diff --git a/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go b/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
index 6643c6cb..acdbdd98 100644
--- a/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
+++ b/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go
@@ -392,7 +392,7 @@ func (o *SybilProtection) reuseCommittee(currentEpoch iotago.EpochIndex, targetE
 	}
 
 	committeeAccounts.SetReused()
-	if err = o.seatManager.SetCommittee(targetEpoch, committeeAccounts); err != nil {
+	if err = o.seatManager.ReuseCommittee(targetEpoch, committeeAccounts); err != nil {
 		return nil, ierrors.Wrapf(err, "failed to set committee for epoch %d", targetEpoch)
 	}
 


Test patch:
diff --git a/pkg/core/account/accounts_test.go b/pkg/core/account/accounts_test.go
index 282511d0..ad420a58 100644
--- a/pkg/core/account/accounts_test.go
+++ b/pkg/core/account/accounts_test.go
@@ -108,11 +108,11 @@ func TestAccounts(t *testing.T) {
 	// check "SelectCommittee"
 
 	// get 1 issuer
-	seated := accounts.SelectCommittee(accountIDs[0])
+	seated := accounts.SeatedAccounts(accountIDs[0])
 	require.Equal(t, 1, seated.SeatCount())
 
 	// get all issuers
-	seated = accounts.SelectCommittee(accountIDs...)
+	seated = accounts.SeatedAccounts(accountIDs...)
 	require.Equal(t, len(accountIDs), seated.SeatCount())
 
 	/*
diff --git a/pkg/protocol/engine/attestation/slotattestation/testframework_test.go b/pkg/protocol/engine/attestation/slotattestation/testframework_test.go
index 67e428c0..3bd3b193 100644
--- a/pkg/protocol/engine/attestation/slotattestation/testframework_test.go
+++ b/pkg/protocol/engine/attestation/slotattestation/testframework_test.go
@@ -66,7 +66,7 @@ func NewTestFramework(test *testing.T) *TestFramework {
 			members = append(members, issuer.accountID)
 			return true
 		})
-		return accounts.SelectCommittee(members...), true
+		return accounts.SeatedAccounts(members...), true
 	}
 
 	t.testAPI = iotago.V3API(
diff --git a/pkg/protocol/engine/mempool/spenddag/tests/accounts_framework.go b/pkg/protocol/engine/mempool/spenddag/tests/accounts_framework.go
index 6a32efef..fe080d6e 100644
--- a/pkg/protocol/engine/mempool/spenddag/tests/accounts_framework.go
+++ b/pkg/protocol/engine/mempool/spenddag/tests/accounts_framework.go
@@ -21,7 +21,7 @@ type AccountsTestFramework struct {
 func NewAccountsTestFramework(test *testing.T, instance *account.Accounts) *AccountsTestFramework {
 	return &AccountsTestFramework{
 		Instance:  instance,
-		Committee: instance.SelectCommittee(),
+		Committee: instance.SeatedAccounts(),
 
 		test:              test,
 		identitiesByAlias: make(map[string]iotago.AccountID),
diff --git a/pkg/protocol/engine/mempool/v1/mempool_test.go b/pkg/protocol/engine/mempool/v1/mempool_test.go
index 606e02ef..957ff020 100644
--- a/pkg/protocol/engine/mempool/v1/mempool_test.go
+++ b/pkg/protocol/engine/mempool/v1/mempool_test.go
@@ -141,7 +141,7 @@ func newTestFramework(t *testing.T) *mempooltests.TestFramework {
 	workers := workerpool.NewGroup(t.Name())
 
 	ledgerState := ledgertests.New(ledgertests.NewMockedState(iotago.EmptyTransactionID, 0))
-	spendDAG := spenddagv1.New[iotago.TransactionID, mempool.StateID, vote.MockedRank](account.NewAccounts().SelectCommittee().SeatCount)
+	spendDAG := spenddagv1.New[iotago.TransactionID, mempool.StateID, vote.MockedRank](account.NewAccounts().SeatedAccounts().SeatCount)
 
 	mutationsFunc := func(index iotago.SlotIndex) (kvstore.KVStore, error) {
 		return mapdb.NewMapDB(), nil
diff --git a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go
index a45851b4..0f825c42 100644
--- a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go
+++ b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go
@@ -27,18 +27,19 @@ func TestTopStakers_InitializeCommittee(t *testing.T) {
 			iotago.WithTargetCommitteeSize(3),
 		),
 	)
+	testAPIProvider := iotago.SingleVersionProvider(testAPI)
 
 	committeeStore := epochstore.NewStore(kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)
 
 	topStakersSeatManager := &SeatManager{
-		apiProvider:     iotago.SingleVersionProvider(testAPI),
+		apiProvider:     testAPIProvider,
 		committeeStore:  committeeStore,
 		events:          seatmanager.NewEvents(),
-		activityTracker: activitytrackerv1.NewActivityTracker(time.Second * 30),
+		activityTracker: activitytrackerv1.NewActivityTracker(testAPIProvider),
 	}
 
 	// Try setting an empty committee.
-	err := topStakersSeatManager.SetCommittee(0, account.NewAccounts())
+	err := topStakersSeatManager.ReuseCommittee(0, account.NewAccounts())
 	require.Error(t, err)
 
 	// Create committee for epoch 0
@@ -54,7 +55,7 @@ func TestTopStakers_InitializeCommittee(t *testing.T) {
 	}
 
 	// Set committee for epoch 0.
-	err = topStakersSeatManager.SetCommittee(0, initialCommittee)
+	err = topStakersSeatManager.ReuseCommittee(0, initialCommittee)
 	require.NoError(t, err)
 	weightedSeats, exists := topStakersSeatManager.CommitteeInEpoch(0)
 	require.True(t, exists)
@@ -79,14 +80,15 @@ func TestTopStakers_RotateCommittee(t *testing.T) {
 			iotago.WithTargetCommitteeSize(10),
 		),
 	)
+	testAPIProvider := iotago.SingleVersionProvider(testAPI)
 
 	committeeStore := epochstore.NewStore(kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)
 
 	s := &SeatManager{
-		apiProvider:     iotago.SingleVersionProvider(testAPI),
+		apiProvider:     testAPIProvider,
 		committeeStore:  committeeStore,
 		events:          seatmanager.NewEvents(),
-		activityTracker: activitytrackerv1.NewActivityTracker(time.Second * 30),
+		activityTracker: activitytrackerv1.NewActivityTracker(testAPIProvider),
 	}
 
 	// Committee should not exist because it was never set.
@@ -107,7 +109,7 @@ func TestTopStakers_RotateCommittee(t *testing.T) {
 		addCommitteeMember(t, expectedCommitteeInEpoch0, &account.Pool{PoolStake: 1900, ValidatorStake: 900, FixedCost: 11})
 
 		// We should be able to set a committee with only 3 members for epoch 0 (this could be set e.g. via the snapshot).
-		err := s.SetCommittee(0, expectedCommitteeInEpoch0)
+		err := s.ReuseCommittee(0, expectedCommitteeInEpoch0)
 		require.NoError(t, err)
 
 		// Make sure that the online committee is handled correctly.
@@ -128,7 +130,7 @@ func TestTopStakers_RotateCommittee(t *testing.T) {
 			assertOnlineCommittee(t, s.OnlineCommittee(), lo.Return1(committeeInEpoch0.GetSeat(committeeInEpoch0IDs[0])), lo.Return1(committeeInEpoch0.GetSeat(committeeInEpoch0IDs[1])), lo.Return1(committeeInEpoch0.GetSeat(committeeInEpoch0IDs[2])))
 
 			// Make sure that after a period of inactivity, the inactive seats are marked as offline.
-			s.activityTracker.MarkSeatActive(lo.Return1(committeeInEpoch0.GetSeat(committeeInEpoch0IDs[2])), committeeInEpoch0IDs[2], testAPI.TimeProvider().SlotEndTime(7))
+			s.activityTracker.MarkSeatActive(lo.Return1(committeeInEpoch0.GetSeat(committeeInEpoch0IDs[2])), committeeInEpoch0IDs[2], testAPI.TimeProvider().SlotEndTime(2+testAPI.ProtocolParameters().MinCommittableAge()))
 			assertOnlineCommittee(t, s.OnlineCommittee(), lo.Return1(committeeInEpoch0.GetSeat(committeeInEpoch0IDs[2])))
 		}
 
@@ -211,7 +213,7 @@ func TestTopStakers_RotateCommittee(t *testing.T) {
 		newCommitteeMemberIDs := committeeInEpoch1Accounts.IDs()
 
 		// A new committee member appears online and makes the previously active committee seat inactive.
-		s.activityTracker.MarkSeatActive(lo.Return1(committeeInEpoch0.GetSeat(newCommitteeMemberIDs[0])), newCommitteeMemberIDs[0], testAPI.TimeProvider().SlotEndTime(14))
+		s.activityTracker.MarkSeatActive(lo.Return1(committeeInEpoch0.GetSeat(newCommitteeMemberIDs[0])), newCommitteeMemberIDs[0], testAPI.TimeProvider().SlotEndTime(2+2*testAPI.ProtocolParameters().MinCommittableAge()))
 		assertOnlineCommittee(t, s.OnlineCommittee(), lo.Return1(committeeInEpoch0.GetSeat(newCommitteeMemberIDs[0])))
 
 		// Make sure that the committee retrieved from the committee store matches the expected.
@@ -267,7 +269,7 @@ func TestTopStakers_RotateCommittee(t *testing.T) {
 
 		// Set reuse of committee manually.
 		expectedCommitteeInEpoch2.SetReused()
-		err = s.SetCommittee(epoch, expectedCommitteeInEpoch2)
+		err = s.ReuseCommittee(epoch, expectedCommitteeInEpoch2)
 		require.NoError(t, err)
 
 		assertCommitteeInEpoch(t, s, testAPI, 3, expectedCommitteeInEpoch2)
@@ -280,7 +282,7 @@ func TestTopStakers_RotateCommittee(t *testing.T) {
 		loadedCommittee, err := s.committeeStore.Load(epoch)
 		require.NoError(t, err)
 		require.True(t, loadedCommittee.IsReused())
-		assertCommittee(t, expectedCommitteeInEpoch2, loadedCommittee.SelectCommittee(loadedCommittee.IDs()...))
+		assertCommittee(t, expectedCommitteeInEpoch2, loadedCommittee.SeatedAccounts(loadedCommittee.IDs()...))
 	}
 }
 
diff --git a/pkg/tests/committee_rotation_test.go b/pkg/tests/committee_rotation_test.go
index a516c89e..8317c26d 100644
--- a/pkg/tests/committee_rotation_test.go
+++ b/pkg/tests/committee_rotation_test.go
@@ -2,7 +2,6 @@ package tests
 
 import (
 	"testing"
-	"time"
 
 	"github.com/iotaledger/hive.go/runtime/options"
 	"github.com/iotaledger/iota-core/pkg/protocol"
@@ -51,10 +50,7 @@ func Test_TopStakersRotation(t *testing.T) {
 		protocol.WithSybilProtectionProvider(
 			sybilprotectionv1.NewProvider(
 				sybilprotectionv1.WithSeatManagerProvider(
-					topstakers.NewProvider(
-						// We need to make sure that inactive nodes are evicted from the committee to continue acceptance.
-						topstakers.WithActivityWindow(15 * time.Second),
-					),
+					topstakers.NewProvider(),
 				),
 			),
 		),
diff --git a/pkg/tests/confirmation_state_test.go b/pkg/tests/confirmation_state_test.go
index 78c6cc29..0323f23f 100644
--- a/pkg/tests/confirmation_state_test.go
+++ b/pkg/tests/confirmation_state_test.go
@@ -3,7 +3,6 @@ package tests
 import (
 	"fmt"
 	"testing"
-	"time"
 
 	"github.com/iotaledger/hive.go/lo"
 	"github.com/iotaledger/hive.go/runtime/options"
@@ -59,7 +58,6 @@ func TestConfirmationFlags(t *testing.T) {
 				sybilprotectionv1.WithSeatManagerProvider(
 					topstakers.NewProvider(
 						topstakers.WithOnlineCommitteeStartup(nodeA.Validator.AccountID),
-						topstakers.WithActivityWindow(2*time.Minute),
 					),
 				),
 			),
diff --git a/pkg/tests/protocol_engine_switching_test.go b/pkg/tests/protocol_engine_switching_test.go
index 046ffbd9..c0d8ce49 100644
--- a/pkg/tests/protocol_engine_switching_test.go
+++ b/pkg/tests/protocol_engine_switching_test.go
@@ -389,7 +389,7 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 			iotago.WithLivenessOptions(
 				10,
 				10,
-				3,
+				2,
 				5,
 				10,
 			),
@@ -413,10 +413,7 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 		protocol.WithSybilProtectionProvider(
 			sybilprotectionv1.NewProvider(
 				sybilprotectionv1.WithSeatManagerProvider(
-					topstakers.NewProvider(
-						// We need to make sure that inactive nodes are evicted from the committee to continue acceptance.
-						topstakers.WithActivityWindow(10 * time.Second),
-					),
+					topstakers.NewProvider(),
 				),
 			),
 		),
@@ -469,13 +466,13 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 			ts.IssueCandidacyAnnouncementInSlot("P0:node2-candidacy:1", 3, "P0:node1-candidacy:1", ts.Wallet("node2"))
 		}
 
-		ts.IssueBlocksAtSlots("P0:", []iotago.SlotIndex{3, 4, 5, 6, 7, 8}, 4, "P0:node2-candidacy:1", ts.Nodes(), true, true)
+		ts.IssueBlocksAtSlots("P0:", []iotago.SlotIndex{3, 4, 5, 6, 7}, 4, "P0:node2-candidacy:1", ts.Nodes(), true, true)
 
 		ts.AssertNodeState(ts.Nodes(),
 			testsuite.WithLatestFinalizedSlot(4),
 			testsuite.WithLatestCommitmentSlotIndex(5),
 			testsuite.WithEqualStoredCommitmentAtIndex(5),
-			testsuite.WithLatestCommitmentCumulativeWeight(4), // 4 * slot 5
+			testsuite.WithLatestCommitmentCumulativeWeight(4), // 4 * slot 6
 			testsuite.WithSybilProtectionOnlineCommittee(ts.SeatOfNodes(5, "node0", "node1", "node2", "node3")...),
 			testsuite.WithSybilProtectionCandidates(0, ts.AccountsOfNodes("node1", "node2")),
 			testsuite.WithEvictedSlot(5),
@@ -483,7 +480,7 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 
 		ts.AssertAttestationsForSlot(5, ts.Blocks("P0:5.3-node0", "P0:5.3-node1", "P0:5.3-node2", "P0:5.3-node3"), ts.Nodes()...)
 
-		ts.AssertStrongTips(ts.Blocks("P0:8.3-node0", "P0:8.3-node1", "P0:8.3-node2", "P0:8.3-node3"), ts.Nodes()...)
+		ts.AssertStrongTips(ts.Blocks("P0:7.3-node0", "P0:7.3-node1", "P0:7.3-node2", "P0:7.3-node3"), ts.Nodes()...)
 
 		ts.AssertBlocksExist(ts.BlocksWithPrefix("P0"), true, ts.Nodes()...)
 	}
@@ -496,15 +493,15 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 
 	// Issue blocks in partition 1.
 	{
-		ts.IssueBlocksAtSlots("P1:", []iotago.SlotIndex{9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 4, "P0:8.3", nodesP1, true, true)
+		ts.IssueBlocksAtSlots("P1:", []iotago.SlotIndex{8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 4, "P0:7.3", nodesP1, true, false)
 
 		ts.AssertNodeState(nodesP1,
-			testsuite.WithLatestFinalizedSlot(16),
-			testsuite.WithLatestCommitmentSlotIndex(17),
-			testsuite.WithEqualStoredCommitmentAtIndex(17),
-			testsuite.WithLatestCommitmentCumulativeWeight(43), // 4 + see attestation assertions below for how to compute
-			testsuite.WithSybilProtectionOnlineCommittee(ts.SeatOfNodes(17, "node1", "node2")...),
-			testsuite.WithEvictedSlot(17),
+			testsuite.WithLatestFinalizedSlot(17),
+			testsuite.WithLatestCommitmentSlotIndex(18),
+			testsuite.WithEqualStoredCommitmentAtIndex(18),
+			testsuite.WithLatestCommitmentCumulativeWeight(47), // 4 + see attestation assertions below for how to compute
+			testsuite.WithSybilProtectionOnlineCommittee(ts.SeatOfNodes(18, "node1", "node2")...),
+			testsuite.WithEvictedSlot(18),
 		)
 
 		// Assert committee in epoch 1.
@@ -516,8 +513,8 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 
 		ts.AssertAttestationsForSlot(6, ts.Blocks("P0:6.3-node0", "P0:6.3-node1", "P0:6.3-node2", "P0:6.3-node3"), nodesP1...) // Committee in epoch 1 is all nodes
 		ts.AssertAttestationsForSlot(7, ts.Blocks("P0:7.3-node0", "P0:7.3-node1", "P0:7.3-node2", "P0:7.3-node3"), nodesP1...) // Committee in epoch 1 is all nodes
-		ts.AssertAttestationsForSlot(8, ts.Blocks("P0:8.3-node0", "P0:8.3-node1", "P0:8.3-node2", "P0:8.3-node3"), nodesP1...) // Committee in epoch 1 is all nodes
-		ts.AssertAttestationsForSlot(9, ts.Blocks("P1:9.3-node0", "P1:9.3-node1", "P1:9.3-node2", "P0:8.3-node3"), nodesP1...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
+		ts.AssertAttestationsForSlot(8, ts.Blocks("P1:8.3-node0", "P1:8.3-node1", "P1:8.3-node2", "P0:7.3-node3"), nodesP1...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
+		ts.AssertAttestationsForSlot(9, ts.Blocks("P1:9.3-node0", "P1:9.3-node1", "P1:9.3-node2", "P0:7.3-node3"), nodesP1...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
 		ts.AssertAttestationsForSlot(10, ts.Blocks("P1:10.3-node0", "P1:10.3-node1", "P1:10.3-node2"), nodesP1...)             // Committee in epoch 1 is all nodes; node3 is in P2
 		ts.AssertAttestationsForSlot(11, ts.Blocks("P1:11.3-node0", "P1:11.3-node1", "P1:11.3-node2"), nodesP1...)             // Committee in epoch 1 is all nodes; node3 is in P2
 		ts.AssertAttestationsForSlot(12, ts.Blocks("P1:12.3-node0", "P1:12.3-node1", "P1:12.3-node2"), nodesP1...)             // Committee in epoch 1 is all nodes; node3 is in P2
@@ -525,7 +522,8 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 		ts.AssertAttestationsForSlot(14, ts.Blocks("P1:14.3-node0", "P1:14.3-node1", "P1:14.3-node2"), nodesP1...)             // Committee in epoch 1 is all nodes; node3 is in P2
 		ts.AssertAttestationsForSlot(15, ts.Blocks("P1:15.3-node0", "P1:15.3-node1", "P1:15.3-node2"), nodesP1...)             // Committee in epoch 1 is all nodes; node3 is in P2
 		ts.AssertAttestationsForSlot(16, ts.Blocks("P1:15.3-node0", "P1:16.3-node1", "P1:16.3-node2"), nodesP1...)             // We're in Epoch 2 (only node1, node2) but we carry attestations of others because of window
-		ts.AssertAttestationsForSlot(17, ts.Blocks("P1:17.3-node1", "P1:17.3-node2"), nodesP1...)                              // Committee in epoch 2 is only node1, node2
+		ts.AssertAttestationsForSlot(17, ts.Blocks("P1:15.3-node0", "P1:17.3-node1", "P1:17.3-node2"), nodesP1...)             // Committee in epoch 2 is only node1, node2
+		ts.AssertAttestationsForSlot(18, ts.Blocks("P1:15.3-node0", "P1:18.3-node1", "P1:18.3-node2"), nodesP1...)             // Committee in epoch 2 is only node1, node2
 
 		ts.AssertStrongTips(ts.Blocks("P1:20.3-node0", "P1:20.3-node1", "P1:20.3-node2"), nodesP1...)
 
@@ -535,15 +533,15 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 
 	// Issue blocks in partition 2.
 	{
-		ts.IssueBlocksAtSlots("P2:", []iotago.SlotIndex{9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 4, "P0:8.3", nodesP2, true, true)
+		ts.IssueBlocksAtSlots("P2:", []iotago.SlotIndex{8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 4, "P0:7.3", nodesP2, true, false)
 
 		ts.AssertNodeState(nodesP2,
 			testsuite.WithLatestFinalizedSlot(4),
-			testsuite.WithLatestCommitmentSlotIndex(17),
-			testsuite.WithEqualStoredCommitmentAtIndex(17),
+			testsuite.WithLatestCommitmentSlotIndex(18),
+			testsuite.WithEqualStoredCommitmentAtIndex(18),
 			// testsuite.WithLatestCommitmentCumulativeWeight(43), // 4 + see attestation assertions below for how to compute
-			testsuite.WithSybilProtectionOnlineCommittee(ts.SeatOfNodes(17, "node3")...),
-			testsuite.WithEvictedSlot(17),
+			testsuite.WithSybilProtectionOnlineCommittee(ts.SeatOfNodes(18, "node3")...),
+			testsuite.WithEvictedSlot(18),
 		)
 
 		// Assert committee in epoch 1.
@@ -555,8 +553,8 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 
 		ts.AssertAttestationsForSlot(6, ts.Blocks("P0:6.3-node0", "P0:6.3-node1", "P0:6.3-node2", "P0:6.3-node3"), nodesP2...) // Committee in epoch 1 is all nodes
 		ts.AssertAttestationsForSlot(7, ts.Blocks("P0:7.3-node0", "P0:7.3-node1", "P0:7.3-node2", "P0:7.3-node3"), nodesP2...) // Committee in epoch 1 is all nodes
-		ts.AssertAttestationsForSlot(8, ts.Blocks("P0:8.3-node0", "P0:8.3-node1", "P0:8.3-node2", "P0:8.3-node3"), nodesP2...) // Committee in epoch 1 is all nodes
-		ts.AssertAttestationsForSlot(9, ts.Blocks("P0:8.3-node0", "P0:8.3-node1", "P0:8.3-node2", "P2:9.3-node3"), nodesP2...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
+		ts.AssertAttestationsForSlot(8, ts.Blocks("P0:7.3-node0", "P0:7.3-node1", "P0:7.3-node2", "P2:8.3-node3"), nodesP2...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
+		ts.AssertAttestationsForSlot(9, ts.Blocks("P0:7.3-node0", "P0:7.3-node1", "P0:7.3-node2", "P2:9.3-node3"), nodesP2...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
 		ts.AssertAttestationsForSlot(10, ts.Blocks("P2:10.3-node3"), nodesP2...)                                               // Committee in epoch 1 is all nodes; only node3 is in P2
 		ts.AssertAttestationsForSlot(11, ts.Blocks("P2:11.3-node3"), nodesP2...)                                               // Committee in epoch 1 is all nodes; only node3 is in P2
 		ts.AssertAttestationsForSlot(12, ts.Blocks("P2:12.3-node3"), nodesP2...)                                               // Committee in epoch 1 is all nodes; only node3 is in P2
@@ -630,8 +628,8 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 
 	ts.AssertAttestationsForSlot(6, ts.Blocks("P0:6.3-node0", "P0:6.3-node1", "P0:6.3-node2", "P0:6.3-node3"), ts.Nodes()...) // Committee in epoch 1 is all nodes
 	ts.AssertAttestationsForSlot(7, ts.Blocks("P0:7.3-node0", "P0:7.3-node1", "P0:7.3-node2", "P0:7.3-node3"), ts.Nodes()...) // Committee in epoch 1 is all nodes
-	ts.AssertAttestationsForSlot(8, ts.Blocks("P0:8.3-node0", "P0:8.3-node1", "P0:8.3-node2", "P0:8.3-node3"), ts.Nodes()...) // Committee in epoch 1 is all nodes
-	ts.AssertAttestationsForSlot(9, ts.Blocks("P1:9.3-node0", "P1:9.3-node1", "P1:9.3-node2", "P0:8.3-node3"), ts.Nodes()...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
+	ts.AssertAttestationsForSlot(8, ts.Blocks("P1:8.3-node0", "P1:8.3-node1", "P1:8.3-node2", "P0:7.3-node3"), ts.Nodes()...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
+	ts.AssertAttestationsForSlot(9, ts.Blocks("P1:9.3-node0", "P1:9.3-node1", "P1:9.3-node2", "P0:7.3-node3"), ts.Nodes()...) // Committee in epoch 1 is all nodes; and we carry attestations of others because of window
 	ts.AssertAttestationsForSlot(10, ts.Blocks("P1:10.3-node0", "P1:10.3-node1", "P1:10.3-node2"), ts.Nodes()...)             // Committee in epoch 1 is all nodes; node3 is in P2
 	ts.AssertAttestationsForSlot(11, ts.Blocks("P1:11.3-node0", "P1:11.3-node1", "P1:11.3-node2"), ts.Nodes()...)             // Committee in epoch 1 is all nodes; node3 is in P2
 	ts.AssertAttestationsForSlot(12, ts.Blocks("P1:12.3-node0", "P1:12.3-node1", "P1:12.3-node2"), ts.Nodes()...)             // Committee in epoch 1 is all nodes; node3 is in P2
@@ -639,5 +637,7 @@ func TestProtocol_EngineSwitching_CommitteeRotation(t *testing.T) {
 	ts.AssertAttestationsForSlot(14, ts.Blocks("P1:14.3-node0", "P1:14.3-node1", "P1:14.3-node2"), ts.Nodes()...)             // Committee in epoch 1 is all nodes; node3 is in P2
 	ts.AssertAttestationsForSlot(15, ts.Blocks("P1:15.3-node0", "P1:15.3-node1", "P1:15.3-node2"), ts.Nodes()...)             // Committee in epoch 1 is all nodes; node3 is in P2
 	ts.AssertAttestationsForSlot(16, ts.Blocks("P1:15.3-node0", "P1:16.3-node1", "P1:16.3-node2"), ts.Nodes()...)             // We're in Epoch 2 (only node1, node2) but we carry attestations of others because of window
-	ts.AssertAttestationsForSlot(17, ts.Blocks("P1:17.3-node1", "P1:17.3-node2"), ts.Nodes()...)                              // Committee in epoch 2 is only node1, node2
+	ts.AssertAttestationsForSlot(17, ts.Blocks("P1:15.3-node0", "P1:17.3-node1", "P1:17.3-node2"), ts.Nodes()...)             // We're in Epoch 2 (only node1, node2) but we carry attestations of others because of window
+	ts.AssertAttestationsForSlot(18, ts.Blocks("P1:15.3-node0", "P1:18.3-node1", "P1:18.3-node2"), ts.Nodes()...)             // We're in Epoch 2 (only node1, node2) but we carry attestations of others because of window
+	ts.AssertAttestationsForSlot(19, ts.Blocks("P1:19.3-node1", "P1:19.3-node2"), ts.Nodes()...)                              // Committee in epoch 2 is only node1, node2
 }
diff --git a/pkg/tests/upgrade_signaling_test.go b/pkg/tests/upgrade_signaling_test.go
index ce7ae6a3..edf7d5a0 100644
--- a/pkg/tests/upgrade_signaling_test.go
+++ b/pkg/tests/upgrade_signaling_test.go
@@ -81,10 +81,7 @@ func Test_Upgrade_Signaling(t *testing.T) {
 		protocol.WithSybilProtectionProvider(
 			sybilprotectionv1.NewProvider(
 				sybilprotectionv1.WithSeatManagerProvider(
-					topstakers.NewProvider(
-						// We need to make sure that inactive nodes are evicted from the committee to continue acceptance.
-						topstakers.WithActivityWindow(15 * time.Second),
-					),
+					topstakers.NewProvider(),
 				),
 			),
 		),
diff --git a/pkg/testsuite/testsuite_issue_blocks.go b/pkg/testsuite/testsuite_issue_blocks.go
index 866a3252..3899662b 100644
--- a/pkg/testsuite/testsuite_issue_blocks.go
+++ b/pkg/testsuite/testsuite_issue_blocks.go
@@ -215,11 +215,10 @@ func (t *TestSuite) IssueBlocksAtSlots(prefix string, slots []iotago.SlotIndex,
 
 		if waitForSlotsCommitted {
 			if slot > t.API.ProtocolParameters().MinCommittableAge() {
-				commitmentSlot := slot - t.API.ProtocolParameters().MinCommittableAge()
-				t.AssertCommitmentSlotIndexExists(commitmentSlot, nodes...)
-
 				if useCommitmentAtMinCommittableAge {
 					// Make sure that all nodes create blocks throughout the slot that commit to the same commitment at slot-minCommittableAge-1.
+					commitmentSlot := slot - t.API.ProtocolParameters().MinCommittableAge()
+					t.AssertCommitmentSlotIndexExists(commitmentSlot, nodes...)
 					for _, node := range nodes {
 						commitment, err := node.Protocol.Engines.Main.Get().Storage.Commitments().Load(commitmentSlot)
 						require.NoError(t.Testing, err)
@@ -229,9 +228,8 @@ func (t *TestSuite) IssueBlocksAtSlots(prefix string, slots []iotago.SlotIndex,
 						}
 					}
 				}
-			} else {
-				t.AssertBlocksExist(blocksInSlot, true, nodes...)
 			}
+			t.AssertBlocksExist(blocksInSlot, true, nodes...)
 		}
 	}