aergoio/aergo

View on GitHub
chain/orphanpool.go

Summary

Maintainability
A
0 mins
Test Coverage
C
72%
/**
 *  @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
    }
}