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

@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lntypes"
)
@ -17,7 +18,7 @@ var (
testAddr, _ = btcutil.DecodeAddress(
"rbsHiPKwAgxeo1EQYiyzJTkA8XEmWSVAKx", nil)
testRequest = &UnchargeRequest{
testRequest = &OutRequest{
Amount: btcutil.Amount(50000),
DestAddr: testAddr,
MaxMinerFee: 50000,
@ -40,13 +41,13 @@ func TestSuccess(t *testing.T) {
// Initiate uncharge.
hash, err := ctx.swapClient.Uncharge(context.Background(), testRequest)
hash, err := ctx.swapClient.LoopOut(context.Background(), testRequest)
if err != nil {
t.Fatal(err)
}
ctx.assertStored()
ctx.assertStatus(StateInitiated)
ctx.assertStatus(loopdb.StateInitiated)
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
@ -67,13 +68,13 @@ func TestFailOffchain(t *testing.T) {
ctx := createClientTestContext(t, nil)
_, err := ctx.swapClient.Uncharge(context.Background(), testRequest)
_, err := ctx.swapClient.LoopOut(context.Background(), testRequest)
if err != nil {
t.Fatal(err)
}
ctx.assertStored()
ctx.assertStatus(StateInitiated)
ctx.assertStatus(loopdb.StateInitiated)
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
@ -86,9 +87,9 @@ func TestFailOffchain(t *testing.T) {
signalPrepaymentResult(
errors.New(lndclient.PaymentResultUnknownPaymentHash),
)
ctx.assertStatus(StateFailOffchainPayments)
ctx.assertStatus(loopdb.StateFailOffchainPayments)
ctx.assertStoreFinished(StateFailOffchainPayments)
ctx.assertStoreFinished(loopdb.StateFailOffchainPayments)
ctx.finish()
}
@ -105,7 +106,7 @@ func TestFailWrongAmount(t *testing.T) {
// Modify mock for this subtest.
modifier(ctx.serverMock)
_, err := ctx.swapClient.Uncharge(
_, err := ctx.swapClient.LoopOut(
context.Background(), testRequest,
)
if err != expectedErr {
@ -175,17 +176,17 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
var receiverKey [33]byte
copy(receiverKey[:], receiverPubKey.SerializeCompressed())
state := StateInitiated
state := loopdb.StateInitiated
if preimageRevealed {
state = StatePreimageRevealed
state = loopdb.StatePreimageRevealed
}
pendingSwap := &PersistentUncharge{
Contract: &UnchargeContract{
pendingSwap := &loopdb.LoopOut{
Contract: &loopdb.LoopOutContract{
DestAddr: dest,
SwapInvoice: swapPayReq,
SweepConfTarget: 2,
MaxSwapRoutingFee: 70000,
SwapContract: SwapContract{
SwapContract: loopdb.SwapContract{
Preimage: preimage,
AmountRequested: amt,
CltvExpiry: 744,
@ -196,7 +197,7 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
MaxMinerFee: 50000,
},
},
Events: []*PersistentUnchargeEvent{
Events: []*loopdb.LoopOutEvent{
{
State: state,
},
@ -210,12 +211,12 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
pendingSwap.Contract.CltvExpiry = 610
}
ctx := createClientTestContext(t, []*PersistentUncharge{pendingSwap})
ctx := createClientTestContext(t, []*loopdb.LoopOut{pendingSwap})
if preimageRevealed {
ctx.assertStatus(StatePreimageRevealed)
ctx.assertStatus(loopdb.StatePreimageRevealed)
} else {
ctx.assertStatus(StateInitiated)
ctx.assertStatus(loopdb.StateInitiated)
}
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
@ -228,8 +229,8 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
signalPrepaymentResult(nil)
if !expectSuccess {
ctx.assertStatus(StateFailTimeout)
ctx.assertStoreFinished(StateFailTimeout)
ctx.assertStatus(loopdb.StateFailTimeout)
ctx.assertStoreFinished(loopdb.StateFailTimeout)
ctx.finish()
return
}
@ -259,7 +260,7 @@ func testSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
ctx.expiryChan <- testTime
if !preimageRevealed {
ctx.assertStatus(StatePreimageRevealed)
ctx.assertStatus(loopdb.StatePreimageRevealed)
ctx.assertStorePreimageReveal()
}
@ -283,9 +284,9 @@ func testSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
ctx.NotifySpend(sweepTx, 0)
ctx.assertStatus(StateSuccess)
ctx.assertStatus(loopdb.StateSuccess)
ctx.assertStoreFinished(StateSuccess)
ctx.assertStoreFinished(loopdb.StateSuccess)
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
import (
"context"
"errors"
"fmt"
"os"
@ -9,7 +8,7 @@ import (
"time"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/utils"
"github.com/lightninglabs/loop/swap"
"github.com/btcsuite/btcutil"
@ -18,131 +17,50 @@ import (
)
var (
swapdAddress = "localhost:11010"
loopdAddress = "localhost:11010"
// 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
// minimum routing fees, which is not very indicative.
// to get a reliable estimate of the routing fees. Best we can do is
// the minimum routing fees, which is not very indicative.
maxRoutingFeeBase = btcutil.Amount(10)
maxRoutingFeeRate = int64(50000)
)
var unchargeCommand = cli.Command{
Name: "uncharge",
Usage: "perform an off-chain to on-chain swap",
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 fatal(err error) {
fmt.Fprintf(os.Stderr, "[loop] %v\n", err)
os.Exit(1)
}
func main() {
app := cli.NewApp()
app.Version = "0.0.1"
app.Usage = "command line interface to swapd"
app.Commands = []cli.Command{unchargeCommand, termsCommand}
app.Action = monitor
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
app.Name = "loop"
app.Usage = "control plane for your loopd"
app.Commands = []cli.Command{
loopOutCommand, termsCommand, monitorCommand,
}
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),
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{})
err := app.Run(os.Args)
if err != nil {
return err
}
for {
swap, err := stream.Recv()
if err != nil {
return fmt.Errorf("recv: %v", err)
}
logSwap(swap)
fatal(err)
}
}
func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) {
conn, err := getSwapCliConn(swapdAddress)
conn, err := getClientConn(loopdAddress)
if err != nil {
return nil, nil, err
}
cleanup := func() { conn.Close() }
swapCliClient := looprpc.NewSwapClientClient(conn)
return swapCliClient, cleanup, nil
loopClient := looprpc.NewSwapClientClient(conn)
return loopClient, cleanup, nil
}
func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
return utils.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate)
return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate)
}
type limits struct {
@ -160,8 +78,8 @@ func getLimits(amt btcutil.Amount, quote *looprpc.QuoteResponse) *limits {
quote.PrepayAmt,
)),
// Apply a multiplier to the estimated miner fee, to not get the swap
// canceled because fees increased in the mean time.
// Apply a multiplier to the estimated miner fee, to not get
// the swap canceled because fees increased in the mean time.
maxMinerFee: btcutil.Amount(quote.MinerFee) * 3,
maxSwapFee: btcutil.Amount(quote.SwapFee),
@ -173,12 +91,15 @@ func displayLimits(amt btcutil.Amount, l *limits) error {
totalSuccessMax := l.maxSwapRoutingFee + l.maxPrepayRoutingFee +
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,
)
fmt.Printf("CONTINUE SWAP? (y/n), expand fee detail (x): ")
var answer string
fmt.Scanln(&answer)
switch answer {
case "y":
return nil
@ -211,73 +132,6 @@ func parseAmt(text string) (btcutil.Amount, error) {
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) {
fmt.Printf("%v %v %v %v - %v\n",
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{
grpc.WithInsecure(),
}

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

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

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

@ -7,31 +7,32 @@ import (
"github.com/lightningnetwork/lnd/queue"
"github.com/lightninglabs/loop"
"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/lightninglabs/loop/client"
"github.com/lightninglabs/loop/looprpc"
)
const completedSwapsCount = 5
// swapClientServer implements the grpc service exposed by swapd.
// swapClientServer implements the grpc service exposed by loopd.
type swapClientServer struct {
impl *client.Client
impl *loop.Client
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
// 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().
func (s *swapClientServer) Uncharge(ctx context.Context,
in *looprpc.UnchargeRequest) (
func (s *swapClientServer) LoopOut(ctx context.Context,
in *looprpc.LoopOutRequest) (
*looprpc.SwapResponse, error) {
logger.Infof("Uncharge request received")
logger.Infof("LoopOut request received")
var sweepAddr btcutil.Address
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),
DestAddr: sweepAddr,
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
@ -59,12 +60,12 @@ func (s *swapClientServer) Uncharge(ctx context.Context,
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
SweepConfTarget: defaultConfTarget,
}
if in.UnchargeChannel != 0 {
req.UnchargeChannel = &in.UnchargeChannel
if in.LoopOutChannel != 0 {
req.LoopOutChannel = &in.LoopOutChannel
}
hash, err := s.impl.Uncharge(ctx, req)
hash, err := s.impl.LoopOut(ctx, req)
if err != nil {
logger.Errorf("Uncharge: %v", err)
logger.Errorf("LoopOut: %v", err)
return nil, err
}
@ -73,24 +74,25 @@ func (s *swapClientServer) Uncharge(ctx context.Context,
}, nil
}
func (s *swapClientServer) marshallSwap(swap *client.SwapInfo) (
func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
*looprpc.SwapStatus, error) {
var state looprpc.SwapState
switch swap.State {
case client.StateInitiated:
switch loopSwap.State {
case loopdb.StateInitiated:
state = looprpc.SwapState_INITIATED
case client.StatePreimageRevealed:
case loopdb.StatePreimageRevealed:
state = looprpc.SwapState_PREIMAGE_REVEALED
case client.StateSuccess:
case loopdb.StateSuccess:
state = looprpc.SwapState_SUCCESS
default:
// Return less granular status over rpc.
state = looprpc.SwapState_FAILED
}
htlc, err := utils.NewHtlc(swap.CltvExpiry, swap.SenderKey,
swap.ReceiverKey, swap.SwapHash,
htlc, err := swap.NewHtlc(
loopSwap.CltvExpiry, loopSwap.SenderKey, loopSwap.ReceiverKey,
loopSwap.SwapHash,
)
if err != nil {
return nil, err
@ -102,13 +104,13 @@ func (s *swapClientServer) marshallSwap(swap *client.SwapInfo) (
}
return &looprpc.SwapStatus{
Amt: int64(swap.AmountRequested),
Id: swap.SwapHash.String(),
Amt: int64(loopSwap.AmountRequested),
Id: loopSwap.SwapHash.String(),
State: state,
InitiationTime: swap.InitiationTime.UnixNano(),
LastUpdateTime: swap.LastUpdate.UnixNano(),
InitiationTime: loopSwap.InitiationTime.UnixNano(),
LastUpdateTime: loopSwap.LastUpdate.UnixNano(),
HtlcAddress: address.EncodeAddress(),
Type: looprpc.SwapType_UNCHARGE,
Type: looprpc.SwapType_LOOP_OUT,
}, nil
}
@ -118,7 +120,7 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
logger.Infof("Monitor request received")
send := func(info client.SwapInfo) error {
send := func(info loop.SwapInfo) error {
rpcSwap, err := s.marshallSwap(&info)
if err != nil {
return err
@ -140,9 +142,9 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
nextSubscriberID++
subscribers[id] = queue.ChanIn()
var pendingSwaps, completedSwaps []client.SwapInfo
var pendingSwaps, completedSwaps []loop.SwapInfo
for _, swap := range swaps {
if swap.State.Type() == client.StateTypePending {
if swap.State.Type() == loopdb.StateTypePending {
pendingSwaps = append(pendingSwaps, swap)
} else {
completedSwaps = append(completedSwaps, swap)
@ -196,7 +198,7 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
return nil
}
swap := queueItem.(client.SwapInfo)
swap := queueItem.(loop.SwapInfo)
if err := send(swap); err != nil {
return err
}
@ -207,12 +209,12 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
}
// GetTerms returns the terms that the server enforces for swaps.
func (s *swapClientServer) GetUnchargeTerms(ctx context.Context, req *looprpc.TermsRequest) (
*looprpc.TermsResponse, error) {
func (s *swapClientServer) GetLoopOutTerms(ctx context.Context,
req *looprpc.TermsRequest) (*looprpc.TermsResponse, error) {
logger.Infof("Terms request received")
terms, err := s.impl.UnchargeTerms(ctx)
terms, err := s.impl.LoopOutTerms(ctx)
if err != nil {
logger.Errorf("Terms request: %v", 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.
func (s *swapClientServer) GetUnchargeQuote(ctx context.Context,
func (s *swapClientServer) GetLoopOutQuote(ctx context.Context,
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),
SweepConfTarget: defaultConfTarget,
})

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

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

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

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

@ -4,11 +4,12 @@ import (
"time"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightningnetwork/lnd/lntypes"
)
// UnchargeRequest contains the required parameters for the swap.
type UnchargeRequest struct {
// OutRequest contains the required parameters for a loop out swap.
type OutRequest struct {
// Amount specifies the requested swap amount in sat. This does not
// include the swap and miner fee.
Amount btcutil.Amount
@ -19,19 +20,19 @@ type UnchargeRequest struct {
// MaxSwapRoutingFee is the maximum off-chain fee in msat that may be
// paid for payment to the server. This limit is applied during path
// finding. Typically this value is taken from the response of the
// UnchargeQuote call.
// LoopOutQuote call.
MaxSwapRoutingFee btcutil.Amount
// MaxPrepayRoutingFee is the maximum off-chain fee in msat that may be
// paid for payment to the server. This limit is applied during path
// finding. Typically this value is taken from the response of the
// UnchargeQuote call.
// LoopOutQuote call.
MaxPrepayRoutingFee btcutil.Amount
// 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
// 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.
MaxSwapFee btcutil.Amount
@ -54,26 +55,32 @@ type UnchargeRequest struct {
// revocation.
//
// MaxMinerFee is typically taken from the response of the
// UnchargeQuote call.
// LoopOutQuote call.
MaxMinerFee btcutil.Amount
// SweepConfTarget specifies the targeted confirmation target for the
// client sweep tx.
SweepConfTarget int32
// UnchargeChannel optionally specifies the short channel id of the
// LoopOutChannel optionally specifies the short channel id of the
// channel to uncharge.
UnchargeChannel *uint64
LoopOutChannel *uint64
}
// UnchargeSwapInfo contains status information for a uncharge swap.
type UnchargeSwapInfo struct {
UnchargeContract
// Out contains the full details of a loop out request. This includes things
// like the payment hash, the total value, and the final CTLV delay of the
// 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.
State SwapState
// SwapInfoKit contains shared data amongst all swap types.
SwapInfoKit
}
// SwapCost is a breakdown of the final swap costs.
@ -85,9 +92,9 @@ type SwapCost struct {
Onchain btcutil.Amount
}
// UnchargeQuoteRequest specifies the swap parameters for which a quote is
// LoopOutQuoteRequest specifies the swap parameters for which a quote is
// requested.
type UnchargeQuoteRequest struct {
type LoopOutQuoteRequest struct {
// Amount specifies the requested swap amount in sat. This does not
// include the swap and miner fee.
Amount btcutil.Amount
@ -107,9 +114,9 @@ type UnchargeQuoteRequest struct {
// 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.
type UnchargeQuote struct {
type LoopOutQuote struct {
// SwapFee is the fee that the swap server is charging for the swap.
SwapFee btcutil.Amount
@ -122,8 +129,8 @@ type UnchargeQuote struct {
MinerFee btcutil.Amount
}
// UnchargeTerms are the server terms on which it executes swaps.
type UnchargeTerms struct {
// LoopOutTerms are the server terms on which it executes swaps.
type LoopOutTerms struct {
// SwapFeeBase is the fixed per-swap base fee.
SwapFeeBase btcutil.Amount
@ -161,23 +168,26 @@ type SwapInfoKit struct {
LastUpdateTime time.Time
}
// SwapType indicates the type of swap.
type SwapType uint8
// Type indicates the type of swap.
type Type uint8
const (
// SwapTypeCharge is a charge swap.
SwapTypeCharge SwapType = iota
// TypeIn is a loop in swap.
TypeIn Type = iota
// SwapTypeUncharge is an uncharge swap.
SwapTypeUncharge
// TypeOut is a loop out swap.
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 {
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"
)
// SwapStore is the priamry database interface used by the loopd system. It
// houses informatino for all pending completed/failed swaps.
// SwapStore is the primary database interface used by the loopd system. It
// houses information for all pending completed/failed swaps.
type SwapStore interface {
// FetchUnchargeSwaps returns all swaps currently in the store.
FetchUnchargeSwaps() ([]*PersistentUncharge, error)
// FetchLoopOutSwaps returns all swaps currently in the store.
FetchLoopOutSwaps() ([]*LoopOut, error)
// CreateUncharge adds an initiated swap to the store.
CreateUncharge(hash lntypes.Hash, swap *UnchargeContract) error
// CreateLoopOut adds an initiated swap to the store.
CreateLoopOut(hash lntypes.Hash, swap *LoopOutContract) error
// UpdateUncharge stores a swap updateUncharge. This appends to the
// event log for a particular swap as it goes through the various
// stages in its lifetime.
UpdateUncharge(hash lntypes.Hash, time time.Time, state SwapState) error
// UpdateLoopOut stores a new event for a target loop out swap. This
// appends to the event log for a particular swap as it goes through
// the various stages in its lifetime.
UpdateLoopOut(hash lntypes.Hash, time time.Time, state SwapState) error
// Close closes the underlying database.
Close() error

@ -12,13 +12,63 @@ import (
"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.
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
// DestAddr is the destination address of the loop out swap.
DestAddr btcutil.Address
// SwapInvoice is the invoice that is to be paid by the client to
// initiate the loop out swap.
SwapInvoice string
// MaxSwapRoutingFee is the maximum off-chain fee in msat that may be
@ -29,13 +79,13 @@ type UnchargeContract struct {
// client sweep tx.
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.
UnchargeChannel *uint64
}
// PersistentUnchargeEvent contains the dynamic data of a swap.
type PersistentUnchargeEvent struct {
// LoopOutEvent contains the dynamic data of a swap.
type LoopOutEvent struct {
// State is the new state for this swap as a result of this event.
State SwapState
@ -43,22 +93,22 @@ type PersistentUnchargeEvent struct {
Time time.Time
}
// PersistentUncharge is a combination of the contract and the updates.
type PersistentUncharge struct {
// LoopOut is a combination of the contract and the updates.
type LoopOut struct {
// Hash is the hash that uniquely identifies this swap.
Hash lntypes.Hash
// Contract is the active contract for this swap. It describes the
// precise details of the swap including the final fee, CLTV value,
// etc.
Contract *UnchargeContract
Contract *LoopOutContract
// Events are each of the state transitions that this swap underwent.
Events []*PersistentUnchargeEvent
Events []*LoopOutEvent
}
// State returns the most recent state of this swap.
func (s *PersistentUncharge) State() SwapState {
func (s *LoopOut) State() SwapState {
lastUpdate := s.LastUpdate()
if lastUpdate == nil {
return StateInitiated
@ -68,7 +118,7 @@ func (s *PersistentUncharge) State() SwapState {
}
// LastUpdate returns the most recent update of this swap.
func (s *PersistentUncharge) LastUpdate() *PersistentUnchargeEvent {
func (s *LoopOut) LastUpdate() *LoopOutEvent {
eventCount := len(s.Events)
if eventCount == 0 {
@ -80,7 +130,7 @@ func (s *PersistentUncharge) LastUpdate() *PersistentUnchargeEvent {
}
// LastUpdateTime returns the last update time of this swap.
func (s *PersistentUncharge) LastUpdateTime() time.Time {
func (s *LoopOut) LastUpdateTime() time.Time {
lastUpdate := s.LastUpdate()
if lastUpdate == nil {
return s.Contract.InitiationTime
@ -89,7 +139,7 @@ func (s *PersistentUncharge) LastUpdateTime() time.Time {
return lastUpdate.Time
}
func deserializeUnchargeContract(value []byte) (*UnchargeContract, error) {
func deserializeLoopOutContract(value []byte) (*LoopOutContract, error) {
r := bytes.NewReader(value)
contract, err := deserializeContract(r)
@ -97,7 +147,7 @@ func deserializeUnchargeContract(value []byte) (*UnchargeContract, error) {
return nil, err
}
swap := UnchargeContract{
swap := LoopOutContract{
SwapContract: *contract,
}
@ -134,7 +184,7 @@ func deserializeUnchargeContract(value []byte) (*UnchargeContract, error) {
return &swap, nil
}
func serializeUnchargeContract(swap *UnchargeContract) (
func serializeLoopOutContract(swap *LoopOutContract) (
[]byte, error) {
var b bytes.Buffer
@ -282,7 +332,7 @@ func serializeContract(swap *SwapContract, b *bytes.Buffer) error {
return nil
}
func serializeUnchargeUpdate(time time.Time, state SwapState) (
func serializeLoopOutEvent(time time.Time, state SwapState) (
[]byte, error) {
var b bytes.Buffer
@ -298,8 +348,8 @@ func serializeUnchargeUpdate(time time.Time, state SwapState) (
return b.Bytes(), nil
}
func deserializeUnchargeUpdate(value []byte) (*PersistentUnchargeEvent, error) {
update := &PersistentUnchargeEvent{}
func deserializeLoopOutEvent(value []byte) (*LoopOutEvent, error) {
update := &LoopOutEvent{}
r := bytes.NewReader(value)

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

@ -42,13 +42,13 @@ func TestBoltSwapStore(t *testing.T) {
}
defer os.RemoveAll(tempDirName)
store, err := newBoltSwapStore(tempDirName)
store, err := NewBoltSwapStore(tempDirName)
if err != nil {
t.Fatal(err)
}
// First, verify that an empty database has no active swaps.
swaps, err := store.FetchUnchargeSwaps()
swaps, err := store.FetchLoopOutSwaps()
if err != nil {
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
// database shortly.
pendingSwap := UnchargeContract{
pendingSwap := LoopOutContract{
SwapContract: SwapContract{
AmountRequested: 100,
Preimage: testPreimage,
@ -90,7 +90,7 @@ func TestBoltSwapStore(t *testing.T) {
checkSwap := func(expectedState SwapState) {
t.Helper()
swaps, err := store.FetchUnchargeSwaps()
swaps, err := store.FetchLoopOutSwaps()
if err != nil {
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
// right after.
if err := store.CreateUncharge(hash, &pendingSwap); err != nil {
if err := store.CreateLoopOut(hash, &pendingSwap); err != nil {
t.Fatal(err)
}
checkSwap(StateInitiated)
// 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")
}
checkSwap(StateInitiated)
// Next, we'll update to the next state of the pre-image being
// revealed. The state should be reflected here again.
err = store.UpdateUncharge(
err = store.UpdateLoopOut(
hash, testTime, StatePreimageRevealed,
)
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
// properly updated.
err = store.UpdateUncharge(
err = store.UpdateLoopOut(
hash, testTime, StateFailInsufficientValue,
)
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
// should be the same.
store, err = newBoltSwapStore(tempDirName)
store, err = NewBoltSwapStore(tempDirName)
if err != nil {
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/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/loop/sweep"
"github.com/lightninglabs/loop/utils"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lntypes"
)
var (
// MinUnchargePreimageRevealDelta configures the minimum number of remaining
// blocks before htlc expiry required to reveal preimage.
MinUnchargePreimageRevealDelta = int32(20)
// MinLoopOutPreimageRevealDelta configures the minimum number of
// remaining blocks before htlc expiry required to reveal preimage.
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.
type unchargeSwap struct {
type loopOutSwap struct {
swapKit
UnchargeContract
loopdb.LoopOutContract
swapPaymentChan chan lndclient.PaymentResult
prePaymentChan chan lndclient.PaymentResult
@ -41,10 +42,10 @@ type executeConfig struct {
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.
func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
currentHeight int32, request *UnchargeRequest) (*unchargeSwap, error) {
func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
currentHeight int32, request *OutRequest) (*loopOutSwap, error) {
// Generate random preimage.
var swapPreimage [32]byte
@ -55,7 +56,7 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
// Derive a receiver key for this swap.
keyDesc, err := cfg.lnd.WalletKit.DeriveNextKey(
globalCtx, utils.SwapKeyFamily,
globalCtx, swap.KeyFamily,
)
if err != nil {
return nil, err
@ -67,14 +68,14 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
// the server revocation key and the swap and prepay invoices.
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,
)
if err != nil {
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 {
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.
initiationTime := time.Now()
contract := UnchargeContract{
contract := loopdb.LoopOutContract{
SwapInvoice: swapResp.swapInvoice,
DestAddr: request.DestAddr,
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
SweepConfTarget: request.SweepConfTarget,
UnchargeChannel: request.UnchargeChannel,
SwapContract: SwapContract{
UnchargeChannel: request.LoopOutChannel,
SwapContract: loopdb.SwapContract{
InitiationHeight: currentHeight,
InitiationTime: initiationTime,
PrepayInvoice: swapResp.prepayInvoice,
@ -104,7 +105,7 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
}
swapKit, err := newSwapKit(
swapHash, SwapTypeUncharge, cfg, &contract.SwapContract,
swapHash, TypeOut, cfg, &contract.SwapContract,
)
if err != nil {
return nil, err
@ -112,14 +113,14 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
swapKit.lastUpdateTime = initiationTime
swap := &unchargeSwap{
UnchargeContract: contract,
swapKit: *swapKit,
swap := &loopOutSwap{
LoopOutContract: contract,
swapKit: *swapKit,
}
// Persist the data before exiting this function, so that the caller can
// trust that this swap will be resumed on restart.
err = cfg.store.createUncharge(swapHash, &swap.UnchargeContract)
// Persist the data before exiting this function, so that the caller
// can trust that this swap will be resumed on restart.
err = cfg.store.CreateLoopOut(swapHash, &swap.LoopOutContract)
if err != nil {
return nil, fmt.Errorf("cannot store swap: %v", err)
}
@ -127,25 +128,25 @@ func newUnchargeSwap(globalCtx context.Context, cfg *swapConfig,
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.
func resumeUnchargeSwap(reqContext context.Context, cfg *swapConfig,
pend *PersistentUncharge) (*unchargeSwap, error) {
func resumeLoopOutSwap(reqContext context.Context, cfg *swapConfig,
pend *loopdb.LoopOut) (*loopOutSwap, error) {
hash := lntypes.Hash(sha256.Sum256(pend.Contract.Preimage[:]))
logger.Infof("Resuming swap %v", hash)
swapKit, err := newSwapKit(
hash, SwapTypeUncharge, cfg, &pend.Contract.SwapContract,
hash, TypeOut, cfg, &pend.Contract.SwapContract,
)
if err != nil {
return nil, err
}
swap := &unchargeSwap{
UnchargeContract: *pend.Contract,
swapKit: *swapKit,
swap := &loopOutSwap{
LoopOutContract: *pend.Contract,
swapKit: *swapKit,
}
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
// 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 {
s.executeConfig = *cfg
@ -170,13 +171,15 @@ func (s *unchargeSwap) execute(mainCtx context.Context,
err := s.executeAndFinalize(mainCtx)
// If an unexpected error happened, report a temporary failure.
// Otherwise for example a connection error could lead to abandoning the
// swap permanently and losing funds.
// Otherwise for example a connection error could lead to abandoning
// the swap permanently and losing funds.
if err != nil {
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)
}
@ -185,7 +188,7 @@ func (s *unchargeSwap) execute(mainCtx context.Context,
// executeAndFinalize executes a swap and awaits the definitive outcome of the
// 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.
err := s.sendUpdate(globalCtx)
if err != nil {
@ -200,7 +203,7 @@ func (s *unchargeSwap) executeAndFinalize(globalCtx context.Context) error {
}
// Sanity check.
if s.state.Type() == StateTypePending {
if s.state.Type() == loopdb.StateTypePending {
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
// 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
// to sort of resume payments.
//
@ -277,7 +280,7 @@ func (s *unchargeSwap) executeSwap(globalCtx context.Context) error {
// attempt.
// Retrieve outpoint for sweep.
htlcOutpoint, htlcValue, err := utils.GetScriptOutput(
htlcOutpoint, htlcValue, err := swap.GetScriptOutput(
txConf.Tx, s.htlc.ScriptHash,
)
if err != nil {
@ -287,11 +290,11 @@ func (s *unchargeSwap) executeSwap(globalCtx context.Context) error {
s.log.Infof("Htlc value: %v", htlcValue)
// 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",
s.AmountRequested, htlcValue)
s.state = StateFailInsufficientValue
s.state = loopdb.StateFailInsufficientValue
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
// 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.
htlcInput, err := getTxInputByOutpoint(
htlcInput, err := swap.GetTxInputByOutpoint(
spendDetails.SpendingTx, htlcOutpoint,
)
if err != nil {
@ -322,22 +325,22 @@ func (s *unchargeSwap) executeSwap(globalCtx context.Context) error {
s.cost.Onchain = htlcValue -
btcutil.Amount(spendDetails.SpendingTx.TxOut[0].Value)
s.state = StateSuccess
s.state = loopdb.StateSuccess
} else {
s.state = StateFailSweepTimeout
s.state = loopdb.StateFailSweepTimeout
}
return nil
}
// 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()
s.lastUpdateTime = updateTime
// 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 {
return err
}
@ -347,12 +350,12 @@ func (s *unchargeSwap) persistState(ctx context.Context) error {
}
// payInvoices pays both swap invoices.
func (s *unchargeSwap) payInvoices(ctx context.Context) {
func (s *loopOutSwap) payInvoices(ctx context.Context) {
// Pay the swap invoice.
s.log.Infof("Sending swap payment %v", s.SwapInvoice)
s.swapPaymentChan = s.lnd.Client.PayInvoice(
ctx, s.SwapInvoice, s.MaxSwapRoutingFee,
s.UnchargeContract.UnchargeChannel,
s.LoopOutContract.UnchargeChannel,
)
// 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
// case we haven't revealed the preimage yet, it also monitors block height and
// off-chain payment failure.
func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
*chainntnfs.TxConfirmation, error) {
// 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
if s.state == StateInitiated {
if s.state == loopdb.StateInitiated {
// Check if it is already too late to start this swap. If we
// already revealed the preimage, this check is irrelevant and
// we need to sweep in any case.
maxPreimageRevealHeight := s.CltvExpiry -
MinUnchargePreimageRevealDelta
MinLoopOutPreimageRevealDelta
checkMaxRevealHeightExceeded := func() bool {
s.log.Infof("Checking preimage reveal height %v "+
@ -408,7 +411,7 @@ func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
"exceeded (height %v)",
maxPreimageRevealHeight, s.height)
s.state = StateFailTimeout
s.state = loopdb.StateFailTimeout
return true
}
@ -429,7 +432,7 @@ func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
case result := <-s.swapPaymentChan:
s.swapPaymentChan = nil
if result.Err != nil {
s.state = StateFailOffchainPayments
s.state = loopdb.StateFailOffchainPayments
s.log.Infof("Failed swap payment: %v",
result.Err)
@ -443,7 +446,7 @@ func (s *unchargeSwap) waitForConfirmedHtlc(globalCtx context.Context) (
case result := <-s.prePaymentChan:
s.prePaymentChan = nil
if result.Err != nil {
s.state = StateFailOffchainPayments
s.state = loopdb.StateFailOffchainPayments
s.log.Infof("Failed prepayment: %v",
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
// sweep offchain. So we must make sure we sweep successfully before on-chain
// timeout.
func (s *unchargeSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
spendFunc func() error) (*chainntnfs.SpendDetail, error) {
// Register the htlc spend notification.
@ -556,7 +559,7 @@ func (s *unchargeSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
// published the tx.
//
// TODO: Use lnd sweeper?
func (s *unchargeSwap) sweep(ctx context.Context,
func (s *loopOutSwap) sweep(ctx context.Context,
htlcOutpoint wire.OutPoint,
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",
fee, s.MaxMinerFee)
if s.state == StatePreimageRevealed {
if s.state == loopdb.StatePreimageRevealed {
// The currently required fee exceeds the max, but we
// already revealed the preimage. The best we can do now
// 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
// is a precaution in case the publish call never returns and would
// leave us thinking we didn't reveal yet.
if s.state != StatePreimageRevealed {
s.state = StatePreimageRevealed
if s.state != loopdb.StatePreimageRevealed {
s.state = loopdb.StatePreimageRevealed
err := s.persistState(ctx)
if err != nil {
@ -624,24 +627,24 @@ func (s *unchargeSwap) sweep(ctx context.Context,
return nil
}
// validateUnchargeContract validates the contract parameters against our
// validateLoopOutContract validates the contract parameters against our
// request.
func validateUnchargeContract(lnd *lndclient.LndServices,
func validateLoopOutContract(lnd *lndclient.LndServices,
height int32,
request *UnchargeRequest,
response *newUnchargeResponse) error {
request *OutRequest,
response *newLoopOutResponse) error {
// Check invoice amounts.
chainParams := lnd.ChainParams
swapInvoiceAmt, err := utils.GetInvoiceAmt(
swapInvoiceAmt, err := swap.GetInvoiceAmt(
chainParams, response.swapInvoice,
)
if err != nil {
return err
}
prepayInvoiceAmt, err := utils.GetInvoiceAmt(
prepayInvoiceAmt, err := swap.GetInvoiceAmt(
chainParams, response.prepayInvoice,
)
if err != nil {
@ -663,7 +666,7 @@ func validateUnchargeContract(lnd *lndclient.LndServices,
return ErrPrepayAmountTooHigh
}
if response.expiry-height < MinUnchargePreimageRevealDelta {
if response.expiry-height < MinLoopOutPreimageRevealDelta {
logger.Warnf("Proposed expiry %v (delta %v) too soon",
response.expiry, response.expiry-height)

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

@ -18,16 +18,16 @@ import (
var (
testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
testUnchargeOnChainCltvDelta = int32(30)
testCltvDelta = 50
testSwapFeeBase = btcutil.Amount(21)
testSwapFeeRate = int64(100)
testInvoiceExpiry = 180 * time.Second
testFixedPrepayAmount = btcutil.Amount(100)
testMinSwapAmount = btcutil.Amount(10000)
testMaxSwapAmount = btcutil.Amount(1000000)
testTxConfTarget = 2
testRepublishDelay = 10 * time.Second
testLoopOutOnChainCltvDelta = int32(30)
testCltvDelta = 50
testSwapFeeBase = btcutil.Amount(21)
testSwapFeeRate = int64(100)
testInvoiceExpiry = 180 * time.Second
testFixedPrepayAmount = btcutil.Amount(100)
testMinSwapAmount = btcutil.Amount(10000)
testMaxSwapAmount = btcutil.Amount(1000000)
testTxConfTarget = 2
testRepublishDelay = 10 * time.Second
)
// 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,
receiverKey [33]byte) (
*newUnchargeResponse, error) {
*newLoopOutResponse, error) {
_, senderKey := test.CreateKey(100)
@ -82,24 +82,24 @@ func (s *serverMock) NewUnchargeSwap(ctx context.Context,
var senderKeyArray [33]byte
copy(senderKeyArray[:], senderKey.SerializeCompressed())
return &newUnchargeResponse{
return &newLoopOutResponse{
senderKey: senderKeyArray,
swapInvoice: swapPayReqString,
prepayInvoice: prePayReqString,
expiry: s.height + testUnchargeOnChainCltvDelta,
expiry: s.height + testLoopOutOnChainCltvDelta,
}, nil
}
func (s *serverMock) GetUnchargeTerms(ctx context.Context) (
*UnchargeTerms, error) {
func (s *serverMock) GetLoopOutTerms(ctx context.Context) (
*LoopOutTerms, error) {
dest := [33]byte{1, 2, 3}
return &UnchargeTerms{
return &LoopOutTerms{
SwapFeeBase: testSwapFeeBase,
SwapFeeRate: testSwapFeeRate,
SwapPaymentDest: dest,
CltvDelta: testUnchargeOnChainCltvDelta,
CltvDelta: testLoopOutOnChainCltvDelta,
MinSwapAmount: testMinSwapAmount,
MaxSwapAmount: testMaxSwapAmount,
PrepayAmt: testFixedPrepayAmount,

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

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

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

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

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

Loading…
Cancel
Save