Skip to content

Commit

Permalink
Merge pull request #258 from carlaKC/65-confirmationparam
Browse files Browse the repository at this point in the history
loopout: allow per-swap confirmation targets for server HTLC
  • Loading branch information
carlaKC committed Aug 5, 2020
2 parents e15549e + 13449fb commit a240c69
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 133 deletions.
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{
Name: "htlc_confs",
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

0 comments on commit a240c69

Please sign in to comment.