Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable dPoS #506

Merged
merged 14 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (g *Gadget) trackConfirmationRatifierWeight(votingBlock *blocks.Block) {

func (g *Gadget) shouldConfirm(block *blocks.Block) bool {
blockSeats := len(block.ConfirmationRatifiers())
totalCommitteeSeats := g.seatManager.SeatCount()
totalCommitteeSeats := g.seatManager.SeatCountInSlot(block.ID().Slot())

return votes.IsThresholdReached(blockSeats, totalCommitteeSeats, g.optsConfirmationThreshold)
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (g *Gadget) TrackWitnessWeight(votingBlock *blocks.Block) {
}

func (g *Gadget) shouldPreAcceptAndPreConfirm(block *blocks.Block) (preAccept bool, preConfirm bool) {
committeeTotalSeats := g.seatManager.SeatCount()
committeeTotalSeats := g.seatManager.SeatCountInSlot(block.ID().Slot())
blockSeats := len(block.Witnesses())

onlineCommitteeTotalSeats := g.seatManager.OnlineCommittee().Size()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ func (g *Gadget) trackVotes(block *blocks.Block) {
}

func (g *Gadget) refreshSlotFinalization(tracker *slottracker.SlotTracker, previousLatestSlotIndex iotago.SlotIndex, newLatestSlotIndex iotago.SlotIndex) (finalizedSlots []iotago.SlotIndex) {
committeeTotalSeats := g.seatManager.SeatCount()

for i := lo.Max(g.lastFinalizedSlot, previousLatestSlotIndex) + 1; i <= newLatestSlotIndex; i++ {
committeeTotalSeats := g.seatManager.SeatCountInSlot(i)
attestorsTotalSeats := len(tracker.Voters(i))

if !votes.IsThresholdReached(attestorsTotalSeats, committeeTotalSeats, g.optsSlotFinalizationThreshold) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func (o *Orchestrator) tryUpgrade(currentEpoch iotago.EpochIndex, lastSlotInEpoc
}

// Check whether the threshold for version was reached.
totalSeatCount := o.seatManager.SeatCount()
totalSeatCount := o.seatManager.SeatCountInEpoch(currentEpoch)
if !votes.IsThresholdReached(mostSupporters, totalSeatCount, votes.SuperMajority) {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ func (m *ManualPOA) OnlineCommittee() ds.Set[account.SeatIndex] {
return m.online
}

func (m *ManualPOA) SeatCount() int {
func (m *ManualPOA) SeatCountInSlot(_ iotago.SlotIndex) int {
return m.committee.SeatCount()
}
func (m *ManualPOA) SeatCountInEpoch(_ iotago.EpochIndex) int {
return m.committee.SeatCount()
}

Expand Down
15 changes: 12 additions & 3 deletions pkg/protocol/sybilprotection/seatmanager/poa/poa.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,14 @@ func (s *SeatManager) OnlineCommittee() ds.Set[account.SeatIndex] {
return s.activityTracker.OnlineCommittee()
}

func (s *SeatManager) SeatCount() int {
func (s *SeatManager) SeatCountInSlot(_ iotago.SlotIndex) int {
s.committeeMutex.RLock()
defer s.committeeMutex.RUnlock()

return s.committee.SeatCount()
}

func (s *SeatManager) SeatCountInEpoch(_ iotago.EpochIndex) int {
s.committeeMutex.RLock()
defer s.committeeMutex.RUnlock()

Expand All @@ -166,9 +173,11 @@ func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime
return ierrors.Wrapf(err, "failed to load PoA committee for epoch %d", epoch)
}

s.committee = committeeAccounts.SelectCommittee(committeeAccounts.IDs()...)
committeeAccountsIDs := committeeAccounts.IDs()
s.committee = committeeAccounts.SelectCommittee(committeeAccountsIDs...)

onlineValidators := committeeAccounts.IDs()
// Set validators that are part of the committee as active.
onlineValidators := committeeAccountsIDs
if len(s.optsOnlineCommitteeStartup) > 0 {
onlineValidators = s.optsOnlineCommitteeStartup
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/protocol/sybilprotection/seatmanager/seatmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ type SeatManager interface {
OnlineCommittee() ds.Set[account.SeatIndex]

// SeatCount returns the number of seats in the SeatManager.
SeatCount() int
SeatCountInSlot(slot iotago.SlotIndex) int

SeatCountInEpoch(epoch iotago.EpochIndex) int

// Interface embeds the required methods of the module.Interface.
module.Interface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,3 @@ func WithOnlineCommitteeStartup(optsOnlineCommittee ...iotago.AccountID) options
p.optsOnlineCommitteeStartup = optsOnlineCommittee
}
}

func WithSeatCount(optsSeatCount uint32) options.Option[SeatManager] {
return func(p *SeatManager) {
p.optsSeatCount = optsSeatCount
}
}
65 changes: 31 additions & 34 deletions pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/iotaledger/hive.go/ds"
"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/hive.go/lo"
"github.com/iotaledger/hive.go/runtime/module"
"github.com/iotaledger/hive.go/runtime/options"
"github.com/iotaledger/hive.go/runtime/syncutils"
Expand All @@ -30,7 +31,6 @@ type SeatManager struct {
committeeMutex syncutils.RWMutex
activityTracker activitytracker.ActivityTracker

optsSeatCount uint32
optsActivityWindow time.Duration
optsOnlineCommitteeStartup []iotago.AccountID

Expand Down Expand Up @@ -85,42 +85,21 @@ func (s *SeatManager) RotateCommittee(epoch iotago.EpochIndex, candidates accoun
s.committeeMutex.Lock()
defer s.committeeMutex.Unlock()

// If there are fewer candidates than required for epoch 0, then the previous committee cannot be copied.
if len(candidates) < s.SeatCount() && epoch == 0 {
return nil, ierrors.Errorf("at least %d candidates are required for committee in epoch 0, got %d", s.SeatCount(), len(candidates))
if len(candidates) == 0 {
return nil, ierrors.New("candidates must not be empty")
}

// If there are fewer candidates than required, then re-use the previous committee.
if len(candidates) < s.SeatCount() {
// TODO: what if staking period of a committee member ends in the next epoch?
committee, exists := s.committeeInEpoch(epoch - 1)
if !exists {
return nil, ierrors.Errorf("cannot re-use previous committee from epoch %d as it does not exist", epoch-1)
}

accounts, err := committee.Accounts()
if err != nil {
return nil, ierrors.Wrapf(err, "error while getting accounts from committee for epoch %d", epoch-1)
}

if err := s.committeeStore.Store(epoch, accounts); err != nil {
return nil, ierrors.Wrapf(err, "error while storing committee for epoch %d", epoch)
}

return committee, nil
}

committee, err := s.selectNewCommittee(candidates)
committee, err := s.selectNewCommittee(epoch, candidates)
if err != nil {
return nil, ierrors.Wrap(err, "error while selecting new committee")
}

accounts, err := committee.Accounts()
committeeAccounts, err := committee.Accounts()
if err != nil {
return nil, ierrors.Wrapf(err, "error while getting accounts for newly selected committee for epoch %d", epoch)
return nil, ierrors.Wrapf(err, "error while getting committeeAccounts for newly selected committee for epoch %d", epoch)
}

if err := s.committeeStore.Store(epoch, accounts); err != nil {
if err := s.committeeStore.Store(epoch, committeeAccounts); err != nil {
return nil, ierrors.Wrapf(err, "error while storing committee for epoch %d", epoch)
}

Expand Down Expand Up @@ -161,8 +140,22 @@ func (s *SeatManager) OnlineCommittee() ds.Set[account.SeatIndex] {
return s.activityTracker.OnlineCommittee()
}

func (s *SeatManager) SeatCount() int {
return int(s.optsSeatCount)
func (s *SeatManager) SeatCountInSlot(slot iotago.SlotIndex) int {
epoch := s.apiProvider.APIForSlot(slot).TimeProvider().EpochFromSlot(slot)

return s.SeatCountInEpoch(epoch)
}

func (s *SeatManager) SeatCountInEpoch(epoch iotago.EpochIndex) int {
s.committeeMutex.RLock()
defer s.committeeMutex.RUnlock()

// TODO: this function is a hot path as it is called for every single block. Maybe accessing the storage is too slow.
if committee, exists := s.committeeInEpoch(epoch); exists {
return committee.SeatCount()
}

return int(s.apiProvider.APIForEpoch(epoch).ProtocolParameters().TargetCommitteeSize())
}

func (s *SeatManager) Shutdown() {
Expand Down Expand Up @@ -202,8 +195,8 @@ func (s *SeatManager) SetCommittee(epoch iotago.EpochIndex, validators *account.
s.committeeMutex.Lock()
defer s.committeeMutex.Unlock()

if validators.Size() != int(s.optsSeatCount) {
return ierrors.Errorf("invalid number of validators: %d, expected: %d", validators.Size(), s.optsSeatCount)
if validators.Size() == 0 {
return ierrors.New("committee must not be empty")
}

err := s.committeeStore.Store(epoch, validators)
Expand All @@ -214,7 +207,7 @@ func (s *SeatManager) SetCommittee(epoch iotago.EpochIndex, validators *account.
return nil
}

func (s *SeatManager) selectNewCommittee(candidates accounts.AccountsData) (*account.SeatedAccounts, error) {
func (s *SeatManager) selectNewCommittee(epoch iotago.EpochIndex, candidates accounts.AccountsData) (*account.SeatedAccounts, error) {
sort.Slice(candidates, func(i int, j int) bool {
// Prioritize the candidate that has a larger pool stake.
if candidates[i].ValidatorStake+candidates[i].DelegationStake != candidates[j].ValidatorStake+candidates[j].DelegationStake {
Expand All @@ -240,10 +233,14 @@ func (s *SeatManager) selectNewCommittee(candidates accounts.AccountsData) (*acc
return bytes.Compare(candidates[i].ID[:], candidates[j].ID[:]) > 0
})

// We try to select up to targetCommitteeSize candidates to be part of the committee. If there are fewer candidates
// than required, then we select all of them and the committee size will be smaller than targetCommitteeSize.
committeeSize := lo.Min(len(candidates), int(s.apiProvider.APIForEpoch(epoch).ProtocolParameters().TargetCommitteeSize()))

// Create new Accounts instance that only included validators selected to be part of the committee.
newCommitteeAccounts := account.NewAccounts()

for _, candidateData := range candidates[:s.optsSeatCount] {
for _, candidateData := range candidates[:committeeSize] {
if err := newCommitteeAccounts.Set(candidateData.ID, &account.Pool{
PoolStake: candidateData.ValidatorStake + candidateData.DelegationStake,
ValidatorStake: candidateData.ValidatorStake,
Expand Down
Loading
Loading