diff --git a/client.go b/client.go index dc11b9b..8489d18 100644 --- a/client.go +++ b/client.go @@ -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. diff --git a/client_test.go b/client_test.go index 6600b03..d1918b3 100644 --- a/client_test.go +++ b/client_test.go @@ -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() } diff --git a/cmd/loop/commands.go b/cmd/loop/commands.go new file mode 100644 index 0000000..6c216a1 --- /dev/null +++ b/cmd/loop/commands.go @@ -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) + } +} diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 2f88a4c..73a4883 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -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(), } diff --git a/cmd/loopd/daemon.go b/cmd/loopd/daemon.go index 3da6658..8acca5c 100644 --- a/cmd/loopd/daemon.go +++ b/cmd/loopd/daemon.go @@ -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 diff --git a/cmd/loopd/log.go b/cmd/loopd/log.go index d31f344..8feea95 100644 --- a/cmd/loopd/log.go +++ b/cmd/loopd/log.go @@ -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") diff --git a/cmd/loopd/main.go b/cmd/loopd/main.go index c6f683f..49f0e3c 100644 --- a/cmd/loopd/main.go +++ b/cmd/loopd/main.go @@ -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) diff --git a/cmd/loopd/swapclient_server.go b/cmd/loopd/swapclient_server.go index 406404f..875cb01 100644 --- a/cmd/loopd/swapclient_server.go +++ b/cmd/loopd/swapclient_server.go @@ -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, }) diff --git a/cmd/loopd/utils.go b/cmd/loopd/utils.go index 2aacf37..7e7f2f9 100644 --- a/cmd/loopd/utils.go +++ b/cmd/loopd/utils.go @@ -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, ) diff --git a/cmd/loopd/view.go b/cmd/loopd/view.go index 18897dd..b7373dd 100644 --- a/cmd/loopd/view.go +++ b/cmd/loopd/view.go @@ -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, diff --git a/config.go b/config.go index 1f4086a..040a5d2 100644 --- a/config.go +++ b/config.go @@ -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 } diff --git a/executor.go b/executor.go index 6116d10..93126e9 100644 --- a/executor.go +++ b/executor.go @@ -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 } diff --git a/interface.go b/interface.go index bed2e5c..b6baabe 100644 --- a/interface.go +++ b/interface.go @@ -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 } diff --git a/loopdb/interface.go b/loopdb/interface.go index 55503e4..5c3f3d4 100644 --- a/loopdb/interface.go +++ b/loopdb/interface.go @@ -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 diff --git a/loopdb/uncharge.go b/loopdb/loopout.go similarity index 71% rename from loopdb/uncharge.go rename to loopdb/loopout.go index 8d69da4..acbf667 100644 --- a/loopdb/uncharge.go +++ b/loopdb/loopout.go @@ -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) diff --git a/loopdb/store.go b/loopdb/store.go index 2578832..7feb588 100644 --- a/loopdb/store.go +++ b/loopdb/store.go @@ -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 } diff --git a/loopdb/store_test.go b/loopdb/store_test.go index 58017f6..ca11fa9 100644 --- a/loopdb/store_test.go +++ b/loopdb/store_test.go @@ -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) } diff --git a/loopdb/swapcontract.go b/loopdb/swapcontract.go deleted file mode 100644 index f9a3b7b..0000000 --- a/loopdb/swapcontract.go +++ /dev/null @@ -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 -} diff --git a/uncharge.go b/loopout.go similarity index 82% rename from uncharge.go rename to loopout.go index 66cabc0..065eaaf 100644 --- a/uncharge.go +++ b/loopout.go @@ -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) diff --git a/uncharge_test.go b/loopout_test.go similarity index 88% rename from uncharge_test.go rename to loopout_test.go index 29b8f49..97493a6 100644 --- a/uncharge_test.go +++ b/loopout_test.go @@ -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") } diff --git a/server_mock_test.go b/server_mock_test.go index 28494bd..9b82ffd 100644 --- a/server_mock_test.go +++ b/server_mock_test.go @@ -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, diff --git a/store_mock_test.go b/store_mock_test.go index e492aa4..fe1832d 100644 --- a/store_mock_test.go +++ b/store_mock_test.go @@ -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) diff --git a/swap.go b/swap.go index 3b447bd..31f2a5d 100644 --- a/swap.go +++ b/swap.go @@ -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 } diff --git a/swap_server_client.go b/swap_server_client.go index b005231..0e1ac55 100644 --- a/swap_server_client.go +++ b/swap_server_client.go @@ -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 diff --git a/sweep/sweeper.go b/sweep/sweeper.go index b3cad65..af5bc10 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -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, diff --git a/testcontext_test.go b/testcontext_test.go index 45e95d2..e986db6 100644 --- a/testcontext_test.go +++ b/testcontext_test.go @@ -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 }