aboutsummaryrefslogtreecommitdiffstats
path: root/core/nodeset-cache.go
blob: 610131b8aff053df0dc73f6f7bc76f0d6ccfecbd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// 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.GetNodeSet(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
}