Skip to content

Commit 943f7d1

Browse files
Move vote bubbling before poll termination (#2100)
1 parent e4b7e82 commit 943f7d1

File tree

2 files changed

+56
-94
lines changed

2 files changed

+56
-94
lines changed

snow/engine/snowman/transitive.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,10 @@ func (t *Transitive) Chits(ctx context.Context, nodeID ids.NodeID, requestID uin
274274

275275
// Will record chits once [blkID] has been issued into consensus
276276
v := &voter{
277-
t: t,
278-
vdr: nodeID,
279-
requestID: requestID,
280-
response: blkID,
277+
t: t,
278+
vdr: nodeID,
279+
requestID: requestID,
280+
responseOptions: []ids.ID{blkID},
281281
}
282282

283283
// Wait until [blkID] has been issued to consensus before applying this chit.

snow/engine/snowman/voter.go

Lines changed: 52 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import (
1515

1616
// Voter records chits received from [vdr] once its dependencies are met.
1717
type voter struct {
18-
t *Transitive
19-
vdr ids.NodeID
20-
requestID uint32
21-
response ids.ID
22-
deps set.Set[ids.ID]
18+
t *Transitive
19+
vdr ids.NodeID
20+
requestID uint32
21+
responseOptions []ids.ID
22+
deps set.Set[ids.ID]
2323
}
2424

2525
func (v *voter) Dependencies() set.Set[ids.ID] {
@@ -42,11 +42,24 @@ func (v *voter) Update(ctx context.Context) {
4242
return
4343
}
4444

45+
var (
46+
vote ids.ID
47+
shouldVote bool
48+
)
49+
for _, voteOption := range v.responseOptions {
50+
// To prevent any potential deadlocks with undisclosed dependencies,
51+
// votes must be bubbled to the nearest valid block
52+
vote, shouldVote = v.getProcessingAncestor(ctx, voteOption)
53+
if shouldVote {
54+
break
55+
}
56+
}
57+
4558
var results []bag.Bag[ids.ID]
46-
if v.response == ids.Empty {
47-
results = v.t.polls.Drop(v.requestID, v.vdr)
59+
if shouldVote {
60+
results = v.t.polls.Vote(v.requestID, v.vdr, vote)
4861
} else {
49-
results = v.t.polls.Vote(v.requestID, v.vdr, v.response)
62+
results = v.t.polls.Drop(v.requestID, v.vdr)
5063
}
5164

5265
if len(results) == 0 {
@@ -55,13 +68,6 @@ func (v *voter) Update(ctx context.Context) {
5568

5669
for _, result := range results {
5770
result := result
58-
v.t.Ctx.Log.Debug("filtering poll results",
59-
zap.Stringer("result", &result),
60-
)
61-
62-
// To prevent any potential deadlocks with un-disclosed dependencies,
63-
// votes must be bubbled to the nearest valid block
64-
result = v.bubbleVotes(ctx, result)
6571
v.t.Ctx.Log.Debug("finishing poll",
6672
zap.Stringer("result", &result),
6773
)
@@ -88,92 +94,48 @@ func (v *voter) Update(ctx context.Context) {
8894
v.t.repoll(ctx)
8995
}
9096

91-
// bubbleVotes bubbles the [votes] a set of the number of votes for specific
92-
// blkIDs that received votes in consensus, to their most recent ancestor that
93-
// has been issued to consensus.
97+
// getProcessingAncestor finds [initialVote]'s most recent ancestor that is
98+
// processing in consensus. If no ancestor could be found, false is returned.
9499
//
95-
// Note: bubbleVotes does not bubbleVotes to all of the ancestors in consensus,
96-
// just the most recent one. bubbling to the rest of the ancestors, which may
97-
// also be in consensus is handled in RecordPoll.
98-
func (v *voter) bubbleVotes(ctx context.Context, votes bag.Bag[ids.ID]) bag.Bag[ids.ID] {
99-
bubbledVotes := bag.Bag[ids.ID]{}
100-
101-
votesLoop:
102-
for _, vote := range votes.List() {
103-
count := votes.Count(vote)
104-
// use rootID in case of this is a non-verified block ID
105-
rootID := v.t.nonVerifieds.GetAncestor(vote)
106-
v.t.Ctx.Log.Verbo("bubbling vote(s) through unverified blocks",
107-
zap.Int("numVotes", count),
108-
zap.Stringer("voteID", vote),
109-
zap.Stringer("parentID", rootID),
110-
)
111-
112-
blk, err := v.t.GetBlock(ctx, rootID)
100+
// Note: If [initialVote] is processing, then [initialVote] will be returned.
101+
func (v *voter) getProcessingAncestor(ctx context.Context, initialVote ids.ID) (ids.ID, bool) {
102+
// If [bubbledVote] != [initialVote], it is guaranteed that [bubbledVote] is
103+
// in processing. Otherwise, we attempt to iterate through any blocks we
104+
// have at our disposal as a best-effort mechanism to find a valid ancestor.
105+
bubbledVote := v.t.nonVerifieds.GetAncestor(initialVote)
106+
for {
107+
blk, err := v.t.GetBlock(ctx, bubbledVote)
113108
// If we cannot retrieve the block, drop [vote]
114109
if err != nil {
115-
v.t.Ctx.Log.Debug("dropping vote(s)",
116-
zap.String("reason", "parent couldn't be fetched"),
117-
zap.Stringer("parentID", rootID),
118-
zap.Int("numVotes", count),
119-
zap.Stringer("voteID", vote),
110+
v.t.Ctx.Log.Debug("dropping vote",
111+
zap.String("reason", "ancestor couldn't be fetched"),
112+
zap.Stringer("initialVoteID", initialVote),
113+
zap.Stringer("bubbledVoteID", bubbledVote),
120114
zap.Error(err),
121115
)
122-
continue
116+
return ids.Empty, false
123117
}
124118

125-
status := blk.Status()
126-
blkID := blk.ID()
127-
// If we have not fetched [blkID] break from the loop. We will drop the
128-
// vote below and move on to the next vote.
129-
//
130-
// If [blk] has already been decided, break from the loop, we will drop
131-
// the vote below since there is no need to count the votes for a [blk]
132-
// we've already finalized.
133-
//
134-
// If [blk] is currently in consensus, break from the loop, we have
135-
// reached the first ancestor of the original [vote] that has been
136-
// issued consensus. In this case, the votes will be bubbled further
137-
// from [blk] to any of its ancestors that are also in consensus.
138-
for status.Fetched() && !(v.t.Consensus.Decided(blk) || v.t.Consensus.Processing(blkID)) {
139-
parentID := blk.Parent()
140-
v.t.Ctx.Log.Verbo("pushing vote(s)",
141-
zap.Int("numVotes", count),
142-
zap.Stringer("voteID", vote),
143-
zap.Stringer("parentID", rootID),
119+
if v.t.Consensus.Decided(blk) {
120+
v.t.Ctx.Log.Debug("dropping vote",
121+
zap.String("reason", "bubbled vote already decided"),
122+
zap.Stringer("initialVoteID", initialVote),
123+
zap.Stringer("bubbledVoteID", bubbledVote),
124+
zap.Stringer("status", blk.Status()),
125+
zap.Uint64("height", blk.Height()),
144126
)
145-
146-
blkID = parentID
147-
blk, err = v.t.GetBlock(ctx, blkID)
148-
// If we cannot retrieve the block, drop [vote]
149-
if err != nil {
150-
v.t.Ctx.Log.Debug("dropping vote(s)",
151-
zap.String("reason", "block couldn't be fetched"),
152-
zap.Stringer("blkID", blkID),
153-
zap.Int("numVotes", count),
154-
zap.Stringer("voteID", vote),
155-
zap.Error(err),
156-
)
157-
continue votesLoop
158-
}
159-
status = blk.Status()
127+
return ids.Empty, false
160128
}
161129

162-
// If [blkID] is currently in consensus, count the votes
163-
if v.t.Consensus.Processing(blkID) {
164-
v.t.Ctx.Log.Verbo("applying vote(s)",
165-
zap.Int("numVotes", count),
166-
zap.Stringer("blkID", blkID),
167-
zap.Stringer("status", status),
168-
)
169-
bubbledVotes.AddCount(blkID, count)
170-
} else {
171-
v.t.Ctx.Log.Verbo("dropping vote(s)",
172-
zap.Int("numVotes", count),
173-
zap.Stringer("blkID", blkID),
174-
zap.Stringer("status", status),
130+
if v.t.Consensus.Processing(bubbledVote) {
131+
v.t.Ctx.Log.Verbo("applying vote",
132+
zap.Stringer("initialVoteID", initialVote),
133+
zap.Stringer("bubbledVoteID", bubbledVote),
134+
zap.Uint64("height", blk.Height()),
175135
)
136+
return bubbledVote, true
176137
}
138+
139+
bubbledVote = blk.Parent()
177140
}
178-
return bubbledVotes
179141
}

0 commit comments

Comments
 (0)