forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 2
/
keeper.go
422 lines (380 loc) · 16.1 KB
/
keeper.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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
package keeper
import (
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/quarantine"
)
type Keeper struct {
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
bankKeeper quarantine.BankKeeper
fundsHolder sdk.AccAddress
}
func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, bankKeeper quarantine.BankKeeper, fundsHolder sdk.AccAddress) Keeper {
if len(fundsHolder) == 0 {
fundsHolder = authtypes.NewModuleAddress(quarantine.ModuleName)
}
rv := Keeper{
cdc: cdc,
storeKey: storeKey,
bankKeeper: bankKeeper,
fundsHolder: fundsHolder,
}
bankKeeper.AppendSendRestriction(rv.SendRestrictionFn)
return rv
}
// GetFundsHolder returns the account address that holds quarantined funds.
func (k Keeper) GetFundsHolder() sdk.AccAddress {
return k.fundsHolder
}
// SetOptIn records that an address has opted into quarantine.
func (k Keeper) SetOptIn(ctx sdk.Context, toAddr sdk.AccAddress) error {
key := quarantine.CreateOptInKey(toAddr)
store := ctx.KVStore(k.storeKey)
store.Set(key, []byte{0x00})
return ctx.EventManager().EmitTypedEvent(&quarantine.EventOptIn{ToAddress: toAddr.String()})
}
// SetOptOut removes an address' quarantine opt-in record.
func (k Keeper) SetOptOut(ctx sdk.Context, toAddr sdk.AccAddress) error {
key := quarantine.CreateOptInKey(toAddr)
store := ctx.KVStore(k.storeKey)
store.Delete(key)
return ctx.EventManager().EmitTypedEvent(&quarantine.EventOptOut{ToAddress: toAddr.String()})
}
// IsQuarantinedAddr returns true if the given address has opted into quarantine.
func (k Keeper) IsQuarantinedAddr(ctx sdk.Context, toAddr sdk.AccAddress) bool {
key := quarantine.CreateOptInKey(toAddr)
store := ctx.KVStore(k.storeKey)
return store.Has(key)
}
// getQuarantinedAccountsPrefixStore returns a kv store prefixed for quarantine opt-in entries, and the prefix bytes.
func (k Keeper) getQuarantinedAccountsPrefixStore(ctx sdk.Context) (sdk.KVStore, []byte) {
return prefix.NewStore(ctx.KVStore(k.storeKey), quarantine.OptInPrefix), quarantine.OptInPrefix
}
// IterateQuarantinedAccounts iterates over all quarantine account addresses.
// The callback function should accept the to address (that has quarantine enabled).
// It should return whether to stop iteration early. I.e. false will allow iteration to continue, true will stop iteration.
func (k Keeper) IterateQuarantinedAccounts(ctx sdk.Context, cb func(toAddr sdk.AccAddress) (stop bool)) {
store, pre := k.getQuarantinedAccountsPrefixStore(ctx)
iter := store.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
addr := quarantine.ParseOptInKey(quarantine.MakeKey(pre, iter.Key()))
if cb(addr) {
break
}
}
}
// SetAutoResponse sets the auto response of sends to toAddr from fromAddr.
// If the response is AUTO_RESPONSE_UNSPECIFIED, the auto-response record is deleted,
// otherwise it is created/updated with the given setting.
func (k Keeper) SetAutoResponse(ctx sdk.Context, toAddr, fromAddr sdk.AccAddress, response quarantine.AutoResponse) {
key := quarantine.CreateAutoResponseKey(toAddr, fromAddr)
val := quarantine.ToAutoB(response)
store := ctx.KVStore(k.storeKey)
if val == quarantine.NoAutoB {
store.Delete(key)
} else {
store.Set(key, []byte{val})
}
}
// GetAutoResponse returns the quarantine auto-response for the given to/from addresses.
func (k Keeper) GetAutoResponse(ctx sdk.Context, toAddr, fromAddr sdk.AccAddress) quarantine.AutoResponse {
if toAddr.Equals(fromAddr) {
return quarantine.AUTO_RESPONSE_ACCEPT
}
key := quarantine.CreateAutoResponseKey(toAddr, fromAddr)
store := ctx.KVStore(k.storeKey)
bz := store.Get(key)
return quarantine.ToAutoResponse(bz)
}
// IsAutoAccept returns true if the to address has enabled auto-accept for ALL the from address.
func (k Keeper) IsAutoAccept(ctx sdk.Context, toAddr sdk.AccAddress, fromAddrs ...sdk.AccAddress) bool {
for _, fromAddr := range fromAddrs {
if !k.GetAutoResponse(ctx, toAddr, fromAddr).IsAccept() {
return false
}
}
return true
}
// IsAutoDecline returns true if the to address has enabled auto-decline for ANY of the from address.
func (k Keeper) IsAutoDecline(ctx sdk.Context, toAddr sdk.AccAddress, fromAddrs ...sdk.AccAddress) bool {
for _, fromAddr := range fromAddrs {
if k.GetAutoResponse(ctx, toAddr, fromAddr).IsDecline() {
return true
}
}
return false
}
// getAutoResponsesPrefixStore returns a kv store prefixed for quarantine auto-responses and the prefix used.
// If a toAddr is provided, the store is prefixed for just the given address.
// If toAddr is empty, it will be prefixed for all quarantine auto-responses.
func (k Keeper) getAutoResponsesPrefixStore(ctx sdk.Context, toAddr sdk.AccAddress) (sdk.KVStore, []byte) {
pre := quarantine.AutoResponsePrefix
if len(toAddr) > 0 {
pre = quarantine.CreateAutoResponseToAddrPrefix(toAddr)
}
return prefix.NewStore(ctx.KVStore(k.storeKey), pre), pre
}
// IterateAutoResponses iterates over the auto-responses for a given recipient address,
// or if no address is provided, iterates over all auto-response entries.
// The callback function should accept a to address, from address, and auto-response setting (in that order).
// It should return whether to stop iteration early. I.e. false will allow iteration to continue, true will stop iteration.
func (k Keeper) IterateAutoResponses(ctx sdk.Context, toAddr sdk.AccAddress, cb func(toAddr, fromAddr sdk.AccAddress, response quarantine.AutoResponse) (stop bool)) {
store, pre := k.getAutoResponsesPrefixStore(ctx, toAddr)
iter := store.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
kToAddr, kFromAddr := quarantine.ParseAutoResponseKey(quarantine.MakeKey(pre, iter.Key()))
val := quarantine.ToAutoResponse(iter.Value())
if cb(kToAddr, kFromAddr, val) {
break
}
}
}
// SetQuarantineRecord sets a quarantine record.
// Panics if the record is nil.
// If the record is fully accepted, it is deleted.
// Otherwise, it is saved.
func (k Keeper) SetQuarantineRecord(ctx sdk.Context, toAddr sdk.AccAddress, record *quarantine.QuarantineRecord) {
if record == nil {
panic("record cannot be nil")
}
fromAddrs := record.GetAllFromAddrs()
key := quarantine.CreateRecordKey(toAddr, fromAddrs...)
store := ctx.KVStore(k.storeKey)
if record.IsFullyAccepted() {
store.Delete(key)
if len(fromAddrs) > 1 {
_, suffix := quarantine.ParseRecordIndexKey(key)
k.deleteQuarantineRecordSuffixIndexes(store, toAddr, fromAddrs, suffix)
}
} else {
val := k.cdc.MustMarshal(record)
store.Set(key, val)
if len(fromAddrs) > 1 {
_, suffix := quarantine.ParseRecordIndexKey(key)
k.addQuarantineRecordSuffixIndexes(store, toAddr, fromAddrs, suffix)
}
}
}
// bzToQuarantineRecord converts the given byte slice into a QuarantineRecord or returns an error.
// If the byte slice is nil or empty, a default QuarantineRecord is returned with zero coins.
func (k Keeper) bzToQuarantineRecord(bz []byte) (*quarantine.QuarantineRecord, error) {
qf := quarantine.QuarantineRecord{
Coins: sdk.Coins{},
}
if len(bz) > 0 {
err := k.cdc.Unmarshal(bz, &qf)
if err != nil {
return &qf, err
}
}
return &qf, nil
}
// mustBzToQuarantineRecord returns bzToQuarantineRecord but panics on error.
func (k Keeper) mustBzToQuarantineRecord(bz []byte) *quarantine.QuarantineRecord {
qf, err := k.bzToQuarantineRecord(bz)
if err != nil {
panic(err)
}
return qf
}
// GetQuarantineRecord gets the single quarantine record to toAddr from all the fromAddrs.
// If the record doesn't exist, nil is returned.
//
// If you want all records from any of the fromAddrs, use GetQuarantineRecords.
func (k Keeper) GetQuarantineRecord(ctx sdk.Context, toAddr sdk.AccAddress, fromAddrs ...sdk.AccAddress) *quarantine.QuarantineRecord {
store := ctx.KVStore(k.storeKey)
key := quarantine.CreateRecordKey(toAddr, fromAddrs...)
if store.Has(key) {
bz := store.Get(key)
qr := k.mustBzToQuarantineRecord(bz)
return qr
}
return nil
}
// GetQuarantineRecords gets all the quarantine records to toAddr that involved any of the fromAddrs.
//
// If you want a single record from all the fromAddrs, use GetQuarantineRecord.
func (k Keeper) GetQuarantineRecords(ctx sdk.Context, toAddr sdk.AccAddress, fromAddrs ...sdk.AccAddress) []*quarantine.QuarantineRecord {
store := ctx.KVStore(k.storeKey)
allSuffixes := k.getQuarantineRecordSuffixes(store, toAddr, fromAddrs)
var rv []*quarantine.QuarantineRecord
for _, suffix := range allSuffixes {
key := quarantine.CreateRecordKey(toAddr, suffix)
if store.Has(key) {
bz := store.Get(key)
rv = append(rv, k.mustBzToQuarantineRecord(bz))
}
}
return rv
}
// AddQuarantinedCoins records that some new funds have been quarantined.
func (k Keeper) AddQuarantinedCoins(ctx sdk.Context, coins sdk.Coins, toAddr sdk.AccAddress, fromAddrs ...sdk.AccAddress) error {
qr := k.GetQuarantineRecord(ctx, toAddr, fromAddrs...)
if qr != nil {
qr.AddCoins(coins...)
} else {
qr = &quarantine.QuarantineRecord{
Coins: coins,
}
for _, fromAddr := range fromAddrs {
if k.IsAutoAccept(ctx, toAddr, fromAddr) {
qr.AcceptedFromAddresses = append(qr.AcceptedFromAddresses, fromAddr)
} else {
qr.UnacceptedFromAddresses = append(qr.UnacceptedFromAddresses, fromAddr)
}
}
}
if qr.IsFullyAccepted() {
fromAddrStrs := make([]string, len(fromAddrs))
for i, addr := range fromAddrs {
fromAddrStrs[i] = addr.String()
}
return fmt.Errorf("cannot add quarantined funds %q to %s from %s: already fully accepted",
coins.String(), toAddr.String(), strings.Join(fromAddrStrs, ", "))
}
// Regardless of if its new or existing, set declined based on current auto-decline info.
qr.Declined = k.IsAutoDecline(ctx, toAddr, fromAddrs...)
k.SetQuarantineRecord(ctx, toAddr, qr)
return ctx.EventManager().EmitTypedEvent(&quarantine.EventFundsQuarantined{
ToAddress: toAddr.String(),
Coins: coins,
})
}
// AcceptQuarantinedFunds looks up all quarantined funds to toAddr from any of the fromAddrs.
// It marks and saves each as accepted and, if fully accepted, releases (sends) the funds to toAddr.
// Returns total funds released and possibly an error.
func (k Keeper) AcceptQuarantinedFunds(ctx sdk.Context, toAddr sdk.AccAddress, fromAddrs ...sdk.AccAddress) (sdk.Coins, error) {
fundsReleased := sdk.Coins{}
for _, record := range k.GetQuarantineRecords(ctx, toAddr, fromAddrs...) {
if record.AcceptFrom(fromAddrs) {
if record.IsFullyAccepted() {
err := k.bankKeeper.SendCoins(quarantine.WithBypass(ctx), k.fundsHolder, toAddr, record.Coins)
if err != nil {
return nil, err
}
fundsReleased = fundsReleased.Add(record.Coins...)
err = ctx.EventManager().EmitTypedEvent(&quarantine.EventFundsReleased{
ToAddress: toAddr.String(),
Coins: record.Coins,
})
if err != nil {
return nil, err
}
} else {
// update declined to false unless one of the unaccepted from addresses is set to auto-decline.
record.Declined = k.IsAutoDecline(ctx, toAddr, record.UnacceptedFromAddresses...)
}
k.SetQuarantineRecord(ctx, toAddr, record)
}
}
return fundsReleased, nil
}
// DeclineQuarantinedFunds marks as declined, all quarantined funds to toAddr where any fromAddr is a sender.
func (k Keeper) DeclineQuarantinedFunds(ctx sdk.Context, toAddr sdk.AccAddress, fromAddrs ...sdk.AccAddress) {
for _, record := range k.GetQuarantineRecords(ctx, toAddr, fromAddrs...) {
if record.DeclineFrom(fromAddrs) {
k.SetQuarantineRecord(ctx, toAddr, record)
}
}
}
// getQuarantineRecordPrefixStore returns a kv store prefixed for quarantine records and the prefix used.
// If a toAddr is provided, the store is prefixed for just the given address.
// If toAddr is empty, it will be prefixed for all quarantine records.
func (k Keeper) getQuarantineRecordPrefixStore(ctx sdk.Context, toAddr sdk.AccAddress) (sdk.KVStore, []byte) {
pre := quarantine.RecordPrefix
if len(toAddr) > 0 {
pre = quarantine.CreateRecordToAddrPrefix(toAddr)
}
return prefix.NewStore(ctx.KVStore(k.storeKey), pre), pre
}
// IterateQuarantineRecords iterates over the quarantine records for a given recipient address,
// or if no address is provided, iterates over all quarantine records.
// The callback function should accept a to address, record suffix, and QuarantineRecord (in that order).
// It should return whether to stop iteration early. I.e. false will allow iteration to continue, true will stop iteration.
func (k Keeper) IterateQuarantineRecords(ctx sdk.Context, toAddr sdk.AccAddress, cb func(toAddr, recordSuffix sdk.AccAddress, record *quarantine.QuarantineRecord) (stop bool)) {
store, pre := k.getQuarantineRecordPrefixStore(ctx, toAddr)
iter := store.Iterator(nil, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
kToAddr, kRecordSuffix := quarantine.ParseRecordKey(quarantine.MakeKey(pre, iter.Key()))
qf := k.mustBzToQuarantineRecord(iter.Value())
if cb(kToAddr, kRecordSuffix, qf) {
break
}
}
}
// setQuarantineRecordSuffixIndex writes the provided suffix index.
// If it is nil or there are no record suffixes, the entry is instead deleted.
func (k Keeper) setQuarantineRecordSuffixIndex(store sdk.KVStore, key []byte, value *quarantine.QuarantineRecordSuffixIndex) {
if value == nil || len(value.RecordSuffixes) == 0 {
store.Delete(key)
} else {
val := k.cdc.MustMarshal(value)
store.Set(key, val)
}
}
// bzToQuarantineRecordSuffixIndex converts the given byte slice into a QuarantineRecordSuffixIndex or returns an error.
// If the byte slice is nil or empty, a default QuarantineRecordSuffixIndex is returned with no suffixes.
func (k Keeper) bzToQuarantineRecordSuffixIndex(bz []byte) (*quarantine.QuarantineRecordSuffixIndex, error) {
var si quarantine.QuarantineRecordSuffixIndex
if len(bz) > 0 {
err := k.cdc.Unmarshal(bz, &si)
if err != nil {
return &si, err
}
}
return &si, nil
}
// mustBzToQuarantineRecordSuffixIndex returns bzToQuarantineRecordSuffixIndex but panics on error.
func (k Keeper) mustBzToQuarantineRecordSuffixIndex(bz []byte) *quarantine.QuarantineRecordSuffixIndex {
si, err := k.bzToQuarantineRecordSuffixIndex(bz)
if err != nil {
panic(err)
}
return si
}
// getQuarantineRecordSuffixIndex gets a quarantine record suffix entry and it's key.
func (k Keeper) getQuarantineRecordSuffixIndex(store sdk.KVStore, toAddr, fromAddr sdk.AccAddress) (*quarantine.QuarantineRecordSuffixIndex, []byte) {
key := quarantine.CreateRecordIndexKey(toAddr, fromAddr)
bz := store.Get(key)
rv := k.mustBzToQuarantineRecordSuffixIndex(bz)
return rv, key
}
// getQuarantineRecordSuffixes gets a sorted list of known record suffixes of quarantine records to toAddr
// from any of the fromAddrs. The list will not contain duplicates, but may contain suffixes that don't point to records.
func (k Keeper) getQuarantineRecordSuffixes(store sdk.KVStore, toAddr sdk.AccAddress, fromAddrs []sdk.AccAddress) [][]byte {
rv := &quarantine.QuarantineRecordSuffixIndex{}
for _, fromAddr := range fromAddrs {
suffixes, _ := k.getQuarantineRecordSuffixIndex(store, toAddr, fromAddr)
rv.AddSuffixes(suffixes.RecordSuffixes...)
rv.AddSuffixes(fromAddr)
}
rv.Simplify()
return rv.RecordSuffixes
}
// addQuarantineRecordSuffixIndexes adds the provided suffix to all to/from suffix index entries.
func (k Keeper) addQuarantineRecordSuffixIndexes(store sdk.KVStore, toAddr sdk.AccAddress, fromAddrs []sdk.AccAddress, suffix []byte) {
for _, fromAddr := range fromAddrs {
ind, key := k.getQuarantineRecordSuffixIndex(store, toAddr, fromAddr)
ind.AddSuffixes(suffix)
ind.Simplify(fromAddr)
k.setQuarantineRecordSuffixIndex(store, key, ind)
}
}
// deleteQuarantineRecordSuffixIndexes removes the provided suffix from all to/from suffix index entries and either saves
// the updated list or deletes it if it's now empty.
func (k Keeper) deleteQuarantineRecordSuffixIndexes(store sdk.KVStore, toAddr sdk.AccAddress, fromAddrs []sdk.AccAddress, suffix []byte) {
for _, fromAddr := range fromAddrs {
ind, key := k.getQuarantineRecordSuffixIndex(store, toAddr, fromAddr)
ind.Simplify(fromAddr, suffix)
k.setQuarantineRecordSuffixIndex(store, key, ind)
}
}