multi: loop in swap

pull/11/head
Joost Jager 5 years ago
parent 0795a894f5
commit c4fd8ce150
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7

@ -118,6 +118,11 @@ func (s *Client) FetchLoopOutSwaps() ([]*loopdb.LoopOut, error) {
return s.Store.FetchLoopOutSwaps()
}
// FetchLoopInSwaps returns a list of all swaps currently in the database.
func (s *Client) FetchLoopInSwaps() ([]*loopdb.LoopIn, error) {
return s.Store.FetchLoopInSwaps()
}
// Run is a blocking call that executes all swaps. Any pending swaps are
// restored from persistent storage and resumed. Subsequent updates will be
// sent through the passed in statusChan. The function can be terminated by
@ -144,7 +149,12 @@ 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.FetchLoopOutSwaps()
pendingLoopOutSwaps, err := s.Store.FetchLoopOutSwaps()
if err != nil {
return err
}
pendingLoopInSwaps, err := s.Store.FetchLoopInSwaps()
if err != nil {
return err
}
@ -154,7 +164,7 @@ func (s *Client) Run(ctx context.Context,
go func() {
defer s.wg.Done()
s.resumeSwaps(mainCtx, pendingSwaps)
s.resumeSwaps(mainCtx, pendingLoopOutSwaps, pendingLoopInSwaps)
// Signal that new requests can be accepted. Otherwise the new
// swap could already have been added to the store and read in
@ -194,19 +204,33 @@ func (s *Client) Run(ctx context.Context,
// resumeSwaps restarts all pending swaps from the provided list.
func (s *Client) resumeSwaps(ctx context.Context,
swaps []*loopdb.LoopOut) {
loopOutSwaps []*loopdb.LoopOut, loopInSwaps []*loopdb.LoopIn) {
swapCfg := &swapConfig{
lnd: s.lndServices,
store: s.Store,
}
for _, pend := range swaps {
for _, pend := range loopOutSwaps {
if pend.State().Type() != loopdb.StateTypePending {
continue
}
swapCfg := &swapConfig{
lnd: s.lndServices,
store: s.Store,
}
swap, err := resumeLoopOutSwap(ctx, swapCfg, pend)
if err != nil {
logger.Errorf("resuming swap: %v", err)
logger.Errorf("resuming loop out swap: %v", err)
continue
}
s.executor.initiateSwap(ctx, swap)
}
for _, pend := range loopInSwaps {
if pend.State().Type() != loopdb.StateTypePending {
continue
}
swap, err := resumeLoopInSwap(ctx, swapCfg, pend)
if err != nil {
logger.Errorf("resuming loop in swap: %v", err)
continue
}
@ -320,3 +344,78 @@ func (s *Client) waitForInitialized(ctx context.Context) error {
return nil
}
// LoopIn initiates a loop in swap.
func (s *Client) LoopIn(globalCtx context.Context,
request *LoopInRequest) (*lntypes.Hash, error) {
logger.Infof("Loop in %v (channel: %v)",
request.Amount,
request.LoopInChannel,
)
if err := s.waitForInitialized(globalCtx); err != nil {
return nil, err
}
// Create a new swap object for this swap.
initiationHeight := s.executor.height()
swapCfg := swapConfig{
lnd: s.lndServices,
store: s.Store,
server: s.Server,
}
swap, err := newLoopInSwap(
globalCtx, &swapCfg, initiationHeight, request,
)
if err != nil {
return nil, err
}
// Post swap to the main loop.
s.executor.initiateSwap(globalCtx, swap)
// Return hash so that the caller can identify this swap in the updates
// stream.
return &swap.hash, nil
}
// LoopInQuote takes an 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) LoopInQuote(ctx context.Context,
request *LoopInQuoteRequest) (*LoopInQuote, error) {
terms, err := s.Server.GetLoopInTerms(ctx)
if err != nil {
return nil, err
}
if request.Amount < terms.MinSwapAmount {
return nil, ErrSwapAmountTooLow
}
if request.Amount > terms.MaxSwapAmount {
return nil, ErrSwapAmountTooHigh
}
swapFee := terms.SwapFeeBase +
request.Amount*btcutil.Amount(terms.SwapFeeRate)/
btcutil.Amount(swap.FeeRateTotalParts)
// TODO: Find a way to get a fee estimate.
minerFee := btcutil.Amount(0)
return &LoopInQuote{
SwapFee: swapFee,
MinerFee: minerFee,
PrepayAmount: btcutil.Amount(terms.PrepayAmt),
}, nil
}
// LoopInTerms returns the terms on which the server executes swaps.
func (s *Client) LoopInTerms(ctx context.Context) (
*LoopInTerms, error) {
return s.Server.GetLoopInTerms(ctx)
}

@ -197,12 +197,14 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
MaxMinerFee: 50000,
},
},
Events: []*loopdb.LoopOutEvent{
{
State: state,
Loop: loopdb.Loop{
Events: []*loopdb.LoopEvent{
{
State: state,
},
},
Hash: hash,
},
Hash: hash,
}
if expired {

@ -0,0 +1,83 @@
package main
import (
"context"
"fmt"
"github.com/lightninglabs/loop/looprpc"
"github.com/urfave/cli"
)
var loopInCommand = cli.Command{
Name: "in",
Usage: "perform an on-chain to off-chain swap (loop in)",
ArgsUsage: "amt",
Description: `
Send the amount in satoshis specified by the amt argument off-chain.`,
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "channel",
Usage: "the 8-byte compact channel ID of the channel to loop in",
},
},
Action: loopIn,
}
func loopIn(ctx *cli.Context) error {
// Show command help if no arguments and flags were provided.
if ctx.NArg() < 1 {
cli.ShowCommandHelp(ctx, "in")
return nil
}
args := ctx.Args()
amt, err := parseAmt(args[0])
if err != nil {
return err
}
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
quote, err := client.GetLoopInQuote(
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 loopInChannel uint64
if ctx.IsSet("channel") {
loopInChannel = ctx.Uint64("channel")
}
resp, err := client.LoopIn(context.Background(), &looprpc.LoopInRequest{
Amt: int64(amt),
MaxMinerFee: int64(limits.maxMinerFee),
MaxPrepayAmt: int64(limits.maxPrepayAmt),
MaxSwapFee: int64(limits.maxSwapFee),
MaxPrepayRoutingFee: int64(limits.maxPrepayRoutingFee),
LoopInChannel: loopInChannel,
})
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
}

@ -81,7 +81,7 @@ func loopOut(ctx *cli.Context) error {
MaxPrepayAmt: int64(limits.maxPrepayAmt),
MaxSwapFee: int64(limits.maxSwapFee),
MaxPrepayRoutingFee: int64(limits.maxPrepayRoutingFee),
MaxSwapRoutingFee: int64(limits.maxSwapRoutingFee),
MaxSwapRoutingFee: int64(*limits.maxSwapRoutingFee),
LoopOutChannel: unchargeChannel,
})
if err != nil {

@ -61,7 +61,8 @@ func main() {
},
}
app.Commands = []cli.Command{
loopOutCommand, termsCommand, monitorCommand, quoteCommand,
loopOutCommand, loopInCommand, termsCommand,
monitorCommand, quoteCommand,
}
err := app.Run(os.Args)
@ -87,7 +88,7 @@ func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
}
type limits struct {
maxSwapRoutingFee btcutil.Amount
maxSwapRoutingFee *btcutil.Amount
maxPrepayRoutingFee btcutil.Amount
maxMinerFee btcutil.Amount
maxSwapFee btcutil.Amount
@ -96,7 +97,6 @@ type limits struct {
func getLimits(amt btcutil.Amount, quote *looprpc.QuoteResponse) *limits {
return &limits{
maxSwapRoutingFee: getMaxRoutingFee(btcutil.Amount(amt)),
maxPrepayRoutingFee: getMaxRoutingFee(btcutil.Amount(
quote.PrepayAmt,
)),
@ -111,8 +111,10 @@ func getLimits(amt btcutil.Amount, quote *looprpc.QuoteResponse) *limits {
}
func displayLimits(amt btcutil.Amount, l *limits) error {
totalSuccessMax := l.maxSwapRoutingFee + l.maxPrepayRoutingFee +
l.maxMinerFee + l.maxSwapFee
totalSuccessMax := l.maxPrepayRoutingFee + l.maxMinerFee + l.maxSwapFee
if l.maxSwapRoutingFee != nil {
totalSuccessMax += *l.maxSwapRoutingFee
}
fmt.Printf("Max swap fees for %d loop out: %d\n",
btcutil.Amount(amt), totalSuccessMax,
@ -129,8 +131,12 @@ func displayLimits(amt btcutil.Amount, l *limits) error {
case "x":
fmt.Println()
fmt.Printf("Max on-chain fee: %d\n", l.maxMinerFee)
fmt.Printf("Max off-chain swap routing fee: %d\n",
l.maxSwapRoutingFee)
if l.maxSwapRoutingFee != nil {
fmt.Printf("Max off-chain swap routing fee: %d\n",
*l.maxSwapRoutingFee)
}
fmt.Printf("Max off-chain prepay routing fee: %d\n",
l.maxPrepayRoutingFee)
fmt.Printf("Max swap fee: %d\n", l.maxSwapFee)

@ -4,11 +4,11 @@ import (
"context"
"fmt"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/swap"
"github.com/urfave/cli"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/looprpc"
"github.com/urfave/cli"
"github.com/lightninglabs/loop/swap"
)
var termsCommand = cli.Command{
@ -25,14 +25,13 @@ func terms(ctx *cli.Context) error {
defer cleanup()
req := &looprpc.TermsRequest{}
terms, err := client.LoopOutTerms(context.Background(), req)
loopOutTerms, err := client.LoopOutTerms(context.Background(), req)
if err != nil {
return err
}
fmt.Printf("Amount: %d - %d\n",
btcutil.Amount(terms.MinSwapAmount),
btcutil.Amount(terms.MaxSwapAmount),
loopInTerms, err := client.GetLoopInTerms(
context.Background(), &looprpc.TermsRequest{},
)
if err != nil {
return err
@ -54,7 +53,13 @@ func terms(ctx *cli.Context) error {
fmt.Println("Loop Out")
fmt.Println("--------")
printTerms(terms)
printTerms(loopOutTerms)
fmt.Println()
fmt.Println("Loop In")
fmt.Println("------")
printTerms(loopInTerms)
return nil
}

@ -37,11 +37,11 @@ func daemon(config *config) 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.FetchLoopOutSwaps()
loopOutSwaps, err := swapClient.FetchLoopOutSwaps()
if err != nil {
return err
}
for _, swap := range storedSwaps {
for _, swap := range loopOutSwaps {
swaps[swap.Hash] = loop.SwapInfo{
SwapType: loop.TypeOut,
SwapContract: swap.Contract.SwapContract,
@ -51,6 +51,20 @@ func daemon(config *config) error {
}
}
loopInSwaps, err := swapClient.FetchLoopInSwaps()
if err != nil {
return err
}
for _, swap := range loopInSwaps {
swaps[swap.Hash] = loop.SwapInfo{
SwapType: loop.TypeIn,
SwapContract: swap.Contract.SwapContract,
State: swap.State(),
SwapHash: swap.Hash,
LastUpdate: swap.LastUpdateTime(),
}
}
// Instantiate the loopd gRPC server.
server := swapClientServer{
impl: swapClient,

@ -2,6 +2,7 @@ package main
import (
"context"
"errors"
"fmt"
"sort"
@ -32,7 +33,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
in *looprpc.LoopOutRequest) (
*looprpc.SwapResponse, error) {
logger.Infof("LoopOut request received")
logger.Infof("Loop out request received")
var sweepAddr btcutil.Address
if in.Dest == "" {
@ -83,6 +84,8 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
state = looprpc.SwapState_INITIATED
case loopdb.StatePreimageRevealed:
state = looprpc.SwapState_PREIMAGE_REVEALED
case loopdb.StateHtlcPublished:
state = looprpc.SwapState_HTLC_PUBLISHED
case loopdb.StateSuccess:
state = looprpc.SwapState_SUCCESS
default:
@ -103,6 +106,16 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
return nil, err
}
var swapType looprpc.SwapType
switch loopSwap.SwapType {
case loop.TypeIn:
swapType = looprpc.SwapType_LOOP_IN
case loop.TypeOut:
swapType = looprpc.SwapType_LOOP_OUT
default:
return nil, errors.New("unknown swap type")
}
return &looprpc.SwapStatus{
Amt: int64(loopSwap.AmountRequested),
Id: loopSwap.SwapHash.String(),
@ -110,7 +123,7 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
InitiationTime: loopSwap.InitiationTime.UnixNano(),
LastUpdateTime: loopSwap.LastUpdate.UnixNano(),
HtlcAddress: address.EncodeAddress(),
Type: looprpc.SwapType_LOOP_OUT,
Type: swapType,
}, nil
}
@ -212,7 +225,7 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
func (s *swapClientServer) LoopOutTerms(ctx context.Context,
req *looprpc.TermsRequest) (*looprpc.TermsResponse, error) {
logger.Infof("Terms request received")
logger.Infof("Loop out terms request received")
terms, err := s.impl.LoopOutTerms(ctx)
if err != nil {
@ -248,3 +261,73 @@ func (s *swapClientServer) LoopOutQuote(ctx context.Context,
SwapFee: int64(quote.SwapFee),
}, nil
}
// GetTerms returns the terms that the server enforces for swaps.
func (s *swapClientServer) GetLoopInTerms(ctx context.Context, req *looprpc.TermsRequest) (
*looprpc.TermsResponse, error) {
logger.Infof("Loop in terms request received")
terms, err := s.impl.LoopInTerms(ctx)
if err != nil {
logger.Errorf("Terms request: %v", err)
return nil, err
}
return &looprpc.TermsResponse{
MinSwapAmount: int64(terms.MinSwapAmount),
MaxSwapAmount: int64(terms.MaxSwapAmount),
PrepayAmt: int64(terms.PrepayAmt),
SwapFeeBase: int64(terms.SwapFeeBase),
SwapFeeRate: int64(terms.SwapFeeRate),
CltvDelta: int32(terms.CltvDelta),
}, nil
}
// GetQuote returns a quote for a swap with the provided parameters.
func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
req *looprpc.QuoteRequest) (*looprpc.QuoteResponse, error) {
logger.Infof("Loop in quote request received")
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
Amount: btcutil.Amount(req.Amt),
HtlcConfTarget: defaultConfTarget,
})
if err != nil {
return nil, err
}
return &looprpc.QuoteResponse{
MinerFee: int64(quote.MinerFee),
PrepayAmt: int64(quote.PrepayAmount),
SwapFee: int64(quote.SwapFee),
}, nil
}
func (s *swapClientServer) LoopIn(ctx context.Context,
in *looprpc.LoopInRequest) (
*looprpc.SwapResponse, error) {
logger.Infof("Loop in request received")
req := &loop.LoopInRequest{
Amount: btcutil.Amount(in.Amt),
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
MaxPrepayAmount: btcutil.Amount(in.MaxPrepayAmt),
MaxPrepayRoutingFee: btcutil.Amount(in.MaxPrepayRoutingFee),
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
HtlcConfTarget: defaultConfTarget,
}
if in.LoopInChannel != 0 {
req.LoopInChannel = &in.LoopInChannel
}
hash, err := s.impl.LoopIn(ctx, req)
if err != nil {
logger.Errorf("Loop in: %v", err)
return nil, err
}
return &looprpc.SwapResponse{
Id: hash.String(),
}, nil
}

@ -158,6 +158,107 @@ type LoopOutTerms struct {
SwapPaymentDest [33]byte
}
// LoopInRequest contains the required parameters for the swap.
type LoopInRequest struct {
// Amount specifies the requested swap amount in sat. This does not
// include the swap and miner fee.
Amount 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.
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
// includes the prepay amount.
MaxSwapFee btcutil.Amount
// MaxPrepayAmount is the maximum amount of the swap fee that may be
// charged as a prepayment.
MaxPrepayAmount btcutil.Amount
// MaxMinerFee is the maximum in on-chain fees that we are willing to
// spent. If we publish the on-chain htlc and the fee estimate turns out
// higher than this value, we cancel the swap.
//
// MaxMinerFee is typically taken from the response of the UnchargeQuote
// call.
MaxMinerFee btcutil.Amount
// HtlcConfTarget specifies the targeted confirmation target for the
// client htlc tx.
HtlcConfTarget int32
// LoopInChannel optionally specifies the short channel id of the
// channel to charge.
LoopInChannel *uint64
}
// LoopInTerms are the server terms on which it executes charge swaps.
type LoopInTerms struct {
// SwapFeeBase is the fixed per-swap base fee.
SwapFeeBase btcutil.Amount
// SwapFeeRate is the variable fee in parts per million.
SwapFeeRate int64
// PrepayAmt is the fixed part of the swap fee that needs to be prepaid.
PrepayAmt btcutil.Amount
// MinSwapAmount is the minimum amount that the server requires for a
// swap.
MinSwapAmount btcutil.Amount
// MaxSwapAmount is the maximum amount that the server accepts for a
// swap.
MaxSwapAmount btcutil.Amount
// Time lock delta relative to current block height that swap server
// will accept on the swap initiation call.
CltvDelta int32
}
// In contains status information for a loop in swap.
type In struct {
loopdb.LoopInContract
SwapInfoKit
// State where the swap is in.
State loopdb.SwapState
}
// LoopInQuoteRequest specifies the swap parameters for which a quote is
// requested.
type LoopInQuoteRequest struct {
// Amount specifies the requested swap amount in sat. This does not
// include the swap and miner fee.
Amount btcutil.Amount
// HtlcConfTarget specifies the targeted confirmation target for the
// client sweep tx.
HtlcConfTarget int32
}
// LoopInQuote contains estimates for the fees making up the total swap cost
// for the client.
type LoopInQuote struct {
// SwapFee is the fee that the swap server is charging for the swap.
SwapFee btcutil.Amount
// PrepayAmount is the part of the swap fee that is requested as a
// prepayment.
PrepayAmount btcutil.Amount
// MinerFee is an estimate of the on-chain fee that needs to be paid to
// sweep the htlc.
MinerFee btcutil.Amount
}
// SwapInfoKit contains common swap info fields.
type SwapInfoKit struct {
// Hash is the sha256 hash of the preimage that unlocks the htlcs. It
@ -191,3 +292,13 @@ type SwapInfo struct {
loopdb.SwapContract
}
// LastUpdate returns the last update time of the swap
func (s *In) LastUpdate() time.Time {
return s.LastUpdateTime
}
// SwapHash returns the swap hash.
func (s *In) SwapHash() lntypes.Hash {
return s.Hash
}

@ -125,7 +125,6 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context,
txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32) (
chan *chainntnfs.TxConfirmation, chan error, error) {
// TODO: Height hint
var txidSlice []byte
if txid != nil {
txidSlice = txid[:]

@ -310,13 +310,19 @@ func (s *lightningClient) AddInvoice(ctx context.Context,
rpcIn := &lnrpc.Invoice{
Memo: in.Memo,
RHash: in.Hash[:],
Value: int64(in.Value),
Expiry: in.Expiry,
CltvExpiry: in.CltvExpiry,
Private: true,
}
if in.Preimage != nil {
rpcIn.RPreimage = in.Preimage[:]
}
if in.Hash != nil {
rpcIn.RHash = in.Hash[:]
}
resp, err := s.client.AddInvoice(rpcCtx, rpcIn)
if err != nil {
return lntypes.Hash{}, "", err

@ -0,0 +1,621 @@
package loop
import (
"context"
"crypto/rand"
"crypto/sha256"
"fmt"
"time"
"github.com/lightninglabs/loop/swap"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightningnetwork/lnd/lntypes"
)
var (
// MaxLoopInAcceptDelta configures the maximum acceptable number of
// remaining blocks until the on-chain htlc expires. This value is used
// to decide whether we want to continue with the swap parameters as
// proposed by the server. It is a protection to prevent the server from
// getting us to lock up our funds to an arbitrary point in the future.
MaxLoopInAcceptDelta = int32(1500)
// MinLoopInPublishDelta defines the minimum number of remaining blocks
// until on-chain htlc expiry required to proceed to publishing the htlc
// tx. This value isn't critical, as we could even safely publish the
// htlc after expiry. The reason we do implement this check is to
// prevent us from publishing an htlc that the server surely wouldn't
// follow up to.
MinLoopInPublishDelta = int32(10)
)
// loopInSwap contains all the in-memory state related to a pending loop in
// swap.
type loopInSwap struct {
swapKit
loopdb.LoopInContract
prePaymentChan chan lndclient.PaymentResult
timeoutAddr btcutil.Address
}
// newLoopInSwap initiates a new loop in swap.
func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
currentHeight int32, request *LoopInRequest) (*loopInSwap, error) {
// Request current server loop in terms and use these to calculate the
// swap fee that we should subtract from the swap amount in the payment
// request that we send to the server.
quote, err := cfg.server.GetLoopInTerms(globalCtx)
if err != nil {
return nil, fmt.Errorf("loop in terms: %v", err)
}
swapFee := swap.CalcFee(
request.Amount, quote.SwapFeeBase, quote.SwapFeeRate,
)
if swapFee > request.MaxSwapFee {
logger.Warnf("Swap fee %v exceeding maximum of %v",
swapFee, request.MaxSwapFee)
return nil, ErrSwapFeeTooHigh
}
// Calculate the swap invoice amount. The prepay is added which
// effectively forces the server to pay us back our prepayment on a
// successful swap.
swapInvoiceAmt := request.Amount - swapFee + quote.PrepayAmt
// Generate random preimage.
var swapPreimage lntypes.Preimage
if _, err := rand.Read(swapPreimage[:]); err != nil {
logger.Error("Cannot generate preimage")
}
swapHash := lntypes.Hash(sha256.Sum256(swapPreimage[:]))
// Derive a sender key for this swap.
keyDesc, err := cfg.lnd.WalletKit.DeriveNextKey(
globalCtx, swap.KeyFamily,
)
if err != nil {
return nil, err
}
var senderKey [33]byte
copy(senderKey[:], keyDesc.PubKey.SerializeCompressed())
// Create the swap invoice in lnd.
_, swapInvoice, err := cfg.lnd.Client.AddInvoice(
globalCtx, &invoicesrpc.AddInvoiceData{
Preimage: &swapPreimage,
Value: swapInvoiceAmt,
Memo: "swap",
Expiry: 3600 * 24 * 365,
},
)
if err != nil {
return nil, err
}
// Post the swap parameters to the swap server. The response contains
// the server revocation key, the prepay invoice and the expiry height
// of the on-chain swap htlc.
logger.Infof("Initiating swap request at height %v", currentHeight)
swapResp, err := cfg.server.NewLoopInSwap(globalCtx, swapHash,
request.Amount, senderKey, swapInvoice,
)
if err != nil {
return nil, fmt.Errorf("cannot initiate swap: %v", err)
}
// Validate the response parameters the prevent us continuing with a
// swap that is based on parameters outside our allowed range.
err = validateLoopInContract(cfg.lnd, currentHeight, request, swapResp)
if err != nil {
return nil, err
}
// Instantiate a struct that contains all required data to start the
// swap.
initiationTime := time.Now()
contract := loopdb.LoopInContract{
HtlcConfTarget: request.HtlcConfTarget,
LoopInChannel: request.LoopInChannel,
SwapContract: loopdb.SwapContract{
InitiationHeight: currentHeight,
InitiationTime: initiationTime,
PrepayInvoice: swapResp.prepayInvoice,
ReceiverKey: swapResp.receiverKey,
SenderKey: senderKey,
Preimage: swapPreimage,
AmountRequested: request.Amount,
CltvExpiry: swapResp.expiry,
MaxMinerFee: request.MaxMinerFee,
MaxSwapFee: request.MaxSwapFee,
MaxPrepayRoutingFee: request.MaxPrepayRoutingFee,
},
}
swapKit, err := newSwapKit(
swapHash, TypeIn, cfg, &contract.SwapContract,
)
if err != nil {
return nil, err
}
swapKit.lastUpdateTime = initiationTime
swap := &loopInSwap{
LoopInContract: 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.CreateLoopIn(swapHash, &swap.LoopInContract)
if err != nil {
return nil, fmt.Errorf("cannot store swap: %v", err)
}
return swap, nil
}
// resumeLoopInSwap returns a swap object representing a pending swap that has
// been restored from the database.
func resumeLoopInSwap(reqContext context.Context, cfg *swapConfig,
pend *loopdb.LoopIn) (*loopInSwap, error) {
hash := lntypes.Hash(sha256.Sum256(pend.Contract.Preimage[:]))
logger.Infof("Resuming loop in swap %v", hash)
swapKit, err := newSwapKit(
hash, TypeIn, cfg, &pend.Contract.SwapContract,
)
if err != nil {
return nil, err
}
swap := &loopInSwap{
LoopInContract: *pend.Contract,
swapKit: *swapKit,
}
lastUpdate := pend.LastUpdate()
if lastUpdate == nil {
swap.lastUpdateTime = pend.Contract.InitiationTime
} else {
swap.state = lastUpdate.State
swap.lastUpdateTime = lastUpdate.Time
}
return swap, nil
}
// validateLoopInContract validates the contract parameters against our
// request.
func validateLoopInContract(lnd *lndclient.LndServices,
height int32,
request *LoopInRequest,
response *newLoopInResponse) error {
// Check prepay amount.
chainParams := lnd.ChainParams
prepayInvoiceAmt, err := swap.GetInvoiceAmt(
chainParams, response.prepayInvoice,
)
if err != nil {
return err
}
if prepayInvoiceAmt > request.MaxPrepayAmount {
logger.Warnf("Prepay amount %v exceeding maximum of %v",
prepayInvoiceAmt, request.MaxPrepayAmount)
return ErrPrepayAmountTooHigh
}
// Verify that we are not forced to publish an htlc that locks up our
// funds for too long in case the server doesn't follow through.
if response.expiry-height > MaxLoopInAcceptDelta {
return ErrExpiryTooFar
}
return nil
}
// execute starts/resumes the swap. It is a thin wrapper around executeSwap to
// conveniently handle the error case.
func (s *loopInSwap) execute(mainCtx context.Context,
cfg *executeConfig, height int32) error {
s.executeConfig = *cfg
s.height = height
// Announce swap by sending out an initial update.
err := s.sendUpdate(mainCtx)
if err != nil {
return err
}
// Execute the swap until it either reaches a final state or a temporary
// error occurs.
err = s.executeSwap(mainCtx)
// Sanity check. If there is no error, the swap must be in a final
// state.
if err == nil && s.state.Type() == loopdb.StateTypePending {
err = fmt.Errorf("swap in non-final state %v", s.state)
}
// If an unexpected error happened, report a temporary failure
// but don't persist the error. 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 = loopdb.StateFailTemporary
// If we cannot send out this update, there is nothing we can do.
_ = s.sendUpdate(mainCtx)
return err
}
s.log.Infof("Loop in swap completed: %v "+
"(final cost: server %v, onchain %v)",
s.state,
s.cost.Server,
s.cost.Onchain,
)
return nil
}
// waitPrepayPaid waits for the prepay payment to complete and adds the amount
// to the balance of payments to the server.
func (s *loopInSwap) waitPrepayPaid(ctx context.Context) error {
s.log.Infof("Wait for prepay outcome")
select {
case result := <-s.prePaymentChan:
if result.Err != nil {
// Server didn't take the prepayment.
s.log.Infof("Prepayment failed: %v",
result.Err)
} else {
s.cost.Server += result.PaidAmt
}
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// executeSwap executes the swap.
func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
var err error
// For loop in, the client takes the first step by publishing the
// on-chain htlc. Only do this is we haven't already done so in a
// previous run.
if s.state == loopdb.StateInitiated {
published, err := s.publishOnChainHtlc(globalCtx)
if err != nil {
return err
}
if !published {
return nil
}
}
// Wait for the htlc to confirm. After a restart this will pick up a
// previously published tx.
conf, err := s.waitForHtlcConf(globalCtx)
if err != nil {
return err
}
// Determine the htlc outpoint by inspecting the htlc tx.
htlcOutpoint, _, err := swap.GetScriptOutput(
conf.Tx, s.htlc.ScriptHash,
)
if err != nil {
return err
}
// Now that the htlc is on the chain, the server needs to take action.
// It requires us to pay the prepay before it will continue.
if err := s.payPrepay(globalCtx); err != nil {
return err
}
// The server is expected to see the htlc on-chain and knowing that it
// can sweep that htlc with the preimage, it should pay our swap
// invoice, receive the preimage and sweep the htlc. We are waiting for
// this to happen and simultaneously watch the htlc expiry height. When
// the htlc expires, we will publish a timeout tx to reclaim the funds.
spend, err := s.waitForHtlcSpend(globalCtx, htlcOutpoint)
if err != nil {
return err
}
// Determine the htlc input of the spending tx and inspect the witness
// to findout whether a success or a timeout tx spend the htlc.
htlcInput := spend.SpendingTx.TxIn[spend.SpenderInputIndex]
if s.htlc.IsSuccessWitness(htlcInput.Witness) {
// The server swept the htlc. Swap invoice should have been
// paid. We are waiting for this event to be sure of this.
err := s.waitForSwapPaid(globalCtx)
if err != nil {
return err
}
s.state = loopdb.StateSuccess
} else {
s.state = loopdb.StateFailTimeout
}
// Wait for outcome of prepay payment (for accounting).
if err := s.waitPrepayPaid(globalCtx); err != nil {
return err
}
// Persist swap outcome.
if err := s.persistState(globalCtx); err != nil {
return err
}
return nil
}
// waitForHtlcConf watches the chain until the htlc confirms.
func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) (
*chainntnfs.TxConfirmation, error) {
ctx, cancel := context.WithCancel(globalCtx)
defer cancel()
confChan, confErr, err := s.lnd.ChainNotifier.RegisterConfirmationsNtfn(
ctx, nil, s.htlc.ScriptHash, 1, s.InitiationHeight,
)
if err != nil {
return nil, err
}
for {
select {
// Htlc confirmed.
case conf := <-confChan:
return conf, nil
// Conf ntfn error.
case err := <-confErr:
return nil, err
// Keep up with block height.
case notification := <-s.blockEpochChan:
s.height = notification.(int32)
// Cancel.
case <-globalCtx.Done():
return nil, globalCtx.Err()
}
}
}
// publishOnChainHtlc checks whether there are still enough blocks left and if
// so, it publishes the htlc and advances the swap state.
func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
var err error
blocksRemaining := s.CltvExpiry - s.height
s.log.Infof("Blocks left until on-chain expiry: %v", blocksRemaining)
// Verify whether it still makes sense to publish the htlc.
if blocksRemaining < MinLoopInPublishDelta {
s.state = loopdb.StateFailTimeout
return false, s.persistState(ctx)
}
// Get fee estimate from lnd.
feeRate, err := s.lnd.WalletKit.EstimateFee(
ctx, s.LoopInContract.HtlcConfTarget,
)
if err != nil {
return false, fmt.Errorf("estimate fee: %v", err)
}
// Transition to state HtlcPublished before calling SendOutputs to
// prevent us from ever paying multiple times after a crash.
s.state = loopdb.StateHtlcPublished
err = s.persistState(ctx)
if err != nil {
return false, err
}
s.log.Infof("Publishing on chain HTLC with fee rate %v", feeRate)
tx, err := s.lnd.WalletKit.SendOutputs(ctx,
[]*wire.TxOut{{
PkScript: s.htlc.ScriptHash,
Value: int64(s.LoopInContract.AmountRequested),
}},
feeRate,
)
if err != nil {
return false, fmt.Errorf("send outputs: %v", err)
}
s.log.Infof("Published on chain HTLC tx %v", tx.TxHash())
return true, nil
}
// payPrepay pays the prepay invoice obtained from the server. It stores the
// result channel for later use.
func (s *loopInSwap) payPrepay(ctx context.Context) error {
// Pay the prepay invoice.
s.log.Infof("Sending prepayment %v", s.PrepayInvoice)
s.prePaymentChan = s.lnd.Client.PayInvoice(
ctx, s.PrepayInvoice, s.MaxPrepayRoutingFee,
nil,
)
return nil
}
// waitForHtlcSpend waits until a spending tx of the htlc gets confirmed and
// returns the spend details.
func (s *loopInSwap) waitForHtlcSpend(ctx context.Context,
htlc *wire.OutPoint) (*chainntnfs.SpendDetail, error) {
// Register the htlc spend notification.
rpcCtx, cancel := context.WithCancel(ctx)
defer cancel()
spendChan, spendErr, err := s.lnd.ChainNotifier.RegisterSpendNtfn(
rpcCtx, nil, s.htlc.ScriptHash, s.InitiationHeight,
)
if err != nil {
return nil, fmt.Errorf("register spend ntfn: %v", err)
}
for {
select {
// Spend notification error.
case err := <-spendErr:
return nil, err
case notification := <-s.blockEpochChan:
s.height = notification.(int32)
if s.height >= s.LoopInContract.CltvExpiry {
err := s.publishTimeoutTx(ctx, htlc)
if err != nil {
return nil, err
}
}
// Htlc spend, break loop.
case spendDetails := <-spendChan:
s.log.Infof("Htlc spend by tx: %v",
spendDetails.SpenderTxHash)
return spendDetails, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
// waitForSwapPaid waits until our swap invoice gets paid by the server.
func (s *loopInSwap) waitForSwapPaid(ctx context.Context) error {
// Wait for swap invoice to be paid.
rpcCtx, cancel := context.WithCancel(ctx)
defer cancel()
s.log.Infof("Subscribing to swap invoice %v", s.hash)
swapInvoiceChan, swapInvoiceErr, err := s.lnd.Invoices.SubscribeSingleInvoice(
rpcCtx, s.hash,
)
if err != nil {
return err
}
for {
select {
// Swap invoice ntfn error.
case err := <-swapInvoiceErr:
return err
case state := <-swapInvoiceChan:
s.log.Infof("Received swap invoice update: %v", state)
if state != channeldb.ContractSettled {
continue
}
return nil
case <-ctx.Done():
return ctx.Err()
}
}
}
// publishTimeoutTx publishes a timeout tx after the on-chain htlc has expired.
// The swap failed and we are reclaiming our funds.
func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
htlc *wire.OutPoint) error {
if s.timeoutAddr == nil {
var err error
s.timeoutAddr, err = s.lnd.WalletKit.NextAddr(ctx)
if err != nil {
return err
}
}
// Calculate sweep tx fee
//
// TODO: Configure timeout conf.
fee, err := s.sweeper.GetSweepFee(
ctx, s.htlc.MaxTimeoutWitnessSize, 2,
)
if err != nil {
return err
}
witnessFunc := func(sig []byte) (wire.TxWitness, error) {
return s.htlc.GenTimeoutWitness(sig)
}
timeoutTx, err := s.sweeper.CreateSweepTx(
ctx, s.height, s.htlc, *htlc, s.SenderKey, witnessFunc,
s.LoopInContract.AmountRequested, fee, s.timeoutAddr,
)
if err != nil {
return err
}
timeoutTxHash := timeoutTx.TxHash()
s.log.Infof("Publishing timeout tx %v with fee %v to addr %v",
timeoutTxHash, fee, s.timeoutAddr)
err = s.lnd.WalletKit.PublishTransaction(ctx, timeoutTx)
if err != nil {
s.log.Warnf("publish timeout: %v", err)
}
return nil
}
// persistState updates the swap state and sends out an update notification.
func (s *loopInSwap) persistState(ctx context.Context) error {
updateTime := time.Now()
s.lastUpdateTime = updateTime
// Update state in store.
err := s.store.UpdateLoopIn(s.hash, updateTime, s.state)
if err != nil {
return err
}
// Send out swap update
return s.sendUpdate(ctx)
}

@ -0,0 +1,385 @@
package loop
import (
"context"
"testing"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
var (
testLoopInRequest = LoopInRequest{
MaxPrepayAmount: btcutil.Amount(100),
Amount: btcutil.Amount(50000),
MaxSwapFee: btcutil.Amount(1000),
HtlcConfTarget: 2,
}
)
func TestLoopInSuccess(t *testing.T) {
defer test.Guard(t)()
ctx := newLoopInTestContext(t)
height := int32(600)
cfg := &swapConfig{
lnd: &ctx.lnd.LndServices,
store: ctx.store,
server: ctx.server,
}
swap, err := newLoopInSwap(
context.Background(), cfg,
height, &testLoopInRequest,
)
if err != nil {
t.Fatal(err)
}
ctx.store.assertLoopInStored()
errChan := make(chan error)
go func() {
err := swap.execute(context.Background(), ctx.cfg, height)
if err != nil {
logger.Error(err)
}
errChan <- err
}()
ctx.assertState(loopdb.StateInitiated)
ctx.assertState(loopdb.StateHtlcPublished)
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
// Expect htlc to be published.
htlcTx := <-ctx.lnd.SendOutputsChannel
// Expect register for htlc conf.
<-ctx.lnd.RegisterConfChannel
// Confirm htlc.
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
Tx: &htlcTx,
}
// Expect prepay to be paid.
prepay := <-ctx.lnd.SendPaymentChannel
prepayInvoice, err := ctx.lnd.DecodeInvoice(prepay.PaymentRequest)
if err != nil {
t.Fatal(err)
}
logger.Infof("Prepay hash: %x", *prepayInvoice.PaymentHash)
// Signal prepay pulled.
prepay.Done <- nil
// Client starts listening for spend of htlc.
<-ctx.lnd.RegisterSpendChannel
// Server spends htlc.
successTx := wire.MsgTx{}
successTx.AddTxIn(&wire.TxIn{
// PreviousOutPoint: wire.OutPoint{
// Hash: htlcTx.TxHash(),
// Index: uint32(htlcIndex),
// },
Witness: [][]byte{{}, {}, {}},
})
ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
SpendingTx: &successTx,
SpenderInputIndex: 0,
}
// Client starts listening for swap invoice updates.
subscription := <-ctx.lnd.SingleInvoiceSubcribeChannel
if subscription.Hash != ctx.server.swapHash {
t.Fatal("client subscribing to wrong invoice")
}
// Server has already paid invoice before spending the htlc. Signal
// settled.
subscription.Update <- channeldb.ContractSettled
ctx.assertState(loopdb.StateSuccess)
ctx.store.assertLoopInState(loopdb.StateSuccess)
err = <-errChan
if err != nil {
t.Fatal(err)
}
}
func TestLoopInTimeout(t *testing.T) {
defer test.Guard(t)()
ctx := newLoopInTestContext(t)
height := int32(600)
cfg := &swapConfig{
lnd: &ctx.lnd.LndServices,
store: ctx.store,
server: ctx.server,
}
swap, err := newLoopInSwap(
context.Background(), cfg,
height, &testLoopInRequest,
)
if err != nil {
t.Fatal(err)
}
ctx.store.assertLoopInStored()
errChan := make(chan error)
go func() {
err := swap.execute(context.Background(), ctx.cfg, height)
if err != nil {
logger.Error(err)
}
errChan <- err
}()
ctx.assertState(loopdb.StateInitiated)
ctx.assertState(loopdb.StateHtlcPublished)
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
// Expect htlc to be published.
htlcTx := <-ctx.lnd.SendOutputsChannel
// Expect register for htlc conf.
<-ctx.lnd.RegisterConfChannel
// Confirm htlc.
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
Tx: &htlcTx,
}
// Expect prepay to be paid.
prepay := <-ctx.lnd.SendPaymentChannel
prepayInvoice, err := ctx.lnd.DecodeInvoice(prepay.PaymentRequest)
if err != nil {
t.Fatal(err)
}
logger.Infof("Prepay hash: %x", *prepayInvoice.PaymentHash)
// Signal prepay pulled.
prepay.Done <- nil
// Client starts listening for spend of htlc.
<-ctx.lnd.RegisterSpendChannel
// Let htlc expire.
ctx.blockEpochChan <- swap.LoopInContract.CltvExpiry
// Expect timeout tx to be published.
timeoutTx := <-ctx.lnd.TxPublishChannel
// Confirm timeout tx.
ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
SpendingTx: timeoutTx,
SpenderInputIndex: 0,
}
ctx.assertState(loopdb.StateFailTimeout)
ctx.store.assertLoopInState(loopdb.StateFailTimeout)
err = <-errChan
if err != nil {
t.Fatal(err)
}
}
func TestLoopInResume(t *testing.T) {
t.Run("initiated", func(t *testing.T) {
testLoopInResume(t, loopdb.StateInitiated, false)
})
t.Run("initiated expired", func(t *testing.T) {
testLoopInResume(t, loopdb.StateInitiated, true)
})
t.Run("htlc published", func(t *testing.T) {
testLoopInResume(t, loopdb.StateHtlcPublished, false)
})
}
func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool) {
defer test.Guard(t)()
ctx := newLoopInTestContext(t)
cfg := &swapConfig{
lnd: &ctx.lnd.LndServices,
store: ctx.store,
server: ctx.server,
}
senderKey := [33]byte{4}
receiverKey := [33]byte{5}
prepayInvoice, err := getInvoice(testPreimage.Hash(), 5000, "prepay")
if err != nil {
t.Fatal(err)
}
contract := &loopdb.LoopInContract{
HtlcConfTarget: 2,
SwapContract: loopdb.SwapContract{
Preimage: testPreimage,
AmountRequested: 100000,
CltvExpiry: 744,
ReceiverKey: receiverKey,
SenderKey: senderKey,
MaxSwapFee: 60000,
PrepayInvoice: prepayInvoice,
MaxMinerFee: 50000,
},
}
pendSwap := &loopdb.LoopIn{
Contract: contract,
Loop: loopdb.Loop{
Events: []*loopdb.LoopEvent{
{
State: state,
},
},
Hash: testPreimage.Hash(),
},
}
htlc, err := swap.NewHtlc(
contract.CltvExpiry, contract.SenderKey, contract.ReceiverKey,
testPreimage.Hash(),
)
if err != nil {
t.Fatal(err)
}
err = ctx.store.CreateLoopIn(testPreimage.Hash(), contract)
if err != nil {
t.Fatal(err)
}
swap, err := resumeLoopInSwap(
context.Background(), cfg,
pendSwap,
)
if err != nil {
t.Fatal(err)
}
var height int32
if expired {
height = 740
} else {
height = 600
}
errChan := make(chan error)
go func() {
err := swap.execute(context.Background(), ctx.cfg, height)
if err != nil {
logger.Error(err)
}
errChan <- err
}()
defer func() {
err = <-errChan
if err != nil {
t.Fatal(err)
}
select {
case <-ctx.lnd.SendPaymentChannel:
t.Fatal("unexpected payment sent")
default:
}
select {
case <-ctx.lnd.SendOutputsChannel:
t.Fatal("unexpected tx published")
default:
}
}()
var htlcTx wire.MsgTx
if state == loopdb.StateInitiated {
ctx.assertState(loopdb.StateInitiated)
if expired {
ctx.assertState(loopdb.StateFailTimeout)
return
}
ctx.assertState(loopdb.StateHtlcPublished)
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
// Expect htlc to be published.
htlcTx = <-ctx.lnd.SendOutputsChannel
} else {
ctx.assertState(loopdb.StateHtlcPublished)
htlcTx.AddTxOut(&wire.TxOut{
PkScript: htlc.ScriptHash,
})
}
// Expect register for htlc conf.
<-ctx.lnd.RegisterConfChannel
// Confirm htlc.
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
Tx: &htlcTx,
}
// Expect prepay to be paid.
prepay := <-ctx.lnd.SendPaymentChannel
// Signal prepay pulled.
prepay.Done <- nil
// Client starts listening for spend of htlc.
<-ctx.lnd.RegisterSpendChannel
// Server spends htlc.
successTx := wire.MsgTx{}
successTx.AddTxIn(&wire.TxIn{
Witness: [][]byte{{}, {}, {}},
})
successTxHash := successTx.TxHash()
ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
SpendingTx: &successTx,
SpenderTxHash: &successTxHash,
SpenderInputIndex: 0,
}
// Client starts listening for swap invoice updates.
subscription := <-ctx.lnd.SingleInvoiceSubcribeChannel
if subscription.Hash != testPreimage.Hash() {
t.Fatal("client subscribing to wrong invoice")
}
// Server has already paid invoice before spending the htlc. Signal
// settled.
subscription.Update <- channeldb.ContractSettled
ctx.assertState(loopdb.StateSuccess)
}

@ -0,0 +1,62 @@
package loop
import (
"testing"
"time"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweep"
"github.com/lightninglabs/loop/test"
)
type loopInTestContext struct {
t *testing.T
lnd *test.LndMockServices
server *serverMock
store *storeMock
sweeper *sweep.Sweeper
cfg *executeConfig
statusChan chan SwapInfo
blockEpochChan chan interface{}
}
func newLoopInTestContext(t *testing.T) *loopInTestContext {
lnd := test.NewMockLnd()
server := newServerMock()
store := newStoreMock(t)
sweeper := sweep.Sweeper{Lnd: &lnd.LndServices}
blockEpochChan := make(chan interface{})
statusChan := make(chan SwapInfo)
expiryChan := make(chan time.Time)
timerFactory := func(expiry time.Duration) <-chan time.Time {
return expiryChan
}
cfg := executeConfig{
statusChan: statusChan,
sweeper: &sweeper,
blockEpochChan: blockEpochChan,
timerFactory: timerFactory,
}
return &loopInTestContext{
t: t,
lnd: lnd,
server: server,
store: store,
sweeper: &sweeper,
cfg: &cfg,
statusChan: statusChan,
blockEpochChan: blockEpochChan,
}
}
func (c *loopInTestContext) assertState(expectedState loopdb.SwapState) {
state := <-c.statusChan
if state.State != expectedState {
c.t.Fatalf("expected state %v but got %v", expectedState,
state.State)
}
}

@ -135,7 +135,7 @@ func resumeLoopOutSwap(reqContext context.Context, cfg *swapConfig,
hash := lntypes.Hash(sha256.Sum256(pend.Contract.Preimage[:]))
logger.Infof("Resuming swap %v", hash)
logger.Infof("Resuming loop out swap %v", hash)
swapKit, err := newSwapKit(
hash, TypeOut, cfg, &pend.Contract.SwapContract,

@ -29,20 +29,24 @@ type SwapType int32
const (
// LOOP_OUT indicates an loop out swap (off-chain to on-chain)
SwapType_LOOP_OUT SwapType = 0
// LOOP_IN indicates a loop in swap (on-chain to off-chain)
SwapType_LOOP_IN SwapType = 1
)
var SwapType_name = map[int32]string{
0: "LOOP_OUT",
1: "LOOP_IN",
}
var SwapType_value = map[string]int32{
"LOOP_OUT": 0,
"LOOP_IN": 1,
}
func (x SwapType) String() string {
return proto.EnumName(SwapType_name, int32(x))
}
func (SwapType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{0}
return fileDescriptor_client_592ed489859e5ea5, []int{0}
}
type SwapState int32
@ -61,6 +65,10 @@ const (
// case where we wait for fees to come down before we sweep.
SwapState_PREIMAGE_REVEALED SwapState = 1
// *
// HTLC_PUBLISHED is reached when the htlc tx has been published in a loop in
// swap.
SwapState_HTLC_PUBLISHED SwapState = 2
// *
// SUCCESS is the final swap state that is reached when the sweep tx has
// the required confirmation depth.
SwapState_SUCCESS SwapState = 3
@ -73,12 +81,14 @@ const (
var SwapState_name = map[int32]string{
0: "INITIATED",
1: "PREIMAGE_REVEALED",
2: "HTLC_PUBLISHED",
3: "SUCCESS",
4: "FAILED",
}
var SwapState_value = map[string]int32{
"INITIATED": 0,
"PREIMAGE_REVEALED": 1,
"HTLC_PUBLISHED": 2,
"SUCCESS": 3,
"FAILED": 4,
}
@ -87,7 +97,7 @@ func (x SwapState) String() string {
return proto.EnumName(SwapState_name, int32(x))
}
func (SwapState) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{1}
return fileDescriptor_client_592ed489859e5ea5, []int{1}
}
type LoopOutRequest struct {
@ -145,7 +155,7 @@ func (m *LoopOutRequest) Reset() { *m = LoopOutRequest{} }
func (m *LoopOutRequest) String() string { return proto.CompactTextString(m) }
func (*LoopOutRequest) ProtoMessage() {}
func (*LoopOutRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{0}
return fileDescriptor_client_592ed489859e5ea5, []int{0}
}
func (m *LoopOutRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LoopOutRequest.Unmarshal(m, b)
@ -221,6 +231,107 @@ func (m *LoopOutRequest) GetLoopOutChannel() uint64 {
return 0
}
type LoopInRequest struct {
// *
// Requested swap amount in sat. This does not include the swap and miner
// fee.
Amt int64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"`
// *
// 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 GetQuote call.
MaxPrepayRoutingFee int64 `protobuf:"varint,2,opt,name=max_prepay_routing_fee,json=maxPrepayRoutingFee,proto3" json:"max_prepay_routing_fee,omitempty"`
// *
// 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 GetQuote call. It includes the prepay amount.
MaxSwapFee int64 `protobuf:"varint,3,opt,name=max_swap_fee,json=maxSwapFee,proto3" json:"max_swap_fee,omitempty"`
// *
// Maximum amount of the swap fee that may be charged as a prepayment.
MaxPrepayAmt int64 `protobuf:"varint,4,opt,name=max_prepay_amt,json=maxPrepayAmt,proto3" json:"max_prepay_amt,omitempty"`
// *
// Maximum in on-chain fees that we are willing to spent. If we want to
// publish the on-chain htlc and the fee estimate turns out higher than this
// value, we cancel the swap.
//
// max_miner_fee is typically taken from the response of the GetQuote call.
MaxMinerFee int64 `protobuf:"varint,5,opt,name=max_miner_fee,json=maxMinerFee,proto3" json:"max_miner_fee,omitempty"`
// *
// The channel to loop in. If zero, the channel to loop in is selected based
// on the lowest routing fee for the swap payment from the server.
LoopInChannel uint64 `protobuf:"varint,6,opt,name=loop_in_channel,json=loopInChannel,proto3" json:"loop_in_channel,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LoopInRequest) Reset() { *m = LoopInRequest{} }
func (m *LoopInRequest) String() string { return proto.CompactTextString(m) }
func (*LoopInRequest) ProtoMessage() {}
func (*LoopInRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_client_592ed489859e5ea5, []int{1}
}
func (m *LoopInRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LoopInRequest.Unmarshal(m, b)
}
func (m *LoopInRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LoopInRequest.Marshal(b, m, deterministic)
}
func (dst *LoopInRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_LoopInRequest.Merge(dst, src)
}
func (m *LoopInRequest) XXX_Size() int {
return xxx_messageInfo_LoopInRequest.Size(m)
}
func (m *LoopInRequest) XXX_DiscardUnknown() {
xxx_messageInfo_LoopInRequest.DiscardUnknown(m)
}
var xxx_messageInfo_LoopInRequest proto.InternalMessageInfo
func (m *LoopInRequest) GetAmt() int64 {
if m != nil {
return m.Amt
}
return 0
}
func (m *LoopInRequest) GetMaxPrepayRoutingFee() int64 {
if m != nil {
return m.MaxPrepayRoutingFee
}
return 0
}
func (m *LoopInRequest) GetMaxSwapFee() int64 {
if m != nil {
return m.MaxSwapFee
}
return 0
}
func (m *LoopInRequest) GetMaxPrepayAmt() int64 {
if m != nil {
return m.MaxPrepayAmt
}
return 0
}
func (m *LoopInRequest) GetMaxMinerFee() int64 {
if m != nil {
return m.MaxMinerFee
}
return 0
}
func (m *LoopInRequest) GetLoopInChannel() uint64 {
if m != nil {
return m.LoopInChannel
}
return 0
}
type SwapResponse struct {
// *
// Swap identifier to track status in the update stream that is returned from
@ -235,7 +346,7 @@ func (m *SwapResponse) Reset() { *m = SwapResponse{} }
func (m *SwapResponse) String() string { return proto.CompactTextString(m) }
func (*SwapResponse) ProtoMessage() {}
func (*SwapResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{1}
return fileDescriptor_client_592ed489859e5ea5, []int{2}
}
func (m *SwapResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SwapResponse.Unmarshal(m, b)
@ -272,7 +383,7 @@ func (m *MonitorRequest) Reset() { *m = MonitorRequest{} }
func (m *MonitorRequest) String() string { return proto.CompactTextString(m) }
func (*MonitorRequest) ProtoMessage() {}
func (*MonitorRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{2}
return fileDescriptor_client_592ed489859e5ea5, []int{3}
}
func (m *MonitorRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MonitorRequest.Unmarshal(m, b)
@ -325,7 +436,7 @@ func (m *SwapStatus) Reset() { *m = SwapStatus{} }
func (m *SwapStatus) String() string { return proto.CompactTextString(m) }
func (*SwapStatus) ProtoMessage() {}
func (*SwapStatus) Descriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{3}
return fileDescriptor_client_592ed489859e5ea5, []int{4}
}
func (m *SwapStatus) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SwapStatus.Unmarshal(m, b)
@ -404,7 +515,7 @@ func (m *TermsRequest) Reset() { *m = TermsRequest{} }
func (m *TermsRequest) String() string { return proto.CompactTextString(m) }
func (*TermsRequest) ProtoMessage() {}
func (*TermsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{4}
return fileDescriptor_client_592ed489859e5ea5, []int{5}
}
func (m *TermsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_TermsRequest.Unmarshal(m, b)
@ -446,10 +557,7 @@ type TermsResponse struct {
MaxSwapAmount int64 `protobuf:"varint,6,opt,name=max_swap_amount,json=maxSwapAmount,proto3" json:"max_swap_amount,omitempty"`
// *
// On-chain cltv expiry delta
CltvDelta int32 `protobuf:"varint,7,opt,name=cltv_delta,json=cltvDelta,proto3" json:"cltv_delta,omitempty"`
// *
// Maximum cltv expiry delta
MaxCltv int32 `protobuf:"varint,8,opt,name=max_cltv,json=maxCltv,proto3" json:"max_cltv,omitempty"`
CltvDelta int32 `protobuf:"varint,7,opt,name=cltv_delta,json=cltvDelta,proto3" json:"cltv_delta,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -459,7 +567,7 @@ func (m *TermsResponse) Reset() { *m = TermsResponse{} }
func (m *TermsResponse) String() string { return proto.CompactTextString(m) }
func (*TermsResponse) ProtoMessage() {}
func (*TermsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{5}
return fileDescriptor_client_592ed489859e5ea5, []int{6}
}
func (m *TermsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_TermsResponse.Unmarshal(m, b)
@ -528,13 +636,6 @@ func (m *TermsResponse) GetCltvDelta() int32 {
return 0
}
func (m *TermsResponse) GetMaxCltv() int32 {
if m != nil {
return m.MaxCltv
}
return 0
}
type QuoteRequest struct {
// *
// The amount to swap in satoshis.
@ -548,7 +649,7 @@ func (m *QuoteRequest) Reset() { *m = QuoteRequest{} }
func (m *QuoteRequest) String() string { return proto.CompactTextString(m) }
func (*QuoteRequest) ProtoMessage() {}
func (*QuoteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{6}
return fileDescriptor_client_592ed489859e5ea5, []int{7}
}
func (m *QuoteRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QuoteRequest.Unmarshal(m, b)
@ -594,7 +695,7 @@ func (m *QuoteResponse) Reset() { *m = QuoteResponse{} }
func (m *QuoteResponse) String() string { return proto.CompactTextString(m) }
func (*QuoteResponse) ProtoMessage() {}
func (*QuoteResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_client_ecf1f0d12250b1cb, []int{7}
return fileDescriptor_client_592ed489859e5ea5, []int{8}
}
func (m *QuoteResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QuoteResponse.Unmarshal(m, b)
@ -637,6 +738,7 @@ func (m *QuoteResponse) GetMinerFee() int64 {
func init() {
proto.RegisterType((*LoopOutRequest)(nil), "looprpc.LoopOutRequest")
proto.RegisterType((*LoopInRequest)(nil), "looprpc.LoopInRequest")
proto.RegisterType((*SwapResponse)(nil), "looprpc.SwapResponse")
proto.RegisterType((*MonitorRequest)(nil), "looprpc.MonitorRequest")
proto.RegisterType((*SwapStatus)(nil), "looprpc.SwapStatus")
@ -666,6 +768,12 @@ type SwapClientClient interface {
// point onwards, progress can be tracked via the SwapStatus stream that is
// returned from Monitor().
LoopOut(ctx context.Context, in *LoopOutRequest, opts ...grpc.CallOption) (*SwapResponse, error)
// *
// LoopIn initiates a loop in 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 SwapStatus stream
// that is returned from Monitor().
LoopIn(ctx context.Context, in *LoopInRequest, opts ...grpc.CallOption) (*SwapResponse, error)
// * loop: `monitor`
// Monitor will return a stream of swap updates for currently active swaps.
// TODO: add MonitorSync version for REST clients.
@ -677,6 +785,12 @@ type SwapClientClient interface {
// LoopOutQuote returns a quote for a loop out swap with the provided
// parameters.
LoopOutQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*QuoteResponse, error)
// *
// GetTerms returns the terms that the server enforces for swaps.
GetLoopInTerms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*TermsResponse, error)
// *
// GetQuote returns a quote for a swap with the provided parameters.
GetLoopInQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*QuoteResponse, error)
}
type swapClientClient struct {
@ -696,6 +810,15 @@ func (c *swapClientClient) LoopOut(ctx context.Context, in *LoopOutRequest, opts
return out, nil
}
func (c *swapClientClient) LoopIn(ctx context.Context, in *LoopInRequest, opts ...grpc.CallOption) (*SwapResponse, error) {
out := new(SwapResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/LoopIn", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapClientClient) Monitor(ctx context.Context, in *MonitorRequest, opts ...grpc.CallOption) (SwapClient_MonitorClient, error) {
stream, err := c.cc.NewStream(ctx, &_SwapClient_serviceDesc.Streams[0], "/looprpc.SwapClient/Monitor", opts...)
if err != nil {
@ -746,6 +869,24 @@ func (c *swapClientClient) LoopOutQuote(ctx context.Context, in *QuoteRequest, o
return out, nil
}
func (c *swapClientClient) GetLoopInTerms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*TermsResponse, error) {
out := new(TermsResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/GetLoopInTerms", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapClientClient) GetLoopInQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*QuoteResponse, error) {
out := new(QuoteResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/GetLoopInQuote", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SwapClientServer is the server API for SwapClient service.
type SwapClientServer interface {
// * loop: `out`
@ -754,6 +895,12 @@ type SwapClientServer interface {
// point onwards, progress can be tracked via the SwapStatus stream that is
// returned from Monitor().
LoopOut(context.Context, *LoopOutRequest) (*SwapResponse, error)
// *
// LoopIn initiates a loop in 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 SwapStatus stream
// that is returned from Monitor().
LoopIn(context.Context, *LoopInRequest) (*SwapResponse, error)
// * loop: `monitor`
// Monitor will return a stream of swap updates for currently active swaps.
// TODO: add MonitorSync version for REST clients.
@ -765,6 +912,12 @@ type SwapClientServer interface {
// LoopOutQuote returns a quote for a loop out swap with the provided
// parameters.
LoopOutQuote(context.Context, *QuoteRequest) (*QuoteResponse, error)
// *
// GetTerms returns the terms that the server enforces for swaps.
GetLoopInTerms(context.Context, *TermsRequest) (*TermsResponse, error)
// *
// GetQuote returns a quote for a swap with the provided parameters.
GetLoopInQuote(context.Context, *QuoteRequest) (*QuoteResponse, error)
}
func RegisterSwapClientServer(s *grpc.Server, srv SwapClientServer) {
@ -789,6 +942,24 @@ func _SwapClient_LoopOut_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler)
}
func _SwapClient_LoopIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoopInRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).LoopIn(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapClient/LoopIn",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).LoopIn(ctx, req.(*LoopInRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SwapClient_Monitor_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(MonitorRequest)
if err := stream.RecvMsg(m); err != nil {
@ -846,6 +1017,42 @@ func _SwapClient_LoopOutQuote_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _SwapClient_GetLoopInTerms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TermsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).GetLoopInTerms(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapClient/GetLoopInTerms",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).GetLoopInTerms(ctx, req.(*TermsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SwapClient_GetLoopInQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QuoteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).GetLoopInQuote(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapClient/GetLoopInQuote",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).GetLoopInQuote(ctx, req.(*QuoteRequest))
}
return interceptor(ctx, in, info, handler)
}
var _SwapClient_serviceDesc = grpc.ServiceDesc{
ServiceName: "looprpc.SwapClient",
HandlerType: (*SwapClientServer)(nil),
@ -854,6 +1061,10 @@ var _SwapClient_serviceDesc = grpc.ServiceDesc{
MethodName: "LoopOut",
Handler: _SwapClient_LoopOut_Handler,
},
{
MethodName: "LoopIn",
Handler: _SwapClient_LoopIn_Handler,
},
{
MethodName: "LoopOutTerms",
Handler: _SwapClient_LoopOutTerms_Handler,
@ -862,6 +1073,14 @@ var _SwapClient_serviceDesc = grpc.ServiceDesc{
MethodName: "LoopOutQuote",
Handler: _SwapClient_LoopOutQuote_Handler,
},
{
MethodName: "GetLoopInTerms",
Handler: _SwapClient_GetLoopInTerms_Handler,
},
{
MethodName: "GetLoopInQuote",
Handler: _SwapClient_GetLoopInQuote_Handler,
},
},
Streams: []grpc.StreamDesc{
{
@ -873,60 +1092,65 @@ var _SwapClient_serviceDesc = grpc.ServiceDesc{
Metadata: "client.proto",
}
func init() { proto.RegisterFile("client.proto", fileDescriptor_client_ecf1f0d12250b1cb) }
var fileDescriptor_client_ecf1f0d12250b1cb = []byte{
// 825 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0xcb, 0x72, 0xe3, 0x44,
0x14, 0x1d, 0xc9, 0x4e, 0x6c, 0xdd, 0xc8, 0x8a, 0xdd, 0xf3, 0xf2, 0x18, 0x86, 0x32, 0x2a, 0x1e,
0xae, 0x2c, 0x22, 0xc8, 0xac, 0x60, 0x67, 0x1c, 0x0f, 0x65, 0x2a, 0x21, 0xa6, 0xed, 0x50, 0xc5,
0x4a, 0xd5, 0x63, 0xf7, 0x64, 0x54, 0xa5, 0x56, 0x6b, 0xd4, 0xad, 0x8c, 0x5d, 0x14, 0x1b, 0x7e,
0x81, 0xdf, 0x60, 0xc5, 0xaf, 0xb0, 0xe0, 0x07, 0xf8, 0x05, 0xf6, 0x54, 0x3f, 0xec, 0x48, 0x81,
0xd9, 0x75, 0x9d, 0x3e, 0xf7, 0x74, 0xf7, 0xb9, 0xe7, 0x36, 0xf8, 0xab, 0x34, 0xa1, 0x99, 0x3c,
0xcd, 0x0b, 0x2e, 0x39, 0x6a, 0xa5, 0x9c, 0xe7, 0x45, 0xbe, 0x1a, 0x7c, 0x78, 0xc3, 0xf9, 0x4d,
0x4a, 0x23, 0x92, 0x27, 0x11, 0xc9, 0x32, 0x2e, 0x89, 0x4c, 0x78, 0x26, 0x0c, 0x2d, 0xfc, 0xc3,
0x85, 0xe0, 0x82, 0xf3, 0xfc, 0xaa, 0x94, 0x98, 0xbe, 0x2d, 0xa9, 0x90, 0xa8, 0x0b, 0x0d, 0xc2,
0x64, 0xdf, 0x19, 0x3a, 0xa3, 0x06, 0x56, 0x4b, 0x84, 0xa0, 0xb9, 0xa6, 0x42, 0xf6, 0xdd, 0xa1,
0x33, 0xf2, 0xb0, 0x5e, 0xa3, 0x08, 0x1e, 0x31, 0xb2, 0x89, 0xc5, 0x3b, 0x92, 0xc7, 0x05, 0x2f,
0x65, 0x92, 0xdd, 0xc4, 0xaf, 0x29, 0xed, 0x37, 0x74, 0x59, 0x8f, 0x91, 0xcd, 0xe2, 0x1d, 0xc9,
0xb1, 0xd9, 0x79, 0x49, 0x29, 0x7a, 0x01, 0x4f, 0x54, 0x41, 0x5e, 0xd0, 0x9c, 0x6c, 0x6b, 0x25,
0x4d, 0x5d, 0xf2, 0x90, 0x91, 0xcd, 0x5c, 0x6f, 0x56, 0x8a, 0x86, 0xe0, 0xef, 0x4f, 0x51, 0xd4,
0x03, 0x4d, 0x05, 0xab, 0xae, 0x18, 0x9f, 0x40, 0x50, 0x91, 0x55, 0x17, 0x3f, 0xd4, 0x1c, 0x7f,
0x2f, 0x37, 0x66, 0x12, 0x85, 0xd0, 0x51, 0x2c, 0x96, 0x64, 0xb4, 0xd0, 0x42, 0x2d, 0x4d, 0x3a,
0x62, 0x64, 0x73, 0xa9, 0x30, 0xa5, 0x34, 0x82, 0xae, 0xf2, 0x2c, 0xe6, 0xa5, 0x8c, 0x57, 0x6f,
0x48, 0x96, 0xd1, 0xb4, 0xdf, 0x1e, 0x3a, 0xa3, 0x26, 0x0e, 0x52, 0xe3, 0xd0, 0xc4, 0xa0, 0xe1,
0x47, 0xe0, 0xeb, 0xc7, 0x51, 0x91, 0xf3, 0x4c, 0x50, 0x14, 0x80, 0x9b, 0xac, 0xb5, 0x61, 0x1e,
0x76, 0x93, 0x75, 0xd8, 0x85, 0xe0, 0x92, 0x67, 0x89, 0xe4, 0x85, 0xf5, 0x34, 0xfc, 0xc7, 0x01,
0x50, 0x25, 0x0b, 0x49, 0x64, 0x29, 0xfe, 0xc7, 0x62, 0x23, 0xe1, 0xee, 0x24, 0xd0, 0xa7, 0xd0,
0x94, 0xdb, 0xdc, 0xd8, 0x19, 0x9c, 0xf5, 0x4e, 0x6d, 0x37, 0x4f, 0x95, 0xc8, 0x72, 0x9b, 0x53,
0xac, 0xb7, 0xd1, 0x08, 0x0e, 0x84, 0x24, 0xd2, 0x78, 0x18, 0x9c, 0xa1, 0x1a, 0x4f, 0x1d, 0x46,
0xb1, 0x21, 0xa0, 0xcf, 0xe1, 0x38, 0xc9, 0x12, 0x99, 0xe8, 0xee, 0xc7, 0x32, 0x61, 0x3b, 0x33,
0x83, 0x3b, 0x78, 0x99, 0x30, 0x63, 0x03, 0x11, 0x32, 0x2e, 0xf3, 0x35, 0x91, 0xd4, 0x30, 0x8d,
0xa5, 0x81, 0xc2, 0xaf, 0x35, 0xac, 0x99, 0x1f, 0x83, 0xff, 0x46, 0xa6, 0xab, 0x98, 0xac, 0xd7,
0x05, 0x15, 0x42, 0x7b, 0xea, 0xe1, 0x23, 0x85, 0x8d, 0x0d, 0x14, 0x06, 0xe0, 0x2f, 0x69, 0xc1,
0xc4, 0xce, 0x87, 0xdf, 0x5d, 0xe8, 0x58, 0xc0, 0x7a, 0x77, 0x02, 0x3d, 0xdd, 0xdd, 0x9c, 0x6c,
0x19, 0xcd, 0x64, 0xac, 0x83, 0x66, 0xac, 0x3c, 0x56, 0x1b, 0x73, 0x83, 0x9f, 0xab, 0xcc, 0x85,
0xd0, 0xd9, 0x25, 0x21, 0x7e, 0x45, 0x04, 0xd5, 0x7e, 0x35, 0xf0, 0x91, 0x30, 0x59, 0xf8, 0x86,
0x08, 0x5a, 0xe3, 0x14, 0xca, 0x99, 0x46, 0x8d, 0x83, 0x95, 0x17, 0xcf, 0x01, 0x2a, 0x79, 0x31,
0xf1, 0xf3, 0xf2, 0x7d, 0x58, 0x3e, 0x83, 0x63, 0x96, 0x64, 0x26, 0x74, 0x84, 0xf1, 0x32, 0x93,
0xd6, 0xaa, 0x0e, 0x4b, 0x32, 0x65, 0xec, 0x58, 0x83, 0x9a, 0xb7, 0x0b, 0xa7, 0xe5, 0x1d, 0x5a,
0x9e, 0xc9, 0xa7, 0xe5, 0x3d, 0x07, 0x58, 0xa5, 0xf2, 0x36, 0x5e, 0xd3, 0x54, 0x12, 0xed, 0xd2,
0x01, 0xf6, 0x14, 0x72, 0xae, 0x00, 0xf4, 0x0c, 0xda, 0x4a, 0x46, 0x01, 0x3a, 0x6f, 0x07, 0xb8,
0xc5, 0xc8, 0x66, 0x92, 0xca, 0xdb, 0x70, 0x08, 0xfe, 0x0f, 0x25, 0x97, 0xf4, 0xbd, 0xa3, 0x19,
0xbe, 0x86, 0x8e, 0x65, 0x58, 0x3f, 0x9f, 0x41, 0x7b, 0x3f, 0x2d, 0x86, 0xd7, 0xb2, 0x4f, 0xbf,
0xf7, 0x6c, 0xf7, 0xfe, 0xb3, 0x3f, 0x00, 0xef, 0x6e, 0x3e, 0x8c, 0x6b, 0x6d, 0x66, 0x87, 0xe3,
0xa4, 0x0f, 0xed, 0x5d, 0xf4, 0x90, 0x0f, 0xed, 0x8b, 0xab, 0xab, 0x79, 0x7c, 0x75, 0xbd, 0xec,
0x3e, 0x38, 0xf9, 0x0e, 0xbc, 0x7d, 0xd8, 0x50, 0x07, 0xbc, 0xd9, 0xf7, 0xb3, 0xe5, 0x6c, 0xbc,
0x9c, 0x9e, 0x77, 0x1f, 0xa0, 0xc7, 0xd0, 0x9b, 0xe3, 0xe9, 0xec, 0x72, 0xfc, 0xed, 0x34, 0xc6,
0xd3, 0x1f, 0xa7, 0xe3, 0x8b, 0xe9, 0x79, 0xd7, 0x41, 0x47, 0xd0, 0x5a, 0x5c, 0x4f, 0x26, 0xd3,
0xc5, 0xa2, 0xdb, 0x40, 0x00, 0x87, 0x2f, 0xc7, 0x33, 0xb5, 0xd1, 0x3c, 0xfb, 0xcb, 0x35, 0x63,
0x32, 0xd1, 0x3f, 0x19, 0xc2, 0xd0, 0xb2, 0x7f, 0x13, 0x7a, 0xba, 0x4f, 0x76, 0xfd, 0xb7, 0x1a,
0x3c, 0xae, 0x45, 0x7e, 0x67, 0x43, 0xf8, 0xf4, 0xd7, 0x3f, 0xff, 0xfe, 0xcd, 0xed, 0x85, 0x7e,
0x74, 0xfb, 0x65, 0xa4, 0x18, 0x11, 0x2f, 0xe5, 0xd7, 0xce, 0x09, 0xfa, 0x0a, 0x5a, 0x76, 0x36,
0x2b, 0x9a, 0xf5, 0x69, 0x1d, 0x3c, 0xfc, 0xcf, 0x18, 0x95, 0xe2, 0x0b, 0x07, 0xfd, 0x04, 0xbe,
0x3d, 0x5c, 0x47, 0x18, 0xdd, 0x1d, 0x5d, 0xcd, 0xf8, 0xe0, 0xc9, 0x7d, 0xd8, 0x5e, 0x69, 0xa0,
0xaf, 0xf4, 0x08, 0xa1, 0xea, 0x95, 0x22, 0xa9, 0xa5, 0xe2, 0xbd, 0xb4, 0xee, 0x66, 0x45, 0xba,
0xda, 0xff, 0x8a, 0x74, 0xad, 0xe9, 0xe1, 0x50, 0x4b, 0x0f, 0x50, 0xbf, 0x26, 0xfd, 0x56, 0x71,
0xa2, 0x9f, 0x09, 0x93, 0xbf, 0xbc, 0x3a, 0xd4, 0xdf, 0xfd, 0x8b, 0x7f, 0x03, 0x00, 0x00, 0xff,
0xff, 0x2d, 0xba, 0xce, 0x65, 0x25, 0x06, 0x00, 0x00,
func init() { proto.RegisterFile("client.proto", fileDescriptor_client_592ed489859e5ea5) }
var fileDescriptor_client_592ed489859e5ea5 = []byte{
// 906 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x6e, 0xdb, 0x46,
0x18, 0x0c, 0x29, 0x59, 0x3f, 0x9f, 0x29, 0x9a, 0xda, 0x24, 0x8e, 0xaa, 0x36, 0x85, 0x4a, 0x34,
0xa9, 0xe0, 0x83, 0xd5, 0x3a, 0x87, 0xa2, 0xbd, 0x14, 0x8a, 0xa4, 0x24, 0x02, 0xe4, 0x58, 0xa5,
0xe4, 0x02, 0xbd, 0x94, 0xd8, 0x48, 0x1b, 0x87, 0x00, 0xb9, 0xcb, 0x90, 0xcb, 0xc4, 0x46, 0xd1,
0x4b, 0xdf, 0xa0, 0xed, 0x9b, 0xf4, 0x55, 0xfa, 0x0a, 0x3d, 0xf5, 0xde, 0x7b, 0xb1, 0x3f, 0xa4,
0x49, 0xbb, 0x06, 0x8c, 0xdc, 0xa8, 0xd9, 0xd9, 0xe1, 0xce, 0x70, 0xbe, 0x15, 0x58, 0x9b, 0x30,
0x20, 0x94, 0x1f, 0xc6, 0x09, 0xe3, 0x0c, 0x35, 0x43, 0xc6, 0xe2, 0x24, 0xde, 0xf4, 0x3f, 0x39,
0x63, 0xec, 0x2c, 0x24, 0x23, 0x1c, 0x07, 0x23, 0x4c, 0x29, 0xe3, 0x98, 0x07, 0x8c, 0xa6, 0x8a,
0xe6, 0xfe, 0x69, 0x82, 0xbd, 0x60, 0x2c, 0x3e, 0xc9, 0xb8, 0x47, 0xde, 0x66, 0x24, 0xe5, 0xc8,
0x81, 0x1a, 0x8e, 0x78, 0xcf, 0x18, 0x18, 0xc3, 0x9a, 0x27, 0x1e, 0x11, 0x82, 0xfa, 0x96, 0xa4,
0xbc, 0x67, 0x0e, 0x8c, 0x61, 0xdb, 0x93, 0xcf, 0x68, 0x04, 0xf7, 0x22, 0x7c, 0xee, 0xa7, 0xef,
0x71, 0xec, 0x27, 0x2c, 0xe3, 0x01, 0x3d, 0xf3, 0x5f, 0x13, 0xd2, 0xab, 0xc9, 0x6d, 0xdd, 0x08,
0x9f, 0xaf, 0xde, 0xe3, 0xd8, 0x53, 0x2b, 0xcf, 0x08, 0x41, 0x4f, 0x60, 0x5f, 0x6c, 0x88, 0x13,
0x12, 0xe3, 0x8b, 0xca, 0x96, 0xba, 0xdc, 0x72, 0x37, 0xc2, 0xe7, 0x4b, 0xb9, 0x58, 0xda, 0x34,
0x00, 0xab, 0x78, 0x8b, 0xa0, 0xee, 0x48, 0x2a, 0x68, 0x75, 0xc1, 0xf8, 0x1c, 0xec, 0x92, 0xac,
0x38, 0x78, 0x43, 0x72, 0xac, 0x42, 0x6e, 0x1c, 0x71, 0xe4, 0x42, 0x47, 0xb0, 0xa2, 0x80, 0x92,
0x44, 0x0a, 0x35, 0x25, 0x69, 0x37, 0xc2, 0xe7, 0xc7, 0x02, 0x13, 0x4a, 0x43, 0x70, 0x44, 0x66,
0x3e, 0xcb, 0xb8, 0xbf, 0x79, 0x83, 0x29, 0x25, 0x61, 0xaf, 0x35, 0x30, 0x86, 0x75, 0xcf, 0x0e,
0x55, 0x42, 0x13, 0x85, 0xba, 0xff, 0x18, 0xd0, 0x11, 0xa1, 0xcd, 0xe9, 0xcd, 0x99, 0xdd, 0x6c,
0xd7, 0xbc, 0xbd, 0xdd, 0xda, 0x2d, 0xec, 0xd6, 0x6f, 0x63, 0x77, 0xe7, 0xba, 0xdd, 0xc7, 0xb0,
0x27, 0xed, 0x06, 0xb4, 0x70, 0xdb, 0x90, 0x6e, 0x3b, 0xa1, 0xb4, 0x96, 0x9b, 0xfd, 0x14, 0x2c,
0xf9, 0x25, 0x49, 0x1a, 0x33, 0x9a, 0x12, 0x64, 0x83, 0x19, 0x6c, 0xa5, 0xd3, 0xb6, 0x67, 0x06,
0x5b, 0xd7, 0x01, 0xfb, 0x98, 0xd1, 0x80, 0xb3, 0x44, 0x87, 0xe1, 0xfe, 0x6b, 0x00, 0x88, 0x2d,
0x2b, 0x8e, 0x79, 0x96, 0xfe, 0x4f, 0x36, 0x4a, 0xc2, 0xcc, 0x25, 0xd0, 0x23, 0xa8, 0xf3, 0x8b,
0x58, 0xd9, 0xb5, 0x8f, 0xba, 0x87, 0xba, 0xba, 0x87, 0x42, 0x64, 0x7d, 0x11, 0x13, 0x4f, 0x2e,
0xa3, 0x21, 0xec, 0xa4, 0x1c, 0x73, 0x55, 0x18, 0xfb, 0x08, 0x55, 0x78, 0xe2, 0x65, 0xc4, 0x53,
0x04, 0xf4, 0x05, 0xec, 0x05, 0x34, 0xe0, 0x81, 0xac, 0xba, 0xcf, 0x83, 0x28, 0x4f, 0xc0, 0xbe,
0x84, 0xd7, 0x41, 0xa4, 0xbe, 0x39, 0x4e, 0xb9, 0x9f, 0xc5, 0x5b, 0xcc, 0x89, 0x62, 0xaa, 0xfe,
0xd8, 0x02, 0x3f, 0x95, 0xb0, 0x64, 0x7e, 0x06, 0xd6, 0x1b, 0x1e, 0x6e, 0x7c, 0xbc, 0xdd, 0x26,
0x24, 0x4d, 0x65, 0x81, 0xda, 0xde, 0xae, 0xc0, 0xc6, 0x0a, 0x72, 0x6d, 0xb0, 0xd6, 0x24, 0x89,
0xd2, 0x3c, 0x87, 0xdf, 0x4c, 0xe8, 0x68, 0x40, 0x67, 0x77, 0x00, 0x5d, 0xf9, 0x6d, 0x63, 0x7c,
0x11, 0x11, 0xca, 0x7d, 0x39, 0x55, 0x2a, 0xca, 0x3d, 0xb1, 0xb0, 0x54, 0xf8, 0x54, 0x54, 0xca,
0x85, 0x4e, 0xde, 0x03, 0xff, 0x15, 0x4e, 0xf3, 0xde, 0xec, 0xa6, 0xaa, 0x09, 0x4f, 0x71, 0x4a,
0x2a, 0x9c, 0x44, 0x24, 0x53, 0xab, 0x70, 0x3c, 0x91, 0xc5, 0x43, 0x80, 0x6b, 0x6d, 0x69, 0xc7,
0x45, 0x55, 0x1e, 0xc3, 0x5e, 0x14, 0x50, 0x55, 0x39, 0x1c, 0xb1, 0x8c, 0x72, 0x1d, 0x55, 0x27,
0x0a, 0xa8, 0x08, 0x76, 0x2c, 0x41, 0xc9, 0xcb, 0xab, 0xa9, 0x79, 0x0d, 0xcd, 0x53, 0xed, 0xd4,
0xbc, 0x87, 0x00, 0x9b, 0x90, 0xbf, 0xf3, 0xb7, 0x24, 0xe4, 0x58, 0xa6, 0xb4, 0xe3, 0xb5, 0x05,
0x32, 0x15, 0x80, 0x3b, 0x00, 0xeb, 0xfb, 0x8c, 0x71, 0x72, 0xe3, 0xe0, 0xb8, 0xaf, 0xa1, 0xa3,
0x19, 0x3a, 0xb4, 0x8f, 0xa0, 0x55, 0x0c, 0x84, 0xe2, 0x35, 0xb5, 0xbf, 0x2b, 0xde, 0xcc, 0xab,
0xde, 0x3e, 0x86, 0xf6, 0xe5, 0x08, 0xa8, 0x68, 0x5a, 0x91, 0xee, 0xff, 0xc1, 0x23, 0x68, 0xe5,
0xfd, 0x42, 0x16, 0xb4, 0x16, 0x27, 0x27, 0x4b, 0xff, 0xe4, 0x74, 0xed, 0xdc, 0x41, 0xbb, 0xd0,
0x94, 0xbf, 0xe6, 0x2f, 0x1d, 0xe3, 0xe0, 0x27, 0x68, 0x17, 0xf5, 0x42, 0x1d, 0x68, 0xcf, 0x5f,
0xce, 0xd7, 0xf3, 0xf1, 0x7a, 0x36, 0x75, 0xee, 0xa0, 0xfb, 0xd0, 0x5d, 0x7a, 0xb3, 0xf9, 0xf1,
0xf8, 0xf9, 0xcc, 0xf7, 0x66, 0x3f, 0xcc, 0xc6, 0x8b, 0xd9, 0xd4, 0x31, 0x10, 0x02, 0xfb, 0xc5,
0x7a, 0x31, 0xf1, 0x97, 0xa7, 0x4f, 0x17, 0xf3, 0xd5, 0x8b, 0xd9, 0xd4, 0x31, 0x85, 0xe6, 0xea,
0x74, 0x32, 0x99, 0xad, 0x56, 0x4e, 0x0d, 0x01, 0x34, 0x9e, 0x8d, 0xe7, 0x82, 0x5c, 0x3f, 0xfa,
0xbd, 0xae, 0x86, 0x65, 0x22, 0x2f, 0x6f, 0xe4, 0x41, 0x53, 0x5f, 0xc7, 0xe8, 0x41, 0xd1, 0xef,
0xea, 0x05, 0xdd, 0xbf, 0x5f, 0x29, 0x7e, 0x9e, 0x93, 0xfb, 0xe0, 0xd7, 0xbf, 0xfe, 0xfe, 0xc3,
0xec, 0xba, 0xd6, 0xe8, 0xdd, 0x57, 0x23, 0xc1, 0x18, 0xb1, 0x8c, 0x7f, 0x6b, 0x1c, 0xa0, 0xaf,
0xa1, 0xa1, 0x6e, 0x2b, 0xb4, 0x5f, 0x91, 0x2c, 0xae, 0xaf, 0x1b, 0x14, 0xd1, 0x37, 0xd0, 0xd4,
0xa3, 0x5d, 0x3a, 0x4c, 0x75, 0xd8, 0xfb, 0x77, 0xaf, 0x4d, 0x61, 0x96, 0x7e, 0x69, 0xa0, 0x1f,
0xc1, 0xd2, 0xa7, 0x96, 0x13, 0x80, 0x2e, 0xdf, 0x50, 0x1e, 0x91, 0xfe, 0xfe, 0x55, 0x58, 0x7b,
0xe9, 0x4b, 0x2f, 0xf7, 0x10, 0x2a, 0x7b, 0x19, 0x71, 0x29, 0xe5, 0x17, 0xd2, 0xb2, 0x27, 0x25,
0xe9, 0x72, 0xb3, 0x4a, 0xd2, 0x95, 0x3a, 0xb9, 0x03, 0x29, 0xdd, 0x47, 0xbd, 0x8a, 0xf4, 0x5b,
0xc1, 0x19, 0xfd, 0x8c, 0x23, 0xfe, 0x0b, 0xfa, 0x0e, 0xec, 0xe7, 0x84, 0xab, 0x84, 0x3e, 0xe4,
0xf4, 0x15, 0x81, 0x0f, 0x39, 0xe3, 0xab, 0x86, 0xfc, 0x73, 0x7e, 0xf2, 0x5f, 0x00, 0x00, 0x00,
0xff, 0xff, 0xc7, 0xd4, 0x7e, 0xf6, 0xd3, 0x07, 0x00, 0x00,
}

@ -22,6 +22,14 @@ service SwapClient {
};
}
/**
LoopIn initiates a loop in 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 SwapStatus stream
that is returned from Monitor().
*/
rpc LoopIn(LoopInRequest) returns (SwapResponse);
/** loop: `monitor`
Monitor will return a stream of swap updates for currently active swaps.
TODO: add MonitorSync version for REST clients.
@ -46,6 +54,16 @@ service SwapClient {
get: "/v1/loop/out/quote/{amt}"
};
}
/**
GetTerms returns the terms that the server enforces for swaps.
*/
rpc GetLoopInTerms(TermsRequest) returns(TermsResponse);
/**
GetQuote returns a quote for a swap with the provided parameters.
*/
rpc GetLoopInQuote(QuoteRequest) returns(QuoteResponse);
}
message LoopOutRequest {
@ -111,6 +129,48 @@ message LoopOutRequest {
uint64 loop_out_channel = 8;
}
message LoopInRequest {
/**
Requested swap amount in sat. This does not include the swap and miner
fee.
*/
int64 amt = 1;
/**
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 GetQuote call.
*/
int64 max_prepay_routing_fee = 2;
/**
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 GetQuote call. It includes the prepay amount.
*/
int64 max_swap_fee = 3;
/**
Maximum amount of the swap fee that may be charged as a prepayment.
*/
int64 max_prepay_amt = 4;
/**
Maximum in on-chain fees that we are willing to spent. If we want to
publish the on-chain htlc and the fee estimate turns out higher than this
value, we cancel the swap.
max_miner_fee is typically taken from the response of the GetQuote call.
*/
int64 max_miner_fee = 5;
/**
The channel to loop in. If zero, the channel to loop in is selected based
on the lowest routing fee for the swap payment from the server.
*/
uint64 loop_in_channel = 6;
}
message SwapResponse {
/**
@ -165,6 +225,9 @@ message SwapStatus {
enum SwapType {
// LOOP_OUT indicates an loop out swap (off-chain to on-chain)
LOOP_OUT = 0;
// LOOP_IN indicates a loop in swap (on-chain to off-chain)
LOOP_IN = 1;
}
enum SwapState {
@ -184,6 +247,12 @@ enum SwapState {
*/
PREIMAGE_REVEALED = 1;
/**
HTLC_PUBLISHED is reached when the htlc tx has been published in a loop in
swap.
*/
HTLC_PUBLISHED = 2;
/**
SUCCESS is the final swap state that is reached when the sweep tx has
the required confirmation depth.
@ -236,11 +305,6 @@ message TermsResponse {
On-chain cltv expiry delta
*/
int32 cltv_delta = 7;
/**
Maximum cltv expiry delta
*/
int32 max_cltv = 8;
}
message QuoteRequest {

@ -165,11 +165,12 @@
"enum": [
"INITIATED",
"PREIMAGE_REVEALED",
"HTLC_PUBLISHED",
"SUCCESS",
"FAILED"
],
"default": "INITIATED",
"description": " - INITIATED: *\nINITIATED is the initial state of a swap. At that point, the initiation\ncall to the server has been made and the payment process has been started\nfor the swap and prepayment invoices.\n - PREIMAGE_REVEALED: *\nPREIMAGE_REVEALED is reached when the sweep tx publication is first\nattempted. From that point on, we should consider the preimage to no\nlonger be secret and we need to do all we can to get the sweep confirmed.\nThis state will mostly coalesce with StateHtlcConfirmed, except in the\ncase where we wait for fees to come down before we sweep.\n - SUCCESS: *\nSUCCESS is the final swap state that is reached when the sweep tx has\nthe required confirmation depth.\n - FAILED: *\nFAILED is the final swap state for a failed swap with or without loss of\nthe swap amount."
"description": " - INITIATED: *\nINITIATED is the initial state of a swap. At that point, the initiation\ncall to the server has been made and the payment process has been started\nfor the swap and prepayment invoices.\n - PREIMAGE_REVEALED: *\nPREIMAGE_REVEALED is reached when the sweep tx publication is first\nattempted. From that point on, we should consider the preimage to no\nlonger be secret and we need to do all we can to get the sweep confirmed.\nThis state will mostly coalesce with StateHtlcConfirmed, except in the\ncase where we wait for fees to come down before we sweep.\n - HTLC_PUBLISHED: *\nHTLC_PUBLISHED is reached when the htlc tx has been published in a loop in\nswap.\n - SUCCESS: *\nSUCCESS is the final swap state that is reached when the sweep tx has\nthe required confirmation depth.\n - FAILED: *\nFAILED is the final swap state for a failed swap with or without loss of\nthe swap amount."
},
"looprpcSwapStatus": {
"type": "object",
@ -210,10 +211,11 @@
"looprpcSwapType": {
"type": "string",
"enum": [
"LOOP_OUT"
"LOOP_OUT",
"LOOP_IN"
],
"default": "LOOP_OUT",
"title": "- LOOP_OUT: LOOP_OUT indicates an loop out swap (off-chain to on-chain)"
"title": "- LOOP_OUT: LOOP_OUT indicates an loop out swap (off-chain to on-chain)\n - LOOP_IN: LOOP_IN indicates a loop in swap (on-chain to off-chain)"
},
"looprpcTermsResponse": {
"type": "object",
@ -251,11 +253,6 @@
"type": "integer",
"format": "int32",
"title": "*\nOn-chain cltv expiry delta"
},
"max_cltv": {
"type": "integer",
"format": "int32",
"title": "*\nMaximum cltv expiry delta"
}
}
}

@ -37,7 +37,7 @@ func (m *ServerLoopOutRequest) Reset() { *m = ServerLoopOutRequest{} }
func (m *ServerLoopOutRequest) String() string { return proto.CompactTextString(m) }
func (*ServerLoopOutRequest) ProtoMessage() {}
func (*ServerLoopOutRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_server_76a33eec530f39a2, []int{0}
return fileDescriptor_server_1af9bb28f4c6b777, []int{0}
}
func (m *ServerLoopOutRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopOutRequest.Unmarshal(m, b)
@ -92,7 +92,7 @@ func (m *ServerLoopOutResponse) Reset() { *m = ServerLoopOutResponse{} }
func (m *ServerLoopOutResponse) String() string { return proto.CompactTextString(m) }
func (*ServerLoopOutResponse) ProtoMessage() {}
func (*ServerLoopOutResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_server_76a33eec530f39a2, []int{1}
return fileDescriptor_server_1af9bb28f4c6b777, []int{1}
}
func (m *ServerLoopOutResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopOutResponse.Unmarshal(m, b)
@ -150,7 +150,7 @@ func (m *ServerLoopOutQuoteRequest) Reset() { *m = ServerLoopOutQuoteReq
func (m *ServerLoopOutQuoteRequest) String() string { return proto.CompactTextString(m) }
func (*ServerLoopOutQuoteRequest) ProtoMessage() {}
func (*ServerLoopOutQuoteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_server_76a33eec530f39a2, []int{2}
return fileDescriptor_server_1af9bb28f4c6b777, []int{2}
}
func (m *ServerLoopOutQuoteRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopOutQuoteRequest.Unmarshal(m, b)
@ -187,7 +187,7 @@ func (m *ServerLoopOutQuote) Reset() { *m = ServerLoopOutQuote{} }
func (m *ServerLoopOutQuote) String() string { return proto.CompactTextString(m) }
func (*ServerLoopOutQuote) ProtoMessage() {}
func (*ServerLoopOutQuote) Descriptor() ([]byte, []int) {
return fileDescriptor_server_76a33eec530f39a2, []int{3}
return fileDescriptor_server_1af9bb28f4c6b777, []int{3}
}
func (m *ServerLoopOutQuote) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopOutQuote.Unmarshal(m, b)
@ -256,11 +256,239 @@ func (m *ServerLoopOutQuote) GetCltvDelta() int32 {
return 0
}
type ServerLoopInRequest struct {
SenderKey []byte `protobuf:"bytes,1,opt,name=sender_key,json=senderKey,proto3" json:"sender_key,omitempty"`
SwapHash []byte `protobuf:"bytes,2,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"`
Amt uint64 `protobuf:"varint,3,opt,name=amt,proto3" json:"amt,omitempty"`
SwapInvoice string `protobuf:"bytes,4,opt,name=swap_invoice,json=swapInvoice,proto3" json:"swap_invoice,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ServerLoopInRequest) Reset() { *m = ServerLoopInRequest{} }
func (m *ServerLoopInRequest) String() string { return proto.CompactTextString(m) }
func (*ServerLoopInRequest) ProtoMessage() {}
func (*ServerLoopInRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_server_1af9bb28f4c6b777, []int{4}
}
func (m *ServerLoopInRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopInRequest.Unmarshal(m, b)
}
func (m *ServerLoopInRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ServerLoopInRequest.Marshal(b, m, deterministic)
}
func (dst *ServerLoopInRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ServerLoopInRequest.Merge(dst, src)
}
func (m *ServerLoopInRequest) XXX_Size() int {
return xxx_messageInfo_ServerLoopInRequest.Size(m)
}
func (m *ServerLoopInRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ServerLoopInRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ServerLoopInRequest proto.InternalMessageInfo
func (m *ServerLoopInRequest) GetSenderKey() []byte {
if m != nil {
return m.SenderKey
}
return nil
}
func (m *ServerLoopInRequest) GetSwapHash() []byte {
if m != nil {
return m.SwapHash
}
return nil
}
func (m *ServerLoopInRequest) GetAmt() uint64 {
if m != nil {
return m.Amt
}
return 0
}
func (m *ServerLoopInRequest) GetSwapInvoice() string {
if m != nil {
return m.SwapInvoice
}
return ""
}
type ServerLoopInResponse struct {
PrepayInvoice string `protobuf:"bytes,1,opt,name=prepay_invoice,json=prepayInvoice,proto3" json:"prepay_invoice,omitempty"`
ReceiverKey []byte `protobuf:"bytes,2,opt,name=receiver_key,json=receiverKey,proto3" json:"receiver_key,omitempty"`
Expiry int32 `protobuf:"varint,3,opt,name=expiry,proto3" json:"expiry,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ServerLoopInResponse) Reset() { *m = ServerLoopInResponse{} }
func (m *ServerLoopInResponse) String() string { return proto.CompactTextString(m) }
func (*ServerLoopInResponse) ProtoMessage() {}
func (*ServerLoopInResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_server_1af9bb28f4c6b777, []int{5}
}
func (m *ServerLoopInResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopInResponse.Unmarshal(m, b)
}
func (m *ServerLoopInResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ServerLoopInResponse.Marshal(b, m, deterministic)
}
func (dst *ServerLoopInResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ServerLoopInResponse.Merge(dst, src)
}
func (m *ServerLoopInResponse) XXX_Size() int {
return xxx_messageInfo_ServerLoopInResponse.Size(m)
}
func (m *ServerLoopInResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ServerLoopInResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ServerLoopInResponse proto.InternalMessageInfo
func (m *ServerLoopInResponse) GetPrepayInvoice() string {
if m != nil {
return m.PrepayInvoice
}
return ""
}
func (m *ServerLoopInResponse) GetReceiverKey() []byte {
if m != nil {
return m.ReceiverKey
}
return nil
}
func (m *ServerLoopInResponse) GetExpiry() int32 {
if m != nil {
return m.Expiry
}
return 0
}
type ServerLoopInQuoteRequest struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ServerLoopInQuoteRequest) Reset() { *m = ServerLoopInQuoteRequest{} }
func (m *ServerLoopInQuoteRequest) String() string { return proto.CompactTextString(m) }
func (*ServerLoopInQuoteRequest) ProtoMessage() {}
func (*ServerLoopInQuoteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_server_1af9bb28f4c6b777, []int{6}
}
func (m *ServerLoopInQuoteRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopInQuoteRequest.Unmarshal(m, b)
}
func (m *ServerLoopInQuoteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ServerLoopInQuoteRequest.Marshal(b, m, deterministic)
}
func (dst *ServerLoopInQuoteRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ServerLoopInQuoteRequest.Merge(dst, src)
}
func (m *ServerLoopInQuoteRequest) XXX_Size() int {
return xxx_messageInfo_ServerLoopInQuoteRequest.Size(m)
}
func (m *ServerLoopInQuoteRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ServerLoopInQuoteRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ServerLoopInQuoteRequest proto.InternalMessageInfo
type ServerLoopInQuoteResponse struct {
SwapFeeBase int64 `protobuf:"varint,1,opt,name=swap_fee_base,json=swapFeeBase,proto3" json:"swap_fee_base,omitempty"`
SwapFeeRate int64 `protobuf:"varint,2,opt,name=swap_fee_rate,json=swapFeeRate,proto3" json:"swap_fee_rate,omitempty"`
PrepayAmt uint64 `protobuf:"varint,3,opt,name=prepay_amt,json=prepayAmt,proto3" json:"prepay_amt,omitempty"`
MinSwapAmount uint64 `protobuf:"varint,4,opt,name=min_swap_amount,json=minSwapAmount,proto3" json:"min_swap_amount,omitempty"`
MaxSwapAmount uint64 `protobuf:"varint,5,opt,name=max_swap_amount,json=maxSwapAmount,proto3" json:"max_swap_amount,omitempty"`
CltvDelta int32 `protobuf:"varint,6,opt,name=cltv_delta,json=cltvDelta,proto3" json:"cltv_delta,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ServerLoopInQuoteResponse) Reset() { *m = ServerLoopInQuoteResponse{} }
func (m *ServerLoopInQuoteResponse) String() string { return proto.CompactTextString(m) }
func (*ServerLoopInQuoteResponse) ProtoMessage() {}
func (*ServerLoopInQuoteResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_server_1af9bb28f4c6b777, []int{7}
}
func (m *ServerLoopInQuoteResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopInQuoteResponse.Unmarshal(m, b)
}
func (m *ServerLoopInQuoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ServerLoopInQuoteResponse.Marshal(b, m, deterministic)
}
func (dst *ServerLoopInQuoteResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ServerLoopInQuoteResponse.Merge(dst, src)
}
func (m *ServerLoopInQuoteResponse) XXX_Size() int {
return xxx_messageInfo_ServerLoopInQuoteResponse.Size(m)
}
func (m *ServerLoopInQuoteResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ServerLoopInQuoteResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ServerLoopInQuoteResponse proto.InternalMessageInfo
func (m *ServerLoopInQuoteResponse) GetSwapFeeBase() int64 {
if m != nil {
return m.SwapFeeBase
}
return 0
}
func (m *ServerLoopInQuoteResponse) GetSwapFeeRate() int64 {
if m != nil {
return m.SwapFeeRate
}
return 0
}
func (m *ServerLoopInQuoteResponse) GetPrepayAmt() uint64 {
if m != nil {
return m.PrepayAmt
}
return 0
}
func (m *ServerLoopInQuoteResponse) GetMinSwapAmount() uint64 {
if m != nil {
return m.MinSwapAmount
}
return 0
}
func (m *ServerLoopInQuoteResponse) GetMaxSwapAmount() uint64 {
if m != nil {
return m.MaxSwapAmount
}
return 0
}
func (m *ServerLoopInQuoteResponse) GetCltvDelta() int32 {
if m != nil {
return m.CltvDelta
}
return 0
}
func init() {
proto.RegisterType((*ServerLoopOutRequest)(nil), "looprpc.ServerLoopOutRequest")
proto.RegisterType((*ServerLoopOutResponse)(nil), "looprpc.ServerLoopOutResponse")
proto.RegisterType((*ServerLoopOutQuoteRequest)(nil), "looprpc.ServerLoopOutQuoteRequest")
proto.RegisterType((*ServerLoopOutQuote)(nil), "looprpc.ServerLoopOutQuote")
proto.RegisterType((*ServerLoopInRequest)(nil), "looprpc.ServerLoopInRequest")
proto.RegisterType((*ServerLoopInResponse)(nil), "looprpc.ServerLoopInResponse")
proto.RegisterType((*ServerLoopInQuoteRequest)(nil), "looprpc.ServerLoopInQuoteRequest")
proto.RegisterType((*ServerLoopInQuoteResponse)(nil), "looprpc.ServerLoopInQuoteResponse")
}
// Reference imports to suppress errors if they are not otherwise used.
@ -277,6 +505,8 @@ const _ = grpc.SupportPackageIsVersion4
type SwapServerClient interface {
NewLoopOutSwap(ctx context.Context, in *ServerLoopOutRequest, opts ...grpc.CallOption) (*ServerLoopOutResponse, error)
LoopOutQuote(ctx context.Context, in *ServerLoopOutQuoteRequest, opts ...grpc.CallOption) (*ServerLoopOutQuote, error)
NewLoopInSwap(ctx context.Context, in *ServerLoopInRequest, opts ...grpc.CallOption) (*ServerLoopInResponse, error)
LoopInQuote(ctx context.Context, in *ServerLoopInQuoteRequest, opts ...grpc.CallOption) (*ServerLoopInQuoteResponse, error)
}
type swapServerClient struct {
@ -305,10 +535,30 @@ func (c *swapServerClient) LoopOutQuote(ctx context.Context, in *ServerLoopOutQu
return out, nil
}
func (c *swapServerClient) NewLoopInSwap(ctx context.Context, in *ServerLoopInRequest, opts ...grpc.CallOption) (*ServerLoopInResponse, error) {
out := new(ServerLoopInResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapServer/NewLoopInSwap", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapServerClient) LoopInQuote(ctx context.Context, in *ServerLoopInQuoteRequest, opts ...grpc.CallOption) (*ServerLoopInQuoteResponse, error) {
out := new(ServerLoopInQuoteResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapServer/LoopInQuote", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SwapServerServer is the server API for SwapServer service.
type SwapServerServer interface {
NewLoopOutSwap(context.Context, *ServerLoopOutRequest) (*ServerLoopOutResponse, error)
LoopOutQuote(context.Context, *ServerLoopOutQuoteRequest) (*ServerLoopOutQuote, error)
NewLoopInSwap(context.Context, *ServerLoopInRequest) (*ServerLoopInResponse, error)
LoopInQuote(context.Context, *ServerLoopInQuoteRequest) (*ServerLoopInQuoteResponse, error)
}
func RegisterSwapServerServer(s *grpc.Server, srv SwapServerServer) {
@ -351,6 +601,42 @@ func _SwapServer_LoopOutQuote_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _SwapServer_NewLoopInSwap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ServerLoopInRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapServerServer).NewLoopInSwap(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapServer/NewLoopInSwap",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapServerServer).NewLoopInSwap(ctx, req.(*ServerLoopInRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SwapServer_LoopInQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ServerLoopInQuoteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapServerServer).LoopInQuote(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapServer/LoopInQuote",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapServerServer).LoopInQuote(ctx, req.(*ServerLoopInQuoteRequest))
}
return interceptor(ctx, in, info, handler)
}
var _SwapServer_serviceDesc = grpc.ServiceDesc{
ServiceName: "looprpc.SwapServer",
HandlerType: (*SwapServerServer)(nil),
@ -363,43 +649,59 @@ var _SwapServer_serviceDesc = grpc.ServiceDesc{
MethodName: "LoopOutQuote",
Handler: _SwapServer_LoopOutQuote_Handler,
},
{
MethodName: "NewLoopInSwap",
Handler: _SwapServer_NewLoopInSwap_Handler,
},
{
MethodName: "LoopInQuote",
Handler: _SwapServer_LoopInQuote_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "server.proto",
}
func init() { proto.RegisterFile("server.proto", fileDescriptor_server_76a33eec530f39a2) }
var fileDescriptor_server_76a33eec530f39a2 = []byte{
// 467 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0xd1, 0x6e, 0xd3, 0x30,
0x14, 0x86, 0x95, 0xb6, 0xeb, 0xe8, 0x59, 0xbb, 0x81, 0x05, 0x28, 0xb4, 0x14, 0x95, 0x48, 0x83,
0x8a, 0x8b, 0x56, 0x82, 0x27, 0xd8, 0x34, 0x21, 0x10, 0x88, 0x42, 0x76, 0xc7, 0x4d, 0x74, 0xda,
0x1e, 0x12, 0x8b, 0xc4, 0x36, 0xb1, 0xd3, 0x36, 0x2f, 0x82, 0x78, 0x0a, 0x9e, 0x11, 0xd9, 0xf1,
0x60, 0x85, 0xb1, 0xbb, 0xe4, 0x3f, 0xdf, 0xf1, 0xf9, 0xfd, 0xe7, 0x04, 0xfa, 0x9a, 0xca, 0x0d,
0x95, 0x33, 0x55, 0x4a, 0x23, 0xd9, 0x61, 0x2e, 0xa5, 0x2a, 0xd5, 0x6a, 0xf8, 0x38, 0x95, 0x32,
0xcd, 0x69, 0x8e, 0x8a, 0xcf, 0x51, 0x08, 0x69, 0xd0, 0x70, 0x29, 0x74, 0x83, 0x45, 0x19, 0xdc,
0xbf, 0x74, 0x6d, 0xef, 0xa5, 0x54, 0x8b, 0xca, 0xc4, 0xf4, 0xad, 0x22, 0x6d, 0xd8, 0x53, 0xe8,
0x97, 0xb4, 0x22, 0xbe, 0xa1, 0x32, 0xf9, 0x4a, 0x75, 0x18, 0x4c, 0x82, 0x69, 0x3f, 0x3e, 0xba,
0xd2, 0xde, 0x51, 0xcd, 0x46, 0xd0, 0xd3, 0x5b, 0x54, 0x49, 0x86, 0x3a, 0x0b, 0x5b, 0xae, 0x7e,
0xc7, 0x0a, 0x6f, 0x50, 0x67, 0xec, 0x2e, 0xb4, 0xb1, 0x30, 0x61, 0x7b, 0x12, 0x4c, 0x3b, 0xb1,
0x7d, 0x8c, 0x7e, 0x04, 0xf0, 0xe0, 0xaf, 0x51, 0x5a, 0x49, 0xa1, 0xc9, 0xce, 0x72, 0x07, 0x71,
0xb1, 0x91, 0x7c, 0x45, 0x6e, 0x56, 0x2f, 0x3e, 0xb2, 0xda, 0xdb, 0x46, 0x62, 0xa7, 0x70, 0xac,
0x4a, 0x52, 0x58, 0xff, 0x86, 0x5a, 0x0e, 0x1a, 0x34, 0xea, 0x15, 0x36, 0x06, 0xd0, 0x24, 0xd6,
0xde, 0x73, 0xdb, 0x79, 0xea, 0x35, 0x8a, 0x75, 0xfc, 0x10, 0xba, 0xb4, 0x53, 0xbc, 0xac, 0xc3,
0xce, 0x24, 0x98, 0x1e, 0xc4, 0xfe, 0x2d, 0x1a, 0xc1, 0xa3, 0x3d, 0x67, 0x9f, 0x2a, 0x69, 0xc8,
0x27, 0x11, 0x7d, 0x6f, 0x01, 0xfb, 0xb7, 0xca, 0x5e, 0xc0, 0x3d, 0x67, 0x5a, 0x61, 0x5d, 0x90,
0x30, 0xc9, 0x9a, 0xb4, 0xf1, 0xce, 0x4f, 0x6c, 0xe1, 0x63, 0xa3, 0x5f, 0xd8, 0x30, 0x23, 0x18,
0x38, 0xf6, 0x0b, 0x51, 0xb2, 0x44, 0xdd, 0x98, 0x6f, 0x37, 0x37, 0x7c, 0x4d, 0x74, 0x8e, 0x9a,
0xf6, 0x98, 0x12, 0x0d, 0x39, 0xf7, 0x7f, 0x98, 0x18, 0x8d, 0xbb, 0x9e, 0x4f, 0xc1, 0x66, 0xdb,
0x71, 0xd9, 0xf6, 0x1a, 0xe5, 0xac, 0x30, 0xec, 0x19, 0x9c, 0x14, 0x5c, 0x24, 0xee, 0x18, 0x2c,
0x64, 0x25, 0x4c, 0x78, 0xe0, 0x98, 0x41, 0xc1, 0xc5, 0xe5, 0x16, 0xd5, 0x99, 0x13, 0x1d, 0x87,
0xbb, 0x3d, 0xae, 0xeb, 0x39, 0xdc, 0x5d, 0xe3, 0xc6, 0x00, 0xab, 0xdc, 0x6c, 0x92, 0x35, 0xe5,
0x06, 0xc3, 0x43, 0x17, 0x59, 0xcf, 0x2a, 0x17, 0x56, 0x78, 0xf9, 0x33, 0x00, 0xb0, 0x74, 0x13,
0x0e, 0x5b, 0xc0, 0xf1, 0x07, 0xda, 0xfa, 0x8c, 0xac, 0xce, 0xc6, 0x33, 0xbf, 0x83, 0xb3, 0x9b,
0x56, 0x6c, 0xf8, 0xe4, 0x7f, 0x65, 0xbf, 0x16, 0x0b, 0xe8, 0xef, 0x25, 0x1e, 0xdd, 0xcc, 0x5f,
0xff, 0x58, 0xc3, 0xd1, 0x2d, 0xcc, 0xf9, 0xf3, 0xcf, 0xa7, 0x29, 0x37, 0x59, 0xb5, 0x9c, 0xad,
0x64, 0x31, 0xcf, 0x79, 0x9a, 0x19, 0xc1, 0x45, 0x9a, 0xe3, 0x52, 0xcf, 0x6d, 0xdb, 0xdc, 0xf7,
0x2e, 0xbb, 0xee, 0xdf, 0x78, 0xf5, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x94, 0x23, 0x1c, 0x6e, 0x52,
0x03, 0x00, 0x00,
func init() { proto.RegisterFile("server.proto", fileDescriptor_server_1af9bb28f4c6b777) }
var fileDescriptor_server_1af9bb28f4c6b777 = []byte{
// 600 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0xdf, 0x6e, 0xd3, 0x30,
0x18, 0xc5, 0x95, 0xa4, 0xeb, 0xe8, 0xb7, 0x76, 0x03, 0xf3, 0x47, 0xa1, 0x5b, 0xd1, 0x16, 0x69,
0x30, 0x71, 0xd1, 0x4a, 0xf0, 0x04, 0x9b, 0x26, 0xc4, 0xc4, 0xc4, 0x20, 0xe3, 0x8a, 0x9b, 0xc8,
0xed, 0x3e, 0x9a, 0x88, 0xc4, 0x36, 0xb1, 0xdb, 0xb5, 0x2f, 0xc0, 0x23, 0x20, 0x9e, 0x8b, 0x37,
0xe1, 0x0d, 0x90, 0x1d, 0x6f, 0x4b, 0x9a, 0x6e, 0x2b, 0x77, 0xed, 0xf9, 0x4e, 0xec, 0x93, 0xe3,
0x5f, 0x0c, 0x6d, 0x89, 0xf9, 0x14, 0xf3, 0xbe, 0xc8, 0xb9, 0xe2, 0x64, 0x3d, 0xe5, 0x5c, 0xe4,
0x62, 0xd4, 0xdd, 0x19, 0x73, 0x3e, 0x4e, 0x71, 0x40, 0x45, 0x32, 0xa0, 0x8c, 0x71, 0x45, 0x55,
0xc2, 0x99, 0x2c, 0x6c, 0x41, 0x0c, 0x4f, 0xce, 0xcd, 0x63, 0xa7, 0x9c, 0x8b, 0xb3, 0x89, 0x0a,
0xf1, 0xc7, 0x04, 0xa5, 0x22, 0x7b, 0xd0, 0xce, 0x71, 0x84, 0xc9, 0x14, 0xf3, 0xe8, 0x3b, 0xce,
0x7d, 0x67, 0xd7, 0x39, 0x68, 0x87, 0x1b, 0x57, 0xda, 0x07, 0x9c, 0x93, 0x6d, 0x68, 0xc9, 0x4b,
0x2a, 0xa2, 0x98, 0xca, 0xd8, 0x77, 0xcd, 0xfc, 0x81, 0x16, 0xde, 0x53, 0x19, 0x93, 0x87, 0xe0,
0xd1, 0x4c, 0xf9, 0xde, 0xae, 0x73, 0xd0, 0x08, 0xf5, 0xcf, 0xe0, 0xb7, 0x03, 0x4f, 0x17, 0xb6,
0x92, 0x82, 0x33, 0x89, 0x7a, 0x2f, 0xb3, 0x50, 0xc2, 0xa6, 0x3c, 0x19, 0xa1, 0xd9, 0xab, 0x15,
0x6e, 0x68, 0xed, 0xa4, 0x90, 0xc8, 0x3e, 0x6c, 0x8a, 0x1c, 0x05, 0x9d, 0x5f, 0x9b, 0x5c, 0x63,
0xea, 0x14, 0xea, 0x95, 0xad, 0x07, 0x20, 0x91, 0x5d, 0xd8, 0xcc, 0x9e, 0xc9, 0xd4, 0x2a, 0x14,
0x9d, 0xf8, 0x19, 0x34, 0x71, 0x26, 0x92, 0x7c, 0xee, 0x37, 0x76, 0x9d, 0x83, 0xb5, 0xd0, 0xfe,
0x0b, 0xb6, 0xe1, 0x79, 0x25, 0xd9, 0xe7, 0x09, 0x57, 0x68, 0x9b, 0x08, 0x7e, 0xb9, 0x40, 0xea,
0x53, 0xf2, 0x1a, 0x1e, 0x99, 0xd0, 0x82, 0xce, 0x33, 0x64, 0x2a, 0xba, 0x40, 0xa9, 0x6c, 0xf2,
0x2d, 0x3d, 0xf8, 0x54, 0xe8, 0xc7, 0xba, 0xcc, 0x00, 0x3a, 0xc6, 0xfb, 0x0d, 0x31, 0x1a, 0x52,
0x59, 0x84, 0xf7, 0x8a, 0x37, 0x7c, 0x87, 0x78, 0x44, 0x25, 0x56, 0x3c, 0x39, 0x55, 0x68, 0xd2,
0xdf, 0x78, 0x42, 0xaa, 0xcc, 0xeb, 0xd9, 0x16, 0x74, 0xb7, 0x0d, 0xd3, 0x6d, 0xab, 0x50, 0x0e,
0x33, 0x45, 0x5e, 0xc2, 0x56, 0x96, 0xb0, 0xc8, 0x2c, 0x43, 0x33, 0x3e, 0x61, 0xca, 0x5f, 0x33,
0x9e, 0x4e, 0x96, 0xb0, 0xf3, 0x4b, 0x2a, 0x0e, 0x8d, 0x68, 0x7c, 0x74, 0x56, 0xf1, 0x35, 0xad,
0x8f, 0xce, 0x4a, 0xbe, 0x1e, 0xc0, 0x28, 0x55, 0xd3, 0xe8, 0x02, 0x53, 0x45, 0xfd, 0x75, 0x53,
0x59, 0x4b, 0x2b, 0xc7, 0x5a, 0x08, 0x7e, 0x3a, 0xf0, 0xf8, 0xa6, 0x98, 0x13, 0x76, 0x85, 0x4e,
0xf5, 0x10, 0x9c, 0xc5, 0x43, 0xf8, 0x3f, 0x6c, 0x6a, 0x70, 0x34, 0x6a, 0x70, 0x04, 0xb3, 0x32,
0xc3, 0x3a, 0x87, 0xe5, 0xaa, 0x0e, 0x8d, 0xb3, 0x0c, 0x9a, 0x45, 0xd4, 0xdd, 0x3a, 0xea, 0x37,
0xe0, 0x78, 0x15, 0x70, 0xba, 0xe0, 0x97, 0x77, 0xae, 0x70, 0xf3, 0xd7, 0x29, 0x53, 0x75, 0x3d,
0xb4, 0xd9, 0x6a, 0x48, 0x38, 0x2b, 0x20, 0xe1, 0xde, 0x87, 0x84, 0xb7, 0x02, 0x12, 0x8d, 0x15,
0x91, 0x58, 0xbb, 0x1f, 0x89, 0xe6, 0x02, 0x12, 0x6f, 0xfe, 0xb8, 0x00, 0xda, 0x5d, 0xbc, 0x37,
0x39, 0x83, 0xcd, 0x8f, 0x78, 0x69, 0x3f, 0x1b, 0xad, 0x93, 0x5e, 0xdf, 0x5e, 0x4b, 0xfd, 0x65,
0xb7, 0x4e, 0xf7, 0xc5, 0x6d, 0x63, 0xdb, 0xda, 0x19, 0xb4, 0x2b, 0x1f, 0x61, 0xb0, 0xdc, 0x5f,
0x3e, 0x87, 0xee, 0xf6, 0x1d, 0x1e, 0x72, 0x0a, 0x1d, 0x9b, 0xf0, 0xc4, 0xd4, 0x41, 0x76, 0x96,
0xb8, 0xaf, 0xd1, 0xee, 0xf6, 0x6e, 0x99, 0xda, 0x78, 0x5f, 0x60, 0xa3, 0x74, 0xd6, 0x64, 0x6f,
0xa9, 0xbb, 0x12, 0x2e, 0xb8, 0xcb, 0x52, 0xac, 0x7a, 0xf4, 0xea, 0xeb, 0xfe, 0x38, 0x51, 0xf1,
0x64, 0xd8, 0x1f, 0xf1, 0x6c, 0x90, 0x26, 0xe3, 0x58, 0xb1, 0x84, 0x8d, 0x53, 0x3a, 0x94, 0x03,
0xfd, 0xf4, 0xc0, 0x2e, 0x31, 0x6c, 0x9a, 0x2b, 0xfd, 0xed, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff,
0x0a, 0x0e, 0xe1, 0xc1, 0x09, 0x06, 0x00, 0x00,
}

@ -10,6 +10,10 @@ service SwapServer {
rpc NewLoopOutSwap(ServerLoopOutRequest) returns (ServerLoopOutResponse);
rpc LoopOutQuote(ServerLoopOutQuoteRequest) returns (ServerLoopOutQuote);
rpc NewLoopInSwap(ServerLoopInRequest) returns (ServerLoopInResponse);
rpc LoopInQuote(ServerLoopInQuoteRequest) returns (ServerLoopInQuoteResponse);
}
message ServerLoopOutRequest {
@ -48,3 +52,28 @@ message ServerLoopOutQuote {
int32 cltv_delta = 7;
}
message ServerLoopInRequest {
bytes sender_key = 1;
bytes swap_hash = 2;
uint64 amt = 3;
string swap_invoice = 4;
}
message ServerLoopInResponse {
string prepay_invoice = 1;
bytes receiver_key = 2;
int32 expiry = 3;
}
message ServerLoopInQuoteRequest {
}
message ServerLoopInQuoteResponse {
int64 swap_fee_base = 1;
int64 swap_fee_rate = 2;
uint64 prepay_amt = 3;
uint64 min_swap_amount = 4;
uint64 max_swap_amount = 5;
int32 cltv_delta = 6;
}

@ -19,6 +19,7 @@ var (
testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
testLoopOutOnChainCltvDelta = int32(30)
testChargeOnChainCltvDelta = int32(100)
testCltvDelta = 50
testSwapFeeBase = btcutil.Amount(21)
testSwapFeeRate = int64(100)
@ -123,3 +124,50 @@ func getInvoice(hash lntypes.Hash, amt btcutil.Amount, memo string) (string, err
return reqString, nil
}
func (s *serverMock) NewLoopInSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount,
senderKey [33]byte, swapInvoice string) (
*newLoopInResponse, error) {
_, receiverKey := test.CreateKey(101)
if amount != s.expectedSwapAmt {
return nil, errors.New("unexpected test swap amount")
}
prepayHash := lntypes.Hash{1, 2, 3}
prePayReqString, err := getInvoice(prepayHash, s.prepayInvoiceAmt,
prepayInvoiceDesc)
if err != nil {
return nil, err
}
var receiverKeyArray [33]byte
copy(receiverKeyArray[:], receiverKey.SerializeCompressed())
s.swapInvoice = swapInvoice
s.swapHash = swapHash
resp := &newLoopInResponse{
expiry: s.height + testChargeOnChainCltvDelta,
receiverKey: receiverKeyArray,
prepayInvoice: prePayReqString,
}
return resp, nil
}
func (s *serverMock) GetLoopInTerms(ctx context.Context) (
*LoopInTerms, error) {
return &LoopInTerms{
SwapFeeBase: testSwapFeeBase,
SwapFeeRate: testSwapFeeRate,
CltvDelta: testChargeOnChainCltvDelta,
MinSwapAmount: testMinSwapAmount,
MaxSwapAmount: testMaxSwapAmount,
PrepayAmt: testFixedPrepayAmount,
}, nil
}

@ -17,6 +17,11 @@ type storeMock struct {
loopOutStoreChan chan loopdb.LoopOutContract
loopOutUpdateChan chan loopdb.SwapState
loopInSwaps map[lntypes.Hash]*loopdb.LoopInContract
loopInUpdates map[lntypes.Hash][]loopdb.SwapState
loopInStoreChan chan loopdb.LoopInContract
loopInUpdateChan chan loopdb.SwapState
t *testing.T
}
@ -33,7 +38,11 @@ func newStoreMock(t *testing.T) *storeMock {
loopOutSwaps: make(map[lntypes.Hash]*loopdb.LoopOutContract),
loopOutUpdates: make(map[lntypes.Hash][]loopdb.SwapState),
t: t,
loopInStoreChan: make(chan loopdb.LoopInContract, 1),
loopInUpdateChan: make(chan loopdb.SwapState, 1),
loopInSwaps: make(map[lntypes.Hash]*loopdb.LoopInContract),
loopInUpdates: make(map[lntypes.Hash][]loopdb.SwapState),
t: t,
}
}
@ -45,17 +54,19 @@ func (s *storeMock) FetchLoopOutSwaps() ([]*loopdb.LoopOut, error) {
for hash, contract := range s.loopOutSwaps {
updates := s.loopOutUpdates[hash]
events := make([]*loopdb.LoopOutEvent, len(updates))
events := make([]*loopdb.LoopEvent, len(updates))
for i, u := range updates {
events[i] = &loopdb.LoopOutEvent{
events[i] = &loopdb.LoopEvent{
State: u,
}
}
swap := &loopdb.LoopOut{
Hash: hash,
Loop: loopdb.Loop{
Hash: hash,
Events: events,
},
Contract: contract,
Events: events,
}
result = append(result, swap)
}
@ -81,6 +92,48 @@ func (s *storeMock) CreateLoopOut(hash lntypes.Hash,
return nil
}
// getChargeSwaps returns all swaps currently in the store.
func (s *storeMock) FetchLoopInSwaps() ([]*loopdb.LoopIn, error) {
result := []*loopdb.LoopIn{}
for hash, contract := range s.loopInSwaps {
updates := s.loopInUpdates[hash]
events := make([]*loopdb.LoopEvent, len(updates))
for i, u := range updates {
events[i] = &loopdb.LoopEvent{
State: u,
}
}
swap := &loopdb.LoopIn{
Loop: loopdb.Loop{
Hash: hash,
Events: events,
},
Contract: contract,
}
result = append(result, swap)
}
return result, nil
}
// createCharge adds an initiated swap to the store.
func (s *storeMock) CreateLoopIn(hash lntypes.Hash,
swap *loopdb.LoopInContract) error {
_, ok := s.loopInSwaps[hash]
if ok {
return errors.New("swap already exists")
}
s.loopInSwaps[hash] = swap
s.loopInUpdates[hash] = []loopdb.SwapState{}
s.loopInStoreChan <- *swap
return nil
}
// 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.
@ -101,6 +154,26 @@ func (s *storeMock) UpdateLoopOut(hash lntypes.Hash, time time.Time,
return nil
}
// UpdateLoopIn stores a new event for a target loop in 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) UpdateLoopIn(hash lntypes.Hash, time time.Time,
state loopdb.SwapState) error {
updates, ok := s.loopInUpdates[hash]
if !ok {
return errors.New("swap does not exists")
}
updates = append(updates, state)
s.loopOutUpdates[hash] = updates
s.loopOutUpdateChan <- state
return nil
}
func (s *storeMock) Close() error {
return nil
}
@ -130,6 +203,21 @@ func (s *storeMock) assertLoopOutStored() {
}
}
func (s *storeMock) assertLoopInStored() {
s.t.Helper()
<-s.loopInStoreChan
}
func (s *storeMock) assertLoopInState(expectedState loopdb.SwapState) {
s.t.Helper()
state := <-s.loopOutUpdateChan
if state != expectedState {
s.t.Fatalf("unexpected state")
}
}
func (s *storeMock) assertStorePreimageReveal() {
s.t.Helper()

@ -20,10 +20,18 @@ type swapServerClient interface {
GetLoopOutTerms(ctx context.Context) (
*LoopOutTerms, error)
GetLoopInTerms(ctx context.Context) (
*LoopInTerms, error)
NewLoopOutSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount,
receiverKey [33]byte) (
*newLoopOutResponse, error)
NewLoopInSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount,
senderKey [33]byte, swapInvoice string) (
*newLoopInResponse, error)
}
type grpcSwapServerClient struct {
@ -80,6 +88,28 @@ func (s *grpcSwapServerClient) GetLoopOutTerms(ctx context.Context) (
}, nil
}
func (s *grpcSwapServerClient) GetLoopInTerms(ctx context.Context) (
*LoopInTerms, error) {
rpcCtx, rpcCancel := context.WithTimeout(ctx, serverRPCTimeout)
defer rpcCancel()
quoteResp, err := s.server.LoopInQuote(rpcCtx,
&looprpc.ServerLoopInQuoteRequest{},
)
if err != nil {
return nil, err
}
return &LoopInTerms{
MinSwapAmount: btcutil.Amount(quoteResp.MinSwapAmount),
MaxSwapAmount: btcutil.Amount(quoteResp.MaxSwapAmount),
PrepayAmt: btcutil.Amount(quoteResp.PrepayAmt),
SwapFeeBase: btcutil.Amount(quoteResp.SwapFeeBase),
SwapFeeRate: quoteResp.SwapFeeRate,
CltvDelta: quoteResp.CltvDelta,
}, nil
}
func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount,
receiverKey [33]byte) (*newLoopOutResponse, error) {
@ -114,6 +144,40 @@ func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
}, nil
}
func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, senderKey [33]byte,
swapInvoice string) (*newLoopInResponse, error) {
rpcCtx, rpcCancel := context.WithTimeout(ctx, serverRPCTimeout)
defer rpcCancel()
swapResp, err := s.server.NewLoopInSwap(rpcCtx,
&looprpc.ServerLoopInRequest{
SwapHash: swapHash[:],
Amt: uint64(amount),
SenderKey: senderKey[:],
SwapInvoice: swapInvoice,
},
)
if err != nil {
return nil, err
}
var receiverKey [33]byte
copy(receiverKey[:], swapResp.ReceiverKey)
// Validate receiver key.
_, err = btcec.ParsePubKey(receiverKey[:], btcec.S256())
if err != nil {
return nil, fmt.Errorf("invalid sender key: %v", err)
}
return &newLoopInResponse{
prepayInvoice: swapResp.PrepayInvoice,
receiverKey: receiverKey,
expiry: swapResp.Expiry,
}, nil
}
func (s *grpcSwapServerClient) Close() {
s.conn.Close()
}
@ -143,3 +207,9 @@ type newLoopOutResponse struct {
senderKey [33]byte
expiry int32
}
type newLoopInResponse struct {
prepayInvoice string
receiverKey [33]byte
expiry int32
}

@ -226,15 +226,4 @@ func (ctx *Context) NotifyServerHeight(height int32) {
if err := ctx.Lnd.NotifyHeight(height); err != nil {
ctx.T.Fatal(err)
}
// TODO: Fix race condition with height not processed yet.
// select {
// case h := <-ctx.swapServer.testEpochChan:
// if h != height {
// ctx.T.Fatal("height not set")
// }
// case <-time.After(test.Timeout):
// ctx.T.Fatal("no height response")
// }
}

@ -29,7 +29,7 @@ func (h *mockLightningClient) PayInvoice(ctx context.Context, invoice string,
done := make(chan lndclient.PaymentResult, 1)
mockChan := make(chan error)
mockChan := make(chan error, 1)
h.wg.Add(1)
go func() {
defer h.wg.Done()

@ -117,10 +117,12 @@ func createClientTestContext(t *testing.T,
ctx.stop = stop
go func() {
ctx.runErr <- swapClient.Run(
err := swapClient.Run(
runCtx,
statusChan,
)
logger.Errorf("client run: %v", err)
ctx.runErr <- err
}()
return ctx

Loading…
Cancel
Save