Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

Commit 642e8e7

Browse files
authored
Merge pull request #666 from iotaledger/feat/reelected-committee-retains-seat
Re-elected committee member retains its SeatIndex
2 parents af804ae + 336eb5c commit 642e8e7

File tree

28 files changed

+1281
-195
lines changed

28 files changed

+1281
-195
lines changed

components/protocol/component.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ func configure() error {
277277
Component.LogDebugf("SlotCommitmentReceived: %s", commitment.ID())
278278
})
279279

280-
deps.Protocol.Events.Engine.SybilProtection.CommitteeSelected.Hook(func(committee *account.Accounts, epoch iotago.EpochIndex) {
280+
deps.Protocol.Events.Engine.SybilProtection.CommitteeSelected.Hook(func(committee *account.SeatedAccounts, epoch iotago.EpochIndex) {
281281
Component.LogInfof("CommitteeSelected, epoch: %d, committeeIDs: %s, reused: %t", epoch, committee.IDs(), committee.IsReused())
282282
})
283283

out.log

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
? github.com/iotaledger/iota-core [no test files]

pkg/core/account/accounts.go

+51-24
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package account
22

33
import (
4+
"bytes"
45
"io"
5-
"sync/atomic"
6+
"sort"
67

78
"github.com/iotaledger/hive.go/core/safemath"
9+
"github.com/iotaledger/hive.go/ds"
810
"github.com/iotaledger/hive.go/ds/shrinkingmap"
911
"github.com/iotaledger/hive.go/ierrors"
1012
"github.com/iotaledger/hive.go/runtime/syncutils"
@@ -19,7 +21,6 @@ type Accounts struct {
1921

2022
totalStake iotago.BaseToken
2123
totalValidatorStake iotago.BaseToken
22-
reused atomic.Bool
2324

2425
mutex syncutils.RWMutex
2526
}
@@ -52,14 +53,6 @@ func (a *Accounts) IDs() []iotago.AccountID {
5253
return ids
5354
}
5455

55-
func (a *Accounts) IsReused() bool {
56-
return a.reused.Load()
57-
}
58-
59-
func (a *Accounts) SetReused() {
60-
a.reused.Store(true)
61-
}
62-
6356
// Get returns the weight of the given identity.
6457
func (a *Accounts) Get(id iotago.AccountID) (pool *Pool, exists bool) {
6558
return a.accountPools.Get(id)
@@ -125,9 +118,54 @@ func (a *Accounts) ForEach(callback func(id iotago.AccountID, pool *Pool) bool)
125118
a.accountPools.ForEach(callback)
126119
}
127120

128-
// SeatedAccounts creates a new SeatedAccounts instance, that maintains the seats of the given members.
129-
func (a *Accounts) SeatedAccounts(members ...iotago.AccountID) *SeatedAccounts {
130-
return NewSeatedAccounts(a, members...)
121+
// SeatedAccounts creates a new SeatedAccounts instance,
122+
// that maintains the seat indices of re-elected members from the previous committee, if provided.
123+
func (a *Accounts) SeatedAccounts(optPrevCommittee ...*SeatedAccounts) *SeatedAccounts {
124+
// If optPrevCommittee not given, use empty SeatedAccounts instance as default to skip the behavior to handle re-elected members.
125+
prevCommittee := NewSeatedAccounts(NewAccounts())
126+
if len(optPrevCommittee) > 0 {
127+
prevCommittee = optPrevCommittee[0]
128+
}
129+
130+
newCommittee := NewSeatedAccounts(a)
131+
132+
// If previous committee exists the seats need to be assigned and re-elected committee members must retain their SeatIndex.
133+
// Keep track of re-elected members' seat indices.
134+
takenSeatIndices := ds.NewSet[SeatIndex]()
135+
136+
// Assign re-elected members' SeatIndex.
137+
a.ForEach(func(id iotago.AccountID, _ *Pool) bool {
138+
if seatIndex, seatExists := prevCommittee.GetSeat(id); seatExists {
139+
newCommittee.Set(seatIndex, id)
140+
takenSeatIndices.Add(seatIndex)
141+
}
142+
143+
return true
144+
})
145+
146+
// Sort members lexicographically.
147+
committeeAccountIDs := a.IDs()
148+
sort.Slice(committeeAccountIDs, func(i int, j int) bool {
149+
return bytes.Compare(committeeAccountIDs[i][:], committeeAccountIDs[j][:]) < 0
150+
})
151+
152+
// Assign SeatIndex to new committee members.
153+
currentSeatIndex := SeatIndex(0)
154+
for _, memberID := range committeeAccountIDs {
155+
// If SeatIndex is taken by re-elected member, then increment it.
156+
for takenSeatIndices.Has(currentSeatIndex) {
157+
currentSeatIndex++
158+
}
159+
160+
// Assign SeatIndex of a fresh committee member.
161+
if _, seatExists := prevCommittee.GetSeat(memberID); !seatExists {
162+
newCommittee.Set(currentSeatIndex, memberID)
163+
164+
currentSeatIndex++
165+
}
166+
}
167+
168+
return newCommittee
131169
}
132170

133171
func AccountsFromBytes(b []byte) (*Accounts, int, error) {
@@ -164,13 +202,6 @@ func AccountsFromReader(reader io.Reader) (*Accounts, error) {
164202
return nil, ierrors.Wrap(err, "failed to read account data")
165203
}
166204

167-
reused, err := stream.Read[bool](reader)
168-
if err != nil {
169-
return nil, ierrors.Wrap(err, "failed to read reused flag")
170-
}
171-
172-
a.reused.Store(reused)
173-
174205
return a, nil
175206
}
176207

@@ -202,9 +233,5 @@ func (a *Accounts) Bytes() ([]byte, error) {
202233
return nil, ierrors.Wrap(err, "failed to write accounts")
203234
}
204235

205-
if err := stream.Write(byteBuffer, a.reused.Load()); err != nil {
206-
return nil, ierrors.Wrap(err, "failed to write reused flag")
207-
}
208-
209236
return byteBuffer.Bytes()
210237
}

pkg/core/account/accounts_test.go

+80-17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/stretchr/testify/require"
88

9+
"github.com/iotaledger/hive.go/lo"
910
"github.com/iotaledger/iota-core/pkg/core/account"
1011
iotago "github.com/iotaledger/iota.go/v4"
1112
"github.com/iotaledger/iota.go/v4/tpkg"
@@ -20,13 +21,11 @@ func TestAccounts(t *testing.T) {
2021

2122
// check "Set"
2223
for id, stake := range issuers {
23-
if err := accounts.Set(id, &account.Pool{
24-
PoolStake: iotago.BaseToken(stake),
24+
require.NoError(t, accounts.Set(id, &account.Pool{
25+
PoolStake: stake,
2526
ValidatorStake: iotago.BaseToken(stake) * 2,
2627
FixedCost: iotago.Mana(stake) * 3,
27-
}); err != nil {
28-
t.Fatal(err)
29-
}
28+
}))
3029
}
3130

3231
// check "Size"
@@ -57,13 +56,13 @@ func TestAccounts(t *testing.T) {
5756
require.True(t, accounts.Has(id))
5857

5958
// check "Get"
60-
account, exists := accounts.Get(id)
59+
member, exists := accounts.Get(id)
6160
require.True(t, exists)
6261

6362
// check the account itself
64-
require.Equal(t, stake, account.PoolStake)
65-
require.Equal(t, stake*2, account.ValidatorStake)
66-
require.Equal(t, iotago.Mana(stake)*3, account.FixedCost)
63+
require.Equal(t, stake, member.PoolStake)
64+
require.Equal(t, stake*2, member.ValidatorStake)
65+
require.Equal(t, iotago.Mana(stake)*3, member.FixedCost)
6766
}
6867

6968
// check total stakes
@@ -74,11 +73,11 @@ func TestAccounts(t *testing.T) {
7473
account1, exists := accounts.Get(accountIDs[0])
7574
require.True(t, exists)
7675

77-
accounts.Set(accountIDs[0], &account.Pool{
76+
require.NoError(t, accounts.Set(accountIDs[0], &account.Pool{
7877
PoolStake: 10 * account1.PoolStake,
7978
ValidatorStake: 10 * account1.ValidatorStake,
8079
FixedCost: 10 * account1.FixedCost,
81-
})
80+
}))
8281

8382
// check if the updated total stakes are correct
8483
require.Equal(t, (totalStake-account1.PoolStake)+(account1.PoolStake*10), accounts.TotalStake())
@@ -107,12 +106,8 @@ func TestAccounts(t *testing.T) {
107106

108107
// check "SelectCommittee"
109108

110-
// get 1 issuer
111-
seated := accounts.SeatedAccounts(accountIDs[0])
112-
require.Equal(t, 1, seated.SeatCount())
113-
114109
// get all issuers
115-
seated = accounts.SeatedAccounts(accountIDs...)
110+
seated := accounts.SeatedAccounts()
116111
require.Equal(t, len(accountIDs), seated.SeatCount())
117112

118113
/*
@@ -124,14 +119,82 @@ func TestAccounts(t *testing.T) {
124119
*/
125120
}
126121

122+
func TestAccounts_SeatedAccounts(t *testing.T) {
123+
committeeSize := 20
124+
125+
prevCommitteeIssuers, _ := generateAccounts(committeeSize)
126+
newCommitteeIssuers, _ := generateAccounts(committeeSize/2 - 1)
127+
128+
newCommitteeAccounts := account.NewAccounts()
129+
prevCommitteeAccounts := account.NewAccounts()
130+
131+
idx := 0
132+
for id, stake := range prevCommitteeIssuers {
133+
// half of the accounts from the previous committee should also be added to the new committee
134+
if idx%2 == 0 || idx == 1 {
135+
require.NoError(t, newCommitteeAccounts.Set(id, &account.Pool{
136+
PoolStake: stake,
137+
ValidatorStake: iotago.BaseToken(stake) * 2,
138+
FixedCost: iotago.Mana(stake) * 3,
139+
}))
140+
}
141+
142+
require.NoError(t, prevCommitteeAccounts.Set(id, &account.Pool{
143+
PoolStake: stake,
144+
ValidatorStake: iotago.BaseToken(stake) * 2,
145+
FixedCost: iotago.Mana(stake) * 3,
146+
}))
147+
148+
idx++
149+
}
150+
151+
// Add remaining accounts to new committee accounts.
152+
for id, stake := range newCommitteeIssuers {
153+
// half of the accounts from the previous committee should also be added to the new committee
154+
require.NoError(t, newCommitteeAccounts.Set(id, &account.Pool{
155+
PoolStake: stake,
156+
ValidatorStake: iotago.BaseToken(stake) * 2,
157+
FixedCost: iotago.Mana(stake) * 3,
158+
}))
159+
}
160+
161+
// Get prev committee and assign seats by default.
162+
prevCommittee := prevCommitteeAccounts.SeatedAccounts()
163+
164+
// New committee seats should be assigned taking previous committee into account.
165+
newCommittee := newCommitteeAccounts.SeatedAccounts(prevCommittee)
166+
167+
prevCommitteeAccounts.ForEach(func(id iotago.AccountID, _ *account.Pool) bool {
168+
if seatIndex, exists := newCommittee.GetSeat(id); exists {
169+
require.Equal(t, lo.Return1(prevCommittee.GetSeat(id)), seatIndex)
170+
}
171+
172+
return true
173+
})
174+
175+
// Make sure that the size of the committee and SeatIndex values are sane.
176+
require.Equal(t, newCommittee.SeatCount(), committeeSize)
177+
178+
// Check that SeatIndices are within <0; committeeSize) range.
179+
newCommitteeAccounts.ForEach(func(id iotago.AccountID, _ *account.Pool) bool {
180+
// SeatIndex must be smaller than CommitteeSize.
181+
require.Less(t, int(lo.Return1(newCommittee.GetSeat(id))), committeeSize)
182+
183+
// SeatIndex must be bigger or equal to 0.
184+
require.GreaterOrEqual(t, int(lo.Return1(newCommittee.GetSeat(id))), 0)
185+
186+
return true
187+
})
188+
}
189+
127190
func generateAccounts(count int) (map[iotago.AccountID]iotago.BaseToken, iotago.BaseToken) {
128191
seenIDs := make(map[iotago.AccountID]bool)
129192
issuers := make(map[iotago.AccountID]iotago.BaseToken)
130193

131194
var totalStake iotago.BaseToken
132195

133196
for i := 0; i < count; i++ {
134-
id := iotago.AccountID([32]byte{tpkg.RandByte()})
197+
id := iotago.AccountID(tpkg.Rand32ByteArray())
135198
if _, exist := seenIDs[id]; exist {
136199
i--
137200
continue

0 commit comments

Comments
 (0)