aboutsummaryrefslogtreecommitdiffstats
path: root/core/nodeset-cache.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/nodeset-cache.go')
-rw-r--r--core/nodeset-cache.go158
1 files changed, 158 insertions, 0 deletions
diff --git a/core/nodeset-cache.go b/core/nodeset-cache.go
new file mode 100644
index 0000000..d574817
--- /dev/null
+++ b/core/nodeset-cache.go
@@ -0,0 +1,158 @@
+// 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]map[types.NodeID]struct{}
+ 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]map[types.NodeID]struct{}),
+ 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[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
+}
+
+// GetNodeIDs returns IDs of nodes set of this round as map.
+func (cache *NodeSetCache) GetNodeIDs(
+ round uint64) (nIDs map[types.NodeID]struct{}, err error) {
+
+ IDs, exists := cache.get(round)
+ if !exists {
+ if IDs, err = cache.update(round); err != nil {
+ return
+ }
+ }
+ // Clone the map.
+ nIDs = make(map[types.NodeID]struct{})
+ for ID := range IDs {
+ nIDs[ID] = struct{}{}
+ }
+ 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 map[types.NodeID]struct{}, err error) {
+
+ cache.lock.Lock()
+ defer cache.lock.Unlock()
+
+ // Get the requested round from governance contract.
+ keySet := cache.gov.GetNodeSet(round)
+ if keySet == nil {
+ // That round is not ready yet.
+ err = ErrRoundNotReady
+ return
+ }
+ // Cache new round.
+ nIDs = make(map[types.NodeID]struct{})
+ for _, key := range keySet {
+ nID := types.NewNodeID(key)
+ nIDs[nID] = struct{}{}
+ 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 {
+ 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 map[types.NodeID]struct{}, exists bool) {
+
+ cache.lock.RLock()
+ defer cache.lock.RUnlock()
+
+ nIDs, exists = cache.rounds[round]
+ return
+}