-
Notifications
You must be signed in to change notification settings - Fork 8
/
cache.go
278 lines (250 loc) · 9.28 KB
/
cache.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package rings
import (
"context"
"fmt"
"sync"
"cosmossdk.io/depinject"
ring_secp256k1 "github.com/athanorlabs/go-dleq/secp256k1"
ringtypes "github.com/athanorlabs/go-dleq/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/noot/ring-go"
"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/crypto"
"github.com/pokt-network/poktroll/pkg/observable/channel"
"github.com/pokt-network/poktroll/pkg/polylog"
)
var _ crypto.RingCache = (*ringCache)(nil)
type ringCache struct {
// logger is the logger for the ring cache.
logger polylog.Logger
// ringPointsCache maintains a map of application addresses to the points
// on the secp256k1 curve that correspond to the public keys of the gateways
// the application is delegated to. These are used to build the app's ring.
ringPointsCache map[string][]ringtypes.Point
ringPointsMu *sync.RWMutex
// delegationClient is used to listen for on-chain delegation events and
// invalidate cache entries for rings that have been updated on chain.
delegationClient client.DelegationClient
// applicationQuerier is the querier for the application module, and is
// used to get the addresses of the gateways an application is delegated to.
applicationQuerier client.ApplicationQueryClient
// accountQuerier is the querier for the account module, and is used to get
// the public keys of the application and its delegated gateways.
accountQuerier client.AccountQueryClient
}
// NewRingCache returns a new RingCache instance. It requires a depinject.Config
// to be passed in, which is used to inject the dependencies of the RingCache.
//
// Required dependencies:
// - polylog.Logger
// - client.DelegationClient
// - client.ApplicationQueryClient
// - client.AccountQueryClient
func NewRingCache(deps depinject.Config) (crypto.RingCache, error) {
rc := &ringCache{
ringPointsCache: make(map[string][]ringtypes.Point),
ringPointsMu: &sync.RWMutex{},
}
// Supply the account and application queriers to the RingCache.
if err := depinject.Inject(
deps,
&rc.logger,
&rc.delegationClient,
&rc.applicationQuerier,
&rc.accountQuerier,
); err != nil {
return nil, err
}
return rc, nil
}
// Start starts the ring cache by subscribing to on-chain redelegation events.
func (rc *ringCache) Start(ctx context.Context) {
rc.logger.Info().Msg("starting ring cache")
// Listen for redelegation events and invalidate the cache if the
// redelegation event's address is stored in the cache.
go func() {
select {
case <-ctx.Done():
// Stop the ring cache if the context is cancelled.
rc.Stop()
}
}()
go rc.goInvalidateCache(ctx)
}
// goInvalidateCache listens for redelegation events and invalidates the
// cache if the app address in the redelegation event is stored in the cache.
// This function is intended to be run in a goroutine.
func (rc *ringCache) goInvalidateCache(ctx context.Context) {
// Get the latest redelegation replay observable.
redelegationObs := rc.delegationClient.RedelegationsSequence(ctx)
// For each redelegation event, check if the redelegation events'
// app address is in the cache. If it is, invalidate the cache entry.
channel.ForEach[client.Redelegation](
ctx, redelegationObs,
func(ctx context.Context, redelegation client.Redelegation) {
// Lock the cache for writing.
rc.ringPointsMu.Lock()
defer rc.ringPointsMu.Unlock()
// Check if the redelegation event's app address is in the cache.
if _, ok := rc.ringPointsCache[redelegation.GetAppAddress()]; ok {
rc.logger.Debug().
Str("app_address", redelegation.GetAppAddress()).
Msg("redelegation event received; invalidating cache entry")
// Invalidate the cache entry.
delete(rc.ringPointsCache, redelegation.GetAppAddress())
}
})
}
// Stop stops the ring cache by unsubscribing from on-chain redelegation events.
func (rc *ringCache) Stop() {
// Clear the cache.
rc.ringPointsMu.Lock()
rc.ringPointsCache = make(map[string][]ringtypes.Point)
rc.ringPointsMu.Unlock()
// Close the delegation client.
rc.delegationClient.Close()
}
// GetCachedAddresses returns the addresses of the applications that are
// currently cached in the ring cache.
func (rc *ringCache) GetCachedAddresses() []string {
rc.ringPointsMu.RLock()
defer rc.ringPointsMu.RUnlock()
keys := make([]string, 0, len(rc.ringPointsCache))
for k := range rc.ringPointsCache {
keys = append(keys, k)
}
return keys
}
// GetRingForAddress returns the ring for the address provided. If it does not
// exist in the cache, it will be created by querying the application module.
// and converting the addresses into their corresponding public key points on
// the secp256k1 curve. It will then be cached for future use.
func (rc *ringCache) GetRingForAddress(
ctx context.Context,
appAddress string,
) (*ring.Ring, error) {
var (
ring *ring.Ring
err error
)
// Lock the cache for reading.
rc.ringPointsMu.RLock()
// Check if the ring is in the cache.
points, ok := rc.ringPointsCache[appAddress]
// Unlock the cache in case it was not cached.
rc.ringPointsMu.RUnlock()
if !ok {
// If the ring is not in the cache, get it from the application module.
rc.logger.Debug().
Str("app_address", appAddress).
Msg("ring cache miss; fetching from application module")
ring, err = rc.getRingForAppAddress(ctx, appAddress)
} else {
// If the ring is in the cache, create it from the points.
rc.logger.Debug().
Str("app_address", appAddress).
Msg("ring cache hit; creating from points")
ring, err = newRingFromPoints(points)
}
if err != nil {
return nil, err
}
// Return the ring.
return ring, nil
}
// getRingForAppAddress returns the RingSinger used to sign relays. It does so by fetching
// the latest information from the application module and creating the correct ring.
// This method also caches the ring's public keys for future use.
func (rc *ringCache) getRingForAppAddress(
ctx context.Context,
appAddress string,
) (*ring.Ring, error) {
points, err := rc.getDelegatedPubKeysForAddress(ctx, appAddress)
if err != nil {
return nil, err
}
// Cache the ring's points for future use
rc.logger.Debug().
Str("app_address", appAddress).
Msg("updating ring cache for app")
rc.ringPointsMu.Lock()
defer rc.ringPointsMu.Unlock()
rc.ringPointsCache[appAddress] = points
return newRingFromPoints(points)
}
// newRingFromPoints creates a new ring from points on the secp256k1 curve
func newRingFromPoints(points []ringtypes.Point) (*ring.Ring, error) {
return ring.NewFixedKeyRingFromPublicKeys(ring_secp256k1.NewCurve(), points)
}
// getDelegatedPubKeysForAddress returns the ring used to sign a message for
// the given application address, by querying the application module for it's
// delegated pubkeys and converting them to points on the secp256k1 curve in
// order to create the ring.
func (rc *ringCache) getDelegatedPubKeysForAddress(
ctx context.Context,
appAddress string,
) ([]ringtypes.Point, error) {
rc.ringPointsMu.Lock()
defer rc.ringPointsMu.Unlock()
// Get the application's on chain state.
app, err := rc.applicationQuerier.GetApplication(ctx, appAddress)
if err != nil {
return nil, err
}
// Create a slice of addresses for the ring.
ringAddresses := make([]string, 0)
ringAddresses = append(ringAddresses, appAddress) // app address is index 0
if len(app.DelegateeGatewayAddresses) == 0 {
// add app address twice to make the ring size of mininmum 2
// TODO_HACK: We are adding the appAddress twice because a ring
// signature requires AT LEAST two pubKeys. When the Application has
// not delegated to any gateways, we add the application's own address
// twice. This is a HACK and should be investigated as to what is the
// best approach to take in this situation.
ringAddresses = append(ringAddresses, appAddress)
} else {
// add the delegatee gateway addresses
ringAddresses = append(ringAddresses, app.DelegateeGatewayAddresses...)
}
// Get the points on the secp256k1 curve for the addresses.
points, err := rc.addressesToPoints(ctx, ringAddresses)
if err != nil {
return nil, err
}
// Return the public key points on the secp256k1 curve.
return points, nil
}
// addressesToPoints converts a slice of addresses to a slice of points on the
// secp256k1 curve, by querying the account module for the public key for each
// address and converting them to the corresponding points on the secp256k1 curve
func (rc *ringCache) addressesToPoints(
ctx context.Context,
addresses []string,
) ([]ringtypes.Point, error) {
curve := ring_secp256k1.NewCurve()
points := make([]ringtypes.Point, len(addresses))
rc.logger.Debug().
// TODO_TECHDEBT: implement and use `polylog.Event#Strs([]string)` instead of formatting here.
Str("addresses", fmt.Sprintf("%v", addresses)).
Msg("converting addresses to points")
for i, addr := range addresses {
// Retrieve the account from the auth module
acc, err := rc.accountQuerier.GetAccount(ctx, addr)
if err != nil {
return nil, err
}
key := acc.GetPubKey()
// Check if the key is a secp256k1 public key
if _, ok := key.(*secp256k1.PubKey); !ok {
return nil, ErrRingsNotSecp256k1Curve.Wrapf("got %T", key)
}
// Convert the public key to the point on the secp256k1 curve
point, err := curve.DecodeToPoint(key.Bytes())
if err != nil {
return nil, err
}
// Insert the point into the slice of points
points[i] = point
}
return points, nil
}