/
guardianset.go
178 lines (147 loc) · 4.65 KB
/
guardianset.go
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package common
import (
"fmt"
"sync"
"time"
gossipv1 "github.com/alephium/wormhole-fork/node/pkg/proto/gossip/v1"
"github.com/ethereum/go-ethereum/common"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
gsIndex = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "wormhole_guardian_set_index",
Help: "The guardians set index",
})
gsSigners = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "wormhole_guardian_set_signers",
Help: "Number of signers in the guardian set.",
})
)
// MaxGuardianCount specifies the maximum number of guardians supported by on-chain contracts.
//
// Matching constants:
// - MAX_LEN_GUARDIAN_KEYS in Solana contract (limited by transaction size - 19 is the maximum amount possible)
//
// The Eth and Terra contracts do not specify a maximum number and support more than that,
// but presumably, chain-specific transaction size limits will apply at some point (untested).
const MaxGuardianCount = 19
// MaxNodesPerGuardian specifies the maximum amount of nodes per guardian key that we'll accept
// whenever we maintain any per-guardian, per-node state.
//
// There currently isn't any state clean up, so the value is on the high side to prevent
// accidentally reaching the limit due to operational mistakes.
const MaxNodesPerGuardian = 15
// MaxStateAge specified the maximum age of state entries in seconds. Expired entries are purged
// from the state by Cleanup().
const MaxStateAge = 1 * time.Minute
type GuardianSet struct {
// Guardian's public key hashes truncated by the ETH standard hashing mechanism (20 bytes).
Keys []common.Address
// On-chain set index
Index uint32
}
func (g *GuardianSet) KeysAsHexStrings() []string {
r := make([]string, len(g.Keys))
for n, k := range g.Keys {
r[n] = k.Hex()
}
return r
}
// KeyIndex returns a given address index from the guardian set. Returns (-1, false)
// if the address wasn't found and (addr, true) otherwise.
func (g *GuardianSet) KeyIndex(addr common.Address) (int, bool) {
for n, k := range g.Keys {
if k == addr {
return n, true
}
}
return -1, false
}
type GuardianSetState struct {
mu sync.Mutex
current *GuardianSet
// Last heartbeat message received per guardian per p2p node. Maintained
// across guardian set updates - these values don't change.
lastHeartbeats map[common.Address]map[peer.ID]*gossipv1.Heartbeat
updateC chan *gossipv1.Heartbeat
}
func NewGuardianSetState(updateC chan *gossipv1.Heartbeat) *GuardianSetState {
return &GuardianSetState{
lastHeartbeats: map[common.Address]map[peer.ID]*gossipv1.Heartbeat{},
updateC: updateC,
}
}
func (st *GuardianSetState) Set(set *GuardianSet) {
st.mu.Lock()
gsIndex.Set(float64(set.Index))
gsSigners.Set(float64(len(set.Keys)))
defer st.mu.Unlock()
st.current = set
}
func (st *GuardianSetState) Get() *GuardianSet {
st.mu.Lock()
defer st.mu.Unlock()
return st.current
}
// LastHeartbeat returns the most recent heartbeat message received for
// a given guardian node, or nil if none have been received.
func (st *GuardianSetState) LastHeartbeat(addr common.Address) map[peer.ID]*gossipv1.Heartbeat {
st.mu.Lock()
defer st.mu.Unlock()
ret := make(map[peer.ID]*gossipv1.Heartbeat)
for k, v := range st.lastHeartbeats[addr] {
ret[k] = v
}
return ret
}
// SetHeartbeat stores a verified heartbeat observed by a given guardian.
func (st *GuardianSetState) SetHeartbeat(addr common.Address, peerId peer.ID, hb *gossipv1.Heartbeat) error {
st.mu.Lock()
defer st.mu.Unlock()
v, ok := st.lastHeartbeats[addr]
if !ok {
v = make(map[peer.ID]*gossipv1.Heartbeat)
st.lastHeartbeats[addr] = v
} else {
if len(v) >= MaxNodesPerGuardian {
// TODO: age out old entries?
return fmt.Errorf("too many nodes (%d) for guardian, cannot store entry", len(v))
}
}
v[peerId] = hb
if st.updateC != nil {
st.updateC <- hb
}
return nil
}
// GetAll returns all stored heartbeats.
func (st *GuardianSetState) GetAll() map[common.Address]map[peer.ID]*gossipv1.Heartbeat {
st.mu.Lock()
defer st.mu.Unlock()
ret := make(map[common.Address]map[peer.ID]*gossipv1.Heartbeat)
// Deep copy
for addr, v := range st.lastHeartbeats {
ret[addr] = make(map[peer.ID]*gossipv1.Heartbeat)
for peerId, hb := range v {
ret[addr][peerId] = hb
}
}
return ret
}
// Cleanup removes expired entries from the state.
func (st *GuardianSetState) Cleanup() {
st.mu.Lock()
defer st.mu.Unlock()
for addr, v := range st.lastHeartbeats {
for peerId, hb := range v {
ts := time.Unix(0, hb.Timestamp)
if time.Since(ts) > MaxStateAge {
delete(st.lastHeartbeats[addr], peerId)
}
}
}
}