multi: store loop out htlc confirmations on disk

To allow users to specify differing confirmation targets, we store the
swap conf target per-swap. This makes us restart safe, so we do not
forget confirmation values for swaps that are in flight when we restart.
pull/258/head
carla 4 years ago
parent e15549e9af
commit 1877b7f08b
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91

@ -36,6 +36,8 @@ var (
swapInvoiceDesc = "swap" swapInvoiceDesc = "swap"
prepayInvoiceDesc = "prepay" prepayInvoiceDesc = "prepay"
defaultConfirmations = int32(loopdb.DefaultLoopOutHtlcConfirmations)
) )
// TestSuccess tests the loop out happy flow. // TestSuccess tests the loop out happy flow.
@ -57,7 +59,7 @@ func TestSuccess(t *testing.T) {
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc) signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
// Expect client to register for conf. // Expect client to register for conf.
confIntent := ctx.AssertRegisterConf(false) confIntent := ctx.AssertRegisterConf(false, defaultConfirmations)
testSuccess(ctx, testRequest.Amount, info.SwapHash, testSuccess(ctx, testRequest.Amount, info.SwapHash,
signalPrepaymentResult, signalSwapPaymentResult, false, signalPrepaymentResult, signalSwapPaymentResult, false,
@ -83,7 +85,7 @@ func TestFailOffchain(t *testing.T) {
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc) signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc) signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
ctx.AssertRegisterConf(false) ctx.AssertRegisterConf(false, defaultConfirmations)
signalSwapPaymentResult( signalSwapPaymentResult(
errors.New(lndclient.PaymentResultUnknownPaymentHash), errors.New(lndclient.PaymentResultUnknownPaymentHash),
@ -196,6 +198,7 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
DestAddr: dest, DestAddr: dest,
SwapInvoice: swapPayReq, SwapInvoice: swapPayReq,
SweepConfTarget: 2, SweepConfTarget: 2,
HtlcConfirmations: loopdb.DefaultLoopOutHtlcConfirmations,
MaxSwapRoutingFee: 70000, MaxSwapRoutingFee: 70000,
PrepayInvoice: prePayReq, PrepayInvoice: prePayReq,
SwapContract: loopdb.SwapContract{ SwapContract: loopdb.SwapContract{
@ -232,7 +235,9 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc) signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
// Expect client to register for conf. // Expect client to register for conf.
confIntent := ctx.AssertRegisterConf(preimageRevealed) confIntent := ctx.AssertRegisterConf(
preimageRevealed, defaultConfirmations,
)
signalSwapPaymentResult(nil) signalSwapPaymentResult(nil)
signalPrepaymentResult(nil) signalPrepaymentResult(nil)

@ -36,6 +36,10 @@ type LoopOutContract struct {
// client sweep tx. // client sweep tx.
SweepConfTarget int32 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. // OutgoingChanSet is the set of short ids of channels that may be used.
// If empty, any channel may be used. // If empty, any channel may be used.
OutgoingChanSet ChannelSet OutgoingChanSet ChannelSet

@ -77,11 +77,23 @@ var (
// value: concatenation of uint64 channel ids // value: concatenation of uint64 channel ids
outgoingChanSetKey = []byte("outgoing-chan-set") 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 byteOrder = binary.BigEndian
keyLength = 33 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. // fileExists returns true if the file exists, and false otherwise.
func fileExists(path string) bool { func fileExists(path string) bool {
if _, err := os.Stat(path); err != nil { if _, err := os.Stat(path); err != nil {
@ -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) updates, err := deserializeUpdates(swapBucket)
if err != nil { if err != nil {
return err return err
@ -471,6 +500,18 @@ func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
return err 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 // Finally, we'll create an empty updates bucket for this swap
// to track any future updates to the swap itself. // to track any future updates to the swap itself.
_, err = swapBucket.CreateBucket(updatesBucketKey) _, err = swapBucket.CreateBucket(updatesBucketKey)

@ -70,6 +70,7 @@ func TestLoopOutStore(t *testing.T) {
SwapInvoice: "swapinvoice", SwapInvoice: "swapinvoice",
MaxSwapRoutingFee: 30, MaxSwapRoutingFee: 30,
SweepConfTarget: 2, SweepConfTarget: 2,
HtlcConfirmations: 2,
SwapPublicationDeadline: time.Unix(0, initiationTime.UnixNano()), SwapPublicationDeadline: time.Unix(0, initiationTime.UnixNano()),
} }

@ -147,6 +147,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
DestAddr: request.DestAddr, DestAddr: request.DestAddr,
MaxSwapRoutingFee: request.MaxSwapRoutingFee, MaxSwapRoutingFee: request.MaxSwapRoutingFee,
SweepConfTarget: request.SweepConfTarget, SweepConfTarget: request.SweepConfTarget,
HtlcConfirmations: loopdb.DefaultLoopOutHtlcConfirmations,
PrepayInvoice: swapResp.prepayInvoice, PrepayInvoice: swapResp.prepayInvoice,
MaxPrepayRoutingFee: request.MaxPrepayRoutingFee, MaxPrepayRoutingFee: request.MaxPrepayRoutingFee,
SwapPublicationDeadline: request.SwapPublicationDeadline, SwapPublicationDeadline: request.SwapPublicationDeadline,
@ -606,8 +607,8 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
// Wait for confirmation of the on-chain htlc by watching for a tx // Wait for confirmation of the on-chain htlc by watching for a tx
// producing the swap script output. // producing the swap script output.
s.log.Infof( s.log.Infof(
"Register conf ntfn for swap script on chain (hh=%v)", "Register %v conf ntfn for swap script on chain (hh=%v)",
s.InitiationHeight, s.HtlcConfirmations, s.InitiationHeight,
) )
// If we've revealed the preimage in a previous run, we expect to have // If we've revealed the preimage in a previous run, we expect to have
@ -624,8 +625,8 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
defer cancel() defer cancel()
htlcConfChan, htlcErrChan, err := htlcConfChan, htlcErrChan, err :=
s.lnd.ChainNotifier.RegisterConfirmationsNtfn( s.lnd.ChainNotifier.RegisterConfirmationsNtfn(
ctx, s.htlcTxHash, s.htlc.PkScript, 1, ctx, s.htlcTxHash, s.htlc.PkScript,
s.InitiationHeight, int32(s.HtlcConfirmations), s.InitiationHeight,
) )
if err != nil { if err != nil {
return nil, err return nil, err

@ -116,7 +116,7 @@ func TestLoopOutPaymentParameters(t *testing.T) {
// Swap is expected to register for confirmation of the htlc. Assert // Swap is expected to register for confirmation of the htlc. Assert
// this to prevent a blocked channel in the mock. // 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 // 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. // parameters don't play a role in the remainder of the swap process.
@ -191,7 +191,7 @@ func TestLateHtlcPublish(t *testing.T) {
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc) signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
// Expect client to register for conf // Expect client to register for conf
ctx.AssertRegisterConf(false) ctx.AssertRegisterConf(false, defaultConfirmations)
// // Wait too long before publishing htlc. // // Wait too long before publishing htlc.
blockEpochChan <- int32(swap.CltvExpiry - 10) blockEpochChan <- int32(swap.CltvExpiry - 10)
@ -290,7 +290,7 @@ func TestCustomSweepConfTarget(t *testing.T) {
signalPrepaymentResult(nil) signalPrepaymentResult(nil)
// Notify the confirmation notification for the HTLC. // Notify the confirmation notification for the HTLC.
ctx.AssertRegisterConf(false) ctx.AssertRegisterConf(false, defaultConfirmations)
blockEpochChan <- ctx.Lnd.Height + 1 blockEpochChan <- ctx.Lnd.Height + 1
@ -494,7 +494,7 @@ func TestPreimagePush(t *testing.T) {
signalPrepaymentResult(nil) signalPrepaymentResult(nil)
// Notify the confirmation notification for the HTLC. // Notify the confirmation notification for the HTLC.
ctx.AssertRegisterConf(false) ctx.AssertRegisterConf(false, defaultConfirmations)
blockEpochChan <- ctx.Lnd.Height + 1 blockEpochChan <- ctx.Lnd.Height + 1

@ -12,6 +12,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/zpay32" "github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/require"
) )
// Context contains shared test context functions. // Context contains shared test context functions.
@ -113,7 +114,7 @@ func (ctx *Context) AssertTrackPayment() TrackPaymentMessage {
} }
// AssertRegisterConf asserts that a register for conf has been received. // AssertRegisterConf asserts that a register for conf has been received.
func (ctx *Context) AssertRegisterConf(expectTxHash bool) *ConfRegistration { func (ctx *Context) AssertRegisterConf(expectTxHash bool, confs int32) *ConfRegistration {
ctx.T.Helper() ctx.T.Helper()
// Expect client to register for conf // Expect client to register for conf
@ -127,6 +128,11 @@ func (ctx *Context) AssertRegisterConf(expectTxHash bool) *ConfRegistration {
case !expectTxHash && confIntent.TxID != nil: case !expectTxHash && confIntent.TxID != nil:
ctx.T.Fatalf("expected script only registration") ctx.T.Fatalf("expected script only registration")
} }
// Require that we registered for the number of confirmations
// the test expects.
require.Equal(ctx.T, confs, confIntent.NumConfs)
case <-time.After(Timeout): case <-time.After(Timeout):
ctx.T.Fatalf("htlc confirmed not subscribed to") ctx.T.Fatalf("htlc confirmed not subscribed to")
} }

Loading…
Cancel
Save