@@ -4,15 +4,24 @@ import (
44 "context"
55 "errors"
66 "fmt"
7+ "time"
78
9+ "github.com/btcsuite/btcd/btcec/v2"
810 "github.com/btcsuite/btcd/chaincfg"
11+ "github.com/btcsuite/btcd/wire"
912 "github.com/lightninglabs/lndclient"
1013 "github.com/lightninglabs/loop/assets"
1114 "github.com/lightninglabs/loop/swapserverrpc"
1215 "github.com/lightninglabs/taproot-assets/address"
16+ "github.com/lightninglabs/taproot-assets/asset"
17+ "github.com/lightninglabs/taproot-assets/taprpc"
1318)
1419
1520var (
21+ // AssetDepositKeyFamily is the key family used for generating asset
22+ // deposit keys.
23+ AssetDepositKeyFamily = int32 (1122 )
24+
1625 // ErrManagerShuttingDown signals that the asset deposit manager is
1726 // shutting down and that no further calls should be made to it.
1827 ErrManagerShuttingDown = errors .New ("asset deposit manager is " +
@@ -270,3 +279,322 @@ func (m *Manager) handleDepositStateUpdate(ctx context.Context,
270279
271280 return nil
272281}
282+
283+ // NewDeposit creates a new asset deposit with the given parameters.
284+ func (m * Manager ) NewDeposit (ctx context.Context , assetID asset.ID ,
285+ amount uint64 , csvExpiry uint32 ) (DepositInfo , error ) {
286+
287+ clientKeyDesc , err := m .walletKit .DeriveNextKey (
288+ ctx , AssetDepositKeyFamily ,
289+ )
290+ if err != nil {
291+ return DepositInfo {}, err
292+ }
293+ clientInternalPubKey , _ , err := DeriveSharedDepositKey (
294+ ctx , m .signer , clientKeyDesc .PubKey ,
295+ )
296+ if err != nil {
297+ return DepositInfo {}, err
298+ }
299+
300+ clientScriptPubKeyBytes := clientKeyDesc .PubKey .SerializeCompressed ()
301+ clientInternalPubKeyBytes := clientInternalPubKey .SerializeCompressed ()
302+
303+ resp , err := m .depositServiceClient .NewAssetDeposit (
304+ ctx , & swapserverrpc.NewAssetDepositServerReq {
305+ AssetId : assetID [:],
306+ Amount : amount ,
307+ ClientInternalPubkey : clientInternalPubKeyBytes ,
308+ ClientScriptPubkey : clientScriptPubKeyBytes ,
309+ CsvExpiry : int32 (csvExpiry ),
310+ },
311+ )
312+ if err != nil {
313+ log .Errorf ("Swap server was unable to create the deposit: %v" ,
314+ err )
315+
316+ return DepositInfo {}, err
317+ }
318+
319+ serverScriptPubKey , err := btcec .ParsePubKey (resp .ServerScriptPubkey )
320+ if err != nil {
321+ return DepositInfo {}, err
322+ }
323+
324+ serverInternalPubKey , err := btcec .ParsePubKey (
325+ resp .ServerInternalPubkey ,
326+ )
327+ if err != nil {
328+ return DepositInfo {}, err
329+ }
330+
331+ kit , err := NewKit (
332+ clientKeyDesc .PubKey , clientInternalPubKey , serverScriptPubKey ,
333+ serverInternalPubKey , clientKeyDesc .KeyLocator , assetID ,
334+ csvExpiry , & m .addressParams ,
335+ )
336+ if err != nil {
337+ return DepositInfo {}, err
338+ }
339+
340+ deposit := & Deposit {
341+ Kit : kit ,
342+ DepositInfo : & DepositInfo {
343+ ID : resp .DepositId ,
344+ Version : CurrentProtocolVersion (),
345+ CreatedAt : time .Now (),
346+ Amount : amount ,
347+ Addr : resp .DepositAddr ,
348+ State : StateInitiated ,
349+ },
350+ }
351+
352+ err = m .store .AddAssetDeposit (ctx , deposit )
353+ if err != nil {
354+ log .Errorf ("Unable to add deposit to store: %v" , err )
355+
356+ return DepositInfo {}, err
357+ }
358+
359+ err = m .handleNewDeposit (ctx , deposit )
360+ if err != nil {
361+ log .Errorf ("Unable to add deposit to active deposits: %v" , err )
362+
363+ return DepositInfo {}, err
364+ }
365+
366+ return * deposit .DepositInfo .Copy (), nil
367+ }
368+
369+ // handleNewDeposit adds the deposit to the active deposits map and starts the
370+ // funding process, all on the main event loop goroutine.
371+ func (m * Manager ) handleNewDeposit (ctx context.Context , deposit * Deposit ) error {
372+ done , err := m .scheduleNextCall ()
373+ if err != nil {
374+ return err
375+ }
376+ defer done ()
377+
378+ m .deposits [deposit .ID ] = deposit
379+
380+ return m .fundDepositIfNeeded (ctx , deposit )
381+ }
382+
383+ // fundDepositIfNeeded attempts to fund the passed deposit if it is not already
384+ // funded.
385+ func (m * Manager ) fundDepositIfNeeded (ctx context.Context , d * Deposit ) error {
386+ // Now list transfers from tapd and check if the deposit is funded.
387+ funded , transfer , outIndex , err := m .isDepositFunded (ctx , d )
388+ if err != nil {
389+ log .Errorf ("Unable to check if deposit %v is funded: %v" , d .ID ,
390+ err )
391+
392+ return err
393+ }
394+
395+ if ! funded {
396+ // No funding transfer found, so we'll attempt to fund the
397+ // deposit by sending the asset to the deposit address. Note
398+ // that we label the send request with a specific label in order
399+ // to be able to subscribe to send events with a label filter.
400+ sendResp , err := m .tapClient .SendAsset (
401+ ctx , & taprpc.SendAssetRequest {
402+ TapAddrs : []string {d .Addr },
403+ Label : d .label (),
404+ },
405+ )
406+ if err != nil {
407+ log .Errorf ("Unable to send asset to deposit %v: %v" ,
408+ d .ID , err )
409+
410+ return err
411+ }
412+
413+ // Extract the funding outpoint from the transfer.
414+ transfer , outIndex , err = d .GetMatchingOut (
415+ d .Amount , []* taprpc.AssetTransfer {sendResp .Transfer },
416+ )
417+ if err != nil {
418+ log .Errorf ("Unable to get funding out for %v: %v " ,
419+ d .ID , err )
420+
421+ return err
422+ }
423+ }
424+
425+ log .Infof ("Deposit %v is funded in anchor %x:%d, " +
426+ "anchor tx block height: %v" , d .ID ,
427+ transfer .AnchorTxHash , outIndex , transfer .AnchorTxBlockHeight )
428+
429+ // If the deposit is confirmed, then we don't need to wait for the
430+ // confirmation to happen.
431+ // TODO(bhandras): once backlog events are supported we can remove this.
432+ if transfer .AnchorTxBlockHeight != 0 {
433+ return m .markDepositConfirmed (ctx , d , transfer )
434+ }
435+
436+ // Wait for deposit confirmation otherwise.
437+ err = m .waitForDepositConfirmation (m .runCtx (), d )
438+ if err != nil {
439+ log .Errorf ("Unable to wait for deposit confirmation: %v" , err )
440+
441+ return err
442+ }
443+
444+ return nil
445+ }
446+
447+ // isDepositFunded checks if the deposit is funded with the expected amount. It
448+ // does so by checking if there is a deposit output with the expected keys and
449+ // amount in the list of transfers of the funder.
450+ func (m * Manager ) isDepositFunded (ctx context.Context , d * Deposit ) (bool ,
451+ * taprpc.AssetTransfer , int , error ) {
452+
453+ res , err := m .tapClient .ListTransfers (
454+ ctx , & taprpc.ListTransfersRequest {},
455+ )
456+ if err != nil {
457+ return false , nil , 0 , err
458+ }
459+
460+ transfer , outIndex , err := d .GetMatchingOut (d .Amount , res .Transfers )
461+ if err != nil {
462+ return false , nil , 0 , err
463+ }
464+
465+ if transfer == nil {
466+ return false , nil , 0 , nil
467+ }
468+
469+ return true , transfer , outIndex , nil
470+ }
471+
472+ // waitForDepositConfirmation waits for the deposit to be confirmed.
473+ //
474+ // NOTE: currently SubscribeSendEvents does not support streaming backlog
475+ // events. To avoid missing the confirmation event, we also poll asset transfers
476+ // upon restart.
477+ func (m * Manager ) waitForDepositConfirmation (ctx context.Context ,
478+ d * Deposit ) error {
479+
480+ log .Infof ("Subscribing to send events for pending deposit %s, " +
481+ "addr=%v, created_at=%v" , d .ID , d .Addr , d .CreatedAt )
482+
483+ resChan , errChan , err := m .tapClient .WaitForSendComplete (
484+ ctx , nil , d .label (),
485+ )
486+ if err != nil {
487+ log .Errorf ("unable to subscribe to send events: %v" , err )
488+ return err
489+ }
490+
491+ go func () {
492+ select {
493+ case res := <- resChan :
494+ done , err := m .scheduleNextCall ()
495+ if err != nil {
496+ log .Errorf ("Unable to schedule next call: %v" ,
497+ err )
498+
499+ m .criticalError (err )
500+ }
501+ defer done ()
502+
503+ err = m .markDepositConfirmed (ctx , d , res .Transfer )
504+ if err != nil {
505+ log .Errorf ("Unable to mark deposit %v as " +
506+ "confirmed: %v" , d .ID , err )
507+
508+ m .criticalError (err )
509+ }
510+
511+ case err := <- errChan :
512+ m .criticalError (err )
513+ }
514+ }()
515+
516+ return nil
517+ }
518+
519+ // cacheProofInfo caches the proof information for the deposit in-memory.
520+ func (m * Manager ) cacheProofInfo (ctx context.Context , d * Deposit ) error {
521+ proofFile , err := d .ExportProof (ctx , m .tapClient , d .Outpoint )
522+ if err != nil {
523+ log .Errorf ("Unable to export proof for deposit %v: %v" , d .ID ,
524+ err )
525+
526+ return err
527+ }
528+
529+ // Import the proof in order to be able to spend the deposit later on
530+ // either into an HTLC or a timeout sweep.
531+ //
532+ // TODO(bhandras): do we need to check/handle if/when the proof is
533+ // already imported?
534+ depositProof , err := m .tapClient .ImportProofFile (
535+ ctx , proofFile .RawProofFile ,
536+ )
537+ if err != nil {
538+ return err
539+ }
540+
541+ d .Proof = depositProof
542+
543+ // Verify that the proof is valid for the deposit and get the root hash
544+ // which we may use later when signing the HTLC transaction.
545+ anchorRootHash , err := d .VerifyProof (depositProof )
546+ if err != nil {
547+ log .Errorf ("failed to verify deposity proof: %v" , err )
548+
549+ return err
550+ }
551+
552+ d .AnchorRootHash = anchorRootHash
553+
554+ return nil
555+ }
556+
557+ // markDepositConfirmed marks the deposit as confirmed in the store and moves it
558+ // to the active deposits map. It also updates the outpoint and the confirmation
559+ // height of the deposit.
560+ func (m * Manager ) markDepositConfirmed (ctx context.Context , d * Deposit ,
561+ transfer * taprpc.AssetTransfer ) error {
562+
563+ // Extract the funding outpoint from the transfer.
564+ _ , outIdx , err := d .GetMatchingOut (
565+ d .Amount , []* taprpc.AssetTransfer {transfer },
566+ )
567+ if err != nil {
568+ return err
569+ }
570+
571+ outpoint , err := wire .NewOutPointFromString (
572+ transfer .Outputs [outIdx ].Anchor .Outpoint ,
573+ )
574+ if err != nil {
575+ log .Errorf ("Unable to parse deposit outpoint %v: %v" ,
576+ transfer .Outputs [outIdx ].Anchor .Outpoint , err )
577+
578+ return err
579+ }
580+
581+ d .Outpoint = outpoint
582+ d .PkScript = transfer .Outputs [outIdx ].Anchor .PkScript
583+ d .ConfirmationHeight = transfer .AnchorTxBlockHeight
584+ d .State = StateConfirmed
585+
586+ err = m .handleDepositStateUpdate (ctx , d )
587+ if err != nil {
588+ return err
589+ }
590+
591+ err = m .cacheProofInfo (ctx , d )
592+ if err != nil {
593+ return err
594+ }
595+
596+ log .Infof ("Deposit %v is confirmed at block %v" , d .ID ,
597+ d .ConfirmationHeight )
598+
599+ return nil
600+ }
0 commit comments