Skip to content

Commit 9e8b032

Browse files
committed
Commit messages tracker
- Removes oldest commit message when capacity is reached - Efficient removal of messages if they get processed - Efficient cleanup of oldest message - Uses a bit more space to store each block hash
1 parent 54c8b6b commit 9e8b032

File tree

3 files changed

+459
-15
lines changed

3 files changed

+459
-15
lines changed

lib/grandpa/message_tracker.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,26 @@ type tracker struct {
1919
blockState BlockState
2020
handler *MessageHandler
2121
votes votesTracker
22-
23-
// map of commit block hash to commit message
24-
commitMessages map[common.Hash]*CommitMessage
25-
mapLock sync.Mutex
26-
in chan *types.Block // receive imported block from BlockState
27-
stopped chan struct{}
22+
commits commitsTracker
23+
mapLock sync.Mutex
24+
in chan *types.Block // receive imported block from BlockState
25+
stopped chan struct{}
2826

2927
catchUpResponseMessageMutex sync.Mutex
3028
// round(uint64) is used as key and *CatchUpResponse as value
3129
catchUpResponseMessages map[uint64]*CatchUpResponse
3230
}
3331

3432
func newTracker(bs BlockState, handler *MessageHandler) *tracker {
35-
const votesCapacity = 1000
33+
const (
34+
votesCapacity = 1000
35+
commitsCapacity = 1000
36+
)
3637
return &tracker{
3738
blockState: bs,
3839
handler: handler,
3940
votes: newVotesTracker(votesCapacity),
40-
commitMessages: make(map[common.Hash]*CommitMessage),
41+
commits: newCommitsTracker(commitsCapacity),
4142
mapLock: sync.Mutex{},
4243
in: bs.GetImportedBlockNotifierChannel(),
4344
stopped: make(chan struct{}),
@@ -68,7 +69,7 @@ func (t *tracker) addVote(peerID peer.ID, message *VoteMessage) {
6869
func (t *tracker) addCommit(cm *CommitMessage) {
6970
t.mapLock.Lock()
7071
defer t.mapLock.Unlock()
71-
t.commitMessages[cm.Vote.Hash] = cm
72+
t.commits.add(cm)
7273
}
7374

7475
func (t *tracker) addCatchUpResponse(_ *CatchUpResponse) {
@@ -115,13 +116,14 @@ func (t *tracker) handleBlock(b *types.Block) {
115116

116117
t.votes.delete(h)
117118

118-
if cm, has := t.commitMessages[h]; has {
119+
cm := t.commits.getMessageForBlockHash(h)
120+
if cm != nil {
119121
_, err := t.handler.handleMessage("", cm)
120122
if err != nil {
121123
logger.Warnf("failed to handle commit message %v: %s", cm, err)
122124
}
123125

124-
delete(t.commitMessages, h)
126+
t.commits.delete(h)
125127
}
126128
}
127129

@@ -145,13 +147,16 @@ func (t *tracker) handleTick() {
145147
t.votes.delete(blockHashDone)
146148
}
147149

148-
for _, cm := range t.commitMessages {
150+
t.commits.forEach(func(cm *CommitMessage) {
149151
_, err := t.handler.handleMessage("", cm)
150152
if err != nil {
151153
logger.Debugf("failed to handle commit message %v: %s", cm, err)
152-
continue
154+
return
153155
}
154156

155-
delete(t.commitMessages, cm.Vote.Hash)
156-
}
157+
// deleting while iterating is safe to do since
158+
// each block hash has at most 1 commit message we
159+
// just handled above.
160+
t.commits.delete(cm.Vote.Hash)
161+
})
157162
}

lib/grandpa/tracker_commits.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2022 ChainSafe Systems (ON)
2+
// SPDX-License-Identifier: LGPL-3.0-only
3+
4+
package grandpa
5+
6+
import (
7+
"container/list"
8+
9+
"github.com/ChainSafe/gossamer/lib/common"
10+
)
11+
12+
// commitsTracker tracks vote messages that could
13+
// not be processed, and removes the oldest ones once
14+
// its maximum capacity is reached.
15+
// It is NOT THREAD SAFE to use.
16+
type commitsTracker struct {
17+
// map of commit block hash to data
18+
// data = message + tracking linked list element pointer
19+
mapping map[common.Hash]commitMessageMapData
20+
// double linked list of block hash
21+
// to track the order commit messages were added in.
22+
linkedList *list.List
23+
capacity int
24+
}
25+
26+
type commitMessageMapData struct {
27+
message *CommitMessage
28+
// element contains a block hash value.
29+
element *list.Element
30+
}
31+
32+
// newCommitsTracker creates a new commit messages tracker
33+
// with the capacity specified.
34+
func newCommitsTracker(capacity int) commitsTracker {
35+
return commitsTracker{
36+
mapping: make(map[common.Hash]commitMessageMapData, capacity),
37+
linkedList: list.New(),
38+
capacity: capacity,
39+
}
40+
}
41+
42+
// add adds a commit message to the commit message tracker.
43+
// If the commit message tracker capacity is reached,
44+
// the oldest commit message is removed.
45+
func (ct *commitsTracker) add(commitMessage *CommitMessage) {
46+
blockHash := commitMessage.Vote.Hash
47+
48+
data, has := ct.mapping[blockHash]
49+
if has {
50+
// commit already exists so override the commit for the block hash;
51+
// do not move the list element in the linked list to avoid
52+
// someone re-sending the same commit message and going at the
53+
// front of the list, hence erasing other possible valid commit messages
54+
// in the tracker.
55+
data.message = commitMessage
56+
ct.mapping[blockHash] = data
57+
return
58+
}
59+
60+
// add new block hash in tracker
61+
ct.cleanup()
62+
element := ct.linkedList.PushFront(blockHash)
63+
data = commitMessageMapData{
64+
message: commitMessage,
65+
element: element,
66+
}
67+
ct.mapping[blockHash] = data
68+
}
69+
70+
// cleanup removes the oldest commit message from the tracker
71+
// if the number of commit messages is at the tracker capacity.
72+
// This method is designed to be called automatically from the
73+
// add method and should not be called elsewhere.
74+
func (ct *commitsTracker) cleanup() {
75+
if ct.linkedList.Len() < ct.capacity {
76+
return
77+
}
78+
79+
oldestElement := ct.linkedList.Back()
80+
ct.linkedList.Remove(oldestElement)
81+
82+
oldestBlockHash := oldestElement.Value.(common.Hash)
83+
delete(ct.mapping, oldestBlockHash)
84+
}
85+
86+
// delete deletes all the vote messages for a particular
87+
// block hash from the vote messages tracker.
88+
func (ct *commitsTracker) delete(blockHash common.Hash) {
89+
data, has := ct.mapping[blockHash]
90+
if !has {
91+
return
92+
}
93+
94+
ct.linkedList.Remove(data.element)
95+
delete(ct.mapping, blockHash)
96+
}
97+
98+
// getMessageForBlockHash returns a pointer to the
99+
// commit message for a particular block hash from
100+
// the tracker. It returns nil if the block hash
101+
// does not exist in the tracker
102+
func (ct *commitsTracker) getMessageForBlockHash(
103+
blockHash common.Hash) (message *CommitMessage) {
104+
data, ok := ct.mapping[blockHash]
105+
if !ok {
106+
return nil
107+
}
108+
109+
return data.message
110+
}
111+
112+
// forEach runs the function `f` on each
113+
// commit message stored in the tracker.
114+
func (ct *commitsTracker) forEach(f func(message *CommitMessage)) {
115+
for _, data := range ct.mapping {
116+
f(data.message)
117+
}
118+
}

0 commit comments

Comments
 (0)