Browse Source

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 8 months ago
parent
commit
1877b7f08b
No known key found for this signature in database GPG Key ID: 4CA7FE54A6213C91
7 changed files with 70 additions and 12 deletions
  1. +8
    -3
      client_test.go
  2. +4
    -0
      loopdb/loopout.go
  3. +41
    -0
      loopdb/store.go
  4. +1
    -0
      loopdb/store_test.go
  5. +5
    -4
      loopout.go
  6. +4
    -4
      loopout_test.go
  7. +7
    -1
      test/context.go

+ 8
- 3
client_test.go View File

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

+ 4
- 0
loopdb/loopout.go View File

@ -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

+ 41
- 0
loopdb/store.go View File

@ -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 {
@ -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
@ -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)

+ 1
- 0
loopdb/store_test.go View File

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

+ 5
- 4
loopout.go View File

@ -147,6 +147,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
DestAddr: request.DestAddr,
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
SweepConfTarget: request.SweepConfTarget,
HtlcConfirmations: loopdb.DefaultLoopOutHtlcConfirmations,
PrepayInvoice: swapResp.prepayInvoice,
MaxPrepayRoutingFee: request.MaxPrepayRoutingFee,
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
// 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
@ -624,8 +625,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

+ 4
- 4
loopout_test.go View File

@ -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.
@ -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)
@ -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
@ -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

+ 7
- 1
test/context.go View File

@ -12,6 +12,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/require"
)
// 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.
func (ctx *Context) AssertRegisterConf(expectTxHash bool) *ConfRegistration {
func (ctx *Context) AssertRegisterConf(expectTxHash bool, confs int32) *ConfRegistration {
ctx.T.Helper()
// Expect client to register for conf
@ -127,6 +128,11 @@ func (ctx *Context) AssertRegisterConf(expectTxHash bool) *ConfRegistration {
case !expectTxHash && confIntent.TxID != nil:
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):
ctx.T.Fatalf("htlc confirmed not subscribed to")
}

Loading…
Cancel
Save