// 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 // . package core import ( "errors" "sync" "github.com/dexon-foundation/dexon-consensus-core/common" "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. ErrRoundNotReady = errors.New("round is not ready") ) type sets struct { nodeSet *types.NodeSet notarySet []map[types.NodeID]struct{} dkgSet map[types.NodeID]struct{} } // NodeSetCacheInterface interface specifies interface used by NodeSetCache. type NodeSetCacheInterface interface { // Configuration returns the configuration at a given round. // Return the genesis configuration if round == 0. Configuration(round uint64) *types.Config // CRS returns the CRS for a given round. // Return the genesis CRS if round == 0. CRS(round uint64) common.Hash // NodeSet returns the node set at a given round. // Return the genesis node set if round == 0. NodeSet(round uint64) []crypto.PublicKey } // NodeSetCache caches node set information. type NodeSetCache struct { lock sync.RWMutex nsIntf NodeSetCacheInterface rounds map[uint64]*sets keyPool map[types.NodeID]*struct { pubKey crypto.PublicKey refCnt int } } // NewNodeSetCache constructs an NodeSetCache instance. func NewNodeSetCache(nsIntf NodeSetCacheInterface) *NodeSetCache { return &NodeSetCache{ nsIntf: nsIntf, rounds: make(map[uint64]*sets), 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.nodeSet.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.nodeSet.Clone() return } // GetNotarySet returns of notary set of this round. func (cache *NodeSetCache) GetNotarySet( round uint64, chainID uint32) (map[types.NodeID]struct{}, error) { IDs, err := cache.getOrUpdate(round) if err != nil { return nil, err } if chainID >= uint32(len(IDs.notarySet)) { return nil, ErrInvalidChainID } return cache.cloneMap(IDs.notarySet[chainID]), nil } // GetDKGSet returns of DKG set of this round. func (cache *NodeSetCache) GetDKGSet( round uint64) (map[types.NodeID]struct{}, error) { IDs, err := cache.getOrUpdate(round) if err != nil { return nil, err } return cache.cloneMap(IDs.dkgSet), nil } func (cache *NodeSetCache) cloneMap( nIDs map[types.NodeID]struct{}) map[types.NodeID]struct{} { nIDsCopy := make(map[types.NodeID]struct{}, len(nIDs)) for k := range nIDs { nIDsCopy[k] = struct{}{} } return nIDsCopy } func (cache *NodeSetCache) getOrUpdate(round uint64) (nIDs *sets, err error) { s, exists := cache.get(round) if !exists { if s, err = cache.update(round); err != nil { return } } nIDs = s 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 *sets, err error) { cache.lock.Lock() defer cache.lock.Unlock() // Get the requested round. keySet := cache.nsIntf.NodeSet(round) if keySet == nil { // That round is not ready yet. err = ErrRoundNotReady return } // Cache new round. nodeSet := types.NewNodeSet() for _, key := range keySet { nID := types.NewNodeID(key) nodeSet.Add(nID) if rec, exists := cache.keyPool[nID]; exists { rec.refCnt++ } else { cache.keyPool[nID] = &struct { pubKey crypto.PublicKey refCnt int }{key, 1} } } cfg := cache.nsIntf.Configuration(round) crs := cache.nsIntf.CRS(round) nIDs = &sets{ nodeSet: nodeSet, notarySet: make([]map[types.NodeID]struct{}, cfg.NumChains), dkgSet: nodeSet.GetSubSet( int(cfg.DKGSetSize), types.NewDKGSetTarget(crs)), } for i := range nIDs.notarySet { nIDs.notarySet[i] = nodeSet.GetSubSet( int(cfg.NotarySetSize), types.NewNotarySetTarget(crs, uint32(i))) } cache.rounds[round] = nIDs // Purge older rounds. for rID, nIDs := range cache.rounds { nodeSet := nIDs.nodeSet if round-rID <= 5 { continue } for nID := range nodeSet.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 *sets, exists bool) { cache.lock.RLock() defer cache.lock.RUnlock() nIDs, exists = cache.rounds[round] return }