|
|
@ -10,24 +10,25 @@ import (
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
|
|
"github.com/lightninglabs/loop/lndclient"
|
|
|
|
"github.com/lightninglabs/loop/lndclient"
|
|
|
|
|
|
|
|
"github.com/lightninglabs/loop/loopdb"
|
|
|
|
|
|
|
|
"github.com/lightninglabs/loop/swap"
|
|
|
|
"github.com/lightninglabs/loop/sweep"
|
|
|
|
"github.com/lightninglabs/loop/sweep"
|
|
|
|
"github.com/lightninglabs/loop/utils"
|
|
|
|
|
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
var (
|
|
|
|
// MinUnchargePreimageRevealDelta configures the minimum number of remaining
|
|
|
|
// MinLoopOutPreimageRevealDelta configures the minimum number of
|
|
|
|
// blocks before htlc expiry required to reveal preimage.
|
|
|
|
// remaining blocks before htlc expiry required to reveal preimage.
|
|
|
|
MinUnchargePreimageRevealDelta = int32(20)
|
|
|
|
MinLoopOutPreimageRevealDelta = int32(20)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// unchargeSwap contains all the in-memory state related to a pending uncharge
|
|
|
|
// loopOutSwap contains all the in-memory state related to a pending loop out
|
|
|
|
// swap.
|
|
|
|
// swap.
|
|
|
|
type unchargeSwap struct {
|
|
|
|
type loopOutSwap struct {
|
|
|
|
swapKit
|
|
|
|
swapKit
|
|
|
|
|
|
|
|
|
|
|
|
UnchargeContract
|
|
|
|
loopdb.LoopOutContract
|
|
|
|
|
|
|
|
|
|
|
|
swapPaymentChan chan lndclient.PaymentResult
|
|
|
|
swapPaymentChan chan lndclient.PaymentResult
|
|
|
|
prePaymentChan chan lndclient.PaymentResult
|
|
|
|
prePaymentChan chan lndclient.PaymentResult
|
|
|
@ -41,10 +42,10 @@ type executeConfig struct {
|
|
|
|
timerFactory func(d time.Duration) <-chan time.Time
|
|
|
|
timerFactory func(d time.Duration) <-chan time.Time
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// newUnchargeSwap initiates a new swap with the server and returns a
|
|
|
|
// newLoopOutSwap initiates a new swap with the server and returns a
|
|
|
|
// corresponding swap object.
|
|
|
|
// corresponding swap object.
|
|
|
|
func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
|
|
|
|
func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
|
|
|
|
currentHeight int32, request *UnchargeRequest) (*unchargeSwap, error) {
|
|
|
|
currentHeight int32, request *OutRequest) (*loopOutSwap, error) {
|
|
|
|
|
|
|
|
|
|
|
|
// Generate random preimage.
|
|
|
|
// Generate random preimage.
|
|
|
|
var swapPreimage [32]byte
|
|
|
|
var swapPreimage [32]byte
|
|
|
@ -55,7 +56,7 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
|
|
|
|
|
|
|
|
|
|
|
|
// Derive a receiver key for this swap.
|
|
|
|
// Derive a receiver key for this swap.
|
|
|
|
keyDesc, err := cfg.lnd.WalletKit.DeriveNextKey(
|
|
|
|
keyDesc, err := cfg.lnd.WalletKit.DeriveNextKey(
|
|
|
|
globalCtx, utils.SwapKeyFamily,
|
|
|
|
globalCtx, swap.KeyFamily,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
return nil, err
|
|
|
@ -67,14 +68,14 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
|
|
|
|
// the server revocation key and the swap and prepay invoices.
|
|
|
|
// the server revocation key and the swap and prepay invoices.
|
|
|
|
logger.Infof("Initiating swap request at height %v", currentHeight)
|
|
|
|
logger.Infof("Initiating swap request at height %v", currentHeight)
|
|
|
|
|
|
|
|
|
|
|
|
swapResp, err := cfg.server.NewUnchargeSwap(globalCtx, swapHash,
|
|
|
|
swapResp, err := cfg.server.NewLoopOutSwap(globalCtx, swapHash,
|
|
|
|
request.Amount, receiverKey,
|
|
|
|
request.Amount, receiverKey,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot initiate swap: %v", err)
|
|
|
|
return nil, fmt.Errorf("cannot initiate swap: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err = validateUnchargeContract(cfg.lnd, currentHeight, request, swapResp)
|
|
|
|
err = validateLoopOutContract(cfg.lnd, currentHeight, request, swapResp)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -82,13 +83,13 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
|
|
|
|
// Instantie a struct that contains all required data to start the swap.
|
|
|
|
// Instantie a struct that contains all required data to start the swap.
|
|
|
|
initiationTime := time.Now()
|
|
|
|
initiationTime := time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
contract := UnchargeContract{
|
|
|
|
contract := loopdb.LoopOutContract{
|
|
|
|
SwapInvoice: swapResp.swapInvoice,
|
|
|
|
SwapInvoice: swapResp.swapInvoice,
|
|
|
|
DestAddr: request.DestAddr,
|
|
|
|
DestAddr: request.DestAddr,
|
|
|
|
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
|
|
|
|
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
|
|
|
|
SweepConfTarget: request.SweepConfTarget,
|
|
|
|
SweepConfTarget: request.SweepConfTarget,
|
|
|
|
UnchargeChannel: request.UnchargeChannel,
|
|
|
|
UnchargeChannel: request.LoopOutChannel,
|
|
|
|
SwapContract: SwapContract{
|
|
|
|
SwapContract: loopdb.SwapContract{
|
|
|
|
InitiationHeight: currentHeight,
|
|
|
|
InitiationHeight: currentHeight,
|
|
|
|
InitiationTime: initiationTime,
|
|
|
|
InitiationTime: initiationTime,
|
|
|
|
PrepayInvoice: swapResp.prepayInvoice,
|
|
|
|
PrepayInvoice: swapResp.prepayInvoice,
|
|
|
@ -104,7 +105,7 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
swapKit, err := newSwapKit(
|
|
|
|
swapKit, err := newSwapKit(
|
|
|
|
swapHash, SwapTypeUncharge, cfg, &contract.SwapContract,
|
|
|
|
swapHash, TypeOut, cfg, &contract.SwapContract,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
return nil, err
|
|
|
@ -112,14 +113,14 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
|
|
|
|
|
|
|
|
|
|
|
|
swapKit.lastUpdateTime = initiationTime
|
|
|
|
swapKit.lastUpdateTime = initiationTime
|
|
|
|
|
|
|
|
|
|
|
|
swap := &unchargeSwap{
|
|
|
|
swap := &loopOutSwap{
|
|
|
|
UnchargeContract: contract,
|
|
|
|
LoopOutContract: contract,
|
|
|
|
swapKit: *swapKit,
|
|
|
|
swapKit: *swapKit,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Persist the data before exiting this function, so that the caller can
|
|
|
|
// Persist the data before exiting this function, so that the caller
|
|
|
|
// trust that this swap will be resumed on restart.
|
|
|
|
// can trust that this swap will be resumed on restart.
|
|
|
|
err = cfg.store.createUncharge(swapHash, &swap.UnchargeContract)
|
|
|
|
err = cfg.store.CreateLoopOut(swapHash, &swap.LoopOutContract)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot store swap: %v", err)
|
|
|
|
return nil, fmt.Errorf("cannot store swap: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -127,25 +128,25 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
|
|
|
|
return swap, nil
|
|
|
|
return swap, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// resumeUnchargeSwap returns a swap object representing a pending swap that has
|
|
|
|
// resumeLoopOutSwap returns a swap object representing a pending swap that has
|
|
|
|
// been restored from the database.
|
|
|
|
// been restored from the database.
|
|
|
|
func resumeUnchargeSwap(reqContext context.Context, cfg *swapConfig,
|
|
|
|
func resumeLoopOutSwap(reqContext context.Context, cfg *swapConfig,
|
|
|
|
pend *PersistentUncharge) (*unchargeSwap, error) {
|
|
|
|
pend *loopdb.LoopOut) (*loopOutSwap, error) {
|
|
|
|
|
|
|
|
|
|
|
|
hash := lntypes.Hash(sha256.Sum256(pend.Contract.Preimage[:]))
|
|
|
|
hash := lntypes.Hash(sha256.Sum256(pend.Contract.Preimage[:]))
|
|
|
|
|
|
|
|
|
|
|
|
logger.Infof("Resuming swap %v", hash)
|
|
|
|
logger.Infof("Resuming swap %v", hash)
|
|
|
|
|
|
|
|
|
|
|
|
swapKit, err := newSwapKit(
|
|
|
|
swapKit, err := newSwapKit(
|
|
|
|
hash, SwapTypeUncharge, cfg, &pend.Contract.SwapContract,
|
|
|
|
hash, TypeOut, cfg, &pend.Contract.SwapContract,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
swap := &unchargeSwap{
|
|
|
|
swap := &loopOutSwap{
|
|
|
|
UnchargeContract: *pend.Contract,
|
|
|
|
LoopOutContract: *pend.Contract,
|
|
|
|
swapKit: *swapKit,
|
|
|
|
swapKit: *swapKit,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
lastUpdate := pend.LastUpdate()
|
|
|
|
lastUpdate := pend.LastUpdate()
|
|
|
@ -161,7 +162,7 @@ func resumeUnchargeSwap(reqContext context.Context, cfg *swapConfig,
|
|
|
|
|
|
|
|
|
|
|
|
// execute starts/resumes the swap. It is a thin wrapper around
|
|
|
|
// execute starts/resumes the swap. It is a thin wrapper around
|
|
|
|
// executeAndFinalize to conveniently handle the error case.
|
|
|
|
// executeAndFinalize to conveniently handle the error case.
|
|
|
|
func (s *unchargeSwap) execute(mainCtx context.Context,
|
|
|
|
func (s *loopOutSwap) execute(mainCtx context.Context,
|
|
|
|
cfg *executeConfig, height int32) error {
|
|
|
|
cfg *executeConfig, height int32) error {
|
|
|
|
|
|
|
|
|
|
|
|
s.executeConfig = *cfg
|
|
|
|
s.executeConfig = *cfg
|
|
|
@ -170,13 +171,15 @@ func (s *unchargeSwap) execute(mainCtx context.Context,
|
|
|
|
err := s.executeAndFinalize(mainCtx)
|
|
|
|
err := s.executeAndFinalize(mainCtx)
|
|
|
|
|
|
|
|
|
|
|
|
// If an unexpected error happened, report a temporary failure.
|
|
|
|
// If an unexpected error happened, report a temporary failure.
|
|
|
|
// Otherwise for example a connection error could lead to abandoning the
|
|
|
|
// Otherwise for example a connection error could lead to abandoning
|
|
|
|
// swap permanently and losing funds.
|
|
|
|
// the swap permanently and losing funds.
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
s.log.Errorf("Swap error: %v", err)
|
|
|
|
s.log.Errorf("Swap error: %v", err)
|
|
|
|
s.state = StateFailTemporary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If we cannot send out this update, there is nothing we can do.
|
|
|
|
s.state = loopdb.StateFailTemporary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If we cannot send out this update, there is nothing we can
|
|
|
|
|
|
|
|
// do.
|
|
|
|
_ = s.sendUpdate(mainCtx)
|
|
|
|
_ = s.sendUpdate(mainCtx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -185,7 +188,7 @@ func (s *unchargeSwap) execute(mainCtx context.Context,
|
|
|
|
|
|
|
|
|
|
|
|
// executeAndFinalize executes a swap and awaits the definitive outcome of the
|
|
|
|
// executeAndFinalize executes a swap and awaits the definitive outcome of the
|
|
|
|
// offchain payments. When this method returns, the swap outcome is final.
|
|
|
|
// offchain payments. When this method returns, the swap outcome is final.
|
|
|
|
func (s *unchargeSwap) executeAndFinalize(globalCtx context.Context) error {
|
|
|
|
func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error {
|
|
|
|
// Announce swap by sending out an initial update.
|
|
|
|
// Announce swap by sending out an initial update.
|
|
|
|
err := s.sendUpdate(globalCtx)
|
|
|
|
err := s.sendUpdate(globalCtx)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -200,7 +203,7 @@ func (s *unchargeSwap) executeAndFinalize(globalCtx context.Context) error {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Sanity check.
|
|
|
|
// Sanity check.
|
|
|
|
if s.state.Type() == StateTypePending {
|
|
|
|
if s.state.Type() == loopdb.StateTypePending {
|
|
|
|
return fmt.Errorf("swap in non-final state %v", s.state)
|
|
|
|
return fmt.Errorf("swap in non-final state %v", s.state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -249,7 +252,7 @@ func (s *unchargeSwap) executeAndFinalize(globalCtx context.Context) error {
|
|
|
|
|
|
|
|
|
|
|
|
// executeSwap executes the swap, but returns as soon as the swap outcome is
|
|
|
|
// executeSwap executes the swap, but returns as soon as the swap outcome is
|
|
|
|
// final. At that point, there may still be pending off-chain payment(s).
|
|
|
|
// final. At that point, there may still be pending off-chain payment(s).
|
|
|
|
func (s *unchargeSwap) executeSwap(globalCtx context.Context) error {
|
|
|
|
func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
|
|
|
|
// We always pay both invoices (again). This is currently the only way
|
|
|
|
// We always pay both invoices (again). This is currently the only way
|
|
|
|
// to sort of resume payments.
|
|
|
|
// to sort of resume payments.
|
|
|
|
//
|
|
|
|
//
|
|
|
@ -277,7 +280,7 @@ func (s *unchargeSwap) executeSwap(globalCtx context.Context) error {
|
|
|
|
// attempt.
|
|
|
|
// attempt.
|
|
|
|
|
|
|
|
|
|
|
|
// Retrieve outpoint for sweep.
|
|
|
|
// Retrieve outpoint for sweep.
|
|
|
|
htlcOutpoint, htlcValue, err := utils.GetScriptOutput(
|
|
|
|
htlcOutpoint, htlcValue, err := swap.GetScriptOutput(
|
|
|
|
txConf.Tx, s.htlc.ScriptHash,
|
|
|
|
txConf.Tx, s.htlc.ScriptHash,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -287,11 +290,11 @@ func (s *unchargeSwap) executeSwap(globalCtx context.Context) error {
|
|
|
|
s.log.Infof("Htlc value: %v", htlcValue)
|
|
|
|
s.log.Infof("Htlc value: %v", htlcValue)
|
|
|
|
|
|
|
|
|
|
|
|
// Verify amount if preimage hasn't been revealed yet.
|
|
|
|
// Verify amount if preimage hasn't been revealed yet.
|
|
|
|
if s.state != StatePreimageRevealed && htlcValue < s.AmountRequested {
|
|
|
|
if s.state != loopdb.StatePreimageRevealed && htlcValue < s.AmountRequested {
|
|
|
|
logger.Warnf("Swap amount too low, expected %v but received %v",
|
|
|
|
logger.Warnf("Swap amount too low, expected %v but received %v",
|
|
|
|
s.AmountRequested, htlcValue)
|
|
|
|
s.AmountRequested, htlcValue)
|
|
|
|
|
|
|
|
|
|
|
|
s.state = StateFailInsufficientValue
|
|
|
|
s.state = loopdb.StateFailInsufficientValue
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -308,7 +311,7 @@ func (s *unchargeSwap) executeSwap(globalCtx context.Context) error {
|
|
|
|
// Inspect witness stack to see if it is a success transaction. We
|
|
|
|
// Inspect witness stack to see if it is a success transaction. We
|
|
|
|
// don't just try to match with the hash of our sweep tx, because it
|
|
|
|
// don't just try to match with the hash of our sweep tx, because it
|
|
|
|
// may be swept by a different (fee) sweep tx from a previous run.
|
|
|
|
// may be swept by a different (fee) sweep tx from a previous run.
|
|
|
|
htlcInput, err := getTxInputByOutpoint(
|
|
|
|
htlcInput, err := swap.GetTxInputByOutpoint(
|
|
|
|
spendDetails.SpendingTx, htlcOutpoint,
|
|
|
|
spendDetails.SpendingTx, htlcOutpoint,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -322,22 +325,22 @@ func (s *unchargeSwap) executeSwap(globalCtx context.Context) error {
|
|
|
|
s.cost.Onchain = htlcValue -
|
|
|
|
s.cost.Onchain = htlcValue -
|
|
|
|
btcutil.Amount(spendDetails.SpendingTx.TxOut[0].Value)
|
|
|
|
btcutil.Amount(spendDetails.SpendingTx.TxOut[0].Value)
|
|
|
|
|
|
|
|
|
|
|
|
s.state = StateSuccess
|
|
|
|
s.state = loopdb.StateSuccess
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
s.state = StateFailSweepTimeout
|
|
|
|
s.state = loopdb.StateFailSweepTimeout
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// persistState updates the swap state and sends out an update notification.
|
|
|
|
// persistState updates the swap state and sends out an update notification.
|
|
|
|
func (s *unchargeSwap) persistState(ctx context.Context) error {
|
|
|
|
func (s *loopOutSwap) persistState(ctx context.Context) error {
|
|
|
|
updateTime := time.Now()
|
|
|
|
updateTime := time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
s.lastUpdateTime = updateTime
|
|
|
|
s.lastUpdateTime = updateTime
|
|
|
|
|
|
|
|
|
|
|
|
// Update state in store.
|
|
|
|
// Update state in store.
|
|
|
|
err := s.store.updateUncharge(s.hash, updateTime, s.state)
|
|
|
|
err := s.store.UpdateLoopOut(s.hash, updateTime, s.state)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -347,12 +350,12 @@ func (s *unchargeSwap) persistState(ctx context.Context) error {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// payInvoices pays both swap invoices.
|
|
|
|
// payInvoices pays both swap invoices.
|
|
|
|
func (s *unchargeSwap) payInvoices(ctx context.Context) {
|
|
|
|
func (s *loopOutSwap) payInvoices(ctx context.Context) {
|
|
|
|
// Pay the swap invoice.
|
|
|
|
// Pay the swap invoice.
|
|
|
|
s.log.Infof("Sending swap payment %v", s.SwapInvoice)
|
|
|
|
s.log.Infof("Sending swap payment %v", s.SwapInvoice)
|
|
|
|
s.swapPaymentChan = s.lnd.Client.PayInvoice(
|
|
|
|
s.swapPaymentChan = s.lnd.Client.PayInvoice(
|
|
|
|
ctx, s.SwapInvoice, s.MaxSwapRoutingFee,
|
|
|
|
ctx, s.SwapInvoice, s.MaxSwapRoutingFee,
|
|
|
|
s.UnchargeContract.UnchargeChannel,
|
|
|
|
s.LoopOutContract.UnchargeChannel,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Pay the prepay invoice.
|
|
|
|
// Pay the prepay invoice.
|
|
|
@ -366,7 +369,7 @@ func (s *unchargeSwap) payInvoices(ctx context.Context) {
|
|
|
|
// waitForConfirmedHtlc waits for a confirmed htlc to appear on the chain. In
|
|
|
|
// waitForConfirmedHtlc waits for a confirmed htlc to appear on the chain. In
|
|
|
|
// case we haven't revealed the preimage yet, it also monitors block height and
|
|
|
|
// case we haven't revealed the preimage yet, it also monitors block height and
|
|
|
|
// off-chain payment failure.
|
|
|
|
// off-chain payment failure.
|
|
|
|
func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
|
|
|
|
func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
|
|
|
|
*chainntnfs.TxConfirmation, error) {
|
|
|
|
*chainntnfs.TxConfirmation, error) {
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for confirmation of the on-chain htlc by watching for a tx
|
|
|
|
// Wait for confirmation of the on-chain htlc by watching for a tx
|
|
|
@ -388,12 +391,12 @@ func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var txConf *chainntnfs.TxConfirmation
|
|
|
|
var txConf *chainntnfs.TxConfirmation
|
|
|
|
if s.state == StateInitiated {
|
|
|
|
if s.state == loopdb.StateInitiated {
|
|
|
|
// Check if it is already too late to start this swap. If we
|
|
|
|
// Check if it is already too late to start this swap. If we
|
|
|
|
// already revealed the preimage, this check is irrelevant and
|
|
|
|
// already revealed the preimage, this check is irrelevant and
|
|
|
|
// we need to sweep in any case.
|
|
|
|
// we need to sweep in any case.
|
|
|
|
maxPreimageRevealHeight := s.CltvExpiry -
|
|
|
|
maxPreimageRevealHeight := s.CltvExpiry -
|
|
|
|
MinUnchargePreimageRevealDelta
|
|
|
|
MinLoopOutPreimageRevealDelta
|
|
|
|
|
|
|
|
|
|
|
|
checkMaxRevealHeightExceeded := func() bool {
|
|
|
|
checkMaxRevealHeightExceeded := func() bool {
|
|
|
|
s.log.Infof("Checking preimage reveal height %v "+
|
|
|
|
s.log.Infof("Checking preimage reveal height %v "+
|
|
|
@ -408,7 +411,7 @@ func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
|
|
|
|
"exceeded (height %v)",
|
|
|
|
"exceeded (height %v)",
|
|
|
|
maxPreimageRevealHeight, s.height)
|
|
|
|
maxPreimageRevealHeight, s.height)
|
|
|
|
|
|
|
|
|
|
|
|
s.state = StateFailTimeout
|
|
|
|
s.state = loopdb.StateFailTimeout
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -429,7 +432,7 @@ func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
|
|
|
|
case result := <-s.swapPaymentChan:
|
|
|
|
case result := <-s.swapPaymentChan:
|
|
|
|
s.swapPaymentChan = nil
|
|
|
|
s.swapPaymentChan = nil
|
|
|
|
if result.Err != nil {
|
|
|
|
if result.Err != nil {
|
|
|
|
s.state = StateFailOffchainPayments
|
|
|
|
s.state = loopdb.StateFailOffchainPayments
|
|
|
|
s.log.Infof("Failed swap payment: %v",
|
|
|
|
s.log.Infof("Failed swap payment: %v",
|
|
|
|
result.Err)
|
|
|
|
result.Err)
|
|
|
|
|
|
|
|
|
|
|
@ -443,7 +446,7 @@ func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
|
|
|
|
case result := <-s.prePaymentChan:
|
|
|
|
case result := <-s.prePaymentChan:
|
|
|
|
s.prePaymentChan = nil
|
|
|
|
s.prePaymentChan = nil
|
|
|
|
if result.Err != nil {
|
|
|
|
if result.Err != nil {
|
|
|
|
s.state = StateFailOffchainPayments
|
|
|
|
s.state = loopdb.StateFailOffchainPayments
|
|
|
|
s.log.Infof("Failed prepayment: %v",
|
|
|
|
s.log.Infof("Failed prepayment: %v",
|
|
|
|
result.Err)
|
|
|
|
result.Err)
|
|
|
|
|
|
|
|
|
|
|
@ -504,7 +507,7 @@ func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
|
|
|
|
// TODO: Improve retry/fee increase mechanism. Once in the mempool, server can
|
|
|
|
// TODO: Improve retry/fee increase mechanism. Once in the mempool, server can
|
|
|
|
// sweep offchain. So we must make sure we sweep successfully before on-chain
|
|
|
|
// sweep offchain. So we must make sure we sweep successfully before on-chain
|
|
|
|
// timeout.
|
|
|
|
// timeout.
|
|
|
|
func (s *unchargeSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
|
|
|
|
func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
|
|
|
|
spendFunc func() error) (*chainntnfs.SpendDetail, error) {
|
|
|
|
spendFunc func() error) (*chainntnfs.SpendDetail, error) {
|
|
|
|
|
|
|
|
|
|
|
|
// Register the htlc spend notification.
|
|
|
|
// Register the htlc spend notification.
|
|
|
@ -556,7 +559,7 @@ func (s *unchargeSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
|
|
|
|
// published the tx.
|
|
|
|
// published the tx.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// TODO: Use lnd sweeper?
|
|
|
|
// TODO: Use lnd sweeper?
|
|
|
|
func (s *unchargeSwap) sweep(ctx context.Context,
|
|
|
|
func (s *loopOutSwap) sweep(ctx context.Context,
|
|
|
|
htlcOutpoint wire.OutPoint,
|
|
|
|
htlcOutpoint wire.OutPoint,
|
|
|
|
htlcValue btcutil.Amount) error {
|
|
|
|
htlcValue btcutil.Amount) error {
|
|
|
|
|
|
|
|
|
|
|
@ -579,7 +582,7 @@ func (s *unchargeSwap) sweep(ctx context.Context,
|
|
|
|
s.log.Warnf("Required miner fee %v exceeds max of %v",
|
|
|
|
s.log.Warnf("Required miner fee %v exceeds max of %v",
|
|
|
|
fee, s.MaxMinerFee)
|
|
|
|
fee, s.MaxMinerFee)
|
|
|
|
|
|
|
|
|
|
|
|
if s.state == StatePreimageRevealed {
|
|
|
|
if s.state == loopdb.StatePreimageRevealed {
|
|
|
|
// The currently required fee exceeds the max, but we
|
|
|
|
// The currently required fee exceeds the max, but we
|
|
|
|
// already revealed the preimage. The best we can do now
|
|
|
|
// already revealed the preimage. The best we can do now
|
|
|
|
// is to republish with the max fee.
|
|
|
|
// is to republish with the max fee.
|
|
|
@ -603,8 +606,8 @@ func (s *unchargeSwap) sweep(ctx context.Context,
|
|
|
|
// Before publishing the tx, already mark the preimage as revealed. This
|
|
|
|
// Before publishing the tx, already mark the preimage as revealed. This
|
|
|
|
// is a precaution in case the publish call never returns and would
|
|
|
|
// is a precaution in case the publish call never returns and would
|
|
|
|
// leave us thinking we didn't reveal yet.
|
|
|
|
// leave us thinking we didn't reveal yet.
|
|
|
|
if s.state != StatePreimageRevealed {
|
|
|
|
if s.state != loopdb.StatePreimageRevealed {
|
|
|
|
s.state = StatePreimageRevealed
|
|
|
|
s.state = loopdb.StatePreimageRevealed
|
|
|
|
|
|
|
|
|
|
|
|
err := s.persistState(ctx)
|
|
|
|
err := s.persistState(ctx)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -624,24 +627,24 @@ func (s *unchargeSwap) sweep(ctx context.Context,
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validateUnchargeContract validates the contract parameters against our
|
|
|
|
// validateLoopOutContract validates the contract parameters against our
|
|
|
|
// request.
|
|
|
|
// request.
|
|
|
|
func validateUnchargeContract(lnd *lndclient.LndServices,
|
|
|
|
func validateLoopOutContract(lnd *lndclient.LndServices,
|
|
|
|
height int32,
|
|
|
|
height int32,
|
|
|
|
request *UnchargeRequest,
|
|
|
|
request *OutRequest,
|
|
|
|
response *newUnchargeResponse) error {
|
|
|
|
response *newLoopOutResponse) error {
|
|
|
|
|
|
|
|
|
|
|
|
// Check invoice amounts.
|
|
|
|
// Check invoice amounts.
|
|
|
|
chainParams := lnd.ChainParams
|
|
|
|
chainParams := lnd.ChainParams
|
|
|
|
|
|
|
|
|
|
|
|
swapInvoiceAmt, err := utils.GetInvoiceAmt(
|
|
|
|
swapInvoiceAmt, err := swap.GetInvoiceAmt(
|
|
|
|
chainParams, response.swapInvoice,
|
|
|
|
chainParams, response.swapInvoice,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
prepayInvoiceAmt, err := utils.GetInvoiceAmt(
|
|
|
|
prepayInvoiceAmt, err := swap.GetInvoiceAmt(
|
|
|
|
chainParams, response.prepayInvoice,
|
|
|
|
chainParams, response.prepayInvoice,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -663,7 +666,7 @@ func validateUnchargeContract(lnd *lndclient.LndServices,
|
|
|
|
return ErrPrepayAmountTooHigh
|
|
|
|
return ErrPrepayAmountTooHigh
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if response.expiry-height < MinUnchargePreimageRevealDelta {
|
|
|
|
if response.expiry-height < MinLoopOutPreimageRevealDelta {
|
|
|
|
logger.Warnf("Proposed expiry %v (delta %v) too soon",
|
|
|
|
logger.Warnf("Proposed expiry %v (delta %v) too soon",
|
|
|
|
response.expiry, response.expiry-height)
|
|
|
|
response.expiry, response.expiry-height)
|
|
|
|
|
|
|
|
|