Skip to content

Commit

Permalink
fix(lib/grandpa): capped number of tracked commit messages (#2490)
Browse files Browse the repository at this point in the history
- Removes oldest commit message when tracker capacity is reached
- Efficient removal of multiple messages at any place in the tracker queue (linked list) if they get processed
- Efficient removal of oldest message
- Uses a bit more space to store each block hash (+2 pointers) for each commit message
- Order is not modified for the same commit message (same block hash)
  • Loading branch information
qdm12 committed Jun 2, 2022
1 parent 4d2b2f6 commit 47c23e6
Show file tree
Hide file tree
Showing 3 changed files with 449 additions and 16 deletions.
108 changes: 108 additions & 0 deletions lib/grandpa/commits_tracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2022 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package grandpa

import (
"container/list"

"github.com/ChainSafe/gossamer/lib/common"
)

// commitsTracker tracks vote messages that could
// not be processed, and removes the oldest ones once
// its maximum capacity is reached.
// It is NOT THREAD SAFE to use.
type commitsTracker struct {
// map of commit block hash to linked list commit message.
mapping map[common.Hash]*list.Element
// double linked list of commit messages
// to track the order commit messages were added in.
linkedList *list.List
capacity int
}

// newCommitsTracker creates a new commit messages tracker
// with the capacity specified.
func newCommitsTracker(capacity int) commitsTracker {
return commitsTracker{
mapping: make(map[common.Hash]*list.Element, capacity),
linkedList: list.New(),
capacity: capacity,
}
}

// add adds a commit message to the commit message tracker.
// If the commit message tracker capacity is reached,
// the oldest commit message is removed.
func (ct *commitsTracker) add(commitMessage *CommitMessage) {
blockHash := commitMessage.Vote.Hash

listElement, has := ct.mapping[blockHash]
if has {
// commit already exists so override the commit message in the linked list;
// do not move the list element in the linked list to avoid
// someone re-sending the same commit message and going at the
// front of the list, hence erasing other possible valid commit messages
// in the tracker.
listElement.Value = commitMessage
return
}

// add new block hash in tracker
ct.cleanup()
listElement = ct.linkedList.PushFront(commitMessage)
ct.mapping[blockHash] = listElement
}

// cleanup removes the oldest commit message from the tracker
// if the number of commit messages is at the tracker capacity.
// This method is designed to be called automatically from the
// add method and should not be called elsewhere.
func (ct *commitsTracker) cleanup() {
if ct.linkedList.Len() < ct.capacity {
return
}

oldestElement := ct.linkedList.Back()
ct.linkedList.Remove(oldestElement)

oldestCommitMessage := oldestElement.Value.(*CommitMessage)
oldestBlockHash := oldestCommitMessage.Vote.Hash
delete(ct.mapping, oldestBlockHash)
}

// delete deletes all the vote messages for a particular
// block hash from the vote messages tracker.
func (ct *commitsTracker) delete(blockHash common.Hash) {
listElement, has := ct.mapping[blockHash]
if !has {
return
}

ct.linkedList.Remove(listElement)
delete(ct.mapping, blockHash)
}

// message returns a pointer to the
// commit message for a particular block hash from
// the tracker. It returns nil if the block hash
// does not exist in the tracker
func (ct *commitsTracker) message(blockHash common.Hash) (
message *CommitMessage) {
listElement, ok := ct.mapping[blockHash]
if !ok {
return nil
}

return listElement.Value.(*CommitMessage)
}

// forEach runs the function `f` on each
// commit message stored in the tracker.
func (ct *commitsTracker) forEach(f func(message *CommitMessage)) {
for _, data := range ct.mapping {
message := data.Value.(*CommitMessage)
f(message)
}
}
Loading

0 comments on commit 47c23e6

Please sign in to comment.