diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index 556fba2d4..23871fb78 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -566,6 +566,21 @@ func (c *ChainArbitrator) UpdateContractSignals(chanPoint wire.OutPoint, return nil } +// GetChannelArbitrator safely returns the channel arbitrator for a given +// channel outpoint. +func (c *ChainArbitrator) GetChannelArbitrator(chanPoint wire.OutPoint) ( + *ChannelArbitrator, error) { + + c.Lock() + arbitrator, ok := c.activeChannels[chanPoint] + c.Unlock() + if !ok { + return nil, fmt.Errorf("unable to find arbitrator") + } + + return arbitrator, nil +} + // forceCloseReq is a request sent from an outside sub-system to the arbitrator // that watches a particular channel to broadcast the commitment transaction, // and enter the resolution phase of the channel. diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 154821cb1..bc47ebe59 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -1,11 +1,13 @@ package contractcourt import ( + "bytes" "errors" "sync" "sync/atomic" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -132,6 +134,36 @@ type ChannelArbitratorConfig struct { ChainArbitratorConfig } +// ContractReport provides a summary of a commitment tx output. +type ContractReport struct { + // Outpoint is the final output that will be swept back to the wallet. + Outpoint wire.OutPoint + + // Incoming indicates whether the htlc was incoming to this channel. + Incoming bool + + // Amount is the final value that will be swept in back to the wallet. + Amount btcutil.Amount + + // MaturityHeight is the absolute block height that this output will + // mature at. + MaturityHeight uint32 + + // Stage indicates whether the htlc is in the CLTV-timeout stage (1) or + // the CSV-delay stage (2). A stage 1 htlc's maturity height will be set + // to its expiry height, while a stage 2 htlc's maturity height will be + // set to its confirmation height plus the maturity requirement. + Stage uint32 + + // LimboBalance is the total number of frozen coins within this + // contract. + LimboBalance btcutil.Amount + + // RecoveredBalance is the total value that has been successfully swept + // back to the user's wallet. + RecoveredBalance btcutil.Amount +} + // htlcSet represents the set of active HTLCs on a given commitment // transaction. type htlcSet struct { @@ -202,6 +234,10 @@ type ChannelArbitrator struct { // be able to signal them for shutdown in the case that we shutdown. activeResolvers []ContractResolver + // activeResolversLock prevents simultaneous read and write to the + // resolvers slice. + activeResolversLock sync.RWMutex + // resolutionSignal is a channel that will be sent upon by contract // resolvers once their contract has been fully resolved. With each // send, we'll check to see if the contract is fully resolved. @@ -461,6 +497,33 @@ func supplementTimeoutResolver(r *htlcTimeoutResolver, return nil } +// Report returns htlc reports for the active resolvers. +func (c *ChannelArbitrator) Report() []*ContractReport { + c.activeResolversLock.RLock() + defer c.activeResolversLock.RUnlock() + + var reports []*ContractReport + for _, resolver := range c.activeResolvers { + r, ok := resolver.(reportingContractResolver) + if !ok { + continue + } + + if r.IsResolved() { + continue + } + + report := r.report() + if report == nil { + continue + } + + reports = append(reports, report) + } + + return reports +} + // Stop signals the ChannelArbitrator for a graceful shutdown. func (c *ChannelArbitrator) Stop() error { if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) { @@ -473,9 +536,11 @@ func (c *ChannelArbitrator) Stop() error { go c.cfg.ChainEvents.Cancel() } + c.activeResolversLock.RLock() for _, activeResolver := range c.activeResolvers { activeResolver.Stop() } + c.activeResolversLock.RUnlock() close(c.quit) c.wg.Wait() @@ -816,7 +881,19 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, log.Infof("ChannelArbitrator(%v): still awaiting contract "+ "resolution", c.cfg.ChanPoint) - nextState = StateWaitingFullResolution + numUnresolved, err := c.log.FetchUnresolvedContracts() + if err != nil { + return StateError, closeTx, err + } + + // If we still have unresolved contracts, then we'll stay alive + // to oversee their resolution. + if len(numUnresolved) != 0 { + nextState = StateWaitingFullResolution + break + } + + nextState = StateFullyResolved // If we start as fully resolved, then we'll end as fully resolved. case StateFullyResolved: @@ -842,6 +919,9 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, // launchResolvers updates the activeResolvers list and starts the resolvers. func (c *ChannelArbitrator) launchResolvers(resolvers []ContractResolver) { + c.activeResolversLock.Lock() + defer c.activeResolversLock.Unlock() + c.activeResolvers = resolvers for _, contract := range resolvers { c.wg.Add(1) @@ -1361,6 +1441,25 @@ func (c *ChannelArbitrator) prepContractResolutions(htlcActions ChainActionMap, return htlcResolvers, msgsToSend, nil } +// replaceResolver replaces a in the list of active resolvers. If the resolver +// to be replaced is not found, it returns an error. +func (c *ChannelArbitrator) replaceResolver(oldResolver, + newResolver ContractResolver) error { + + c.activeResolversLock.Lock() + defer c.activeResolversLock.Unlock() + + oldKey := oldResolver.ResolverKey() + for i, r := range c.activeResolvers { + if bytes.Equal(r.ResolverKey(), oldKey) { + c.activeResolvers[i] = newResolver + return nil + } + } + + return errors.New("resolver to be replaced not found") +} + // resolveContract is a goroutine tasked with fully resolving an unresolved // contract. Either the initial contract will be resolved after a single step, // or the contract will itself create another contract to be resolved. In @@ -1410,6 +1509,7 @@ func (c *ChannelArbitrator) resolveContract(currentContract ContractResolver) { c.cfg.ChanPoint, currentContract, nextContract) + // Swap contract in log. err := c.log.SwapContract( currentContract, nextContract, ) @@ -1418,6 +1518,17 @@ func (c *ChannelArbitrator) resolveContract(currentContract ContractResolver) { "contract: %v", err) } + // Swap contract in resolvers list. This is to + // make sure that reports are queried from the + // new resolver. + err = c.replaceResolver( + currentContract, nextContract, + ) + if err != nil { + log.Errorf("unable to replace "+ + "contract: %v", err) + } + // As this contract produced another, we'll // re-assign, so we can continue our resolution // loop. @@ -1722,29 +1833,22 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { log.Infof("ChannelArbitrator(%v): a contract has been "+ "fully resolved!", c.cfg.ChanPoint) - numUnresolved, err := c.log.FetchUnresolvedContracts() + nextState, _, err := c.advanceState( + uint32(bestHeight), chainTrigger, + ) if err != nil { - log.Errorf("unable to query resolved "+ - "contracts: %v", err) + log.Errorf("unable to advance state: %v", err) } - // If we still have unresolved contracts, then we'll - // stay alive to oversee their resolution. - if len(numUnresolved) != 0 { - continue - } + // If we don't have anything further to do after + // advancing our state, then we'll exit. + if nextState == StateFullyResolved { + log.Infof("ChannelArbitrator(%v): all "+ + "contracts fully resolved, exiting", + c.cfg.ChanPoint) - log.Infof("ChannelArbitrator(%v): all contracts fully "+ - "resolved, exiting", c.cfg.ChanPoint) - - // Otherwise, our job is finished here, the contract is - // now fully resolved! We'll mark it as such, then exit - // ourselves. - if err := c.cfg.MarkChannelResolved(); err != nil { - log.Errorf("unable to mark contract "+ - "resolved: %v", err) + return } - return // We've just received a request to forcibly close out the // channel. We'll diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 4518fac05..3a0d68a51 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -611,10 +611,7 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { notifier.spendChan <- &chainntnfs.SpendDetail{} // At this point channel should be marked as resolved. - - // It should transition StateWaitingFullResolution -> - // StateFullyResolved, but this isn't happening. - // assertStateTransitions(t, arbLog.newStates, StateFullyResolved) + assertStateTransitions(t, arbLog.newStates, StateFullyResolved) select { case <-resolved: diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go index 5b836ad2d..1a19e166a 100644 --- a/contractcourt/contract_resolvers.go +++ b/contractcourt/contract_resolvers.go @@ -60,6 +60,14 @@ type ContractResolver interface { Stop() } +// reportingContractResolver is a ContractResolver that also exposes a report on +// the resolution state of the contract. +type reportingContractResolver interface { + ContractResolver + + report() *ContractReport +} + // ResolverKit is meant to be used as a mix-in struct to be embedded within a // given ContractResolver implementation. It contains all the items that a // resolver requires to carry out its duties. diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index 56d25327b..ec6762289 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -6,6 +6,8 @@ import ( "encoding/binary" "fmt" "io" + + "github.com/btcsuite/btcutil" ) // htlcIncomingContestResolver is a ContractResolver that's able to resolve an @@ -166,6 +168,27 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { } } +// report returns a report on the resolution state of the contract. +func (h *htlcIncomingContestResolver) report() *ContractReport { + // No locking needed as these values are read-only. + + finalAmt := h.htlcAmt.ToSatoshis() + if h.htlcResolution.SignedSuccessTx != nil { + finalAmt = btcutil.Amount( + h.htlcResolution.SignedSuccessTx.TxOut[0].Value, + ) + } + + return &ContractReport{ + Outpoint: h.htlcResolution.ClaimOutpoint, + Incoming: true, + Amount: finalAmt, + MaturityHeight: h.htlcExpiry, + LimboBalance: finalAmt, + Stage: 1, + } +} + // Stop signals the resolver to cancel any current resolution processes, and // suspend. // diff --git a/contractcourt/htlc_outgoing_contest_resolver.go b/contractcourt/htlc_outgoing_contest_resolver.go index 826631ddb..6075b3799 100644 --- a/contractcourt/htlc_outgoing_contest_resolver.go +++ b/contractcourt/htlc_outgoing_contest_resolver.go @@ -6,6 +6,7 @@ import ( "io" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" ) @@ -108,6 +109,9 @@ func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) { scriptToWatch []byte err error ) + + // TODO(joostjager): output already set properly in + // lnwallet.newOutgoingHtlcResolution? And script too? if h.htlcResolution.SignedTimeoutTx == nil { outPointToWatch = h.htlcResolution.ClaimOutpoint scriptToWatch = h.htlcResolution.SweepSignDesc.Output.PkScript @@ -235,6 +239,27 @@ func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) { } } +// report returns a report on the resolution state of the contract. +func (h *htlcOutgoingContestResolver) report() *ContractReport { + // No locking needed as these values are read-only. + + finalAmt := h.htlcAmt.ToSatoshis() + if h.htlcResolution.SignedTimeoutTx != nil { + finalAmt = btcutil.Amount( + h.htlcResolution.SignedTimeoutTx.TxOut[0].Value, + ) + } + + return &ContractReport{ + Outpoint: h.htlcResolution.ClaimOutpoint, + Incoming: false, + Amount: finalAmt, + MaturityHeight: h.htlcResolution.Expiry, + LimboBalance: finalAmt, + Stage: 1, + } +} + // Stop signals the resolver to cancel any current resolution processes, and // suspend. // diff --git a/lnd_test.go b/lnd_test.go index 65de40be9..07025a611 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -2803,6 +2803,12 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { assertTxInBlock(t, block, sweepTx.Hash()) + // Update current height + _, curHeight, err = net.Miner.Node.GetBestBlock() + if err != nil { + t.Fatalf("unable to get best block height") + } + err = lntest.WaitPredicate(func() bool { // Now that the commit output has been fully swept, check to see // that the channel remains open for the pending htlc outputs. @@ -2824,21 +2830,25 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { // The commitment funds will have been recovered after the // commit txn was included in the last block. The htlc funds - // will not be shown in limbo, since they are still in their - // first stage and the nursery hasn't received them from the - // contract court. + // will be shown in limbo. forceClose, err := findForceClosedChannel(pendingChanResp, &op) if err != nil { predErr = err return false } - predErr = checkPendingChannelNumHtlcs(forceClose, 0) + predErr = checkPendingChannelNumHtlcs(forceClose, numInvoices) if predErr != nil { return false } - if forceClose.LimboBalance != 0 { - predErr = fmt.Errorf("expected 0 funds in limbo, "+ - "found %d", forceClose.LimboBalance) + predErr = checkPendingHtlcStageAndMaturity( + forceClose, 1, htlcExpiryHeight, + int32(htlcExpiryHeight)-curHeight, + ) + if predErr != nil { + return false + } + if forceClose.LimboBalance == 0 { + predErr = fmt.Errorf("expected funds in limbo, found 0") return false } diff --git a/rpcserver.go b/rpcserver.go index 5c8bfa856..8f9e347a6 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2063,58 +2063,27 @@ func (r *rpcServer) PendingChannels(ctx context.Context, ClosingTxid: closeTXID, } - // Query for the maturity state for this force closed - // channel. If we didn't have any time-locked outputs, - // then the nursery may not know of the contract. - nurseryInfo, err := r.server.utxoNursery.NurseryReport(&chanPoint) - if err != nil && err != ErrContractNotFound { - return nil, fmt.Errorf("unable to obtain "+ - "nursery report for ChannelPoint(%v): %v", - chanPoint, err) + // Fetch reports from both nursery and resolvers. At the + // moment this is not an atomic snapshot. This is + // planned to be resolved when the nursery is removed + // and channel arbitrator will be the single source for + // these kind of reports. + err := r.nurseryPopulateForceCloseResp( + &chanPoint, currentHeight, forceClose, + ) + if err != nil { + return nil, err } - // If the nursery knows of this channel, then we can - // populate information detailing exactly how much - // funds are time locked and also the height in which - // we can ultimately sweep the funds into the wallet. - if nurseryInfo != nil { - forceClose.LimboBalance = int64(nurseryInfo.limboBalance) - forceClose.RecoveredBalance = int64(nurseryInfo.recoveredBalance) - forceClose.MaturityHeight = nurseryInfo.maturityHeight - - // If the transaction has been confirmed, then - // we can compute how many blocks it has left. - if forceClose.MaturityHeight != 0 { - forceClose.BlocksTilMaturity = - int32(forceClose.MaturityHeight) - - currentHeight - } - - for _, htlcReport := range nurseryInfo.htlcs { - // TODO(conner) set incoming flag - // appropriately after handling incoming - // incubation - htlc := &lnrpc.PendingHTLC{ - Incoming: false, - Amount: int64(htlcReport.amount), - Outpoint: htlcReport.outpoint.String(), - MaturityHeight: htlcReport.maturityHeight, - Stage: htlcReport.stage, - } - - if htlc.MaturityHeight != 0 { - htlc.BlocksTilMaturity = - int32(htlc.MaturityHeight) - - currentHeight - } - - forceClose.PendingHtlcs = append(forceClose.PendingHtlcs, - htlc) - } - - resp.TotalLimboBalance += int64(nurseryInfo.limboBalance) + err = r.arbitratorPopulateForceCloseResp( + &chanPoint, currentHeight, forceClose, + ) + if err != nil { + return nil, err } + resp.TotalLimboBalance += int64(forceClose.LimboBalance) + resp.PendingForceClosingChannels = append( resp.PendingForceClosingChannels, forceClose, @@ -2158,6 +2127,101 @@ func (r *rpcServer) PendingChannels(ctx context.Context, return resp, nil } +// arbitratorPopulateForceCloseResp populates the pending channels response +// message with channel resolution information from the contract resolvers. +func (r *rpcServer) arbitratorPopulateForceCloseResp(chanPoint *wire.OutPoint, + currentHeight int32, + forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel) error { + + // Query for contract resolvers state. + arbitrator, err := r.server.chainArb.GetChannelArbitrator(*chanPoint) + if err != nil { + return err + } + reports := arbitrator.Report() + + for _, report := range reports { + htlc := &lnrpc.PendingHTLC{ + Incoming: report.Incoming, + Amount: int64(report.Amount), + Outpoint: report.Outpoint.String(), + MaturityHeight: report.MaturityHeight, + Stage: report.Stage, + } + + if htlc.MaturityHeight != 0 { + htlc.BlocksTilMaturity = + int32(htlc.MaturityHeight) - currentHeight + } + + forceClose.LimboBalance += int64(report.LimboBalance) + forceClose.RecoveredBalance += int64(report.RecoveredBalance) + + forceClose.PendingHtlcs = append(forceClose.PendingHtlcs, htlc) + } + + return nil +} + +// nurseryPopulateForceCloseResp populates the pending channels response +// message with contract resolution information from utxonursery. +func (r *rpcServer) nurseryPopulateForceCloseResp(chanPoint *wire.OutPoint, + currentHeight int32, + forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel) error { + + // Query for the maturity state for this force closed channel. If we + // didn't have any time-locked outputs, then the nursery may not know of + // the contract. + nurseryInfo, err := r.server.utxoNursery.NurseryReport(chanPoint) + if err == ErrContractNotFound { + return nil + } + if err != nil { + return fmt.Errorf("unable to obtain "+ + "nursery report for ChannelPoint(%v): %v", + chanPoint, err) + } + + // If the nursery knows of this channel, then we can populate + // information detailing exactly how much funds are time locked and also + // the height in which we can ultimately sweep the funds into the + // wallet. + forceClose.LimboBalance = int64(nurseryInfo.limboBalance) + forceClose.RecoveredBalance = int64(nurseryInfo.recoveredBalance) + forceClose.MaturityHeight = nurseryInfo.maturityHeight + + // If the transaction has been confirmed, then we can compute how many + // blocks it has left. + if forceClose.MaturityHeight != 0 { + forceClose.BlocksTilMaturity = + int32(forceClose.MaturityHeight) - + currentHeight + } + + for _, htlcReport := range nurseryInfo.htlcs { + // TODO(conner) set incoming flag appropriately after handling + // incoming incubation + htlc := &lnrpc.PendingHTLC{ + Incoming: false, + Amount: int64(htlcReport.amount), + Outpoint: htlcReport.outpoint.String(), + MaturityHeight: htlcReport.maturityHeight, + Stage: htlcReport.stage, + } + + if htlc.MaturityHeight != 0 { + htlc.BlocksTilMaturity = + int32(htlc.MaturityHeight) - + currentHeight + } + + forceClose.PendingHtlcs = append(forceClose.PendingHtlcs, + htlc) + } + + return nil +} + // ClosedChannels returns a list of all the channels have been closed. // This does not include channels that are still in the process of closing. func (r *rpcServer) ClosedChannels(ctx context.Context, diff --git a/utxonursery.go b/utxonursery.go index 20e0f0c23..260685586 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -494,9 +494,7 @@ func (u *utxoNursery) NurseryReport( utxnLog.Infof("NurseryReport: building nursery report for channel %v", chanPoint) - report := &contractMaturityReport{ - chanPoint: *chanPoint, - } + report := &contractMaturityReport{} if err := u.cfg.Store.ForChanOutputs(chanPoint, func(k, v []byte) error { switch { @@ -536,11 +534,18 @@ func (u *utxoNursery) NurseryReport( case input.CommitmentTimeLock: report.AddLimboCommitment(&kid) - // An HTLC output on our commitment transaction - // where the second-layer transaction hasn't - // yet confirmed. case input.HtlcAcceptedSuccessSecondLevel: + // An HTLC output on our commitment transaction + // where the second-layer transaction hasn't + // yet confirmed. report.AddLimboStage1SuccessHtlc(&kid) + + case input.HtlcOfferedRemoteTimeout: + // This is an HTLC output on the + // commitment transaction of the remote + // party. We are waiting for the CLTV + // timelock expire. + report.AddLimboDirectHtlc(&kid) } case bytes.HasPrefix(k, kndrPrefix): @@ -1051,10 +1056,6 @@ func (u *utxoNursery) waitForPreschoolConf(kid *kidOutput, // contractMaturityReport is a report that details the maturity progress of a // particular force closed contract. type contractMaturityReport struct { - // chanPoint is the channel point of the original contract that is now - // awaiting maturity within the utxoNursery. - chanPoint wire.OutPoint - // limboBalance is the total number of frozen coins within this // contract. limboBalance btcutil.Amount @@ -1063,16 +1064,6 @@ type contractMaturityReport struct { // back to the user's wallet. recoveredBalance btcutil.Amount - // localAmount is the local value of the commitment output. - localAmount btcutil.Amount - - // confHeight is the block height that this output originally confirmed. - confHeight uint32 - - // maturityRequirement is the input age required for this output to - // reach maturity. - maturityRequirement uint32 - // maturityHeight is the absolute block height that this output will // mature at. maturityHeight uint32 @@ -1090,13 +1081,6 @@ type htlcMaturityReport struct { // amount is the final value that will be swept in back to the wallet. amount btcutil.Amount - // confHeight is the block height that this output originally confirmed. - confHeight uint32 - - // maturityRequirement is the input age required for this output to - // reach maturity. - maturityRequirement uint32 - // maturityHeight is the absolute block height that this output will // mature at. maturityHeight uint32 @@ -1113,10 +1097,6 @@ type htlcMaturityReport struct { func (c *contractMaturityReport) AddLimboCommitment(kid *kidOutput) { c.limboBalance += kid.Amount() - c.localAmount += kid.Amount() - c.confHeight = kid.ConfHeight() - c.maturityRequirement = kid.BlocksToMaturity() - // If the confirmation height is set, then this means the contract has // been confirmed, and we know the final maturity height. if kid.ConfHeight() != 0 { @@ -1129,9 +1109,6 @@ func (c *contractMaturityReport) AddLimboCommitment(kid *kidOutput) { func (c *contractMaturityReport) AddRecoveredCommitment(kid *kidOutput) { c.recoveredBalance += kid.Amount() - c.localAmount += kid.Amount() - c.confHeight = kid.ConfHeight() - c.maturityRequirement = kid.BlocksToMaturity() c.maturityHeight = kid.BlocksToMaturity() + kid.ConfHeight() } @@ -1144,7 +1121,6 @@ func (c *contractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) { c.htlcs = append(c.htlcs, htlcMaturityReport{ outpoint: *baby.OutPoint(), amount: baby.Amount(), - confHeight: baby.ConfHeight(), maturityHeight: baby.expiry, stage: 1, }) @@ -1152,14 +1128,13 @@ func (c *contractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) { // AddLimboDirectHtlc adds a direct HTLC on the commitment transaction of the // remote party to the maturity report. This a CLTV time-locked output that -// hasn't yet expired. +// has or hasn't expired yet. func (c *contractMaturityReport) AddLimboDirectHtlc(kid *kidOutput) { c.limboBalance += kid.Amount() htlcReport := htlcMaturityReport{ outpoint: *kid.OutPoint(), amount: kid.Amount(), - confHeight: kid.ConfHeight(), maturityHeight: kid.absoluteMaturity, stage: 2, } @@ -1174,11 +1149,9 @@ func (c *contractMaturityReport) AddLimboStage1SuccessHtlc(kid *kidOutput) { c.limboBalance += kid.Amount() c.htlcs = append(c.htlcs, htlcMaturityReport{ - outpoint: *kid.OutPoint(), - amount: kid.Amount(), - confHeight: kid.ConfHeight(), - maturityRequirement: kid.BlocksToMaturity(), - stage: 1, + outpoint: *kid.OutPoint(), + amount: kid.Amount(), + stage: 1, }) } @@ -1188,11 +1161,9 @@ func (c *contractMaturityReport) AddLimboStage2Htlc(kid *kidOutput) { c.limboBalance += kid.Amount() htlcReport := htlcMaturityReport{ - outpoint: *kid.OutPoint(), - amount: kid.Amount(), - confHeight: kid.ConfHeight(), - maturityRequirement: kid.BlocksToMaturity(), - stage: 2, + outpoint: *kid.OutPoint(), + amount: kid.Amount(), + stage: 2, } // If the confirmation height is set, then this means the first stage @@ -1211,11 +1182,9 @@ func (c *contractMaturityReport) AddRecoveredHtlc(kid *kidOutput) { c.recoveredBalance += kid.Amount() c.htlcs = append(c.htlcs, htlcMaturityReport{ - outpoint: *kid.OutPoint(), - amount: kid.Amount(), - confHeight: kid.ConfHeight(), - maturityRequirement: kid.BlocksToMaturity(), - maturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(), + outpoint: *kid.OutPoint(), + amount: kid.Amount(), + maturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(), }) } diff --git a/utxonursery_test.go b/utxonursery_test.go index 4da0a239a..b300e4adc 100644 --- a/utxonursery_test.go +++ b/utxonursery_test.go @@ -664,12 +664,7 @@ func incubateTestOutput(t *testing.T, nursery *utxoNursery, if onLocalCommitment { expectedStage = 1 } - - // TODO(joostjager): Nursery is currently not reporting this limbo - // balance. - if onLocalCommitment { - assertNurseryReport(t, nursery, 1, expectedStage, 10000) - } + assertNurseryReport(t, nursery, 1, expectedStage, 10000) return outgoingRes }