@@ -12,6 +12,7 @@ import (
1212 "github.com/btcsuite/btcutil"
1313
1414 "github.com/btcsuite/btcd/chaincfg/chainhash"
15+ "github.com/btcsuite/btcd/mempool"
1516 "github.com/btcsuite/btcd/wire"
1617 "github.com/lightninglabs/lndclient"
1718 "github.com/lightninglabs/loop/labels"
@@ -21,6 +22,7 @@ import (
2122 "github.com/lightningnetwork/lnd/channeldb"
2223 "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
2324 "github.com/lightningnetwork/lnd/lntypes"
25+ "github.com/lightningnetwork/lnd/lnwallet/chainfee"
2426 "github.com/lightningnetwork/lnd/lnwire"
2527)
2628
@@ -311,7 +313,7 @@ func awaitProbe(ctx context.Context, lnd lndclient.LndServices,
311313
312314// resumeLoopInSwap returns a swap object representing a pending swap that has
313315// been restored from the database.
314- func resumeLoopInSwap (reqContext context.Context , cfg * swapConfig ,
316+ func resumeLoopInSwap (_ context.Context , cfg * swapConfig ,
315317 pend * loopdb.LoopIn ) (* loopInSwap , error ) {
316318
317319 hash := lntypes .Hash (sha256 .Sum256 (pend .Contract .Preimage [:]))
@@ -339,6 +341,7 @@ func resumeLoopInSwap(reqContext context.Context, cfg *swapConfig,
339341 swap .state = lastUpdate .State
340342 swap .lastUpdateTime = lastUpdate .Time
341343 swap .htlcTxHash = lastUpdate .HtlcTxHash
344+ swap .cost = lastUpdate .Cost
342345 }
343346
344347 return swap , nil
@@ -516,8 +519,6 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
516519 return s .persistAndAnnounceState (globalCtx )
517520 }
518521
519- // TODO: Add miner fee of htlc tx to swap cost balance.
520-
521522 // The server is expected to see the htlc on-chain and knowing that it
522523 // can sweep that htlc with the preimage, it should pay our swap
523524 // invoice, receive the preimage and sweep the htlc. We are waiting for
@@ -662,8 +663,11 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
662663 if err != nil {
663664 return false , fmt .Errorf ("send outputs: %v" , err )
664665 }
666+
665667 txHash := tx .TxHash ()
666- s .log .Infof ("Published on chain HTLC tx %v" , txHash )
668+ fee := getTxFee (tx , feeRate .FeePerKVByte ())
669+
670+ s .log .Infof ("Published on chain HTLC tx %v, fee: %v" , txHash , fee )
667671
668672 // Persist the htlc hash so that after a restart we are still waiting
669673 // for our own htlc. We don't need to announce to clients, because the
@@ -672,6 +676,12 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
672676 // TODO(joostjager): Store tx hash before calling SendOutputs. This is
673677 // not yet possible with the current lnd api.
674678 s .htlcTxHash = & txHash
679+
680+ // We do not expect any on-chain fees to be recorded yet, and we only
681+ // publish our htlc once, so we set our total on-chain costs to equal
682+ // the fee for publishing the htlc.
683+ s .cost .Onchain = fee
684+
675685 s .lastUpdateTime = time .Now ()
676686 if err := s .persistState (); err != nil {
677687 return false , fmt .Errorf ("persist htlc tx: %v" , err )
@@ -681,6 +691,16 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
681691
682692}
683693
694+ // getTxFee calculates our fee for a transaction that we have broadcast. We use
695+ // sat per kvbyte because this is what lnd uses, and we will run into rounding
696+ // issues if we do not use the same fee rate as lnd.
697+ func getTxFee (tx * wire.MsgTx , fee chainfee.SatPerKVByte ) btcutil.Amount {
698+ btcTx := btcutil .NewTx (tx )
699+ vsize := mempool .GetTxVirtualSize (btcTx )
700+
701+ return fee .FeeForVSize (vsize )
702+ }
703+
684704// waitForSwapComplete waits until a spending tx of the htlc gets confirmed and
685705// the swap invoice is either settled or canceled. If the htlc times out, the
686706// timeout tx will be published.
@@ -709,17 +729,18 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
709729 }
710730
711731 // checkTimeout publishes the timeout tx if the contract has expired.
712- checkTimeout := func () error {
732+ checkTimeout := func () (btcutil. Amount , error ) {
713733 if s .height >= s .LoopInContract .CltvExpiry {
714734 return s .publishTimeoutTx (ctx , htlcOutpoint , htlcValue )
715735 }
716736
717- return nil
737+ return 0 , nil
718738 }
719739
720740 // Check timeout at current height. After a restart we may want to
721741 // publish the tx immediately.
722- err = checkTimeout ()
742+ var sweepFee btcutil.Amount
743+ sweepFee , err = checkTimeout ()
723744 if err != nil {
724745 return err
725746 }
@@ -737,7 +758,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
737758 case notification := <- s .blockEpochChan :
738759 s .height = notification .(int32 )
739760
740- err : = checkTimeout ()
761+ sweepFee , err = checkTimeout ()
741762 if err != nil {
742763 return err
743764 }
@@ -749,7 +770,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
749770 spendDetails .SpenderTxHash )
750771
751772 err := s .processHtlcSpend (
752- ctx , spendDetails , htlcValue ,
773+ ctx , spendDetails , htlcValue , sweepFee ,
753774 )
754775 if err != nil {
755776 return err
@@ -804,7 +825,8 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
804825}
805826
806827func (s * loopInSwap ) processHtlcSpend (ctx context.Context ,
807- spend * chainntnfs.SpendDetail , htlcValue btcutil.Amount ) error {
828+ spend * chainntnfs.SpendDetail , htlcValue ,
829+ sweepFee btcutil.Amount ) error {
808830
809831 // Determine the htlc input of the spending tx and inspect the witness
810832 // to findout whether a success or a timeout tx spend the htlc.
@@ -817,6 +839,9 @@ func (s *loopInSwap) processHtlcSpend(ctx context.Context,
817839 // server cost balance.
818840 s .cost .Server += htlcValue
819841 } else {
842+ // We needed another on chain tx to sweep the timeout clause,
843+ // which we now include in our costs.
844+ s .cost .Onchain += sweepFee
820845 s .setState (loopdb .StateFailTimeout )
821846
822847 // Now that the timeout tx confirmed, we can safely cancel the
@@ -828,23 +853,24 @@ func (s *loopInSwap) processHtlcSpend(ctx context.Context,
828853 if err != nil && err != channeldb .ErrInvoiceAlreadySettled {
829854 return err
830855 }
831-
832- // TODO: Add miner fee of timeout tx to swap cost balance.
833856 }
834857
835858 return nil
836859}
837860
838- // publishTimeoutTx publishes a timeout tx after the on-chain htlc has expired.
839- // The swap failed and we are reclaiming our funds.
861+ // publishTimeoutTx publishes a timeout tx after the on-chain htlc has expired,
862+ // returning the fee that is paid by the sweep tx. We cannot update our swap
863+ // costs in this function because it is called multiple times. The swap failed
864+ // and we are reclaiming our funds.
840865func (s * loopInSwap ) publishTimeoutTx (ctx context.Context ,
841- htlcOutpoint * wire.OutPoint , htlcValue btcutil.Amount ) error {
866+ htlcOutpoint * wire.OutPoint , htlcValue btcutil.Amount ) (btcutil.Amount ,
867+ error ) {
842868
843869 if s .timeoutAddr == nil {
844870 var err error
845871 s .timeoutAddr , err = s .lnd .WalletKit .NextAddr (ctx )
846872 if err != nil {
847- return err
873+ return 0 , err
848874 }
849875 }
850876
@@ -854,7 +880,7 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
854880 TimeoutTxConfTarget ,
855881 )
856882 if err != nil {
857- return err
883+ return 0 , err
858884 }
859885
860886 witnessFunc := func (sig []byte ) (wire.TxWitness , error ) {
@@ -867,7 +893,7 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
867893 witnessFunc , htlcValue , fee , s .timeoutAddr ,
868894 )
869895 if err != nil {
870- return err
896+ return 0 , err
871897 }
872898
873899 timeoutTxHash := timeoutTx .TxHash ()
@@ -882,7 +908,7 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
882908 s .log .Warnf ("publish timeout: %v" , err )
883909 }
884910
885- return nil
911+ return fee , nil
886912}
887913
888914// persistAndAnnounceState updates the swap state on disk and sends out an
0 commit comments