@@ -2,10 +2,13 @@ package async_account
22
33import (
44 "context"
5+ "crypto/sha256"
56 "errors"
7+ "math"
68 "sync"
79 "time"
810
11+ "github.com/mr-tron/base58"
912 "github.com/newrelic/go-agent/v3/newrelic"
1013 "github.com/sirupsen/logrus"
1114
@@ -15,13 +18,14 @@ import (
1518 code_data "github.com/code-payments/code-server/pkg/code/data"
1619 "github.com/code-payments/code-server/pkg/code/data/account"
1720 "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"
1824 "github.com/code-payments/code-server/pkg/metrics"
25+ "github.com/code-payments/code-server/pkg/pointer"
1926 "github.com/code-payments/code-server/pkg/retry"
2027)
2128
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-
2529const (
2630 giftCardAutoReturnIntentPrefix = "auto-return-gc-"
2731 giftCardExpiry = 24 * time .Hour
@@ -39,7 +43,8 @@ func (p *service) giftCardAutoReturnWorker(serviceCtx context.Context, interval
3943 defer m .End ()
4044 tracedCtx := newrelic .NewContext (serviceCtx , m )
4145
42- records , err := p .data .GetPrioritizedAccountInfosRequiringAutoReturnCheck (tracedCtx , giftCardExpiry , 10 )
46+ // todo: configurable batch size
47+ records , err := p .data .GetPrioritizedAccountInfosRequiringAutoReturnCheck (tracedCtx , giftCardExpiry , 32 )
4348 if err == account .ErrAccountInfoNotFound {
4449 return nil
4550 } else if err != nil {
@@ -91,13 +96,14 @@ func (p *service) maybeInitiateGiftCardAutoReturn(ctx context.Context, accountIn
9196 if err == nil {
9297 log .Trace ("gift card is claimed and will be removed from worker queue" )
9398
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.
101107 return markAutoReturnCheckComplete (ctx , p .data , accountInfoRecord )
102108 } else if err != action .ErrActionNotFound {
103109 return err
@@ -128,67 +134,82 @@ func (p *service) maybeInitiateGiftCardAutoReturn(ctx context.Context, accountIn
128134// Note: This is the first instance of handling a conditional action, and could be
129135// a good guide for similar actions in the future.
130136func (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+ }
136141
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+ }
141146
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+ }
146151
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+ }
152157
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+ }
173177
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+ }
186195
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 ])
192213}
193214
194215func 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
200221 return data .UpdateAccountInfo (ctx , record )
201222}
202223
203- /*
204-
205224// Note: Structured like a generic utility because it could very well evolve
206225// into that, but there's no reason to call this on anything else as of
207226// 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" )
211230 }
212231
213232 if actionRecord .State == action .StatePending {
@@ -226,16 +245,16 @@ func scheduleCloseDormantAccountAction(ctx context.Context, data code_data.Provi
226245// Note: Structured like a generic utility because it could very well evolve
227246// into that, but there's no reason to call this on anything else as of
228247// writing this comment.
229- func updateCloseDormantAccountFulfillmentPreSorting (
248+ func updateAutoReturnFulfillmentPreSorting (
230249 ctx context.Context ,
231250 data code_data.Provider ,
232251 fulfillmentRecord * fulfillment.Record ,
233252 intentOrderingIndex uint64 ,
234253 actionOrderingIndex uint32 ,
235254 fulfillmentOrderingIndex uint32 ,
236255) 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" )
239258 }
240259
241260 if fulfillmentRecord .IntentOrderingIndex == intentOrderingIndex &&
@@ -254,24 +273,7 @@ func updateCloseDormantAccountFulfillmentPreSorting(
254273 return data .UpdateFulfillment (ctx , fulfillmentRecord )
255274}
256275
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 {
275277 usdExchangeRecord , err := data .GetExchangeRate (ctx , currency .USD , time .Now ())
276278 if err != nil {
277279 return err
@@ -280,29 +282,24 @@ func insertAutoReturnPaymentHistoryItem(ctx context.Context, data code_data.Prov
280282 // We need to insert a faked completed public receive intent so it can appear
281283 // as a return in the user's payment history. Think of it as a server-initiated
282284 // 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.
289285 intentRecord := & intent.Record {
290286 IntentId : getAutoReturnIntentId (giftCardIssuedIntent .IntentId ),
291287 IntentType : intent .ReceivePaymentsPublicly ,
292288
293289 InitiatorOwnerAccount : giftCardIssuedIntent .InitiatorOwnerAccount ,
294290
295291 ReceivePaymentsPubliclyMetadata : & intent.ReceivePaymentsPubliclyMetadata {
296- Source: giftCardIssuedIntent.SendPrivatePaymentMetadata.DestinationTokenAccount,
297- Quantity: giftCardIssuedIntent.SendPrivatePaymentMetadata.Quantity,
292+ Source : giftCardIssuedIntent .SendPublicPaymentMetadata .DestinationTokenAccount ,
293+ Quantity : giftCardIssuedIntent .SendPublicPaymentMetadata .Quantity ,
294+
298295 IsRemoteSend : true ,
299296 IsReturned : true ,
300297
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 ,
304301
305- UsdMarketValue: usdExchangeRecord.Rate * float64(common.FromCoreMintQuarks( giftCardIssuedIntent.SendPrivatePaymentMetadata .Quantity)),
302+ UsdMarketValue : usdExchangeRecord .Rate * float64 (giftCardIssuedIntent .SendPublicPaymentMetadata .Quantity ) / float64 ( common . CoreMintQuarksPerUnit ),
306303 },
307304
308305 State : intent .StateConfirmed ,
@@ -312,10 +309,58 @@ func insertAutoReturnPaymentHistoryItem(ctx context.Context, data code_data.Prov
312309 return data .SaveIntent (ctx , intentRecord )
313310}
314311
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+
315361// Must be unique, but consistent for idempotency, and ideally fit in a 32
316362// byte buffer.
317363func getAutoReturnIntentId (originalIntentId string ) string {
318364 hashed := sha256 .Sum256 ([]byte (giftCardAutoReturnIntentPrefix + originalIntentId ))
319365 return base58 .Encode (hashed [:])
320366}
321- */
0 commit comments