Skip to content

Commit ee3380b

Browse files
committed
fix wrapper base
1 parent 8458a59 commit ee3380b

File tree

5 files changed

+239
-30
lines changed

5 files changed

+239
-30
lines changed

systemcontractindex/stakingindex/candidate_votes.go

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ import (
1111
"github.com/iotexproject/iotex-core/v2/systemcontracts"
1212
)
1313

14+
var (
15+
// ErrCandidateVotesIsDirty is returned when candidate votes are dirty
16+
ErrCandidateVotesIsDirty = errors.New("candidate votes is dirty")
17+
)
18+
1419
// CandidateVotes is the interface to manage candidate votes
1520
type CandidateVotes interface {
1621
Clone() CandidateVotes
1722
Votes(fCtx protocol.FeatureCtx, cand string) *big.Int
1823
Add(cand string, amount *big.Int, votes *big.Int)
19-
Clear()
2024
Commit() CandidateVotes
2125
Base() CandidateVotes
2226
IsDirty() bool
@@ -35,6 +39,11 @@ type candidateVotes struct {
3539
cands map[string]*candidate
3640
}
3741

42+
type candidateVotesWithBuffer struct {
43+
base *candidateVotes
44+
change *candidateVotes
45+
}
46+
3847
type candidateVotesWraper struct {
3948
base CandidateVotes
4049
change *candidateVotes
@@ -51,7 +60,7 @@ func newCandidate() *candidate {
5160
}
5261
}
5362

54-
func (cv *candidateVotes) Clone() CandidateVotes {
63+
func (cv *candidateVotes) Clone() *candidateVotes {
5564
newCands := make(map[string]*candidate)
5665
for cand, c := range cv.cands {
5766
newCands[cand] = &candidate{
@@ -64,10 +73,6 @@ func (cv *candidateVotes) Clone() CandidateVotes {
6473
}
6574
}
6675

67-
func (cv *candidateVotes) IsDirty() bool {
68-
return false
69-
}
70-
7176
func (cv *candidateVotes) Votes(fCtx protocol.FeatureCtx, cand string) *big.Int {
7277
c := cv.cands[cand]
7378
if c == nil {
@@ -91,10 +96,6 @@ func (cv *candidateVotes) Add(cand string, amount *big.Int, votes *big.Int) {
9196
}
9297
}
9398

94-
func (cv *candidateVotes) Clear() {
95-
cv.cands = make(map[string]*candidate)
96-
}
97-
9899
func (cv *candidateVotes) Serialize() ([]byte, error) {
99100
cl := stakingpb.CandidateList{}
100101
for cand, c := range cv.cands {
@@ -138,14 +139,6 @@ func (cv *candidateVotes) Decode(data systemcontracts.GenericValue) error {
138139
return cv.Deserialize(data.PrimaryData)
139140
}
140141

141-
func (cv *candidateVotes) Commit() CandidateVotes {
142-
return cv
143-
}
144-
145-
func (cv *candidateVotes) Base() CandidateVotes {
146-
return cv
147-
}
148-
149142
func newCandidateVotes() *candidateVotes {
150143
return &candidateVotes{
151144
cands: make(map[string]*candidate),
@@ -162,12 +155,12 @@ func newCandidateVotesWrapper(base CandidateVotes) *candidateVotesWraper {
162155
func (cv *candidateVotesWraper) Clone() CandidateVotes {
163156
return &candidateVotesWraper{
164157
base: cv.base.Clone(),
165-
change: cv.change.Clone().(*candidateVotes),
158+
change: cv.change.Clone(),
166159
}
167160
}
168161

169162
func (cv *candidateVotesWraper) IsDirty() bool {
170-
return cv.change.IsDirty() || cv.base.IsDirty()
163+
return len(cv.change.cands) > 0 || cv.base.IsDirty()
171164
}
172165

173166
func (cv *candidateVotesWraper) Votes(fCtx protocol.FeatureCtx, cand string) *big.Int {
@@ -186,11 +179,6 @@ func (cv *candidateVotesWraper) Add(cand string, amount *big.Int, votes *big.Int
186179
cv.change.Add(cand, amount, votes)
187180
}
188181

189-
func (cv *candidateVotesWraper) Clear() {
190-
cv.change.Clear()
191-
cv.base.Clear()
192-
}
193-
194182
func (cv *candidateVotesWraper) Commit() CandidateVotes {
195183
// Commit the changes to the base
196184
for cand, change := range cv.change.cands {
@@ -202,15 +190,19 @@ func (cv *candidateVotesWraper) Commit() CandidateVotes {
202190
}
203191

204192
func (cv *candidateVotesWraper) Serialize() ([]byte, error) {
205-
return nil, errors.New("not implemented")
193+
if cv.IsDirty() {
194+
return nil, errors.Wrap(ErrCandidateVotesIsDirty, "cannot serialize dirty candidate votes")
195+
}
196+
return cv.base.Serialize()
206197
}
207198

208199
func (cv *candidateVotesWraper) Deserialize(data []byte) error {
209-
return errors.New("not implemented")
200+
cv.change = newCandidateVotes()
201+
return cv.base.Deserialize(data)
210202
}
211203

212204
func (cv *candidateVotesWraper) Base() CandidateVotes {
213-
return cv.base
205+
return cv.base.Base()
214206
}
215207

216208
func newCandidateVotesWrapperCommitInClone(base CandidateVotes) *candidateVotesWraperCommitInClone {
@@ -229,3 +221,66 @@ func (cv *candidateVotesWraperCommitInClone) Commit() CandidateVotes {
229221
cv.base = cv.base.Clone()
230222
return cv.candidateVotesWraper.Commit()
231223
}
224+
225+
func (cv *candidateVotesWraperCommitInClone) Base() CandidateVotes {
226+
return cv.base
227+
}
228+
229+
func newCandidateVotesWithBuffer(base *candidateVotes) *candidateVotesWithBuffer {
230+
return &candidateVotesWithBuffer{
231+
base: base,
232+
change: newCandidateVotes(),
233+
}
234+
}
235+
236+
func (cv *candidateVotesWithBuffer) Clone() CandidateVotes {
237+
return &candidateVotesWithBuffer{
238+
base: cv.base.Clone(),
239+
change: cv.change.Clone(),
240+
}
241+
}
242+
243+
func (cv *candidateVotesWithBuffer) IsDirty() bool {
244+
return len(cv.change.cands) > 0
245+
}
246+
247+
func (cv *candidateVotesWithBuffer) Votes(fCtx protocol.FeatureCtx, cand string) *big.Int {
248+
base := cv.base.Votes(fCtx, cand)
249+
change := cv.change.Votes(fCtx, cand)
250+
if change == nil {
251+
return base
252+
}
253+
if base == nil {
254+
return change
255+
}
256+
return new(big.Int).Add(base, change)
257+
}
258+
259+
func (cv *candidateVotesWithBuffer) Add(cand string, amount *big.Int, votes *big.Int) {
260+
cv.change.Add(cand, amount, votes)
261+
}
262+
263+
func (cv *candidateVotesWithBuffer) Commit() CandidateVotes {
264+
// Commit the changes to the base
265+
for cand, change := range cv.change.cands {
266+
cv.base.Add(cand, change.amount, change.votes)
267+
}
268+
cv.change = newCandidateVotes()
269+
return cv
270+
}
271+
272+
func (cv *candidateVotesWithBuffer) Serialize() ([]byte, error) {
273+
if cv.IsDirty() {
274+
return nil, errors.Wrap(ErrCandidateVotesIsDirty, "cannot serialize dirty candidate votes")
275+
}
276+
return cv.base.Serialize()
277+
}
278+
279+
func (cv *candidateVotesWithBuffer) Deserialize(data []byte) error {
280+
cv.change = newCandidateVotes()
281+
return cv.base.Deserialize(data)
282+
}
283+
284+
func (cv *candidateVotesWithBuffer) Base() CandidateVotes {
285+
return newCandidateVotesWithBuffer(cv.base)
286+
}

systemcontractindex/stakingindex/candidate_votes_manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (s *candidateVotesManager) Load(ctx context.Context, sr protocol.StateReade
5252
if err != nil {
5353
return nil, errors.Wrap(err, "failed to get candidate votes state")
5454
}
55-
return cur, nil
55+
return newCandidateVotesWithBuffer(cur), nil
5656
}
5757

5858
func (s *candidateVotesManager) key() []byte {

systemcontractindex/stakingindex/candidate_votes_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package stakingindex
22

33
import (
44
"context"
5+
"fmt"
56
"math/big"
67
"testing"
78

@@ -60,3 +61,154 @@ func TestCandidateVotes(t *testing.T) {
6061
require.EqualValues(120, newVotes.Sub(newVotes, originCandVotes).Uint64())
6162
})
6263
}
64+
65+
func TestCandidateVotesInterface(t *testing.T) {
66+
r := require.New(t)
67+
68+
cvs := []CandidateVotes{
69+
newCandidateVotesWithBuffer(newCandidateVotes()),
70+
newCandidateVotesWrapper(newCandidateVotesWithBuffer(newCandidateVotes())),
71+
newCandidateVotesWrapperCommitInClone(newCandidateVotesWithBuffer(newCandidateVotes())),
72+
}
73+
g := genesis.TestDefault()
74+
ctx := genesis.WithGenesisContext(context.Background(), g)
75+
ctxBeforeRedsea := protocol.MustGetFeatureCtx(protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: g.RedseaBlockHeight - 1})))
76+
ctxAfterRedsea := protocol.MustGetFeatureCtx(protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: g.RedseaBlockHeight})))
77+
for _, cv := range cvs {
78+
t.Run(fmt.Sprintf("%T", cv), func(t *testing.T) {
79+
// not exist candidate
80+
r.Nil(cv.Votes(ctxAfterRedsea, "notexist"))
81+
r.Nil(cv.Votes(ctxBeforeRedsea, "notexist"))
82+
r.False(cv.IsDirty())
83+
84+
// add candidate
85+
adds := []struct {
86+
cand string
87+
amount string
88+
votes string
89+
}{
90+
{"candidate1", "100", "120"},
91+
{"candidate2", "200", "240"},
92+
{"candidate1", "300", "360"},
93+
{"candidate3", "400", "480"},
94+
{"candidate2", "500", "600"},
95+
{"candidate2", "-200", "-240"},
96+
}
97+
expects := []struct {
98+
cand string
99+
amount string
100+
votes string
101+
}{
102+
{"candidate1", "400", "480"},
103+
{"candidate2", "500", "600"},
104+
{"candidate3", "400", "480"},
105+
}
106+
for _, add := range adds {
107+
amount := big.NewInt(0)
108+
votes := big.NewInt(0)
109+
_, ok := amount.SetString(add.amount, 10)
110+
r.True(ok)
111+
_, ok = votes.SetString(add.votes, 10)
112+
r.True(ok)
113+
cv.Add(add.cand, amount, votes)
114+
}
115+
checkVotes := func(cv CandidateVotes) {
116+
for _, expect := range expects {
117+
amount := big.NewInt(0)
118+
votes := big.NewInt(0)
119+
_, ok := amount.SetString(expect.amount, 10)
120+
r.True(ok)
121+
_, ok = votes.SetString(expect.votes, 10)
122+
r.True(ok)
123+
r.Equal(amount, cv.Votes(ctxBeforeRedsea, expect.cand))
124+
r.Equal(votes, cv.Votes(ctxAfterRedsea, expect.cand))
125+
}
126+
}
127+
checkVotes(cv)
128+
cl := cv.Clone()
129+
checkVotes(cl)
130+
// both cv and cl are dirty
131+
r.True(cv.IsDirty())
132+
r.True(cl.IsDirty())
133+
// serialize dirty cv should fail
134+
_, err := cv.Serialize()
135+
r.ErrorIs(err, ErrCandidateVotesIsDirty)
136+
// commit cv
137+
reset := cv.Commit()
138+
r.False(reset.IsDirty())
139+
data, err := reset.Serialize()
140+
r.NoError(err)
141+
// deserialize to new cv
142+
decv := newCandidateVotes()
143+
err = decv.Deserialize(data)
144+
r.NoError(err)
145+
checkVotes(newCandidateVotesWithBuffer(decv))
146+
// adds not affect base
147+
cv.Add("candidate4", big.NewInt(1000), big.NewInt(1200))
148+
cv.Add("candidate1", big.NewInt(-100), big.NewInt(-120))
149+
cv.Add("candidate2", big.NewInt(100), big.NewInt(120))
150+
checkVotes(cv.Base())
151+
})
152+
}
153+
}
154+
155+
func TestCandidateVotesWrapper(t *testing.T) {
156+
r := require.New(t)
157+
baseCv := newCandidateVotesWithBuffer(newCandidateVotes())
158+
baseCv.Add("candidate1", big.NewInt(100), big.NewInt(120))
159+
baseCv.Add("candidate2", big.NewInt(200), big.NewInt(240))
160+
baseCv.Add("candidate3", big.NewInt(400), big.NewInt(480))
161+
base := baseCv.Commit()
162+
// wrap's changes should not affect base
163+
wrap := newCandidateVotesWrapper(base)
164+
wrap.Add("candidate1", big.NewInt(300), big.NewInt(360))
165+
wrap.Add("candidate4", big.NewInt(1000), big.NewInt(1200))
166+
candidateVotesEqual(r, base, wrap.Base(), []string{"candidate1", "candidate2", "candidate3", "candidate4"})
167+
// multiple wraps return base recursively
168+
wrap2 := newCandidateVotesWrapper(wrap)
169+
wrap2.Add("candidate2", big.NewInt(500), big.NewInt(600))
170+
wrap2.Add("candidate5", big.NewInt(2000), big.NewInt(2400))
171+
candidateVotesEqual(r, base, wrap2.Base(), []string{"candidate1", "candidate2", "candidate3", "candidate4", "candidate5"})
172+
// commit wrap should apply all changes to base
173+
wrap2.Commit()
174+
candidateVotesEqual(r, base, wrap2, []string{"candidate1", "candidate2", "candidate3", "candidate4", "candidate5"})
175+
}
176+
177+
func TestCandidateVotesWrapperCommitInClone(t *testing.T) {
178+
r := require.New(t)
179+
baseCv := newCandidateVotesWithBuffer(newCandidateVotes())
180+
baseCv.Add("candidate1", big.NewInt(100), big.NewInt(120))
181+
baseCv.Add("candidate2", big.NewInt(200), big.NewInt(240))
182+
baseCv.Add("candidate3", big.NewInt(400), big.NewInt(480))
183+
base := baseCv.Commit()
184+
wrap := newCandidateVotesWrapper(base)
185+
wrap.Add("candidate1", big.NewInt(300), big.NewInt(360))
186+
wrap.Add("candidate4", big.NewInt(1000), big.NewInt(1200))
187+
wrap2 := newCandidateVotesWrapper(wrap)
188+
wrap2.Add("candidate2", big.NewInt(500), big.NewInt(600))
189+
wrap2.Add("candidate5", big.NewInt(2000), big.NewInt(2400))
190+
wrap3 := newCandidateVotesWrapper(wrap)
191+
wrap3.Add("candidate3", big.NewInt(700), big.NewInt(840))
192+
wrap3.Add("candidate1", big.NewInt(-3000), big.NewInt(-3600))
193+
wrap3Clone := wrap3.Clone()
194+
195+
// base return the first base
196+
wrap4 := newCandidateVotesWrapperCommitInClone(wrap3)
197+
wrap4.Add("candidate2", big.NewInt(3000), big.NewInt(3600))
198+
wrap4.Add("candidate3", big.NewInt(4000), big.NewInt(4800))
199+
candidateVotesEqual(r, wrap4.Base(), wrap3, []string{"candidate1", "candidate2", "candidate3", "candidate4", "candidate5"})
200+
// commit wrap4 should not apply all changes to base
201+
wrap4.Commit()
202+
candidateVotesEqual(r, wrap3, wrap3Clone, []string{"candidate1", "candidate2", "candidate3", "candidate4", "candidate5"})
203+
}
204+
205+
func candidateVotesEqual(r *require.Assertions, cv1, cv2 CandidateVotes, cands []string) {
206+
g := genesis.TestDefault()
207+
ctx := genesis.WithGenesisContext(context.Background(), g)
208+
ctxBeforeRedsea := protocol.MustGetFeatureCtx(protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: g.RedseaBlockHeight - 1})))
209+
ctxAfterRedsea := protocol.MustGetFeatureCtx(protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: g.RedseaBlockHeight})))
210+
for _, cand := range cands {
211+
r.Equal(cv1.Votes(ctxBeforeRedsea, cand), cv2.Votes(ctxBeforeRedsea, cand))
212+
r.Equal(cv1.Votes(ctxAfterRedsea, cand), cv2.Votes(ctxAfterRedsea, cand))
213+
}
214+
}

systemcontractindex/stakingindex/index.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,5 +401,5 @@ func AggregateCandidateVotes(bkts map[uint64]*Bucket, calculateUnmutedVoteWeight
401401
votes := calculateUnmutedVoteWeight(bkt)
402402
res.Add(bkt.Candidate.String(), bkt.StakedAmount, votes)
403403
}
404-
return res
404+
return newCandidateVotesWithBuffer(res)
405405
}

systemcontractindex/stakingindex/voteview_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func TestVoteView(t *testing.T) {
2222
defer ctrl.Finish()
2323

2424
mockIndexer := staking.NewMockContractStakingIndexer(ctrl)
25+
mr := NewCandidateVotesManager(identityset.Address(0))
2526
vv := NewVoteView(
2627
mockIndexer,
2728
&VoteViewConfig{
@@ -30,6 +31,7 @@ func TestVoteView(t *testing.T) {
3031
100,
3132
nil,
3233
nil,
34+
mr,
3335
func(b *contractstaking.Bucket, h uint64) *big.Int {
3436
return big.NewInt(1)
3537
},

0 commit comments

Comments
 (0)