Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loopout: allow per-swap confirmation targets for server HTLC #258

Merged
merged 3 commits into from Aug 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 26 additions & 10 deletions client_test.go
Expand Up @@ -27,6 +27,7 @@ var (
Amount: btcutil.Amount(50000),
DestAddr: testAddr,
MaxMinerFee: 50000,
HtlcConfirmations: defaultConfirmations,
SweepConfTarget: 2,
MaxSwapFee: 1050,
MaxPrepayAmount: 100,
Expand All @@ -36,16 +37,22 @@ var (

swapInvoiceDesc = "swap"
prepayInvoiceDesc = "prepay"

defaultConfirmations = int32(loopdb.DefaultLoopOutHtlcConfirmations)
)

// TestSuccess tests the loop out happy flow.
// TestSuccess tests the loop out happy flow, using a custom htlc confirmation
// target.
func TestSuccess(t *testing.T) {
defer test.Guard(t)()

ctx := createClientTestContext(t, nil)

req := *testRequest
req.HtlcConfirmations = 2

// Initiate loop out.
info, err := ctx.swapClient.LoopOut(context.Background(), testRequest)
info, err := ctx.swapClient.LoopOut(context.Background(), &req)
if err != nil {
t.Fatal(err)
}
Expand All @@ -57,7 +64,7 @@ func TestSuccess(t *testing.T) {
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)

// Expect client to register for conf.
confIntent := ctx.AssertRegisterConf(false)
confIntent := ctx.AssertRegisterConf(false, req.HtlcConfirmations)

testSuccess(ctx, testRequest.Amount, info.SwapHash,
signalPrepaymentResult, signalSwapPaymentResult, false,
Expand All @@ -83,7 +90,7 @@ func TestFailOffchain(t *testing.T) {
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)

ctx.AssertRegisterConf(false)
ctx.AssertRegisterConf(false, defaultConfirmations)

signalSwapPaymentResult(
errors.New(lndclient.PaymentResultUnknownPaymentHash),
Expand Down Expand Up @@ -141,18 +148,25 @@ func TestFailWrongAmount(t *testing.T) {
func TestResume(t *testing.T) {
defer test.Guard(t)()

defaultConfs := loopdb.DefaultLoopOutHtlcConfirmations

t.Run("not expired", func(t *testing.T) {
testResume(t, false, false, true)
testResume(t, defaultConfs, false, false, true)
})
t.Run("not expired, custom confirmations", func(t *testing.T) {
testResume(t, 3, false, false, true)
})
t.Run("expired not revealed", func(t *testing.T) {
testResume(t, true, false, false)
testResume(t, defaultConfs, true, false, false)
})
t.Run("expired revealed", func(t *testing.T) {
testResume(t, true, true, true)
testResume(t, defaultConfs, true, true, true)
})
}

func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
func testResume(t *testing.T, confs uint32, expired, preimageRevealed,
expectSuccess bool) {

defer test.Guard(t)()

preimage := testPreimage
Expand Down Expand Up @@ -191,11 +205,13 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
update.HtlcTxHash = &chainhash.Hash{1, 2, 6}
}

// Create a pending swap with our custom number of confirmations.
pendingSwap := &loopdb.LoopOut{
Contract: &loopdb.LoopOutContract{
DestAddr: dest,
SwapInvoice: swapPayReq,
SweepConfTarget: 2,
HtlcConfirmations: confs,
MaxSwapRoutingFee: 70000,
PrepayInvoice: prePayReq,
SwapContract: loopdb.SwapContract{
Expand Down Expand Up @@ -231,8 +247,8 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)

// Expect client to register for conf.
confIntent := ctx.AssertRegisterConf(preimageRevealed)
// Expect client to register for our expected number of confirmations.
confIntent := ctx.AssertRegisterConf(preimageRevealed, int32(confs))

signalSwapPaymentResult(nil)
signalPrepaymentResult(nil)
Expand Down
14 changes: 14 additions & 0 deletions cmd/loop/loopout.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/labels"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/looprpc"
"github.com/urfave/cli"
)
Expand Down Expand Up @@ -42,6 +43,13 @@ var loopOutCommand = cli.Command{
Name: "amt",
Usage: "the amount in satoshis to loop out",
},
cli.Uint64Flag{
carlaKC marked this conversation as resolved.
Show resolved Hide resolved
Name: "htlc_confs",
carlaKC marked this conversation as resolved.
Show resolved Hide resolved
Usage: "the number of of confirmations, in blocks " +
"that we require for the htlc extended by " +
"the server before we reveal the preimage.",
Value: uint64(loopdb.DefaultLoopOutHtlcConfirmations),
},
cli.Uint64Flag{
Name: "conf_target",
Usage: "the number of blocks from the swap " +
Expand Down Expand Up @@ -135,6 +143,11 @@ func loopOut(ctx *cli.Context) error {
}

sweepConfTarget := int32(ctx.Uint64("conf_target"))
htlcConfs := int32(ctx.Uint64("htlc_confs"))
if htlcConfs == 0 {
return fmt.Errorf("at least 1 confirmation required for htlcs")
}

quoteReq := &looprpc.QuoteRequest{
Amt: int64(amt),
ConfTarget: sweepConfTarget,
Expand Down Expand Up @@ -179,6 +192,7 @@ func loopOut(ctx *cli.Context) error {
MaxSwapRoutingFee: int64(limits.maxSwapRoutingFee),
OutgoingChanSet: outgoingChanSet,
SweepConfTarget: sweepConfTarget,
HtlcConfirmations: htlcConfs,
SwapPublicationDeadline: uint64(swapDeadline.Unix()),
Label: label,
})
Expand Down
4 changes: 4 additions & 0 deletions interface.go
Expand Up @@ -64,6 +64,10 @@ type OutRequest struct {
// client sweep tx.
SweepConfTarget int32

// HtlcConfirmations specifies the number of confirmations we require
// for on chain loop out htlcs.
HtlcConfirmations int32

// OutgoingChanSet optionally specifies the short channel ids of the
// channels that may be used to loop out.
OutgoingChanSet loopdb.ChannelSet
Expand Down
1 change: 1 addition & 0 deletions loopd/swapclient_server.go
Expand Up @@ -86,6 +86,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
MaxSwapRoutingFee: btcutil.Amount(in.MaxSwapRoutingFee),
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
SweepConfTarget: sweepConfTarget,
HtlcConfirmations: in.HtlcConfirmations,
SwapPublicationDeadline: time.Unix(
int64(in.SwapPublicationDeadline), 0,
),
Expand Down
4 changes: 4 additions & 0 deletions loopdb/loopout.go
Expand Up @@ -36,6 +36,10 @@ type LoopOutContract struct {
// client sweep tx.
SweepConfTarget int32

// HtlcConfirmations is the number of confirmations we require the on
// chain htlc to have before proceeding with the swap.
HtlcConfirmations uint32

// OutgoingChanSet is the set of short ids of channels that may be used.
// If empty, any channel may be used.
OutgoingChanSet ChannelSet
Expand Down
41 changes: 41 additions & 0 deletions loopdb/store.go
Expand Up @@ -77,11 +77,23 @@ var (
// value: concatenation of uint64 channel ids
outgoingChanSetKey = []byte("outgoing-chan-set")

// confirmationsKey is the key that stores the number of confirmations
// that were requested for a loop out swap.
//
// path: loopOutBucket -> swapBucket[hash] -> confirmationsKey
//
// value: uint32 confirmation value
confirmationsKey = []byte("confirmations")

byteOrder = binary.BigEndian

keyLength = 33
)

// DefaultLoopOutHtlcConfirmations is the default number of confirmations we
// set for a loop out htlc.
const DefaultLoopOutHtlcConfirmations uint32 = 1

// fileExists returns true if the file exists, and false otherwise.
func fileExists(path string) bool {
if _, err := os.Stat(path); err != nil {
Expand Down Expand Up @@ -242,6 +254,23 @@ func (s *boltSwapStore) FetchLoopOutSwaps() ([]*LoopOut, error) {
}
}

// Set our default number of confirmations for the swap.
contract.HtlcConfirmations = DefaultLoopOutHtlcConfirmations

// If we have the number of confirmations stored for
// this swap, we overwrite our default with the stored
// value.
confBytes := swapBucket.Get(confirmationsKey)
if confBytes != nil {
r := bytes.NewReader(confBytes)
err := binary.Read(
r, byteOrder, &contract.HtlcConfirmations,
)
if err != nil {
return err
}
}

updates, err := deserializeUpdates(swapBucket)
if err != nil {
return err
Expand Down Expand Up @@ -471,6 +500,18 @@ func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
return err
}

// Write our confirmation target under its own key.
var buf bytes.Buffer
err = binary.Write(&buf, byteOrder, swap.HtlcConfirmations)
if err != nil {
return err
}

err = swapBucket.Put(confirmationsKey, buf.Bytes())
if err != nil {
return err
}

// Finally, we'll create an empty updates bucket for this swap
// to track any future updates to the swap itself.
_, err = swapBucket.CreateBucket(updatesBucketKey)
Expand Down
1 change: 1 addition & 0 deletions loopdb/store_test.go
Expand Up @@ -70,6 +70,7 @@ func TestLoopOutStore(t *testing.T) {
SwapInvoice: "swapinvoice",
MaxSwapRoutingFee: 30,
SweepConfTarget: 2,
HtlcConfirmations: 2,
SwapPublicationDeadline: time.Unix(0, initiationTime.UnixNano()),
}

Expand Down
18 changes: 14 additions & 4 deletions loopout.go
Expand Up @@ -138,6 +138,15 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
return nil, err
}

// If a htlc confirmation target was not provided, we use the default
// number of confirmations. We overwrite this value rather than failing
// it because the field is a new addition to the rpc, and we don't want
// to break older clients that are not aware of this new field.
confs := uint32(request.HtlcConfirmations)
if confs == 0 {
confs = loopdb.DefaultLoopOutHtlcConfirmations
}

// Instantiate a struct that contains all required data to start the
// swap.
initiationTime := time.Now()
Expand All @@ -147,6 +156,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
DestAddr: request.DestAddr,
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
SweepConfTarget: request.SweepConfTarget,
HtlcConfirmations: confs,
PrepayInvoice: swapResp.prepayInvoice,
MaxPrepayRoutingFee: request.MaxPrepayRoutingFee,
SwapPublicationDeadline: request.SwapPublicationDeadline,
Expand Down Expand Up @@ -606,8 +616,8 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
// Wait for confirmation of the on-chain htlc by watching for a tx
// producing the swap script output.
s.log.Infof(
"Register conf ntfn for swap script on chain (hh=%v)",
s.InitiationHeight,
"Register %v conf ntfn for swap script on chain (hh=%v)",
s.HtlcConfirmations, s.InitiationHeight,
)

// If we've revealed the preimage in a previous run, we expect to have
Expand All @@ -624,8 +634,8 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
defer cancel()
htlcConfChan, htlcErrChan, err :=
s.lnd.ChainNotifier.RegisterConfirmationsNtfn(
ctx, s.htlcTxHash, s.htlc.PkScript, 1,
s.InitiationHeight,
ctx, s.htlcTxHash, s.htlc.PkScript,
int32(s.HtlcConfirmations), s.InitiationHeight,
)
if err != nil {
return nil, err
Expand Down
8 changes: 4 additions & 4 deletions loopout_test.go
Expand Up @@ -116,7 +116,7 @@ func TestLoopOutPaymentParameters(t *testing.T) {

// Swap is expected to register for confirmation of the htlc. Assert
// this to prevent a blocked channel in the mock.
ctx.AssertRegisterConf(false)
ctx.AssertRegisterConf(false, defaultConfirmations)

// Cancel the swap. There is nothing else we need to assert. The payment
// parameters don't play a role in the remainder of the swap process.
Expand Down Expand Up @@ -191,7 +191,7 @@ func TestLateHtlcPublish(t *testing.T) {
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)

// Expect client to register for conf
ctx.AssertRegisterConf(false)
ctx.AssertRegisterConf(false, defaultConfirmations)

// // Wait too long before publishing htlc.
blockEpochChan <- int32(swap.CltvExpiry - 10)
Expand Down Expand Up @@ -290,7 +290,7 @@ func TestCustomSweepConfTarget(t *testing.T) {
signalPrepaymentResult(nil)

// Notify the confirmation notification for the HTLC.
ctx.AssertRegisterConf(false)
ctx.AssertRegisterConf(false, defaultConfirmations)

blockEpochChan <- ctx.Lnd.Height + 1

Expand Down Expand Up @@ -494,7 +494,7 @@ func TestPreimagePush(t *testing.T) {
signalPrepaymentResult(nil)

// Notify the confirmation notification for the HTLC.
ctx.AssertRegisterConf(false)
ctx.AssertRegisterConf(false, defaultConfirmations)

blockEpochChan <- ctx.Lnd.Height + 1

Expand Down