129 lines
3.1 KiB
Go
129 lines
3.1 KiB
Go
// Copyright (c) 2019-2020 The Zcash developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
|
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"sync"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/zcash/lightwalletd/walletrpc"
|
|
)
|
|
|
|
type blockCacheEntry struct {
|
|
data []byte
|
|
hash []byte
|
|
}
|
|
|
|
// BlockCache contains a set of recent compact blocks in marshalled form.
|
|
type BlockCache struct {
|
|
MaxEntries int
|
|
|
|
// m[firstBlock..nextBlock) are valid
|
|
m map[int]*blockCacheEntry
|
|
firstBlock int
|
|
nextBlock int
|
|
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewBlockCache returns an instance of a block cache object.
|
|
func NewBlockCache(maxEntries int) *BlockCache {
|
|
return &BlockCache{
|
|
MaxEntries: maxEntries,
|
|
m: make(map[int]*blockCacheEntry),
|
|
}
|
|
}
|
|
|
|
// Add adds the given block to the cache at the given height, returning true
|
|
// if a reorg was detected.
|
|
func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) (bool, error) {
|
|
// Invariant: m[firstBlock..nextBlock) are valid.
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
if height > c.nextBlock {
|
|
// restarting the cache (never happens currently), or first time
|
|
for i := c.firstBlock; i < c.nextBlock; i++ {
|
|
delete(c.m, i)
|
|
}
|
|
c.firstBlock = height
|
|
c.nextBlock = height
|
|
}
|
|
// Invariant: m[firstBlock..nextBlock) are valid.
|
|
|
|
// If we already have this block, a reorg must have occurred;
|
|
// this block (and all higher) must be re-added.
|
|
h := height
|
|
if h < c.firstBlock {
|
|
h = c.firstBlock
|
|
}
|
|
for i := h; i < c.nextBlock; i++ {
|
|
delete(c.m, i)
|
|
}
|
|
c.nextBlock = height
|
|
if c.firstBlock > c.nextBlock {
|
|
c.firstBlock = c.nextBlock
|
|
}
|
|
// Invariant: m[firstBlock..nextBlock) are valid.
|
|
|
|
// Detect reorg, ingestor needs to handle it
|
|
if height > c.firstBlock && !bytes.Equal(block.PrevHash, c.m[height-1].hash) {
|
|
return true, nil
|
|
}
|
|
|
|
// Add the entry and update the counters
|
|
data, err := proto.Marshal(block)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
c.m[height] = &blockCacheEntry{
|
|
data: data,
|
|
hash: block.GetHash(),
|
|
}
|
|
c.nextBlock++
|
|
// Invariant: m[firstBlock..nextBlock) are valid.
|
|
|
|
// remove any blocks that are older than the capacity of the cache
|
|
for c.firstBlock < c.nextBlock-c.MaxEntries {
|
|
// Invariant: m[firstBlock..nextBlock) are valid.
|
|
delete(c.m, c.firstBlock)
|
|
c.firstBlock++
|
|
}
|
|
// Invariant: m[firstBlock..nextBlock) are valid.
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// Get returns the compact block at the requested height if it is
|
|
// in the cache, else nil.
|
|
func (c *BlockCache) Get(height int) *walletrpc.CompactBlock {
|
|
c.mutex.RLock()
|
|
defer c.mutex.RUnlock()
|
|
|
|
if height < c.firstBlock || height >= c.nextBlock {
|
|
return nil
|
|
}
|
|
|
|
serialized := &walletrpc.CompactBlock{}
|
|
err := proto.Unmarshal(c.m[height].data, serialized)
|
|
if err != nil {
|
|
println("Error unmarshalling compact block")
|
|
return nil
|
|
}
|
|
|
|
return serialized
|
|
}
|
|
|
|
// GetLatestHeight returns the block with the greatest height, or nil
|
|
// if the cache is empty.
|
|
func (c *BlockCache) GetLatestHeight() int {
|
|
c.mutex.RLock()
|
|
defer c.mutex.RUnlock()
|
|
if c.firstBlock == c.nextBlock {
|
|
return -1
|
|
}
|
|
return c.nextBlock - 1
|
|
}
|