From 9c1e6941c367c372f45ca6e7578a1d4bb7e8b0cc Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 29 Mar 2024 22:59:24 +0800 Subject: [PATCH] contractcourt: specify deadline and budget for nursery --- contractcourt/chain_arbitrator.go | 3 +- contractcourt/channel_arbitrator_test.go | 2 +- contractcourt/htlc_success_resolver.go | 2 +- contractcourt/htlc_success_resolver_test.go | 2 +- contractcourt/htlc_timeout_resolver.go | 2 +- contractcourt/htlc_timeout_resolver_test.go | 2 +- contractcourt/utxonursery.go | 62 +++++++++++++++++---- contractcourt/utxonursery_test.go | 10 ++-- server.go | 6 +- 9 files changed, 68 insertions(+), 23 deletions(-) diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index d475ef1f1..e9f68c66a 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -122,7 +122,8 @@ type ChainArbitratorConfig struct { // absolute/relative item block. IncubateOutputs func(wire.OutPoint, fn.Option[lnwallet.OutgoingHtlcResolution], - fn.Option[lnwallet.IncomingHtlcResolution], uint32) error + fn.Option[lnwallet.IncomingHtlcResolution], + uint32, fn.Option[int32]) error // PreimageDB is a global store of all known pre-images. We'll use this // to decide if we should broadcast a commitment transaction to claim diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 6d9ab95e8..a68e9d6b3 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -364,7 +364,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog, IncubateOutputs: func(wire.OutPoint, fn.Option[lnwallet.OutgoingHtlcResolution], fn.Option[lnwallet.IncomingHtlcResolution], - uint32) error { + uint32, fn.Option[int32]) error { incubateChan <- struct{}{} return nil diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index 5fb1cc543..9483938b3 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -208,7 +208,7 @@ func (h *htlcSuccessResolver) broadcastSuccessTx() (*wire.OutPoint, error) { err := h.IncubateOutputs( h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](), fn.Some(h.htlcResolution), - h.broadcastHeight, + h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)), ) if err != nil { return nil, err diff --git a/contractcourt/htlc_success_resolver_test.go b/contractcourt/htlc_success_resolver_test.go index 39b593e0f..0160dfb52 100644 --- a/contractcourt/htlc_success_resolver_test.go +++ b/contractcourt/htlc_success_resolver_test.go @@ -69,7 +69,7 @@ func newHtlcResolverTestContext(t *testing.T, IncubateOutputs: func(wire.OutPoint, fn.Option[lnwallet.OutgoingHtlcResolution], fn.Option[lnwallet.IncomingHtlcResolution], - uint32) error { + uint32, fn.Option[int32]) error { return nil }, diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index d99c650c8..0b749039d 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -547,7 +547,7 @@ func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error { err := h.IncubateOutputs( h.ChanPoint, fn.Some(h.htlcResolution), fn.None[lnwallet.IncomingHtlcResolution](), - h.broadcastHeight, + h.broadcastHeight, h.incomingHTLCExpiryHeight, ) if err != nil { return err diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go index 1b7bb9243..2d639a82f 100644 --- a/contractcourt/htlc_timeout_resolver_test.go +++ b/contractcourt/htlc_timeout_resolver_test.go @@ -288,7 +288,7 @@ func TestHtlcTimeoutResolver(t *testing.T) { IncubateOutputs: func(wire.OutPoint, fn.Option[lnwallet.OutgoingHtlcResolution], fn.Option[lnwallet.IncomingHtlcResolution], - uint32) error { + uint32, fn.Option[int32]) error { incubateChan <- struct{}{} return nil diff --git a/contractcourt/utxonursery.go b/contractcourt/utxonursery.go index e558d438d..f419881da 100644 --- a/contractcourt/utxonursery.go +++ b/contractcourt/utxonursery.go @@ -205,6 +205,9 @@ type NurseryConfig struct { // Sweep sweeps an input back to the wallet. SweepInput func(input.Input, sweep.Params) (chan sweep.Result, error) + + // Budget is the configured budget for the nursery. + Budget *BudgetConfig } // UtxoNursery is a system dedicated to incubating time-locked outputs created @@ -336,7 +339,7 @@ func (u *UtxoNursery) Stop() error { func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, outgoingHtlc fn.Option[lnwallet.OutgoingHtlcResolution], incomingHtlc fn.Option[lnwallet.IncomingHtlcResolution], - broadcastHeight uint32) error { + broadcastHeight uint32, deadlineHeight fn.Option[int32]) error { // Add to wait group because nursery might shut down during execution of // this function. Otherwise it could happen that nursery thinks it is @@ -386,7 +389,7 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, htlcOutput := makeKidOutput( &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay, - witType, &htlcRes.SweepSignDesc, 0, + witType, &htlcRes.SweepSignDesc, 0, deadlineHeight, ) if htlcOutput.Amount() > 0 { @@ -403,7 +406,9 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, // a baby output as we need to go to the second level to sweep // it. if htlcRes.SignedTimeoutTx != nil { - htlcOutput := makeBabyOutput(&chanPoint, &htlcRes) + htlcOutput := makeBabyOutput( + &chanPoint, &htlcRes, deadlineHeight, + ) if htlcOutput.Amount() > 0 { babyOutputs = append(babyOutputs, htlcOutput) @@ -434,6 +439,7 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, htlcOutput := makeKidOutput( &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay, witType, &htlcRes.SweepSignDesc, htlcRes.Expiry, + deadlineHeight, ) kidOutputs = append(kidOutputs, htlcOutput) }) @@ -815,6 +821,32 @@ func (u *UtxoNursery) graduateClass(classHeight uint32) error { return nil } +// decideDeadlineAndBudget returns the deadline and budget for a given output. +func (u *UtxoNursery) decideDeadlineAndBudget(k kidOutput) (fn.Option[int32], + btcutil.Amount) { + + // Assume this is a to_local output and use a None deadline. + deadline := fn.None[int32]() + + // Exit early if this is not HTLC. + if !k.isHtlc { + budget := calculateBudget( + k.amt, u.cfg.Budget.ToLocalRatio, u.cfg.Budget.ToLocal, + ) + + return deadline, budget + } + + // Otherwise it's the first-level HTLC output, we'll use the + // time-sensitive settings for it. + budget := calculateBudget( + k.amt, u.cfg.Budget.DeadlineHTLCRatio, + u.cfg.Budget.DeadlineHTLC, + ) + + return k.deadlineHeight, budget +} + // sweepMatureOutputs generates and broadcasts the transaction that transfers // control of funds from a prior channel commitment transaction to the user's // wallet. The outputs swept were previously time locked (either absolute or @@ -825,15 +857,17 @@ func (u *UtxoNursery) sweepMatureOutputs(classHeight uint32, utxnLog.Infof("Sweeping %v CSV-delayed outputs with sweep tx for "+ "height %v", len(kgtnOutputs), classHeight) - feePref := sweep.FeeEstimateInfo{ConfTarget: kgtnOutputConfTarget} for _, output := range kgtnOutputs { // Create local copy to prevent pointer to loop variable to be // passed in with disastrous consequences. local := output + // Calculate the deadline height and budget for this output. + deadline, budget := u.decideDeadlineAndBudget(local) + resultChan, err := u.cfg.SweepInput(&local, sweep.Params{ - Fee: feePref, - Force: true, + DeadlineHeight: deadline, + Budget: budget, }) if err != nil { return err @@ -1270,7 +1304,8 @@ type babyOutput struct { // provided sign descriptors and witness types will be used once the output // reaches the delay and claim stage. func makeBabyOutput(chanPoint *wire.OutPoint, - htlcResolution *lnwallet.OutgoingHtlcResolution) babyOutput { + htlcResolution *lnwallet.OutgoingHtlcResolution, + deadlineHeight fn.Option[int32]) babyOutput { htlcOutpoint := htlcResolution.ClaimOutpoint blocksToMaturity := htlcResolution.CsvDelay @@ -1288,7 +1323,7 @@ func makeBabyOutput(chanPoint *wire.OutPoint, kid := makeKidOutput( &htlcOutpoint, chanPoint, blocksToMaturity, witnessType, - &htlcResolution.SweepSignDesc, 0, + &htlcResolution.SweepSignDesc, 0, deadlineHeight, ) return babyOutput{ @@ -1361,12 +1396,18 @@ type kidOutput struct { // NOTE: This will only be set for: outgoing HTLC's on the commitment // transaction of the remote party. absoluteMaturity uint32 + + // deadlineHeight is the absolute height that this output should be + // confirmed at. For an incoming HTLC, this is the CLTV expiry height. + // For outgoing HTLC, this is its corresponding incoming HTLC's CLTV + // expiry height. + deadlineHeight fn.Option[int32] } func makeKidOutput(outpoint, originChanPoint *wire.OutPoint, blocksToMaturity uint32, witnessType input.StandardWitnessType, - signDescriptor *input.SignDescriptor, - absoluteMaturity uint32) kidOutput { + signDescriptor *input.SignDescriptor, absoluteMaturity uint32, + deadlineHeight fn.Option[int32]) kidOutput { // This is an HTLC either if it's an incoming HTLC on our commitment // transaction, or is an outgoing HTLC on the commitment transaction of @@ -1389,6 +1430,7 @@ func makeKidOutput(outpoint, originChanPoint *wire.OutPoint, originChanPoint: *originChanPoint, blocksToMaturity: blocksToMaturity, absoluteMaturity: absoluteMaturity, + deadlineHeight: deadlineHeight, } } diff --git a/contractcourt/utxonursery_test.go b/contractcourt/utxonursery_test.go index e617bbc6f..796d1ed23 100644 --- a/contractcourt/utxonursery_test.go +++ b/contractcourt/utxonursery_test.go @@ -464,6 +464,7 @@ func createNurseryTestContext(t *testing.T, PublishTransaction: func(tx *wire.MsgTx, _ string) error { return publishFunc(tx, "nursery") }, + Budget: DefaultBudgetConfig(), } nursery := NewUtxoNursery(&nurseryCfg) @@ -628,9 +629,8 @@ func incubateTestOutput(t *testing.T, nursery *UtxoNursery, // Hand off to nursery. err := nursery.IncubateOutputs( - testChanPoint, - fn.Some(*outgoingRes), - fn.None[lnwallet.IncomingHtlcResolution](), 0, + testChanPoint, fn.Some(*outgoingRes), + fn.None[lnwallet.IncomingHtlcResolution](), 0, fn.None[int32](), ) if err != nil { t.Fatal(err) @@ -710,9 +710,9 @@ func TestRejectedCribTransaction(t *testing.T) { // Hand off to nursery. err := ctx.nursery.IncubateOutputs( - testChanPoint, - fn.Some(*outgoingRes), + testChanPoint, fn.Some(*outgoingRes), fn.None[lnwallet.IncomingHtlcResolution](), 0, + fn.None[int32](), ) if test.expectErr { require.ErrorIs(t, err, test.broadcastErr) diff --git a/server.go b/server.go index 32aa42a5f..d8f4850f3 100644 --- a/server.go +++ b/server.go @@ -1099,6 +1099,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, PublishTransaction: cc.Wallet.PublishTransaction, Store: utxnStore, SweepInput: s.sweeper.SweepInput, + Budget: s.cfg.Sweeper.Budget, }) // Construct a closure that wraps the htlcswitch's CloseLink method. @@ -1151,11 +1152,12 @@ func newServer(cfg *Config, listenAddrs []net.Addr, IncubateOutputs: func(chanPoint wire.OutPoint, outHtlcRes fn.Option[lnwallet.OutgoingHtlcResolution], inHtlcRes fn.Option[lnwallet.IncomingHtlcResolution], - broadcastHeight uint32) error { + broadcastHeight uint32, + deadlineHeight fn.Option[int32]) error { return s.utxoNursery.IncubateOutputs( chanPoint, outHtlcRes, inHtlcRes, - broadcastHeight, + broadcastHeight, deadlineHeight, ) }, PreimageDB: s.witnessBeacon,