multi: finalize rename from uncharge to loop out

pull/9/head
Olaoluwa Osuntokun 5 years ago
parent e299dc696c
commit 94f347e673
No known key found for this signature in database
GPG Key ID: CE58F7F8E20FD9A2

@ -11,8 +11,9 @@ import (
"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/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
@ -66,7 +67,7 @@ type Client struct {
func NewClient(dbDir string, serverAddress string, insecure bool, func NewClient(dbDir string, serverAddress string, insecure bool,
lnd *lndclient.LndServices) (*Client, func(), error) { lnd *lndclient.LndServices) (*Client, func(), error) {
store, err := newBoltSwapClientStore(dbDir) store, err := loopdb.NewBoltSwapStore(dbDir)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -112,9 +113,9 @@ func NewClient(dbDir string, serverAddress string, insecure bool,
return client, cleanup, nil return client, cleanup, nil
} }
// GetUnchargeSwaps returns a list of all swaps currently in the database. // FetchLoopOutSwaps returns a list of all swaps currently in the database.
func (s *Client) GetUnchargeSwaps() ([]*PersistentUncharge, error) { func (s *Client) FetchLoopOutSwaps() ([]*loopdb.LoopOut, error) {
return s.Store.getUnchargeSwaps() return s.Store.FetchLoopOutSwaps()
} }
// Run is a blocking call that executes all swaps. Any pending swaps are // Run is a blocking call that executes all swaps. Any pending swaps are
@ -143,7 +144,7 @@ func (s *Client) Run(ctx context.Context,
// Query store before starting event loop to prevent new swaps from // Query store before starting event loop to prevent new swaps from
// being treated as swaps that need to be resumed. // being treated as swaps that need to be resumed.
pendingSwaps, err := s.Store.getUnchargeSwaps() pendingSwaps, err := s.Store.FetchLoopOutSwaps()
if err != nil { if err != nil {
return err return err
} }
@ -193,17 +194,17 @@ func (s *Client) Run(ctx context.Context,
// resumeSwaps restarts all pending swaps from the provided list. // resumeSwaps restarts all pending swaps from the provided list.
func (s *Client) resumeSwaps(ctx context.Context, func (s *Client) resumeSwaps(ctx context.Context,
swaps []*PersistentUncharge) { swaps []*loopdb.LoopOut) {
for _, pend := range swaps { for _, pend := range swaps {
if pend.State().Type() != StateTypePending { if pend.State().Type() != loopdb.StateTypePending {
continue continue
} }
swapCfg := &swapConfig{ swapCfg := &swapConfig{
lnd: s.lndServices, lnd: s.lndServices,
store: s.Store, store: s.Store,
} }
swap, err := resumeUnchargeSwap(ctx, swapCfg, pend) swap, err := resumeLoopOutSwap(ctx, swapCfg, pend)
if err != nil { if err != nil {
logger.Errorf("resuming swap: %v", err) logger.Errorf("resuming swap: %v", err)
continue continue
@ -213,21 +214,21 @@ func (s *Client) resumeSwaps(ctx context.Context,
} }
} }
// Uncharge initiates a uncharge swap. It blocks until the swap is initiation // LoopOut initiates a loop out swap. It blocks until the swap is initiation
// with the swap server is completed (typically this takes only a short amount // with the swap server is completed (typically this takes only a short amount
// of time). From there on further status information can be acquired through // of time). From there on further status information can be acquired through
// the status channel returned from the Run call. // the status channel returned from the Run call.
// //
// When the call returns, the swap has been persisted and will be // When the call returns, the swap has been persisted and will be resumed
// resumed automatically after restarts. // automatically after restarts.
// //
// The return value is a hash that uniquely identifies the new swap. // The return value is a hash that uniquely identifies the new swap.
func (s *Client) Uncharge(globalCtx context.Context, func (s *Client) LoopOut(globalCtx context.Context,
request *UnchargeRequest) (*lntypes.Hash, error) { request *OutRequest) (*lntypes.Hash, error) {
logger.Infof("Uncharge %v to %v (channel: %v)", logger.Infof("LoopOut %v to %v (channel: %v)",
request.Amount, request.DestAddr, request.Amount, request.DestAddr,
request.UnchargeChannel, request.LoopOutChannel,
) )
if err := s.waitForInitialized(globalCtx); err != nil { if err := s.waitForInitialized(globalCtx); err != nil {
@ -241,7 +242,7 @@ func (s *Client) Uncharge(globalCtx context.Context,
store: s.Store, store: s.Store,
server: s.Server, server: s.Server,
} }
swap, err := newUnchargeSwap( swap, err := newLoopOutSwap(
globalCtx, swapCfg, initiationHeight, request, globalCtx, swapCfg, initiationHeight, request,
) )
if err != nil { if err != nil {
@ -256,13 +257,13 @@ func (s *Client) Uncharge(globalCtx context.Context,
return &swap.hash, nil return &swap.hash, nil
} }
// UnchargeQuote takes a Uncharge amount and returns a break down of estimated // LoopOutQuote takes a LoopOut amount and returns a break down of estimated
// costs for the client. Both the swap server and the on-chain fee estimator // costs for the client. Both the swap server and the on-chain fee estimator
// are queried to get to build the quote response. // are queried to get to build the quote response.
func (s *Client) UnchargeQuote(ctx context.Context, func (s *Client) LoopOutQuote(ctx context.Context,
request *UnchargeQuoteRequest) (*UnchargeQuote, error) { request *LoopOutQuoteRequest) (*LoopOutQuote, error) {
terms, err := s.Server.GetUnchargeTerms(ctx) terms, err := s.Server.GetLoopOutTerms(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -277,30 +278,30 @@ func (s *Client) UnchargeQuote(ctx context.Context,
logger.Infof("Offchain swap destination: %x", terms.SwapPaymentDest) logger.Infof("Offchain swap destination: %x", terms.SwapPaymentDest)
swapFee := utils.CalcFee( swapFee := swap.CalcFee(
request.Amount, terms.SwapFeeBase, terms.SwapFeeRate, request.Amount, terms.SwapFeeBase, terms.SwapFeeRate,
) )
minerFee, err := s.sweeper.GetSweepFee( minerFee, err := s.sweeper.GetSweepFee(
ctx, utils.QuoteHtlc.MaxSuccessWitnessSize, ctx, swap.QuoteHtlc.MaxSuccessWitnessSize,
request.SweepConfTarget, request.SweepConfTarget,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &UnchargeQuote{ return &LoopOutQuote{
SwapFee: swapFee, SwapFee: swapFee,
MinerFee: minerFee, MinerFee: minerFee,
PrepayAmount: btcutil.Amount(terms.PrepayAmt), PrepayAmount: btcutil.Amount(terms.PrepayAmt),
}, nil }, nil
} }
// UnchargeTerms returns the terms on which the server executes swaps. // LoopOutTerms returns the terms on which the server executes swaps.
func (s *Client) UnchargeTerms(ctx context.Context) ( func (s *Client) LoopOutTerms(ctx context.Context) (
*UnchargeTerms, error) { *LoopOutTerms, error) {
return s.Server.GetUnchargeTerms(ctx) return s.Server.GetLoopOutTerms(ctx)
} }
// waitForInitialized for swaps to be resumed and executor ready. // waitForInitialized for swaps to be resumed and executor ready.

@ -9,6 +9,7 @@ import (
"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/test" "github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
@ -17,7 +18,7 @@ var (
testAddr, _ = btcutil.DecodeAddress( testAddr, _ = btcutil.DecodeAddress(
"rbsHiPKwAgxeo1EQYiyzJTkA8XEmWSVAKx", nil) "rbsHiPKwAgxeo1EQYiyzJTkA8XEmWSVAKx", nil)
testRequest = &UnchargeRequest{ testRequest = &OutRequest{
Amount: btcutil.Amount(50000), Amount: btcutil.Amount(50000),
DestAddr: testAddr, DestAddr: testAddr,
MaxMinerFee: 50000, MaxMinerFee: 50000,
@ -40,13 +41,13 @@ func TestSuccess(t *testing.T) {
// Initiate uncharge. // Initiate uncharge.
hash, err := ctx.swapClient.Uncharge(context.Background(), testRequest) hash, err := ctx.swapClient.LoopOut(context.Background(), testRequest)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ctx.assertStored() ctx.assertStored()
ctx.assertStatus(StateInitiated) ctx.assertStatus(loopdb.StateInitiated)
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc) signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc) signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
@ -67,13 +68,13 @@ func TestFailOffchain(t *testing.T) {
ctx := createClientTestContext(t, nil) ctx := createClientTestContext(t, nil)
_, err := ctx.swapClient.Uncharge(context.Background(), testRequest) _, err := ctx.swapClient.LoopOut(context.Background(), testRequest)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ctx.assertStored() ctx.assertStored()
ctx.assertStatus(StateInitiated) ctx.assertStatus(loopdb.StateInitiated)
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc) signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc) signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
@ -86,9 +87,9 @@ func TestFailOffchain(t *testing.T) {
signalPrepaymentResult( signalPrepaymentResult(
errors.New(lndclient.PaymentResultUnknownPaymentHash), errors.New(lndclient.PaymentResultUnknownPaymentHash),
) )
ctx.assertStatus(StateFailOffchainPayments) ctx.assertStatus(loopdb.StateFailOffchainPayments)
ctx.assertStoreFinished(StateFailOffchainPayments) ctx.assertStoreFinished(loopdb.StateFailOffchainPayments)
ctx.finish() ctx.finish()
} }
@ -105,7 +106,7 @@ func TestFailWrongAmount(t *testing.T) {
// Modify mock for this subtest. // Modify mock for this subtest.
modifier(ctx.serverMock) modifier(ctx.serverMock)
_, err := ctx.swapClient.Uncharge( _, err := ctx.swapClient.LoopOut(
context.Background(), testRequest, context.Background(), testRequest,
) )
if err != expectedErr { if err != expectedErr {
@ -175,17 +176,17 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
var receiverKey [33]byte var receiverKey [33]byte
copy(receiverKey[:], receiverPubKey.SerializeCompressed()) copy(receiverKey[:], receiverPubKey.SerializeCompressed())
state := StateInitiated state := loopdb.StateInitiated
if preimageRevealed { if preimageRevealed {
state = StatePreimageRevealed state = loopdb.StatePreimageRevealed
} }
pendingSwap := &PersistentUncharge{ pendingSwap := &loopdb.LoopOut{
Contract: &UnchargeContract{ Contract: &loopdb.LoopOutContract{
DestAddr: dest, DestAddr: dest,
SwapInvoice: swapPayReq, SwapInvoice: swapPayReq,
SweepConfTarget: 2, SweepConfTarget: 2,
MaxSwapRoutingFee: 70000, MaxSwapRoutingFee: 70000,
SwapContract: SwapContract{ SwapContract: loopdb.SwapContract{
Preimage: preimage, Preimage: preimage,
AmountRequested: amt, AmountRequested: amt,
CltvExpiry: 744, CltvExpiry: 744,
@ -196,7 +197,7 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
MaxMinerFee: 50000, MaxMinerFee: 50000,
}, },
}, },
Events: []*PersistentUnchargeEvent{ Events: []*loopdb.LoopOutEvent{
{ {
State: state, State: state,
}, },
@ -210,12 +211,12 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
pendingSwap.Contract.CltvExpiry = 610 pendingSwap.Contract.CltvExpiry = 610
} }
ctx := createClientTestContext(t, []*PersistentUncharge{pendingSwap}) ctx := createClientTestContext(t, []*loopdb.LoopOut{pendingSwap})
if preimageRevealed { if preimageRevealed {
ctx.assertStatus(StatePreimageRevealed) ctx.assertStatus(loopdb.StatePreimageRevealed)
} else { } else {
ctx.assertStatus(StateInitiated) ctx.assertStatus(loopdb.StateInitiated)
} }
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc) signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
@ -228,8 +229,8 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
signalPrepaymentResult(nil) signalPrepaymentResult(nil)
if !expectSuccess { if !expectSuccess {
ctx.assertStatus(StateFailTimeout) ctx.assertStatus(loopdb.StateFailTimeout)
ctx.assertStoreFinished(StateFailTimeout) ctx.assertStoreFinished(loopdb.StateFailTimeout)
ctx.finish() ctx.finish()
return return
} }
@ -259,7 +260,7 @@ func testSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
ctx.expiryChan <- testTime ctx.expiryChan <- testTime
if !preimageRevealed { if !preimageRevealed {
ctx.assertStatus(StatePreimageRevealed) ctx.assertStatus(loopdb.StatePreimageRevealed)
ctx.assertStorePreimageReveal() ctx.assertStorePreimageReveal()
} }
@ -283,9 +284,9 @@ func testSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
ctx.NotifySpend(sweepTx, 0) ctx.NotifySpend(sweepTx, 0)
ctx.assertStatus(StateSuccess) ctx.assertStatus(loopdb.StateSuccess)
ctx.assertStoreFinished(StateSuccess) ctx.assertStoreFinished(loopdb.StateSuccess)
ctx.finish() ctx.finish()
} }

@ -0,0 +1,177 @@
package main
import (
"context"
"fmt"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/swap"
"github.com/urfave/cli"
)
var loopOutCommand = cli.Command{
Name: "out",
Usage: "perform an off-chain to on-chain swap (looping out)",
ArgsUsage: "amt [addr]",
Description: `
Attempts loop out the target amount into either the backing lnd's
wallet, or a targeted address.
The amount is to be specified in satoshis.
Optionally a BASE58/bech32 encoded bitcoin destination address may be
specified. If not specified, a new wallet address will be generated.`,
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "channel",
Usage: "the 8-byte compact channel ID of the channel to loop out",
},
},
Action: loopOut,
}
func loopOut(ctx *cli.Context) error {
// Show command help if no arguments and flags were provided.
if ctx.NArg() < 1 {
cli.ShowCommandHelp(ctx, "out")
return nil
}
args := ctx.Args()
amt, err := parseAmt(args[0])
if err != nil {
return err
}
var destAddr string
args = args.Tail()
if args.Present() {
destAddr = args.First()
}
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
quote, err := client.GetLoopOutQuote(
context.Background(),
&looprpc.QuoteRequest{
Amt: int64(amt),
},
)
if err != nil {
return err
}
limits := getLimits(amt, quote)
if err := displayLimits(amt, limits); err != nil {
return err
}
var unchargeChannel uint64
if ctx.IsSet("channel") {
unchargeChannel = ctx.Uint64("channel")
}
resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{
Amt: int64(amt),
Dest: destAddr,
MaxMinerFee: int64(limits.maxMinerFee),
MaxPrepayAmt: int64(limits.maxPrepayAmt),
MaxSwapFee: int64(limits.maxSwapFee),
MaxPrepayRoutingFee: int64(limits.maxPrepayRoutingFee),
MaxSwapRoutingFee: int64(limits.maxSwapRoutingFee),
LoopOutChannel: unchargeChannel,
})
if err != nil {
return err
}
fmt.Printf("Swap initiated with id: %v\n", resp.Id[:8])
fmt.Printf("Run `loop monitor` to monitor progress.\n")
return nil
}
var termsCommand = cli.Command{
Name: "terms",
Usage: "show current server swap terms",
Action: terms,
}
func terms(ctx *cli.Context) error {
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
terms, err := client.GetLoopOutTerms(
context.Background(), &looprpc.TermsRequest{},
)
if err != nil {
return err
}
fmt.Printf("Amount: %d - %d\n",
btcutil.Amount(terms.MinSwapAmount),
btcutil.Amount(terms.MaxSwapAmount),
)
if err != nil {
return err
}
printTerms := func(terms *looprpc.TermsResponse) {
fmt.Printf("Amount: %d - %d\n",
btcutil.Amount(terms.MinSwapAmount),
btcutil.Amount(terms.MaxSwapAmount),
)
fmt.Printf("Fee: %d + %.4f %% (%d prepaid)\n",
btcutil.Amount(terms.SwapFeeBase),
swap.FeeRateAsPercentage(terms.SwapFeeRate),
btcutil.Amount(terms.PrepayAmt),
)
fmt.Printf("Cltv delta: %v blocks\n", terms.CltvDelta)
}
fmt.Println("Loop Out")
fmt.Println("--------")
printTerms(terms)
return nil
}
var monitorCommand = cli.Command{
Name: "monitor",
Usage: "monitor progress of any active swaps",
Description: "Allows the user to monitor progress of any active swaps",
Action: monitor,
}
func monitor(ctx *cli.Context) error {
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
stream, err := client.Monitor(
context.Background(), &looprpc.MonitorRequest{})
if err != nil {
return err
}
for {
swap, err := stream.Recv()
if err != nil {
return fmt.Errorf("recv: %v", err)
}
logSwap(swap)
}
}

@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -9,7 +8,7 @@ import (
"time" "time"
"github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/utils" "github.com/lightninglabs/loop/swap"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
@ -18,131 +17,50 @@ import (
) )
var ( var (
swapdAddress = "localhost:11010" loopdAddress = "localhost:11010"
// Define route independent max routing fees. We have currently no way // Define route independent max routing fees. We have currently no way
// to get a reliable estimate of the routing fees. Best we can do is the // to get a reliable estimate of the routing fees. Best we can do is
// minimum routing fees, which is not very indicative. // the minimum routing fees, which is not very indicative.
maxRoutingFeeBase = btcutil.Amount(10) maxRoutingFeeBase = btcutil.Amount(10)
maxRoutingFeeRate = int64(50000) maxRoutingFeeRate = int64(50000)
) )
var unchargeCommand = cli.Command{ func fatal(err error) {
Name: "uncharge", fmt.Fprintf(os.Stderr, "[loop] %v\n", err)
Usage: "perform an off-chain to on-chain swap", os.Exit(1)
ArgsUsage: "amt [addr]",
Description: `
Send the amount in satoshis specified by the amt argument on-chain.
Optionally a BASE58 encoded bitcoin destination address may be
specified. If not specified, a new wallet address will be generated.`,
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "channel",
Usage: "the 8-byte compact channel ID of the channel to uncharge",
},
},
Action: uncharge,
}
var termsCommand = cli.Command{
Name: "terms",
Usage: "show current server swap terms",
Action: terms,
} }
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
app.Version = "0.0.1" app.Version = "0.0.1"
app.Usage = "command line interface to swapd" app.Name = "loop"
app.Commands = []cli.Command{unchargeCommand, termsCommand} app.Usage = "control plane for your loopd"
app.Action = monitor app.Commands = []cli.Command{
loopOutCommand, termsCommand, monitorCommand,
err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
}
}
func terms(ctx *cli.Context) error {
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
terms, err := client.GetUnchargeTerms(
context.Background(), &looprpc.TermsRequest{},
)
if err != nil {
return err
} }
fmt.Printf("Amount: %d - %d\n", err := app.Run(os.Args)
btcutil.Amount(terms.MinSwapAmount),
btcutil.Amount(terms.MaxSwapAmount),
)
if err != nil {
return err
}
printTerms := func(terms *looprpc.TermsResponse) {
fmt.Printf("Amount: %d - %d\n",
btcutil.Amount(terms.MinSwapAmount),
btcutil.Amount(terms.MaxSwapAmount),
)
fmt.Printf("Fee: %d + %.4f %% (%d prepaid)\n",
btcutil.Amount(terms.SwapFeeBase),
utils.FeeRateAsPercentage(terms.SwapFeeRate),
btcutil.Amount(terms.PrepayAmt),
)
fmt.Printf("Cltv delta: %v blocks\n", terms.CltvDelta)
}
fmt.Println("Uncharge")
fmt.Println("--------")
printTerms(terms)
return nil
}
func monitor(ctx *cli.Context) error {
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
stream, err := client.Monitor(
context.Background(), &looprpc.MonitorRequest{})
if err != nil { if err != nil {
return err fatal(err)
}
for {
swap, err := stream.Recv()
if err != nil {
return fmt.Errorf("recv: %v", err)
}
logSwap(swap)
} }
} }
func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) { func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) {
conn, err := getSwapCliConn(swapdAddress) conn, err := getClientConn(loopdAddress)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
cleanup := func() { conn.Close() } cleanup := func() { conn.Close() }
swapCliClient := looprpc.NewSwapClientClient(conn) loopClient := looprpc.NewSwapClientClient(conn)
return swapCliClient, cleanup, nil return loopClient, cleanup, nil
} }
func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount { func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
return utils.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate) return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate)
} }
type limits struct { type limits struct {
@ -160,8 +78,8 @@ func getLimits(amt btcutil.Amount, quote *looprpc.QuoteResponse) *limits {
quote.PrepayAmt, quote.PrepayAmt,
)), )),
// Apply a multiplier to the estimated miner fee, to not get the swap // Apply a multiplier to the estimated miner fee, to not get
// canceled because fees increased in the mean time. // the swap canceled because fees increased in the mean time.
maxMinerFee: btcutil.Amount(quote.MinerFee) * 3, maxMinerFee: btcutil.Amount(quote.MinerFee) * 3,
maxSwapFee: btcutil.Amount(quote.SwapFee), maxSwapFee: btcutil.Amount(quote.SwapFee),
@ -173,12 +91,15 @@ func displayLimits(amt btcutil.Amount, l *limits) error {
totalSuccessMax := l.maxSwapRoutingFee + l.maxPrepayRoutingFee + totalSuccessMax := l.maxSwapRoutingFee + l.maxPrepayRoutingFee +
l.maxMinerFee + l.maxSwapFee l.maxMinerFee + l.maxSwapFee
fmt.Printf("Max swap fees for %d uncharge: %d\n", fmt.Printf("Max swap fees for %d loop out: %d\n",
btcutil.Amount(amt), totalSuccessMax, btcutil.Amount(amt), totalSuccessMax,
) )
fmt.Printf("CONTINUE SWAP? (y/n), expand fee detail (x): ") fmt.Printf("CONTINUE SWAP? (y/n), expand fee detail (x): ")
var answer string var answer string
fmt.Scanln(&answer) fmt.Scanln(&answer)
switch answer { switch answer {
case "y": case "y":
return nil return nil
@ -211,73 +132,6 @@ func parseAmt(text string) (btcutil.Amount, error) {
return btcutil.Amount(amtInt64), nil return btcutil.Amount(amtInt64), nil
} }
func uncharge(ctx *cli.Context) error {
// Show command help if no arguments and flags were provided.
if ctx.NArg() < 1 {
cli.ShowCommandHelp(ctx, "uncharge")
return nil
}
args := ctx.Args()
amt, err := parseAmt(args[0])
if err != nil {
return err
}
var destAddr string
args = args.Tail()
if args.Present() {
destAddr = args.First()
}
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
quote, err := client.GetUnchargeQuote(
context.Background(),
&looprpc.QuoteRequest{
Amt: int64(amt),
},
)
if err != nil {
return err
}
limits := getLimits(amt, quote)
if err := displayLimits(amt, limits); err != nil {
return err
}
var unchargeChannel uint64
if ctx.IsSet("channel") {
unchargeChannel = ctx.Uint64("channel")
}
resp, err := client.Uncharge(context.Background(), &looprpc.UnchargeRequest{
Amt: int64(amt),
Dest: destAddr,
MaxMinerFee: int64(limits.maxMinerFee),
MaxPrepayAmt: int64(limits.maxPrepayAmt),
MaxSwapFee: int64(limits.maxSwapFee),
MaxPrepayRoutingFee: int64(limits.maxPrepayRoutingFee),
MaxSwapRoutingFee: int64(limits.maxSwapRoutingFee),
UnchargeChannel: unchargeChannel,
})
if err != nil {
return err
}
fmt.Printf("Swap initiated with id: %v\n", resp.Id[:8])
fmt.Printf("Run swapcli without a command to monitor progress.\n")
return nil
}
func logSwap(swap *looprpc.SwapStatus) { func logSwap(swap *looprpc.SwapStatus) {
fmt.Printf("%v %v %v %v - %v\n", fmt.Printf("%v %v %v %v - %v\n",
time.Unix(0, swap.LastUpdateTime).Format(time.RFC3339), time.Unix(0, swap.LastUpdateTime).Format(time.RFC3339),
@ -286,7 +140,7 @@ func logSwap(swap *looprpc.SwapStatus) {
) )
} }
func getSwapCliConn(address string) (*grpc.ClientConn, error) { func getClientConn(address string) (*grpc.ClientConn, error) {
opts := []grpc.DialOption{ opts := []grpc.DialOption{
grpc.WithInsecure(), grpc.WithInsecure(),
} }

@ -10,7 +10,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/lightninglabs/loop/client" "github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/looprpc"
"github.com/urfave/cli" "github.com/urfave/cli"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -34,13 +34,13 @@ func daemon(ctx *cli.Context) error {
// Before starting the client, build an in-memory view of all swaps. // Before starting the client, build an in-memory view of all swaps.
// This view is used to update newly connected clients with the most // This view is used to update newly connected clients with the most
// recent swaps. // recent swaps.
storedSwaps, err := swapClient.GetUnchargeSwaps() storedSwaps, err := swapClient.FetchLoopOutSwaps()
if err != nil { if err != nil {
return err return err
} }
for _, swap := range storedSwaps { for _, swap := range storedSwaps {
swaps[swap.Hash] = client.SwapInfo{ swaps[swap.Hash] = loop.SwapInfo{
SwapType: client.SwapTypeUncharge, SwapType: loop.TypeOut,
SwapContract: swap.Contract.SwapContract, SwapContract: swap.Contract.SwapContract,
State: swap.State(), State: swap.State(),
SwapHash: swap.Hash, SwapHash: swap.Hash,
@ -68,7 +68,7 @@ func daemon(ctx *cli.Context) error {
} }
defer lis.Close() defer lis.Close()
statusChan := make(chan client.SwapInfo) statusChan := make(chan loop.SwapInfo)
mainCtx, cancel := context.WithCancel(context.Background()) mainCtx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup var wg sync.WaitGroup

@ -6,9 +6,9 @@ import (
"github.com/btcsuite/btclog" "github.com/btcsuite/btclog"
) )
// log is a logger that is initialized with no output filters. This // log is a logger that is initialized with no output filters. This means the
// means the package will not perform any logging by default until the caller // package will not perform any logging by default until the caller requests
// requests it. // it.
var ( var (
backendLog = btclog.NewBackend(logWriter{}) backendLog = btclog.NewBackend(logWriter{})
logger = backendLog.Logger("SWAPD") logger = backendLog.Logger("SWAPD")

@ -6,7 +6,7 @@ import (
"sync" "sync"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/client" "github.com/lightninglabs/loop"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -20,7 +20,7 @@ var (
defaultListenAddr = fmt.Sprintf("localhost:%d", defaultListenPort) defaultListenAddr = fmt.Sprintf("localhost:%d", defaultListenPort)
defaultSwapletDir = btcutil.AppDataDir("swaplet", false) defaultSwapletDir = btcutil.AppDataDir("swaplet", false)
swaps = make(map[lntypes.Hash]client.SwapInfo) swaps = make(map[lntypes.Hash]loop.SwapInfo)
subscribers = make(map[int]chan<- interface{}) subscribers = make(map[int]chan<- interface{})
nextSubscriberID int nextSubscriberID int
swapsLock sync.Mutex swapsLock sync.Mutex
@ -58,9 +58,12 @@ func main() {
Usage: "disable tls", Usage: "disable tls",
}, },
} }
app.Name = "loopd"
app.Version = "0.0.1" app.Version = "0.0.1"
app.Usage = "swaps execution daemon" app.Usage = "Lightning Loop Client Daemon"
app.Commands = []cli.Command{viewCommand} app.Commands = []cli.Command{
viewCommand,
}
app.Action = daemon app.Action = daemon
err := app.Run(os.Args) err := app.Run(os.Args)

@ -7,31 +7,32 @@ import (
"github.com/lightningnetwork/lnd/queue" "github.com/lightningnetwork/lnd/queue"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient" "github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/utils" "github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/client"
"github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/looprpc"
) )
const completedSwapsCount = 5 const completedSwapsCount = 5
// swapClientServer implements the grpc service exposed by swapd. // swapClientServer implements the grpc service exposed by loopd.
type swapClientServer struct { type swapClientServer struct {
impl *client.Client impl *loop.Client
lnd *lndclient.LndServices lnd *lndclient.LndServices
} }
// Uncharge initiates an uncharge swap with the given parameters. The call // LoopOut initiates an loop out swap with the given parameters. The call
// returns after the swap has been set up with the swap server. From that point // returns after the swap has been set up with the swap server. From that point
// onwards, progress can be tracked via the UnchargeStatus stream that is // onwards, progress can be tracked via the LoopOutStatus stream that is
// returned from Monitor(). // returned from Monitor().
func (s *swapClientServer) Uncharge(ctx context.Context, func (s *swapClientServer) LoopOut(ctx context.Context,
in *looprpc.UnchargeRequest) ( in *looprpc.LoopOutRequest) (
*looprpc.SwapResponse, error) { *looprpc.SwapResponse, error) {
logger.Infof("Uncharge request received") logger.Infof("LoopOut request received")
var sweepAddr btcutil.Address var sweepAddr btcutil.Address
if in.Dest == "" { if in.Dest == "" {
@ -49,7 +50,7 @@ func (s *swapClientServer) Uncharge(ctx context.Context,
} }
} }
req := &client.UnchargeRequest{ req := &loop.OutRequest{
Amount: btcutil.Amount(in.Amt), Amount: btcutil.Amount(in.Amt),
DestAddr: sweepAddr, DestAddr: sweepAddr,
MaxMinerFee: btcutil.Amount(in.MaxMinerFee), MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
@ -59,12 +60,12 @@ func (s *swapClientServer) Uncharge(ctx context.Context,
MaxSwapFee: btcutil.Amount(in.MaxSwapFee), MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
SweepConfTarget: defaultConfTarget, SweepConfTarget: defaultConfTarget,
} }
if in.UnchargeChannel != 0 { if in.LoopOutChannel != 0 {
req.UnchargeChannel = &in.UnchargeChannel req.LoopOutChannel = &in.LoopOutChannel
} }
hash, err := s.impl.Uncharge(ctx, req) hash, err := s.impl.LoopOut(ctx, req)
if err != nil { if err != nil {
logger.Errorf("Uncharge: %v", err) logger.Errorf("LoopOut: %v", err)
return nil, err return nil, err
} }
@ -73,24 +74,25 @@ func (s *swapClientServer) Uncharge(ctx context.Context,
}, nil }, nil
} }
func (s *swapClientServer) marshallSwap(swap *client.SwapInfo) ( func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
*looprpc.SwapStatus, error) { *looprpc.SwapStatus, error) {
var state looprpc.SwapState var state looprpc.SwapState
switch swap.State { switch loopSwap.State {
case client.StateInitiated: case loopdb.StateInitiated:
state = looprpc.SwapState_INITIATED state = looprpc.SwapState_INITIATED
case client.StatePreimageRevealed: case loopdb.StatePreimageRevealed:
state = looprpc.SwapState_PREIMAGE_REVEALED state = looprpc.SwapState_PREIMAGE_REVEALED
case client.StateSuccess: case loopdb.StateSuccess:
state = looprpc.SwapState_SUCCESS state = looprpc.SwapState_SUCCESS
default: default:
// Return less granular status over rpc. // Return less granular status over rpc.
state = looprpc.SwapState_FAILED state = looprpc.SwapState_FAILED
} }
htlc, err := utils.NewHtlc(swap.CltvExpiry, swap.SenderKey, htlc, err := swap.NewHtlc(
swap.ReceiverKey, swap.SwapHash, loopSwap.CltvExpiry, loopSwap.SenderKey, loopSwap.ReceiverKey,
loopSwap.SwapHash,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -102,13 +104,13 @@ func (s *swapClientServer) marshallSwap(swap *client.SwapInfo) (
} }
return &looprpc.SwapStatus{ return &looprpc.SwapStatus{
Amt: int64(swap.AmountRequested), Amt: int64(loopSwap.AmountRequested),
Id: swap.SwapHash.String(), Id: loopSwap.SwapHash.String(),
State: state, State: state,
InitiationTime: swap.InitiationTime.UnixNano(), InitiationTime: loopSwap.InitiationTime.UnixNano(),
LastUpdateTime: swap.LastUpdate.UnixNano(), LastUpdateTime: loopSwap.LastUpdate.UnixNano(),
HtlcAddress: address.EncodeAddress(), HtlcAddress: address.EncodeAddress(),
Type: looprpc.SwapType_UNCHARGE, Type: looprpc.SwapType_LOOP_OUT,
}, nil }, nil
} }
@ -118,7 +120,7 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
logger.Infof("Monitor request received") logger.Infof("Monitor request received")
send := func(info client.SwapInfo) error { send := func(info loop.SwapInfo) error {
rpcSwap, err := s.marshallSwap(&info) rpcSwap, err := s.marshallSwap(&info)
if err != nil { if err != nil {
return err return err
@ -140,9 +142,9 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
nextSubscriberID++ nextSubscriberID++
subscribers[id] = queue.ChanIn() subscribers[id] = queue.ChanIn()
var pendingSwaps, completedSwaps []client.SwapInfo var pendingSwaps, completedSwaps []loop.SwapInfo
for _, swap := range swaps { for _, swap := range swaps {
if swap.State.Type() == client.StateTypePending { if swap.State.Type() == loopdb.StateTypePending {
pendingSwaps = append(pendingSwaps, swap) pendingSwaps = append(pendingSwaps, swap)
} else { } else {
completedSwaps = append(completedSwaps, swap) completedSwaps = append(completedSwaps, swap)
@ -196,7 +198,7 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
return nil return nil
} }
swap := queueItem.(client.SwapInfo) swap := queueItem.(loop.SwapInfo)
if err := send(swap); err != nil { if err := send(swap); err != nil {
return err return err
} }
@ -207,12 +209,12 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
} }
// GetTerms returns the terms that the server enforces for swaps. // GetTerms returns the terms that the server enforces for swaps.
func (s *swapClientServer) GetUnchargeTerms(ctx context.Context, req *looprpc.TermsRequest) ( func (s *swapClientServer) GetLoopOutTerms(ctx context.Context,
*looprpc.TermsResponse, error) { req *looprpc.TermsRequest) (*looprpc.TermsResponse, error) {
logger.Infof("Terms request received") logger.Infof("Terms request received")
terms, err := s.impl.UnchargeTerms(ctx) terms, err := s.impl.LoopOutTerms(ctx)
if err != nil { if err != nil {
logger.Errorf("Terms request: %v", err) logger.Errorf("Terms request: %v", err)
return nil, err return nil, err
@ -229,10 +231,10 @@ func (s *swapClientServer) GetUnchargeTerms(ctx context.Context, req *looprpc.Te
} }
// GetQuote returns a quote for a swap with the provided parameters. // GetQuote returns a quote for a swap with the provided parameters.
func (s *swapClientServer) GetUnchargeQuote(ctx context.Context, func (s *swapClientServer) GetLoopOutQuote(ctx context.Context,
req *looprpc.QuoteRequest) (*looprpc.QuoteResponse, error) { req *looprpc.QuoteRequest) (*looprpc.QuoteResponse, error) {
quote, err := s.impl.UnchargeQuote(ctx, &client.UnchargeQuoteRequest{ quote, err := s.impl.LoopOutQuote(ctx, &loop.LoopOutQuoteRequest{
Amount: btcutil.Amount(req.Amt), Amount: btcutil.Amount(req.Amt),
SweepConfTarget: defaultConfTarget, SweepConfTarget: defaultConfTarget,
}) })

@ -4,7 +4,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/lightninglabs/loop/client" "github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient" "github.com/lightninglabs/loop/lndclient"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -20,7 +20,9 @@ func getLnd(ctx *cli.Context) (*lndclient.GrpcLndServices, error) {
} }
// getClient returns an instance of the swap client. // getClient returns an instance of the swap client.
func getClient(ctx *cli.Context, lnd *lndclient.LndServices) (*client.Client, func(), error) { func getClient(ctx *cli.Context,
lnd *lndclient.LndServices) (*loop.Client, func(), error) {
network := ctx.GlobalString("network") network := ctx.GlobalString("network")
storeDir, err := getStoreDir(network) storeDir, err := getStoreDir(network)
@ -28,7 +30,7 @@ func getClient(ctx *cli.Context, lnd *lndclient.LndServices) (*client.Client, fu
return nil, nil, err return nil, nil, err
} }
swapClient, cleanUp, err := client.NewClient( swapClient, cleanUp, err := loop.NewClient(
storeDir, ctx.GlobalString("swapserver"), storeDir, ctx.GlobalString("swapserver"),
ctx.GlobalBool("insecure"), lnd, ctx.GlobalBool("insecure"), lnd,
) )

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/lightninglabs/loop/utils" "github.com/lightninglabs/loop/swap"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -21,7 +21,7 @@ var viewCommand = cli.Command{
func view(ctx *cli.Context) error { func view(ctx *cli.Context) error {
network := ctx.GlobalString("network") network := ctx.GlobalString("network")
chainParams, err := utils.ChainParamsFromNetwork(network) chainParams, err := swap.ChainParamsFromNetwork(network)
if err != nil { if err != nil {
return err return err
} }
@ -38,13 +38,13 @@ func view(ctx *cli.Context) error {
} }
defer cleanup() defer cleanup()
swaps, err := swapClient.GetUnchargeSwaps() swaps, err := swapClient.FetchLoopOutSwaps()
if err != nil { if err != nil {
return err return err
} }
for _, s := range swaps { for _, s := range swaps {
htlc, err := utils.NewHtlc( htlc, err := swap.NewHtlc(
s.Contract.CltvExpiry, s.Contract.CltvExpiry,
s.Contract.SenderKey, s.Contract.SenderKey,
s.Contract.ReceiverKey, s.Contract.ReceiverKey,

@ -4,12 +4,13 @@ import (
"time" "time"
"github.com/lightninglabs/loop/lndclient" "github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
) )
// clientConfig contains config items for the swap client. // clientConfig contains config items for the swap client.
type clientConfig struct { type clientConfig struct {
LndServices *lndclient.LndServices LndServices *lndclient.LndServices
Server swapServerClient Server swapServerClient
Store swapClientStore Store loopdb.SwapStore
CreateExpiryTimer func(expiry time.Duration) <-chan time.Time CreateExpiryTimer func(expiry time.Duration) <-chan time.Time
} }

@ -8,15 +8,19 @@ import (
"time" "time"
"github.com/lightninglabs/loop/lndclient" "github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweep" "github.com/lightninglabs/loop/sweep"
"github.com/lightningnetwork/lnd/queue" "github.com/lightningnetwork/lnd/queue"
) )
// executorConfig contains executor configuration data. // executorConfig contains executor configuration data.
type executorConfig struct { type executorConfig struct {
lnd *lndclient.LndServices lnd *lndclient.LndServices
sweeper *sweep.Sweeper
store swapClientStore sweeper *sweep.Sweeper
store loopdb.SwapStore
createExpiryTimer func(expiry time.Duration) <-chan time.Time createExpiryTimer func(expiry time.Duration) <-chan time.Time
} }

@ -4,11 +4,12 @@ import (
"time" "time"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
// UnchargeRequest contains the required parameters for the swap. // OutRequest contains the required parameters for a loop out swap.
type UnchargeRequest struct { type OutRequest struct {
// Amount specifies the requested swap amount in sat. This does not // Amount specifies the requested swap amount in sat. This does not
// include the swap and miner fee. // include the swap and miner fee.
Amount btcutil.Amount Amount btcutil.Amount
@ -19,19 +20,19 @@ type UnchargeRequest struct {
// MaxSwapRoutingFee is the maximum off-chain fee in msat that may be // MaxSwapRoutingFee is the maximum off-chain fee in msat that may be
// paid for payment to the server. This limit is applied during path // paid for payment to the server. This limit is applied during path
// finding. Typically this value is taken from the response of the // finding. Typically this value is taken from the response of the
// UnchargeQuote call. // LoopOutQuote call.
MaxSwapRoutingFee btcutil.Amount MaxSwapRoutingFee btcutil.Amount
// MaxPrepayRoutingFee is the maximum off-chain fee in msat that may be // MaxPrepayRoutingFee is the maximum off-chain fee in msat that may be
// paid for payment to the server. This limit is applied during path // paid for payment to the server. This limit is applied during path
// finding. Typically this value is taken from the response of the // finding. Typically this value is taken from the response of the
// UnchargeQuote call. // LoopOutQuote call.
MaxPrepayRoutingFee btcutil.Amount MaxPrepayRoutingFee btcutil.Amount
// MaxSwapFee is the maximum we are willing to pay the server for the // MaxSwapFee is the maximum we are willing to pay the server for the
// swap. This value is not disclosed in the swap initiation call, but // swap. This value is not disclosed in the swap initiation call, but
// if the server asks for a higher fee, we abort the swap. Typically // if the server asks for a higher fee, we abort the swap. Typically
// this value is taken from the response of the UnchargeQuote call. It // this value is taken from the response of the LoopOutQuote call. It
// includes the prepay amount. // includes the prepay amount.
MaxSwapFee btcutil.Amount MaxSwapFee btcutil.Amount
@ -54,26 +55,32 @@ type UnchargeRequest struct {
// revocation. // revocation.
// //
// MaxMinerFee is typically taken from the response of the // MaxMinerFee is typically taken from the response of the
// UnchargeQuote call. // LoopOutQuote call.
MaxMinerFee btcutil.Amount MaxMinerFee btcutil.Amount
// SweepConfTarget specifies the targeted confirmation target for the // SweepConfTarget specifies the targeted confirmation target for the
// client sweep tx. // client sweep tx.
SweepConfTarget int32 SweepConfTarget int32
// UnchargeChannel optionally specifies the short channel id of the // LoopOutChannel optionally specifies the short channel id of the
// channel to uncharge. // channel to uncharge.
UnchargeChannel *uint64 LoopOutChannel *uint64
} }
// UnchargeSwapInfo contains status information for a uncharge swap. // Out contains the full details of a loop out request. This includes things
type UnchargeSwapInfo struct { // like the payment hash, the total value, and the final CTLV delay of the
UnchargeContract // swap. We'll use this to track an active swap throughout that various swap
// stages.
type Out struct {
// LoopOutContract describes the details of this loop.Out. Using these
// details,the full swap can be executed.
loopdb.LoopOutContract
SwapInfoKit // State is the current state of the target swap.
State loopdb.SwapState
// State where the swap is in. // SwapInfoKit contains shared data amongst all swap types.
State SwapState SwapInfoKit
} }
// SwapCost is a breakdown of the final swap costs. // SwapCost is a breakdown of the final swap costs.
@ -85,9 +92,9 @@ type SwapCost struct {
Onchain btcutil.Amount Onchain btcutil.Amount
} }
// UnchargeQuoteRequest specifies the swap parameters for which a quote is // LoopOutQuoteRequest specifies the swap parameters for which a quote is
// requested. // requested.
type UnchargeQuoteRequest struct { type LoopOutQuoteRequest struct {
// Amount specifies the requested swap amount in sat. This does not // Amount specifies the requested swap amount in sat. This does not
// include the swap and miner fee. // include the swap and miner fee.
Amount btcutil.Amount Amount btcutil.Amount
@ -107,9 +114,9 @@ type UnchargeQuoteRequest struct {
// final cltv delta values for the off-chain payments. // final cltv delta values for the off-chain payments.
} }
// UnchargeQuote contains estimates for the fees making up the total swap cost // LoopOutQuote contains estimates for the fees making up the total swap cost
// for the client. // for the client.
type UnchargeQuote struct { type LoopOutQuote struct {
// SwapFee is the fee that the swap server is charging for the swap. // SwapFee is the fee that the swap server is charging for the swap.
SwapFee btcutil.Amount SwapFee btcutil.Amount
@ -122,8 +129,8 @@ type UnchargeQuote struct {
MinerFee btcutil.Amount MinerFee btcutil.Amount
} }
// UnchargeTerms are the server terms on which it executes swaps. // LoopOutTerms are the server terms on which it executes swaps.
type UnchargeTerms struct { type LoopOutTerms struct {
// SwapFeeBase is the fixed per-swap base fee. // SwapFeeBase is the fixed per-swap base fee.
SwapFeeBase btcutil.Amount SwapFeeBase btcutil.Amount
@ -161,23 +168,26 @@ type SwapInfoKit struct {
LastUpdateTime time.Time LastUpdateTime time.Time
} }
// SwapType indicates the type of swap. // Type indicates the type of swap.
type SwapType uint8 type Type uint8
const ( const (
// SwapTypeCharge is a charge swap. // TypeIn is a loop in swap.
SwapTypeCharge SwapType = iota TypeIn Type = iota
// SwapTypeUncharge is an uncharge swap. // TypeOut is a loop out swap.
SwapTypeUncharge TypeOut
) )
// SwapInfo exposes common info fields for charge and uncharge swaps. // SwapInfo exposes common info fields for loop in and loop out swaps.
type SwapInfo struct { type SwapInfo struct {
LastUpdate time.Time LastUpdate time.Time
SwapHash lntypes.Hash
State SwapState
SwapType SwapType
SwapContract SwapHash lntypes.Hash
State loopdb.SwapState
SwapType Type
loopdb.SwapContract
} }

@ -6,19 +6,19 @@ import (
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
// SwapStore is the priamry database interface used by the loopd system. It // SwapStore is the primary database interface used by the loopd system. It
// houses informatino for all pending completed/failed swaps. // houses information for all pending completed/failed swaps.
type SwapStore interface { type SwapStore interface {
// FetchUnchargeSwaps returns all swaps currently in the store. // FetchLoopOutSwaps returns all swaps currently in the store.
FetchUnchargeSwaps() ([]*PersistentUncharge, error) FetchLoopOutSwaps() ([]*LoopOut, error)
// CreateUncharge adds an initiated swap to the store. // CreateLoopOut adds an initiated swap to the store.
CreateUncharge(hash lntypes.Hash, swap *UnchargeContract) error CreateLoopOut(hash lntypes.Hash, swap *LoopOutContract) error
// UpdateUncharge stores a swap updateUncharge. This appends to the // UpdateLoopOut stores a new event for a target loop out swap. This
// event log for a particular swap as it goes through the various // appends to the event log for a particular swap as it goes through
// stages in its lifetime. // the various stages in its lifetime.
UpdateUncharge(hash lntypes.Hash, time time.Time, state SwapState) error UpdateLoopOut(hash lntypes.Hash, time time.Time, state SwapState) error
// Close closes the underlying database. // Close closes the underlying database.
Close() error Close() error

@ -12,13 +12,63 @@ import (
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
// UnchargeContract contains the data that is serialized to persistent storage // SwapContract contains the base data that is serialized to persistent storage
// for pending swaps. // for pending swaps.
type UnchargeContract struct { type SwapContract struct {
// Preimage is the preimage for the swap.
Preimage lntypes.Preimage
// AmountRequested is the total amount of the swap.
AmountRequested btcutil.Amount
// PrepayInvoice is the invoice that the client should pay to the
// server that will be returned if the swap is complete.
PrepayInvoice string
// SenderKey is the key of the sender that will be used in the on-chain
// HTLC.
SenderKey [33]byte
// ReceiverKey is the of the receiver that will be used in the on-chain
// HTLC.
ReceiverKey [33]byte
// CltvExpiry is the total absolute CLTV expiry of the swap.
CltvExpiry int32
// MaxPrepayRoutingFee is the maximum off-chain fee in msat that may be
// paid for the prepayment to the server.
MaxPrepayRoutingFee btcutil.Amount
// MaxSwapFee is the maximum we are willing to pay the server for the
// swap.
MaxSwapFee btcutil.Amount
// MaxMinerFee is the maximum in on-chain fees that we are willing to
// spend.
MaxMinerFee btcutil.Amount
// InitiationHeight is the block height at which the swap was
// initiated.
InitiationHeight int32
// InitiationTime is the time at which the swap was initiated.
InitiationTime time.Time
}
// LoopOutContract contains the data that is serialized to persistent storage
// for pending swaps.
type LoopOutContract struct {
// SwapContract contains basic information pertaining to this swap.
// Each swap type has a base contract, then swap specific information
// on top of it.
SwapContract SwapContract
// DestAddr is the destination address of the loop out swap.
DestAddr btcutil.Address DestAddr btcutil.Address
// SwapInvoice is the invoice that is to be paid by the client to
// initiate the loop out swap.
SwapInvoice string SwapInvoice string
// MaxSwapRoutingFee is the maximum off-chain fee in msat that may be // MaxSwapRoutingFee is the maximum off-chain fee in msat that may be
@ -29,13 +79,13 @@ type UnchargeContract struct {
// client sweep tx. // client sweep tx.
SweepConfTarget int32 SweepConfTarget int32
// UnchargeChannel is the channel to uncharge. If zero, any channel may // TargetChannel is the channel to loop out. If zero, any channel may
// be used. // be used.
UnchargeChannel *uint64 UnchargeChannel *uint64
} }
// PersistentUnchargeEvent contains the dynamic data of a swap. // LoopOutEvent contains the dynamic data of a swap.
type PersistentUnchargeEvent struct { type LoopOutEvent struct {
// State is the new state for this swap as a result of this event. // State is the new state for this swap as a result of this event.
State SwapState State SwapState
@ -43,22 +93,22 @@ type PersistentUnchargeEvent struct {
Time time.Time Time time.Time
} }
// PersistentUncharge is a combination of the contract and the updates. // LoopOut is a combination of the contract and the updates.
type PersistentUncharge struct { type LoopOut struct {
// Hash is the hash that uniquely identifies this swap. // Hash is the hash that uniquely identifies this swap.
Hash lntypes.Hash Hash lntypes.Hash
// Contract is the active contract for this swap. It describes the // Contract is the active contract for this swap. It describes the
// precise details of the swap including the final fee, CLTV value, // precise details of the swap including the final fee, CLTV value,
// etc. // etc.
Contract *UnchargeContract Contract *LoopOutContract
// Events are each of the state transitions that this swap underwent. // Events are each of the state transitions that this swap underwent.
Events []*PersistentUnchargeEvent Events []*LoopOutEvent
} }
// State returns the most recent state of this swap. // State returns the most recent state of this swap.
func (s *PersistentUncharge) State() SwapState { func (s *LoopOut) State() SwapState {
lastUpdate := s.LastUpdate() lastUpdate := s.LastUpdate()
if lastUpdate == nil { if lastUpdate == nil {
return StateInitiated return StateInitiated
@ -68,7 +118,7 @@ func (s *PersistentUncharge) State() SwapState {
} }
// LastUpdate returns the most recent update of this swap. // LastUpdate returns the most recent update of this swap.
func (s *PersistentUncharge) LastUpdate() *PersistentUnchargeEvent { func (s *LoopOut) LastUpdate() *LoopOutEvent {
eventCount := len(s.Events) eventCount := len(s.Events)
if eventCount == 0 { if eventCount == 0 {
@ -80,7 +130,7 @@ func (s *PersistentUncharge) LastUpdate() *PersistentUnchargeEvent {
} }
// LastUpdateTime returns the last update time of this swap. // LastUpdateTime returns the last update time of this swap.
func (s *PersistentUncharge) LastUpdateTime() time.Time { func (s *LoopOut) LastUpdateTime() time.Time {
lastUpdate := s.LastUpdate() lastUpdate := s.LastUpdate()
if lastUpdate == nil { if lastUpdate == nil {
return s.Contract.InitiationTime return s.Contract.InitiationTime
@ -89,7 +139,7 @@ func (s *PersistentUncharge) LastUpdateTime() time.Time {
return lastUpdate.Time return lastUpdate.Time
} }
func deserializeUnchargeContract(value []byte) (*UnchargeContract, error) { func deserializeLoopOutContract(value []byte) (*LoopOutContract, error) {
r := bytes.NewReader(value) r := bytes.NewReader(value)
contract, err := deserializeContract(r) contract, err := deserializeContract(r)
@ -97,7 +147,7 @@ func deserializeUnchargeContract(value []byte) (*UnchargeContract, error) {
return nil, err return nil, err
} }
swap := UnchargeContract{ swap := LoopOutContract{
SwapContract: *contract, SwapContract: *contract,
} }
@ -134,7 +184,7 @@ func deserializeUnchargeContract(value []byte) (*UnchargeContract, error) {
return &swap, nil return &swap, nil
} }
func serializeUnchargeContract(swap *UnchargeContract) ( func serializeLoopOutContract(swap *LoopOutContract) (
[]byte, error) { []byte, error) {
var b bytes.Buffer var b bytes.Buffer
@ -282,7 +332,7 @@ func serializeContract(swap *SwapContract, b *bytes.Buffer) error {
return nil return nil
} }
func serializeUnchargeUpdate(time time.Time, state SwapState) ( func serializeLoopOutEvent(time time.Time, state SwapState) (
[]byte, error) { []byte, error) {
var b bytes.Buffer var b bytes.Buffer
@ -298,8 +348,8 @@ func serializeUnchargeUpdate(time time.Time, state SwapState) (
return b.Bytes(), nil return b.Bytes(), nil
} }
func deserializeUnchargeUpdate(value []byte) (*PersistentUnchargeEvent, error) { func deserializeLoopOutEvent(value []byte) (*LoopOutEvent, error) {
update := &PersistentUnchargeEvent{} update := &LoopOutEvent{}
r := bytes.NewReader(value) r := bytes.NewReader(value)

@ -67,9 +67,8 @@ type boltSwapStore struct {
// interface. // interface.
var _ = (*boltSwapStore)(nil) var _ = (*boltSwapStore)(nil)
// newBoltSwapStore creates a new client swap store. // NewBoltSwapStore creates a new client swap store.
func newBoltSwapStore(dbPath string) (*boltSwapStore, error) { func NewBoltSwapStore(dbPath string) (*boltSwapStore, error) {
// If the target path for the swap store doesn't exist, then we'll // If the target path for the swap store doesn't exist, then we'll
// create it now before we proceed. // create it now before we proceed.
if !fileExists(dbPath) { if !fileExists(dbPath) {
@ -119,11 +118,11 @@ func newBoltSwapStore(dbPath string) (*boltSwapStore, error) {
}, nil }, nil
} }
// FetchUnchargeSwaps returns all swaps currently in the store. // FetchLoopOutSwaps returns all swaps currently in the store.
// //
// NOTE: Part of the loopdb.SwapStore interface. // NOTE: Part of the loopdb.SwapStore interface.
func (s *boltSwapStore) FetchUnchargeSwaps() ([]*PersistentUncharge, error) { func (s *boltSwapStore) FetchLoopOutSwaps() ([]*LoopOut, error) {
var swaps []*PersistentUncharge var swaps []*LoopOut
err := s.db.View(func(tx *bbolt.Tx) error { err := s.db.View(func(tx *bbolt.Tx) error {
// First, we'll grab our main loop out swap bucket key. // First, we'll grab our main loop out swap bucket key.
@ -155,7 +154,7 @@ func (s *boltSwapStore) FetchUnchargeSwaps() ([]*PersistentUncharge, error) {
if contractBytes == nil { if contractBytes == nil {
return errors.New("contract not found") return errors.New("contract not found")
} }
contract, err := deserializeUnchargeContract( contract, err := deserializeLoopOutContract(
contractBytes, contractBytes,
) )
if err != nil { if err != nil {
@ -171,9 +170,9 @@ func (s *boltSwapStore) FetchUnchargeSwaps() ([]*PersistentUncharge, error) {
// De serialize and collect each swap update into our // De serialize and collect each swap update into our
// slice of swap events. // slice of swap events.
var updates []*PersistentUnchargeEvent var updates []*LoopOutEvent
err = stateBucket.ForEach(func(k, v []byte) error { err = stateBucket.ForEach(func(k, v []byte) error {
event, err := deserializeUnchargeUpdate(v) event, err := deserializeLoopOutEvent(v)
if err != nil { if err != nil {
return err return err
} }
@ -188,7 +187,7 @@ func (s *boltSwapStore) FetchUnchargeSwaps() ([]*PersistentUncharge, error) {
var hash lntypes.Hash var hash lntypes.Hash
copy(hash[:], swapHash) copy(hash[:], swapHash)
swap := PersistentUncharge{ swap := LoopOut{
Contract: contract, Contract: contract,
Hash: hash, Hash: hash,
Events: updates, Events: updates,
@ -205,11 +204,11 @@ func (s *boltSwapStore) FetchUnchargeSwaps() ([]*PersistentUncharge, error) {
return swaps, nil return swaps, nil
} }
// CreateUncharge adds an initiated swap to the store. // CreateLoopOut adds an initiated swap to the store.
// //
// NOTE: Part of the loopdb.SwapStore interface. // NOTE: Part of the loopdb.SwapStore interface.
func (s *boltSwapStore) CreateUncharge(hash lntypes.Hash, func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
swap *UnchargeContract) error { swap *LoopOutContract) error {
// If the hash doesn't match the pre-image, then this is an invalid // If the hash doesn't match the pre-image, then this is an invalid
// swap so we'll bail out early. // swap so we'll bail out early.
@ -244,7 +243,7 @@ func (s *boltSwapStore) CreateUncharge(hash lntypes.Hash,
// With out swap bucket created, we'll serialize and store the // With out swap bucket created, we'll serialize and store the
// swap itself. // swap itself.
contract, err := serializeUnchargeContract(swap) contract, err := serializeLoopOutContract(swap)
if err != nil { if err != nil {
return err return err
} }
@ -259,11 +258,11 @@ func (s *boltSwapStore) CreateUncharge(hash lntypes.Hash,
}) })
} }
// UpdateUncharge stores a swap updateUncharge. This appends to the event log // UpdateLoopOut stores a swap updateLoopOut. This appends to the event log for
// for a particular swap as it goes through the various stages in its lifetime. // a particular swap as it goes through the various stages in its lifetime.
// //
// NOTE: Part of the loopdb.SwapStore interface. // NOTE: Part of the loopdb.SwapStore interface.
func (s *boltSwapStore) UpdateUncharge(hash lntypes.Hash, time time.Time, func (s *boltSwapStore) UpdateLoopOut(hash lntypes.Hash, time time.Time,
state SwapState) error { state SwapState) error {
return s.db.Update(func(tx *bbolt.Tx) error { return s.db.Update(func(tx *bbolt.Tx) error {
@ -291,7 +290,7 @@ func (s *boltSwapStore) UpdateUncharge(hash lntypes.Hash, time time.Time,
} }
// With the ID obtained, we'll write out this new update value. // With the ID obtained, we'll write out this new update value.
updateValue, err := serializeUnchargeUpdate(time, state) updateValue, err := serializeLoopOutEvent(time, state)
if err != nil { if err != nil {
return err return err
} }

@ -42,13 +42,13 @@ func TestBoltSwapStore(t *testing.T) {
} }
defer os.RemoveAll(tempDirName) defer os.RemoveAll(tempDirName)
store, err := newBoltSwapStore(tempDirName) store, err := NewBoltSwapStore(tempDirName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// First, verify that an empty database has no active swaps. // First, verify that an empty database has no active swaps.
swaps, err := store.FetchUnchargeSwaps() swaps, err := store.FetchLoopOutSwaps()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -62,7 +62,7 @@ func TestBoltSwapStore(t *testing.T) {
// Next, we'll make a new pending swap that we'll insert into the // Next, we'll make a new pending swap that we'll insert into the
// database shortly. // database shortly.
pendingSwap := UnchargeContract{ pendingSwap := LoopOutContract{
SwapContract: SwapContract{ SwapContract: SwapContract{
AmountRequested: 100, AmountRequested: 100,
Preimage: testPreimage, Preimage: testPreimage,
@ -90,7 +90,7 @@ func TestBoltSwapStore(t *testing.T) {
checkSwap := func(expectedState SwapState) { checkSwap := func(expectedState SwapState) {
t.Helper() t.Helper()
swaps, err := store.FetchUnchargeSwaps() swaps, err := store.FetchLoopOutSwaps()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -113,20 +113,20 @@ func TestBoltSwapStore(t *testing.T) {
// If we create a new swap, then it should show up as being initialized // If we create a new swap, then it should show up as being initialized
// right after. // right after.
if err := store.CreateUncharge(hash, &pendingSwap); err != nil { if err := store.CreateLoopOut(hash, &pendingSwap); err != nil {
t.Fatal(err) t.Fatal(err)
} }
checkSwap(StateInitiated) checkSwap(StateInitiated)
// Trying to make the same swap again should result in an error. // Trying to make the same swap again should result in an error.
if err := store.CreateUncharge(hash, &pendingSwap); err == nil { if err := store.CreateLoopOut(hash, &pendingSwap); err == nil {
t.Fatal("expected error on storing duplicate") t.Fatal("expected error on storing duplicate")
} }
checkSwap(StateInitiated) checkSwap(StateInitiated)
// Next, we'll update to the next state of the pre-image being // Next, we'll update to the next state of the pre-image being
// revealed. The state should be reflected here again. // revealed. The state should be reflected here again.
err = store.UpdateUncharge( err = store.UpdateLoopOut(
hash, testTime, StatePreimageRevealed, hash, testTime, StatePreimageRevealed,
) )
if err != nil { if err != nil {
@ -136,7 +136,7 @@ func TestBoltSwapStore(t *testing.T) {
// Next, we'll update to the final state to ensure that the state is // Next, we'll update to the final state to ensure that the state is
// properly updated. // properly updated.
err = store.UpdateUncharge( err = store.UpdateLoopOut(
hash, testTime, StateFailInsufficientValue, hash, testTime, StateFailInsufficientValue,
) )
if err != nil { if err != nil {
@ -150,7 +150,7 @@ func TestBoltSwapStore(t *testing.T) {
// If we re-open the same store, then the state of the current swap // If we re-open the same store, then the state of the current swap
// should be the same. // should be the same.
store, err = newBoltSwapStore(tempDirName) store, err = NewBoltSwapStore(tempDirName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -1,41 +0,0 @@
package loopdb
import (
"time"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lntypes"
)
// SwapContract contains the base data that is serialized to persistent storage
// for pending swaps.
type SwapContract struct {
Preimage lntypes.Preimage
AmountRequested btcutil.Amount
PrepayInvoice string
SenderKey [33]byte
ReceiverKey [33]byte
CltvExpiry int32
// MaxPrepayRoutingFee is the maximum off-chain fee in msat that may be
// paid for the prepayment to the server.
MaxPrepayRoutingFee btcutil.Amount
// MaxSwapFee is the maximum we are willing to pay the server for the
// swap.
MaxSwapFee btcutil.Amount
// MaxMinerFee is the maximum in on-chain fees that we are willing to
// spend.
MaxMinerFee btcutil.Amount
// InitiationHeight is the block height at which the swap was
// initiated.
InitiationHeight int32
// InitiationTime is the time at which the swap was initiated.
InitiationTime time.Time
}

@ -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)

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/lightninglabs/loop/lndclient" "github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweep" "github.com/lightninglabs/loop/sweep"
"github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/test"
) )
@ -37,7 +38,7 @@ func TestLateHtlcPublish(t *testing.T) {
server: server, server: server,
} }
swap, err := newUnchargeSwap( swap, err := newLoopOutSwap(
context.Background(), cfg, height, testRequest, context.Background(), cfg, height, testRequest,
) )
if err != nil { if err != nil {
@ -63,10 +64,10 @@ func TestLateHtlcPublish(t *testing.T) {
errChan <- err errChan <- err
}() }()
store.assertUnchargeStored() store.assertLoopOutStored()
state := <-statusChan state := <-statusChan
if state.State != StateInitiated { if state.State != loopdb.StateInitiated {
t.Fatal("unexpected state") t.Fatal("unexpected state")
} }
@ -86,10 +87,10 @@ func TestLateHtlcPublish(t *testing.T) {
errors.New(lndclient.PaymentResultUnknownPaymentHash), errors.New(lndclient.PaymentResultUnknownPaymentHash),
) )
store.assertStoreFinished(StateFailTimeout) store.assertStoreFinished(loopdb.StateFailTimeout)
status := <-statusChan status := <-statusChan
if status.State != StateFailTimeout { if status.State != loopdb.StateFailTimeout {
t.Fatal("unexpected state") t.Fatal("unexpected state")
} }

@ -18,16 +18,16 @@ import (
var ( var (
testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
testUnchargeOnChainCltvDelta = int32(30) testLoopOutOnChainCltvDelta = int32(30)
testCltvDelta = 50 testCltvDelta = 50
testSwapFeeBase = btcutil.Amount(21) testSwapFeeBase = btcutil.Amount(21)
testSwapFeeRate = int64(100) testSwapFeeRate = int64(100)
testInvoiceExpiry = 180 * time.Second testInvoiceExpiry = 180 * time.Second
testFixedPrepayAmount = btcutil.Amount(100) testFixedPrepayAmount = btcutil.Amount(100)
testMinSwapAmount = btcutil.Amount(10000) testMinSwapAmount = btcutil.Amount(10000)
testMaxSwapAmount = btcutil.Amount(1000000) testMaxSwapAmount = btcutil.Amount(1000000)
testTxConfTarget = 2 testTxConfTarget = 2
testRepublishDelay = 10 * time.Second testRepublishDelay = 10 * time.Second
) )
// serverMock is used in client unit tests to simulate swap server behaviour. // serverMock is used in client unit tests to simulate swap server behaviour.
@ -56,10 +56,10 @@ func newServerMock() *serverMock {
} }
} }
func (s *serverMock) NewUnchargeSwap(ctx context.Context, func (s *serverMock) NewLoopOutSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, swapHash lntypes.Hash, amount btcutil.Amount,
receiverKey [33]byte) ( receiverKey [33]byte) (
*newUnchargeResponse, error) { *newLoopOutResponse, error) {
_, senderKey := test.CreateKey(100) _, senderKey := test.CreateKey(100)
@ -82,24 +82,24 @@ func (s *serverMock) NewUnchargeSwap(ctx context.Context,
var senderKeyArray [33]byte var senderKeyArray [33]byte
copy(senderKeyArray[:], senderKey.SerializeCompressed()) copy(senderKeyArray[:], senderKey.SerializeCompressed())
return &newUnchargeResponse{ return &newLoopOutResponse{
senderKey: senderKeyArray, senderKey: senderKeyArray,
swapInvoice: swapPayReqString, swapInvoice: swapPayReqString,
prepayInvoice: prePayReqString, prepayInvoice: prePayReqString,
expiry: s.height + testUnchargeOnChainCltvDelta, expiry: s.height + testLoopOutOnChainCltvDelta,
}, nil }, nil
} }
func (s *serverMock) GetUnchargeTerms(ctx context.Context) ( func (s *serverMock) GetLoopOutTerms(ctx context.Context) (
*UnchargeTerms, error) { *LoopOutTerms, error) {
dest := [33]byte{1, 2, 3} dest := [33]byte{1, 2, 3}
return &UnchargeTerms{ return &LoopOutTerms{
SwapFeeBase: testSwapFeeBase, SwapFeeBase: testSwapFeeBase,
SwapFeeRate: testSwapFeeRate, SwapFeeRate: testSwapFeeRate,
SwapPaymentDest: dest, SwapPaymentDest: dest,
CltvDelta: testUnchargeOnChainCltvDelta, CltvDelta: testLoopOutOnChainCltvDelta,
MinSwapAmount: testMinSwapAmount, MinSwapAmount: testMinSwapAmount,
MaxSwapAmount: testMaxSwapAmount, MaxSwapAmount: testMaxSwapAmount,
PrepayAmt: testFixedPrepayAmount, PrepayAmt: testFixedPrepayAmount,

@ -5,51 +5,54 @@ import (
"testing" "testing"
"time" "time"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
// storeMock implements a mock client swap store. // storeMock implements a mock client swap store.
type storeMock struct { type storeMock struct {
unchargeSwaps map[lntypes.Hash]*UnchargeContract loopOutSwaps map[lntypes.Hash]*loopdb.LoopOutContract
unchargeUpdates map[lntypes.Hash][]SwapState loopOutUpdates map[lntypes.Hash][]loopdb.SwapState
unchargeStoreChan chan UnchargeContract loopOutStoreChan chan loopdb.LoopOutContract
unchargeUpdateChan chan SwapState loopOutUpdateChan chan loopdb.SwapState
t *testing.T t *testing.T
} }
type finishData struct { type finishData struct {
preimage lntypes.Hash preimage lntypes.Hash
result SwapState result loopdb.SwapState
} }
// NewStoreMock instantiates a new mock store. // NewStoreMock instantiates a new mock store.
func newStoreMock(t *testing.T) *storeMock { func newStoreMock(t *testing.T) *storeMock {
return &storeMock{ return &storeMock{
unchargeStoreChan: make(chan UnchargeContract, 1), loopOutStoreChan: make(chan loopdb.LoopOutContract, 1),
unchargeUpdateChan: make(chan SwapState, 1), loopOutUpdateChan: make(chan loopdb.SwapState, 1),
unchargeSwaps: make(map[lntypes.Hash]*UnchargeContract), loopOutSwaps: make(map[lntypes.Hash]*loopdb.LoopOutContract),
unchargeUpdates: make(map[lntypes.Hash][]SwapState), loopOutUpdates: make(map[lntypes.Hash][]loopdb.SwapState),
t: t, t: t,
} }
} }
// getUnchargeSwaps returns all swaps currently in the store. // FetchLoopOutSwaps returns all swaps currently in the store.
func (s *storeMock) getUnchargeSwaps() ([]*PersistentUncharge, error) { //
result := []*PersistentUncharge{} // NOTE: Part of the loopdb.SwapStore interface.
func (s *storeMock) FetchLoopOutSwaps() ([]*loopdb.LoopOut, error) {
result := []*loopdb.LoopOut{}
for hash, contract := range s.unchargeSwaps { for hash, contract := range s.loopOutSwaps {
updates := s.unchargeUpdates[hash] updates := s.loopOutUpdates[hash]
events := make([]*PersistentUnchargeEvent, len(updates)) events := make([]*loopdb.LoopOutEvent, len(updates))
for i, u := range updates { for i, u := range updates {
events[i] = &PersistentUnchargeEvent{ events[i] = &loopdb.LoopOutEvent{
State: u, State: u,
} }
} }
swap := &PersistentUncharge{ swap := &loopdb.LoopOut{
Hash: hash, Hash: hash,
Contract: contract, Contract: contract,
Events: events, Events: events,
@ -60,58 +63,68 @@ func (s *storeMock) getUnchargeSwaps() ([]*PersistentUncharge, error) {
return result, nil return result, nil
} }
// createUncharge adds an initiated swap to the store. // CreateLoopOut adds an initiated swap to the store.
func (s *storeMock) createUncharge(hash lntypes.Hash, //
swap *UnchargeContract) error { // NOTE: Part of the loopdb.SwapStore interface.
func (s *storeMock) CreateLoopOut(hash lntypes.Hash,
swap *loopdb.LoopOutContract) error {
_, ok := s.unchargeSwaps[hash] _, ok := s.loopOutSwaps[hash]
if ok { if ok {
return errors.New("swap already exists") return errors.New("swap already exists")
} }
s.unchargeSwaps[hash] = swap s.loopOutSwaps[hash] = swap
s.unchargeUpdates[hash] = []SwapState{} s.loopOutUpdates[hash] = []loopdb.SwapState{}
s.unchargeStoreChan <- *swap s.loopOutStoreChan <- *swap
return nil return nil
} }
// Finalize stores the final swap result. // UpdateLoopOut stores a new event for a target loop out swap. This appends to
func (s *storeMock) updateUncharge(hash lntypes.Hash, time time.Time, // the event log for a particular swap as it goes through the various stages in
state SwapState) error { // its lifetime.
//
// NOTE: Part of the loopdb.SwapStore interface.
func (s *storeMock) UpdateLoopOut(hash lntypes.Hash, time time.Time,
state loopdb.SwapState) error {
updates, ok := s.unchargeUpdates[hash] updates, ok := s.loopOutUpdates[hash]
if !ok { if !ok {
return errors.New("swap does not exists") return errors.New("swap does not exists")
} }
updates = append(updates, state) updates = append(updates, state)
s.unchargeUpdates[hash] = updates s.loopOutUpdates[hash] = updates
s.unchargeUpdateChan <- state s.loopOutUpdateChan <- state
return nil return nil
} }
func (s *storeMock) Close() error {
return nil
}
func (s *storeMock) isDone() error { func (s *storeMock) isDone() error {
select { select {
case <-s.unchargeStoreChan: case <-s.loopOutStoreChan:
return errors.New("storeChan not empty") return errors.New("storeChan not empty")
default: default:
} }
select { select {
case <-s.unchargeUpdateChan: case <-s.loopOutUpdateChan:
return errors.New("updateChan not empty") return errors.New("updateChan not empty")
default: default:
} }
return nil return nil
} }
func (s *storeMock) assertUnchargeStored() { func (s *storeMock) assertLoopOutStored() {
s.t.Helper() s.t.Helper()
select { select {
case <-s.unchargeStoreChan: case <-s.loopOutStoreChan:
case <-time.After(test.Timeout): case <-time.After(test.Timeout):
s.t.Fatalf("expected swap to be stored") s.t.Fatalf("expected swap to be stored")
} }
@ -122,8 +135,8 @@ func (s *storeMock) assertStorePreimageReveal() {
s.t.Helper() s.t.Helper()
select { select {
case state := <-s.unchargeUpdateChan: case state := <-s.loopOutUpdateChan:
if state != StatePreimageRevealed { if state != loopdb.StatePreimageRevealed {
s.t.Fatalf("unexpected state") s.t.Fatalf("unexpected state")
} }
case <-time.After(test.Timeout): case <-time.After(test.Timeout):
@ -131,11 +144,11 @@ func (s *storeMock) assertStorePreimageReveal() {
} }
} }
func (s *storeMock) assertStoreFinished(expectedResult SwapState) { func (s *storeMock) assertStoreFinished(expectedResult loopdb.SwapState) {
s.t.Helper() s.t.Helper()
select { select {
case state := <-s.unchargeUpdateChan: case state := <-s.loopOutUpdateChan:
if state != expectedResult { if state != expectedResult {
s.t.Fatalf("expected result %v, but got %v", s.t.Fatalf("expected result %v, but got %v",
expectedResult, state) expectedResult, state)

@ -5,33 +5,34 @@ import (
"time" "time"
"github.com/lightninglabs/loop/lndclient" "github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/utils" "github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
type swapKit struct { type swapKit struct {
htlc *utils.Htlc htlc *swap.Htlc
hash lntypes.Hash hash lntypes.Hash
height int32 height int32
log *utils.SwapLog log *SwapLog
lastUpdateTime time.Time lastUpdateTime time.Time
cost SwapCost cost SwapCost
state SwapState state loopdb.SwapState
executeConfig executeConfig
swapConfig swapConfig
contract *SwapContract contract *loopdb.SwapContract
swapType SwapType swapType Type
} }
func newSwapKit(hash lntypes.Hash, swapType SwapType, cfg *swapConfig, func newSwapKit(hash lntypes.Hash, swapType Type, cfg *swapConfig,
contract *SwapContract) (*swapKit, error) { contract *loopdb.SwapContract) (*swapKit, error) {
// Compose expected on-chain swap script // Compose expected on-chain swap script
htlc, err := utils.NewHtlc( htlc, err := swap.NewHtlc(
contract.CltvExpiry, contract.SenderKey, contract.CltvExpiry, contract.SenderKey,
contract.ReceiverKey, hash, contract.ReceiverKey, hash,
) )
@ -45,7 +46,7 @@ func newSwapKit(hash lntypes.Hash, swapType SwapType, cfg *swapConfig,
return nil, err return nil, err
} }
log := &utils.SwapLog{ log := &SwapLog{
Hash: hash, Hash: hash,
Logger: logger, Logger: logger,
} }
@ -57,7 +58,7 @@ func newSwapKit(hash lntypes.Hash, swapType SwapType, cfg *swapConfig,
hash: hash, hash: hash,
log: log, log: log,
htlc: htlc, htlc: htlc,
state: StateInitiated, state: loopdb.StateInitiated,
contract: contract, contract: contract,
swapType: swapType, swapType: swapType,
}, nil }, nil
@ -91,6 +92,6 @@ type genericSwap interface {
type swapConfig struct { type swapConfig struct {
lnd *lndclient.LndServices lnd *lndclient.LndServices
store swapClientStore store loopdb.SwapStore
server swapServerClient server swapServerClient
} }

@ -17,13 +17,13 @@ import (
) )
type swapServerClient interface { type swapServerClient interface {
GetUnchargeTerms(ctx context.Context) ( GetLoopOutTerms(ctx context.Context) (
*UnchargeTerms, error) *LoopOutTerms, error)
NewUnchargeSwap(ctx context.Context, NewLoopOutSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, swapHash lntypes.Hash, amount btcutil.Amount,
receiverKey [33]byte) ( receiverKey [33]byte) (
*newUnchargeResponse, error) *newLoopOutResponse, error)
} }
type grpcSwapServerClient struct { type grpcSwapServerClient struct {
@ -31,7 +31,9 @@ type grpcSwapServerClient struct {
conn *grpc.ClientConn conn *grpc.ClientConn
} }
func newSwapServerClient(address string, insecure bool) (*grpcSwapServerClient, error) { func newSwapServerClient(address string,
insecure bool) (*grpcSwapServerClient, error) {
serverConn, err := getSwapServerConn(address, insecure) serverConn, err := getSwapServerConn(address, insecure)
if err != nil { if err != nil {
return nil, err return nil, err
@ -45,13 +47,13 @@ func newSwapServerClient(address string, insecure bool) (*grpcSwapServerClient,
}, nil }, nil
} }
func (s *grpcSwapServerClient) GetUnchargeTerms(ctx context.Context) ( func (s *grpcSwapServerClient) GetLoopOutTerms(ctx context.Context) (
*UnchargeTerms, error) { *LoopOutTerms, error) {
rpcCtx, rpcCancel := context.WithTimeout(ctx, serverRPCTimeout) rpcCtx, rpcCancel := context.WithTimeout(ctx, serverRPCTimeout)
defer rpcCancel() defer rpcCancel()
quoteResp, err := s.server.UnchargeQuote(rpcCtx, quoteResp, err := s.server.LoopOutQuote(rpcCtx,
&looprpc.ServerUnchargeQuoteRequest{}, &looprpc.ServerLoopOutQuoteRequest{},
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -67,7 +69,7 @@ func (s *grpcSwapServerClient) GetUnchargeTerms(ctx context.Context) (
var destArray [33]byte var destArray [33]byte
copy(destArray[:], dest) copy(destArray[:], dest)
return &UnchargeTerms{ return &LoopOutTerms{
MinSwapAmount: btcutil.Amount(quoteResp.MinSwapAmount), MinSwapAmount: btcutil.Amount(quoteResp.MinSwapAmount),
MaxSwapAmount: btcutil.Amount(quoteResp.MaxSwapAmount), MaxSwapAmount: btcutil.Amount(quoteResp.MaxSwapAmount),
PrepayAmt: btcutil.Amount(quoteResp.PrepayAmt), PrepayAmt: btcutil.Amount(quoteResp.PrepayAmt),
@ -78,14 +80,14 @@ func (s *grpcSwapServerClient) GetUnchargeTerms(ctx context.Context) (
}, nil }, nil
} }
func (s *grpcSwapServerClient) NewUnchargeSwap(ctx context.Context, func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, receiverKey [33]byte) ( swapHash lntypes.Hash, amount btcutil.Amount,
*newUnchargeResponse, error) { receiverKey [33]byte) (*newLoopOutResponse, error) {
rpcCtx, rpcCancel := context.WithTimeout(ctx, serverRPCTimeout) rpcCtx, rpcCancel := context.WithTimeout(ctx, serverRPCTimeout)
defer rpcCancel() defer rpcCancel()
swapResp, err := s.server.NewUnchargeSwap(rpcCtx, swapResp, err := s.server.NewLoopOutSwap(rpcCtx,
&looprpc.ServerUnchargeSwapRequest{ &looprpc.ServerLoopOutRequest{
SwapHash: swapHash[:], SwapHash: swapHash[:],
Amt: uint64(amount), Amt: uint64(amount),
ReceiverKey: receiverKey[:], ReceiverKey: receiverKey[:],
@ -104,7 +106,7 @@ func (s *grpcSwapServerClient) NewUnchargeSwap(ctx context.Context,
return nil, fmt.Errorf("invalid sender key: %v", err) return nil, fmt.Errorf("invalid sender key: %v", err)
} }
return &newUnchargeResponse{ return &newLoopOutResponse{
swapInvoice: swapResp.SwapInvoice, swapInvoice: swapResp.SwapInvoice,
prepayInvoice: swapResp.PrepayInvoice, prepayInvoice: swapResp.PrepayInvoice,
senderKey: senderKey, senderKey: senderKey,
@ -135,7 +137,7 @@ func getSwapServerConn(address string, insecure bool) (*grpc.ClientConn, error)
return conn, nil return conn, nil
} }
type newUnchargeResponse struct { type newLoopOutResponse struct {
swapInvoice string swapInvoice string
prepayInvoice string prepayInvoice string
senderKey [33]byte senderKey [33]byte

@ -9,7 +9,7 @@ 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/utils" "github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
) )
@ -22,7 +22,7 @@ type Sweeper struct {
// CreateSweepTx creates an htlc sweep tx. // CreateSweepTx creates an htlc sweep tx.
func (s *Sweeper) CreateSweepTx( func (s *Sweeper) CreateSweepTx(
globalCtx context.Context, height int32, globalCtx context.Context, height int32,
htlc *utils.Htlc, htlcOutpoint wire.OutPoint, htlc *swap.Htlc, htlcOutpoint wire.OutPoint,
keyBytes [33]byte, keyBytes [33]byte,
witnessFunc func(sig []byte) (wire.TxWitness, error), witnessFunc func(sig []byte) (wire.TxWitness, error),
amount, fee btcutil.Amount, amount, fee btcutil.Amount,

@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweep" "github.com/lightninglabs/loop/sweep"
"github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
@ -68,7 +69,7 @@ func newSwapClient(config *clientConfig) *Client {
} }
func createClientTestContext(t *testing.T, func createClientTestContext(t *testing.T,
pendingSwaps []*PersistentUncharge) *testContext { pendingSwaps []*loopdb.LoopOut) *testContext {
serverMock := newServerMock() serverMock := newServerMock()
@ -76,13 +77,13 @@ func createClientTestContext(t *testing.T,
store := newStoreMock(t) store := newStoreMock(t)
for _, s := range pendingSwaps { for _, s := range pendingSwaps {
store.unchargeSwaps[s.Hash] = s.Contract store.loopOutSwaps[s.Hash] = s.Contract
updates := []SwapState{} updates := []loopdb.SwapState{}
for _, e := range s.Events { for _, e := range s.Events {
updates = append(updates, e.State) updates = append(updates, e.State)
} }
store.unchargeUpdates[s.Hash] = updates store.loopOutUpdates[s.Hash] = updates
} }
expiryChan := make(chan time.Time) expiryChan := make(chan time.Time)
@ -169,7 +170,7 @@ func (ctx *testContext) assertIsDone() {
func (ctx *testContext) assertStored() { func (ctx *testContext) assertStored() {
ctx.T.Helper() ctx.T.Helper()
ctx.store.assertUnchargeStored() ctx.store.assertLoopOutStored()
} }
func (ctx *testContext) assertStorePreimageReveal() { func (ctx *testContext) assertStorePreimageReveal() {
@ -178,21 +179,21 @@ func (ctx *testContext) assertStorePreimageReveal() {
ctx.store.assertStorePreimageReveal() ctx.store.assertStorePreimageReveal()
} }
func (ctx *testContext) assertStoreFinished(expectedResult SwapState) { func (ctx *testContext) assertStoreFinished(expectedResult loopdb.SwapState) {
ctx.T.Helper() ctx.T.Helper()
ctx.store.assertStoreFinished(expectedResult) ctx.store.assertStoreFinished(expectedResult)
} }
func (ctx *testContext) assertStatus(expectedState SwapState) { func (ctx *testContext) assertStatus(expectedState loopdb.SwapState) {
ctx.T.Helper() ctx.T.Helper()
for { for {
select { select {
case update := <-ctx.statusChan: case update := <-ctx.statusChan:
if update.SwapType != SwapTypeUncharge { if update.SwapType != TypeOut {
continue continue
} }

Loading…
Cancel
Save