@@ -2,10 +2,13 @@ package async_account
2
2
3
3
import (
4
4
"context"
5
+ "crypto/sha256"
5
6
"errors"
7
+ "math"
6
8
"sync"
7
9
"time"
8
10
11
+ "github.com/mr-tron/base58"
9
12
"github.com/newrelic/go-agent/v3/newrelic"
10
13
"github.com/sirupsen/logrus"
11
14
@@ -15,13 +18,14 @@ import (
15
18
code_data "github.com/code-payments/code-server/pkg/code/data"
16
19
"github.com/code-payments/code-server/pkg/code/data/account"
17
20
"github.com/code-payments/code-server/pkg/code/data/action"
21
+ "github.com/code-payments/code-server/pkg/code/data/fulfillment"
22
+ "github.com/code-payments/code-server/pkg/code/data/intent"
23
+ "github.com/code-payments/code-server/pkg/currency"
18
24
"github.com/code-payments/code-server/pkg/metrics"
25
+ "github.com/code-payments/code-server/pkg/pointer"
19
26
"github.com/code-payments/code-server/pkg/retry"
20
27
)
21
28
22
- // todo: Is this even relevant anymore with the VM? If so, we need new logic because
23
- // closing dormant accounts is no longer a thing with the VM.
24
-
25
29
const (
26
30
giftCardAutoReturnIntentPrefix = "auto-return-gc-"
27
31
giftCardExpiry = 24 * time .Hour
@@ -39,7 +43,8 @@ func (p *service) giftCardAutoReturnWorker(serviceCtx context.Context, interval
39
43
defer m .End ()
40
44
tracedCtx := newrelic .NewContext (serviceCtx , m )
41
45
42
- records , err := p .data .GetPrioritizedAccountInfosRequiringAutoReturnCheck (tracedCtx , giftCardExpiry , 10 )
46
+ // todo: configurable batch size
47
+ records , err := p .data .GetPrioritizedAccountInfosRequiringAutoReturnCheck (tracedCtx , giftCardExpiry , 32 )
43
48
if err == account .ErrAccountInfoNotFound {
44
49
return nil
45
50
} else if err != nil {
@@ -91,13 +96,14 @@ func (p *service) maybeInitiateGiftCardAutoReturn(ctx context.Context, accountIn
91
96
if err == nil {
92
97
log .Trace ("gift card is claimed and will be removed from worker queue" )
93
98
94
- // Gift card is claimed, so take it out of the worker queue. The auto-return
95
- // action and fulfillment will be revoked in the fulfillment worker from generic
96
- // account closing flows.
97
- //
98
- // Note: It is possible the original issuer "claimed" the gift card. This is
99
- // actually ideal because funds move in a more private manner through the
100
- // temp incoming account versus the primary account.
99
+ // Cleanup anything related to gift card auto-return, since it cannot be scheduled
100
+ err = p .initiateProcessToCleanupGiftCardAutoReturn (ctx , giftCardVaultAccount )
101
+ if err != nil {
102
+ log .WithError (err ).Warn ("failure cleaning up auto-return action" )
103
+ return err
104
+ }
105
+
106
+ // Gift card is claimed, so take it out of the worker queue.
101
107
return markAutoReturnCheckComplete (ctx , p .data , accountInfoRecord )
102
108
} else if err != action .ErrActionNotFound {
103
109
return err
@@ -128,67 +134,82 @@ func (p *service) maybeInitiateGiftCardAutoReturn(ctx context.Context, accountIn
128
134
// Note: This is the first instance of handling a conditional action, and could be
129
135
// a good guide for similar actions in the future.
130
136
func (p * service ) initiateProcessToAutoReturnGiftCard (ctx context.Context , giftCardVaultAccount * common.Account ) error {
131
- /*
132
- giftCardIssuedIntent, err := p.data.GetOriginalGiftCardIssuedIntent(ctx, giftCardVaultAccount.PublicKey().ToBase58())
133
- if err != nil {
134
- return err
135
- }
137
+ giftCardIssuedIntent , err := p .data .GetOriginalGiftCardIssuedIntent (ctx , giftCardVaultAccount .PublicKey ().ToBase58 ())
138
+ if err != nil {
139
+ return err
140
+ }
136
141
137
- autoReturnAction, err := p.data.GetGiftCardAutoReturnAction(ctx, giftCardVaultAccount.PublicKey().ToBase58())
138
- if err != nil {
139
- return err
140
- }
142
+ autoReturnAction , err := p .data .GetGiftCardAutoReturnAction (ctx , giftCardVaultAccount .PublicKey ().ToBase58 ())
143
+ if err != nil {
144
+ return err
145
+ }
141
146
142
- autoReturnFulfillment, err := p.data.GetAllFulfillmentsByAction(ctx, autoReturnAction.Intent, autoReturnAction.ActionId)
143
- if err != nil {
144
- return err
145
- }
147
+ autoReturnFulfillment , err := p .data .GetAllFulfillmentsByAction (ctx , autoReturnAction .Intent , autoReturnAction .ActionId )
148
+ if err != nil {
149
+ return err
150
+ }
146
151
147
- // Add a payment history item to show the funds being returned back to the issuer
148
- err = insertAutoReturnPaymentHistoryItem (ctx, p.data, giftCardIssuedIntent)
149
- if err != nil {
150
- return err
151
- }
152
+ // Add a intent record to show the funds being returned back to the issuer
153
+ err = insertAutoReturnIntentRecord (ctx , p .data , giftCardIssuedIntent )
154
+ if err != nil {
155
+ return err
156
+ }
152
157
153
- // We need to update pre-sorting because close dormant fulfillments are always
154
- // inserted at the very last spot in the line.
155
- //
156
- // Must be the first thing to succeed! We cannot risk a deposit back into the
157
- // organizer to win a race in scheduling. By pre-sorting this to the end of
158
- // the gift card issued intent, we ensure the auto-return is blocked on any
159
- // fulfillments to setup the gift card. We'll also guarantee that subsequent
160
- // intents that utilize the primary account as a source of funds will be blocked
161
- // by the auto-return.
162
- err = updateCloseDormantAccountFulfillmentPreSorting(
163
- ctx,
164
- p.data,
165
- autoReturnFulfillment[0],
166
- giftCardIssuedIntent.Id,
167
- math.MaxInt32,
168
- 0,
169
- )
170
- if err != nil {
171
- return err
172
- }
158
+ // We need to update pre-sorting because auto-return fulfillments are always
159
+ // inserted at the very last spot in the line.
160
+ //
161
+ // Must be the first thing to succeed! By pre-sorting this to the end of
162
+ // the gift card issued intent, we ensure the auto-return is blocked on any
163
+ // fulfillments to setup the gift card. We'll also guarantee that subsequent
164
+ // intents that utilize the primary account as a source of funds will be blocked
165
+ // by the auto-return.
166
+ err = updateAutoReturnFulfillmentPreSorting (
167
+ ctx ,
168
+ p .data ,
169
+ autoReturnFulfillment [0 ],
170
+ giftCardIssuedIntent .Id ,
171
+ math .MaxInt32 ,
172
+ 0 ,
173
+ )
174
+ if err != nil {
175
+ return err
176
+ }
173
177
174
- // This will update the action's quantity, so balance changes are reflected. We
175
- // also unblock fulfillment scheduling by moving the action out of the unknown
176
- // state and into the pending state.
177
- err = scheduleCloseDormantAccountAction(
178
- ctx,
179
- p.data,
180
- autoReturnAction,
181
- giftCardIssuedIntent.SendPrivatePaymentMetadata.Quantity,
182
- )
183
- if err != nil {
184
- return err
185
- }
178
+ // This will update the action's quantity, so balance changes are reflected. We
179
+ // also unblock fulfillment scheduling by moving the action out of the unknown
180
+ // state and into the pending state.
181
+ err = scheduleAutoReturnAction (
182
+ ctx ,
183
+ p .data ,
184
+ autoReturnAction ,
185
+ giftCardIssuedIntent .SendPublicPaymentMetadata .Quantity ,
186
+ )
187
+ if err != nil {
188
+ return err
189
+ }
190
+
191
+ // This will trigger the fulfillment worker to poll for the fulfillment. This
192
+ // should be the very last DB update called.
193
+ return markFulfillmentAsActivelyScheduled (ctx , p .data , autoReturnFulfillment [0 ])
194
+ }
186
195
187
- // This will trigger the fulfillment worker to poll for the fulfillment. This
188
- // should be the very last DB update called.
189
- return markFulfillmentAsActivelyScheduled(ctx, p.data, autoReturnFulfillment[0])
190
- */
191
- return errors .New ("requires rewrite" )
196
+ func (p * service ) initiateProcessToCleanupGiftCardAutoReturn (ctx context.Context , giftCardVaultAccount * common.Account ) error {
197
+ autoReturnAction , err := p .data .GetGiftCardAutoReturnAction (ctx , giftCardVaultAccount .PublicKey ().ToBase58 ())
198
+ if err != nil {
199
+ return err
200
+ }
201
+
202
+ autoReturnFulfillment , err := p .data .GetAllFulfillmentsByAction (ctx , autoReturnAction .Intent , autoReturnAction .ActionId )
203
+ if err != nil {
204
+ return err
205
+ }
206
+
207
+ err = markActionAsRevoked (ctx , p .data , autoReturnAction )
208
+ if err != nil {
209
+ return err
210
+ }
211
+
212
+ return markFulfillmentAsRevoked (ctx , p .data , autoReturnFulfillment [0 ])
192
213
}
193
214
194
215
func markAutoReturnCheckComplete (ctx context.Context , data code_data.Provider , record * account.Record ) error {
@@ -200,14 +221,12 @@ func markAutoReturnCheckComplete(ctx context.Context, data code_data.Provider, r
200
221
return data .UpdateAccountInfo (ctx , record )
201
222
}
202
223
203
- /*
204
-
205
224
// Note: Structured like a generic utility because it could very well evolve
206
225
// into that, but there's no reason to call this on anything else as of
207
226
// writing this comment.
208
- func scheduleCloseDormantAccountAction (ctx context.Context, data code_data.Provider, actionRecord *action.Record, balance uint64) error {
209
- if actionRecord.ActionType != action.CloseDormantAccount {
210
- return errors.New("expected a close dormant account action")
227
+ func scheduleAutoReturnAction (ctx context.Context , data code_data.Provider , actionRecord * action.Record , balance uint64 ) error {
228
+ if actionRecord .ActionType != action .NoPrivacyWithdraw {
229
+ return errors .New ("expected a no privacy withdraw action" )
211
230
}
212
231
213
232
if actionRecord .State == action .StatePending {
@@ -226,16 +245,16 @@ func scheduleCloseDormantAccountAction(ctx context.Context, data code_data.Provi
226
245
// Note: Structured like a generic utility because it could very well evolve
227
246
// into that, but there's no reason to call this on anything else as of
228
247
// writing this comment.
229
- func updateCloseDormantAccountFulfillmentPreSorting (
248
+ func updateAutoReturnFulfillmentPreSorting (
230
249
ctx context.Context ,
231
250
data code_data.Provider ,
232
251
fulfillmentRecord * fulfillment.Record ,
233
252
intentOrderingIndex uint64 ,
234
253
actionOrderingIndex uint32 ,
235
254
fulfillmentOrderingIndex uint32 ,
236
255
) error {
237
- if fulfillmentRecord.FulfillmentType != fulfillment.CloseDormantTimelockAccount {
238
- return errors.New("expected a close dormant timelock account fulfillment")
256
+ if fulfillmentRecord .FulfillmentType != fulfillment .NoPrivacyWithdraw {
257
+ return errors .New ("expected a no privacy withdraw fulfillment" )
239
258
}
240
259
241
260
if fulfillmentRecord .IntentOrderingIndex == intentOrderingIndex &&
@@ -254,24 +273,7 @@ func updateCloseDormantAccountFulfillmentPreSorting(
254
273
return data .UpdateFulfillment (ctx , fulfillmentRecord )
255
274
}
256
275
257
- func markFulfillmentAsActivelyScheduled(ctx context.Context, data code_data.Provider, fulfillmentRecord *fulfillment.Record) error {
258
- if fulfillmentRecord.Id == 0 {
259
- return errors.New("fulfillment id is zero")
260
- }
261
-
262
- if !fulfillmentRecord.DisableActiveScheduling {
263
- return nil
264
- }
265
-
266
- if fulfillmentRecord.State != fulfillment.StateUnknown {
267
- return errors.New("expected fulfillment in unknown state")
268
- }
269
-
270
- // Note: different than Save, since we don't have distributed locks
271
- return data.MarkFulfillmentAsActivelyScheduled(ctx, fulfillmentRecord.Id)
272
- }
273
-
274
- func insertAutoReturnPaymentHistoryItem(ctx context.Context, data code_data.Provider, giftCardIssuedIntent *intent.Record) error {
276
+ func insertAutoReturnIntentRecord (ctx context.Context , data code_data.Provider , giftCardIssuedIntent * intent.Record ) error {
275
277
usdExchangeRecord , err := data .GetExchangeRate (ctx , currency .USD , time .Now ())
276
278
if err != nil {
277
279
return err
@@ -280,29 +282,24 @@ func insertAutoReturnPaymentHistoryItem(ctx context.Context, data code_data.Prov
280
282
// We need to insert a faked completed public receive intent so it can appear
281
283
// as a return in the user's payment history. Think of it as a server-initiated
282
284
// intent on behalf of the user based on pre-approved conditional actions.
283
- //
284
- // Deprecated in favour of chats (for history purposes)
285
- //
286
- // todo: Should we remap the CloseDormantAccount action and fulfillments, then
287
- // tie the fulfillment/action state to the intent state? Just doing the
288
- // easiest thing for now to get auto-return out the door.
289
285
intentRecord := & intent.Record {
290
286
IntentId : getAutoReturnIntentId (giftCardIssuedIntent .IntentId ),
291
287
IntentType : intent .ReceivePaymentsPublicly ,
292
288
293
289
InitiatorOwnerAccount : giftCardIssuedIntent .InitiatorOwnerAccount ,
294
290
295
291
ReceivePaymentsPubliclyMetadata : & intent.ReceivePaymentsPubliclyMetadata {
296
- Source: giftCardIssuedIntent.SendPrivatePaymentMetadata.DestinationTokenAccount,
297
- Quantity: giftCardIssuedIntent.SendPrivatePaymentMetadata.Quantity,
292
+ Source : giftCardIssuedIntent .SendPublicPaymentMetadata .DestinationTokenAccount ,
293
+ Quantity : giftCardIssuedIntent .SendPublicPaymentMetadata .Quantity ,
294
+
298
295
IsRemoteSend : true ,
299
296
IsReturned : true ,
300
297
301
- OriginalExchangeCurrency: giftCardIssuedIntent.SendPrivatePaymentMetadata .ExchangeCurrency,
302
- OriginalExchangeRate: giftCardIssuedIntent.SendPrivatePaymentMetadata .ExchangeRate,
303
- OriginalNativeAmount: giftCardIssuedIntent.SendPrivatePaymentMetadata .NativeAmount,
298
+ OriginalExchangeCurrency : giftCardIssuedIntent .SendPublicPaymentMetadata .ExchangeCurrency ,
299
+ OriginalExchangeRate : giftCardIssuedIntent .SendPublicPaymentMetadata .ExchangeRate ,
300
+ OriginalNativeAmount : giftCardIssuedIntent .SendPublicPaymentMetadata .NativeAmount ,
304
301
305
- UsdMarketValue: usdExchangeRecord.Rate * float64(common.FromCoreMintQuarks( giftCardIssuedIntent.SendPrivatePaymentMetadata .Quantity)),
302
+ UsdMarketValue : usdExchangeRecord .Rate * float64 (giftCardIssuedIntent .SendPublicPaymentMetadata .Quantity ) / float64 ( common . CoreMintQuarksPerUnit ),
306
303
},
307
304
308
305
State : intent .StateConfirmed ,
@@ -312,10 +309,58 @@ func insertAutoReturnPaymentHistoryItem(ctx context.Context, data code_data.Prov
312
309
return data .SaveIntent (ctx , intentRecord )
313
310
}
314
311
312
+ func markActionAsRevoked (ctx context.Context , data code_data.Provider , actionRecord * action.Record ) error {
313
+ if actionRecord .State == action .StateRevoked {
314
+ return nil
315
+ }
316
+
317
+ if actionRecord .State != action .StateUnknown {
318
+ return errors .New ("expected fulfillment in unknown state" )
319
+ }
320
+
321
+ actionRecord .State = action .StateRevoked
322
+
323
+ return data .UpdateAction (ctx , actionRecord )
324
+ }
325
+
326
+ func markFulfillmentAsActivelyScheduled (ctx context.Context , data code_data.Provider , fulfillmentRecord * fulfillment.Record ) error {
327
+ if fulfillmentRecord .Id == 0 {
328
+ return errors .New ("fulfillment id is zero" )
329
+ }
330
+
331
+ if ! fulfillmentRecord .DisableActiveScheduling {
332
+ return nil
333
+ }
334
+
335
+ if fulfillmentRecord .State != fulfillment .StateUnknown {
336
+ return errors .New ("expected fulfillment in unknown state" )
337
+ }
338
+
339
+ // Note: different than Save, since we don't have distributed locks
340
+ return data .MarkFulfillmentAsActivelyScheduled (ctx , fulfillmentRecord .Id )
341
+ }
342
+
343
+ func markFulfillmentAsRevoked (ctx context.Context , data code_data.Provider , fulfillmentRecord * fulfillment.Record ) error {
344
+ if fulfillmentRecord .Id == 0 {
345
+ return errors .New ("fulfillment id is zero" )
346
+ }
347
+
348
+ if fulfillmentRecord .State == fulfillment .StateRevoked {
349
+ return nil
350
+ }
351
+
352
+ if fulfillmentRecord .State != fulfillment .StateUnknown {
353
+ return errors .New ("expected fulfillment in unknown state" )
354
+ }
355
+
356
+ fulfillmentRecord .State = fulfillment .StateRevoked
357
+
358
+ return data .UpdateFulfillment (ctx , fulfillmentRecord )
359
+ }
360
+
315
361
// Must be unique, but consistent for idempotency, and ideally fit in a 32
316
362
// byte buffer.
317
363
func getAutoReturnIntentId (originalIntentId string ) string {
318
364
hashed := sha256 .Sum256 ([]byte (giftCardAutoReturnIntentPrefix + originalIntentId ))
319
365
return base58 .Encode (hashed [:])
320
366
}
321
- */
0 commit comments