aboutsummaryrefslogblamecommitdiffstats
path: root/core/nodeset-cache.go
blob: 49521ab2db2e7df7d0db9ac29311c028d7c42f9e (plain) (tree)



































                                                                               
                                         









                                                       
                                                        
















                                                                     
                                    
















                                                                  


                                                            






                                                               
                          







                                                                         
                                                        




                                                            
                                          





                                               
                                 

                                           
                             














                                                              
                                           










                                                          
                                                          






                                          
// Copyright 2018 The dexon-consensus-core Authors
// This file is part of the dexon-consensus-core library.
//
// The dexon-consensus-core library is free software: you can redistribute it
// and/or modify it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// The dexon-consensus-core library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the dexon-consensus-core library. If not, see
// <http://www.gnu.org/licenses/>.

package core

import (
    "errors"
    "sync"

    "github.com/dexon-foundation/dexon-consensus-core/core/crypto"
    "github.com/dexon-foundation/dexon-consensus-core/core/types"
)

var (
    // ErrRoundNotReady means we got nil config from governance contract.
    ErrRoundNotReady = errors.New("round is not ready")
)

// NodeSetCache caches node set information from governance contract.
type NodeSetCache struct {
    lock    sync.RWMutex
    gov     Governance
    rounds  map[uint64]*types.NodeSet
    keyPool map[types.NodeID]*struct {
        pubKey crypto.PublicKey
        refCnt int
    }
}

// NewNodeSetCache constructs an NodeSetCache instance.
func NewNodeSetCache(gov Governance) *NodeSetCache {
    return &NodeSetCache{
        gov:    gov,
        rounds: make(map[uint64]*types.NodeSet),
        keyPool: make(map[types.NodeID]*struct {
            pubKey crypto.PublicKey
            refCnt int
        }),
    }
}

// Exists checks if a node is in node set of that round.
func (cache *NodeSetCache) Exists(
    round uint64, nodeID types.NodeID) (exists bool, err error) {

    nIDs, exists := cache.get(round)
    if !exists {
        if nIDs, err = cache.update(round); err != nil {
            return
        }
    }
    _, exists = nIDs.IDs[nodeID]
    return
}

// GetPublicKey return public key for that node:
func (cache *NodeSetCache) GetPublicKey(
    nodeID types.NodeID) (key crypto.PublicKey, exists bool) {

    cache.lock.RLock()
    defer cache.lock.RUnlock()

    rec, exists := cache.keyPool[nodeID]
    if exists {
        key = rec.pubKey
    }
    return
}

// GetNodeSet returns IDs of nodes set of this round as map.
func (cache *NodeSetCache) GetNodeSet(
    round uint64) (nIDs *types.NodeSet, err error) {

    IDs, exists := cache.get(round)
    if !exists {
        if IDs, err = cache.update(round); err != nil {
            return
        }
    }
    nIDs = IDs.Clone()
    return
}

// update node set for that round.
//
// This cache would maintain 10 rounds before the updated round and purge
// rounds not in this range.
func (cache *NodeSetCache) update(
    round uint64) (nIDs *types.NodeSet, err error) {

    cache.lock.Lock()
    defer cache.lock.Unlock()

    // Get the requested round from governance contract.
    keySet := cache.gov.NodeSet(round)
    if keySet == nil {
        // That round is not ready yet.
        err = ErrRoundNotReady
        return
    }
    // Cache new round.
    nIDs = types.NewNodeSet()
    for _, key := range keySet {
        nID := types.NewNodeID(key)
        nIDs.Add(nID)
        if rec, exists := cache.keyPool[nID]; exists {
            rec.refCnt++
        } else {
            cache.keyPool[nID] = &struct {
                pubKey crypto.PublicKey
                refCnt int
            }{key, 1}
        }
    }
    cache.rounds[round] = nIDs
    // Purge older rounds.
    for rID, nIDs := range cache.rounds {
        if round-rID <= 5 {
            continue
        }
        for nID := range nIDs.IDs {
            rec := cache.keyPool[nID]
            if rec.refCnt--; rec.refCnt == 0 {
                delete(cache.keyPool, nID)
            }
        }
        delete(cache.rounds, rID)
    }
    return
}

func (cache *NodeSetCache) get(
    round uint64) (nIDs *types.NodeSet, exists bool) {

    cache.lock.RLock()
    defer cache.lock.RUnlock()

    nIDs, exists = cache.rounds[round]
    return
}