chain/orphanpool.go
/**
* @file
* @copyright defined in aergo/LICENSE.txt
*/
package chain
import (
"errors"
"sync"
"github.com/aergoio/aergo/v2/internal/enc/base58"
"github.com/aergoio/aergo/v2/types"
"github.com/hashicorp/golang-lru/simplelru"
)
var (
DfltOrphanPoolSize = 100
ErrRemoveOldestOrphan = errors.New("failed to remove oldest orphan block")
ErrNotExistOrphanLRU = errors.New("given orphan doesn't exist in lru")
)
type OrphanBlock struct {
*types.Block
}
type OrphanPool struct {
sync.RWMutex
cache map[types.BlockID]*OrphanBlock
lru *simplelru.LRU
maxCnt int
curCnt int
}
func NewOrphanPool(size int) *OrphanPool {
lru, err := simplelru.NewLRU(DfltOrphanPoolSize, nil)
if err != nil {
logger.Fatal().Err(err).Msg("failed to init lru")
return nil
}
return &OrphanPool{
cache: map[types.BlockID]*OrphanBlock{},
lru: lru,
maxCnt: size,
curCnt: 0,
}
}
// add Orphan into the orphan cache pool
func (op *OrphanPool) addOrphan(block *types.Block) error {
logger.Warn().Str("prev", base58.Encode(block.GetHeader().GetPrevBlockHash())).Msg("add orphan Block")
id := types.ToBlockID(block.Header.PrevBlockHash)
cachedblock, exists := op.cache[id]
if exists {
logger.Debug().Str("hash", block.ID()).
Str("cached", cachedblock.ID()).Msg("already exist")
return nil
}
if op.isFull() {
logger.Debug().Msg("orphan block pool is full")
// replace one
if err := op.removeOldest(); err != nil {
return err
}
}
orpEntry := &OrphanBlock{Block: block}
op.cache[id] = orpEntry
op.lru.Add(id, orpEntry)
op.curCnt++
return nil
}
// get the BlockID of Root Block of Orphan branch
func (op *OrphanPool) getRoot(block *types.Block) types.BlockID {
orphanRoot := types.ToBlockID(block.Header.PrevBlockHash)
prevID := orphanRoot
for {
orphan, exists := op.cache[prevID]
if !exists {
break
}
orphanRoot = prevID
prevID = types.ToBlockID(orphan.Header.PrevBlockHash)
}
return orphanRoot
}
func (op *OrphanPool) isFull() bool {
return op.maxCnt == op.curCnt
}
// remove oldest block, but also remove expired
func (op *OrphanPool) removeOldest() error {
var (
id types.BlockID
)
if !op.isFull() {
return nil
}
key, _, ok := op.lru.GetOldest()
if !ok {
return ErrRemoveOldestOrphan
}
id = key.(types.BlockID)
if err := op.removeOrphan(id); err != nil {
return err
}
logger.Debug().Str("hash", id.String()).Msg("orphan block removed(oldest)")
return nil
}
// remove one single element by id (must succeed)
func (op *OrphanPool) removeOrphan(id types.BlockID) error {
op.curCnt--
delete(op.cache, id)
if exist := op.lru.Remove(id); !exist {
return ErrNotExistOrphanLRU
}
return nil
}
func (op *OrphanPool) getOrphan(hash []byte) *types.Block {
prevID := types.ToBlockID(hash)
orphan, exists := op.cache[prevID]
if !exists {
return nil
} else {
return orphan.Block
}
}