package lite
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"sort"
|
|
"sync"
|
|
|
|
liteErr "github.com/tendermint/tendermint/lite/errors"
|
|
)
|
|
|
|
type memStoreProvider struct {
|
|
mtx sync.RWMutex
|
|
// byHeight is always sorted by Height... need to support range search (nil, h]
|
|
// btree would be more efficient for larger sets
|
|
byHeight fullCommits
|
|
byHash map[string]FullCommit
|
|
|
|
sorted bool
|
|
}
|
|
|
|
// fullCommits just exists to allow easy sorting
|
|
type fullCommits []FullCommit
|
|
|
|
func (s fullCommits) Len() int { return len(s) }
|
|
func (s fullCommits) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
func (s fullCommits) Less(i, j int) bool {
|
|
return s[i].Height() < s[j].Height()
|
|
}
|
|
|
|
// NewMemStoreProvider returns a new in-memory provider.
|
|
func NewMemStoreProvider() Provider {
|
|
return &memStoreProvider{
|
|
byHeight: fullCommits{},
|
|
byHash: map[string]FullCommit{},
|
|
}
|
|
}
|
|
|
|
func (m *memStoreProvider) encodeHash(hash []byte) string {
|
|
return hex.EncodeToString(hash)
|
|
}
|
|
|
|
// StoreCommit stores a FullCommit after verifying it.
|
|
func (m *memStoreProvider) StoreCommit(fc FullCommit) error {
|
|
// make sure the fc is self-consistent before saving
|
|
err := fc.ValidateBasic(fc.Commit.Header.ChainID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// store the valid fc
|
|
key := m.encodeHash(fc.ValidatorsHash())
|
|
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
m.byHash[key] = fc
|
|
m.byHeight = append(m.byHeight, fc)
|
|
m.sorted = false
|
|
return nil
|
|
}
|
|
|
|
// GetByHeight returns the FullCommit for height h or an error if the commit is not found.
|
|
func (m *memStoreProvider) GetByHeight(h int64) (FullCommit, error) {
|
|
// By heuristics, GetByHeight with linearsearch is fast enough
|
|
// for about 50 keys but after that, it needs binary search.
|
|
// See https://github.com/tendermint/tendermint/pull/1043#issue-285188242
|
|
m.mtx.RLock()
|
|
n := len(m.byHeight)
|
|
m.mtx.RUnlock()
|
|
|
|
if n <= 50 {
|
|
return m.getByHeightLinearSearch(h)
|
|
}
|
|
return m.getByHeightBinarySearch(h)
|
|
}
|
|
|
|
func (m *memStoreProvider) sortByHeightIfNecessaryLocked() {
|
|
if !m.sorted {
|
|
sort.Sort(m.byHeight)
|
|
m.sorted = true
|
|
}
|
|
}
|
|
|
|
func (m *memStoreProvider) getByHeightLinearSearch(h int64) (FullCommit, error) {
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
m.sortByHeightIfNecessaryLocked()
|
|
// search from highest to lowest
|
|
for i := len(m.byHeight) - 1; i >= 0; i-- {
|
|
if fc := m.byHeight[i]; fc.Height() <= h {
|
|
return fc, nil
|
|
}
|
|
}
|
|
return FullCommit{}, liteErr.ErrCommitNotFound()
|
|
}
|
|
|
|
func (m *memStoreProvider) getByHeightBinarySearch(h int64) (FullCommit, error) {
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
m.sortByHeightIfNecessaryLocked()
|
|
low, high := 0, len(m.byHeight)-1
|
|
var mid int
|
|
var hmid int64
|
|
var midFC FullCommit
|
|
// Our goal is to either find:
|
|
// * item ByHeight with the query
|
|
// * greatest height with a height <= query
|
|
for low <= high {
|
|
mid = int(uint(low+high) >> 1) // Avoid an overflow
|
|
midFC = m.byHeight[mid]
|
|
hmid = midFC.Height()
|
|
switch {
|
|
case hmid == h:
|
|
return midFC, nil
|
|
case hmid < h:
|
|
low = mid + 1
|
|
case hmid > h:
|
|
high = mid - 1
|
|
}
|
|
}
|
|
|
|
if high >= 0 {
|
|
if highFC := m.byHeight[high]; highFC.Height() < h {
|
|
return highFC, nil
|
|
}
|
|
}
|
|
return FullCommit{}, liteErr.ErrCommitNotFound()
|
|
}
|
|
|
|
// GetByHash returns the FullCommit for the hash or an error if the commit is not found.
|
|
func (m *memStoreProvider) GetByHash(hash []byte) (FullCommit, error) {
|
|
m.mtx.RLock()
|
|
defer m.mtx.RUnlock()
|
|
|
|
fc, ok := m.byHash[m.encodeHash(hash)]
|
|
if !ok {
|
|
return fc, liteErr.ErrCommitNotFound()
|
|
}
|
|
return fc, nil
|
|
}
|
|
|
|
// LatestCommit returns the latest FullCommit or an error if no commits exist.
|
|
func (m *memStoreProvider) LatestCommit() (FullCommit, error) {
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
|
|
l := len(m.byHeight)
|
|
if l == 0 {
|
|
return FullCommit{}, liteErr.ErrCommitNotFound()
|
|
}
|
|
m.sortByHeightIfNecessaryLocked()
|
|
return m.byHeight[l-1], nil
|
|
}
|