Merge pull request #1875 from joostjager/resolverstate
rpc+contractcourt: merge the contractResolver state into the pendingchannels RPC response
This commit is contained in:
commit
6032e47fe8
@ -566,6 +566,21 @@ func (c *ChainArbitrator) UpdateContractSignals(chanPoint wire.OutPoint,
|
|||||||
return nil
|
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
|
// forceCloseReq is a request sent from an outside sub-system to the arbitrator
|
||||||
// that watches a particular channel to broadcast the commitment transaction,
|
// that watches a particular channel to broadcast the commitment transaction,
|
||||||
// and enter the resolution phase of the channel.
|
// and enter the resolution phase of the channel.
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package contractcourt
|
package contractcourt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
@ -132,6 +134,36 @@ type ChannelArbitratorConfig struct {
|
|||||||
ChainArbitratorConfig
|
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
|
// htlcSet represents the set of active HTLCs on a given commitment
|
||||||
// transaction.
|
// transaction.
|
||||||
type htlcSet struct {
|
type htlcSet struct {
|
||||||
@ -202,6 +234,10 @@ type ChannelArbitrator struct {
|
|||||||
// be able to signal them for shutdown in the case that we shutdown.
|
// be able to signal them for shutdown in the case that we shutdown.
|
||||||
activeResolvers []ContractResolver
|
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
|
// resolutionSignal is a channel that will be sent upon by contract
|
||||||
// resolvers once their contract has been fully resolved. With each
|
// resolvers once their contract has been fully resolved. With each
|
||||||
// send, we'll check to see if the contract is fully resolved.
|
// send, we'll check to see if the contract is fully resolved.
|
||||||
@ -461,6 +497,33 @@ func supplementTimeoutResolver(r *htlcTimeoutResolver,
|
|||||||
return nil
|
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.
|
// Stop signals the ChannelArbitrator for a graceful shutdown.
|
||||||
func (c *ChannelArbitrator) Stop() error {
|
func (c *ChannelArbitrator) Stop() error {
|
||||||
if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
|
if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
|
||||||
@ -473,9 +536,11 @@ func (c *ChannelArbitrator) Stop() error {
|
|||||||
go c.cfg.ChainEvents.Cancel()
|
go c.cfg.ChainEvents.Cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.activeResolversLock.RLock()
|
||||||
for _, activeResolver := range c.activeResolvers {
|
for _, activeResolver := range c.activeResolvers {
|
||||||
activeResolver.Stop()
|
activeResolver.Stop()
|
||||||
}
|
}
|
||||||
|
c.activeResolversLock.RUnlock()
|
||||||
|
|
||||||
close(c.quit)
|
close(c.quit)
|
||||||
c.wg.Wait()
|
c.wg.Wait()
|
||||||
@ -816,7 +881,19 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32,
|
|||||||
log.Infof("ChannelArbitrator(%v): still awaiting contract "+
|
log.Infof("ChannelArbitrator(%v): still awaiting contract "+
|
||||||
"resolution", c.cfg.ChanPoint)
|
"resolution", c.cfg.ChanPoint)
|
||||||
|
|
||||||
|
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
|
nextState = StateWaitingFullResolution
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
nextState = StateFullyResolved
|
||||||
|
|
||||||
// If we start as fully resolved, then we'll end as fully resolved.
|
// If we start as fully resolved, then we'll end as fully resolved.
|
||||||
case StateFullyResolved:
|
case StateFullyResolved:
|
||||||
@ -842,6 +919,9 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32,
|
|||||||
|
|
||||||
// launchResolvers updates the activeResolvers list and starts the resolvers.
|
// launchResolvers updates the activeResolvers list and starts the resolvers.
|
||||||
func (c *ChannelArbitrator) launchResolvers(resolvers []ContractResolver) {
|
func (c *ChannelArbitrator) launchResolvers(resolvers []ContractResolver) {
|
||||||
|
c.activeResolversLock.Lock()
|
||||||
|
defer c.activeResolversLock.Unlock()
|
||||||
|
|
||||||
c.activeResolvers = resolvers
|
c.activeResolvers = resolvers
|
||||||
for _, contract := range resolvers {
|
for _, contract := range resolvers {
|
||||||
c.wg.Add(1)
|
c.wg.Add(1)
|
||||||
@ -1361,6 +1441,25 @@ func (c *ChannelArbitrator) prepContractResolutions(htlcActions ChainActionMap,
|
|||||||
return htlcResolvers, msgsToSend, nil
|
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
|
// resolveContract is a goroutine tasked with fully resolving an unresolved
|
||||||
// contract. Either the initial contract will be resolved after a single step,
|
// contract. Either the initial contract will be resolved after a single step,
|
||||||
// or the contract will itself create another contract to be resolved. In
|
// 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,
|
c.cfg.ChanPoint, currentContract,
|
||||||
nextContract)
|
nextContract)
|
||||||
|
|
||||||
|
// Swap contract in log.
|
||||||
err := c.log.SwapContract(
|
err := c.log.SwapContract(
|
||||||
currentContract, nextContract,
|
currentContract, nextContract,
|
||||||
)
|
)
|
||||||
@ -1418,6 +1518,17 @@ func (c *ChannelArbitrator) resolveContract(currentContract ContractResolver) {
|
|||||||
"contract: %v", err)
|
"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
|
// As this contract produced another, we'll
|
||||||
// re-assign, so we can continue our resolution
|
// re-assign, so we can continue our resolution
|
||||||
// loop.
|
// loop.
|
||||||
@ -1722,29 +1833,22 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
|
|||||||
log.Infof("ChannelArbitrator(%v): a contract has been "+
|
log.Infof("ChannelArbitrator(%v): a contract has been "+
|
||||||
"fully resolved!", c.cfg.ChanPoint)
|
"fully resolved!", c.cfg.ChanPoint)
|
||||||
|
|
||||||
numUnresolved, err := c.log.FetchUnresolvedContracts()
|
nextState, _, err := c.advanceState(
|
||||||
|
uint32(bestHeight), chainTrigger,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("unable to query resolved "+
|
log.Errorf("unable to advance state: %v", err)
|
||||||
"contracts: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we still have unresolved contracts, then we'll
|
// If we don't have anything further to do after
|
||||||
// stay alive to oversee their resolution.
|
// advancing our state, then we'll exit.
|
||||||
if len(numUnresolved) != 0 {
|
if nextState == StateFullyResolved {
|
||||||
continue
|
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
|
// We've just received a request to forcibly close out the
|
||||||
// channel. We'll
|
// channel. We'll
|
||||||
|
@ -611,10 +611,7 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
|
|||||||
notifier.spendChan <- &chainntnfs.SpendDetail{}
|
notifier.spendChan <- &chainntnfs.SpendDetail{}
|
||||||
|
|
||||||
// At this point channel should be marked as resolved.
|
// At this point channel should be marked as resolved.
|
||||||
|
assertStateTransitions(t, arbLog.newStates, StateFullyResolved)
|
||||||
// It should transition StateWaitingFullResolution ->
|
|
||||||
// StateFullyResolved, but this isn't happening.
|
|
||||||
// assertStateTransitions(t, arbLog.newStates, StateFullyResolved)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-resolved:
|
case <-resolved:
|
||||||
|
@ -60,6 +60,14 @@ type ContractResolver interface {
|
|||||||
Stop()
|
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
|
// 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
|
// given ContractResolver implementation. It contains all the items that a
|
||||||
// resolver requires to carry out its duties.
|
// resolver requires to carry out its duties.
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// htlcIncomingContestResolver is a ContractResolver that's able to resolve an
|
// 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
|
// Stop signals the resolver to cancel any current resolution processes, and
|
||||||
// suspend.
|
// suspend.
|
||||||
//
|
//
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
)
|
)
|
||||||
@ -108,6 +109,9 @@ func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
|
|||||||
scriptToWatch []byte
|
scriptToWatch []byte
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(joostjager): output already set properly in
|
||||||
|
// lnwallet.newOutgoingHtlcResolution? And script too?
|
||||||
if h.htlcResolution.SignedTimeoutTx == nil {
|
if h.htlcResolution.SignedTimeoutTx == nil {
|
||||||
outPointToWatch = h.htlcResolution.ClaimOutpoint
|
outPointToWatch = h.htlcResolution.ClaimOutpoint
|
||||||
scriptToWatch = h.htlcResolution.SweepSignDesc.Output.PkScript
|
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
|
// Stop signals the resolver to cancel any current resolution processes, and
|
||||||
// suspend.
|
// suspend.
|
||||||
//
|
//
|
||||||
|
24
lnd_test.go
24
lnd_test.go
@ -2803,6 +2803,12 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
|
|
||||||
assertTxInBlock(t, block, sweepTx.Hash())
|
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 {
|
err = lntest.WaitPredicate(func() bool {
|
||||||
// Now that the commit output has been fully swept, check to see
|
// Now that the commit output has been fully swept, check to see
|
||||||
// that the channel remains open for the pending htlc outputs.
|
// 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
|
// The commitment funds will have been recovered after the
|
||||||
// commit txn was included in the last block. The htlc funds
|
// commit txn was included in the last block. The htlc funds
|
||||||
// will not be shown in limbo, since they are still in their
|
// will be shown in limbo.
|
||||||
// first stage and the nursery hasn't received them from the
|
|
||||||
// contract court.
|
|
||||||
forceClose, err := findForceClosedChannel(pendingChanResp, &op)
|
forceClose, err := findForceClosedChannel(pendingChanResp, &op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
predErr = err
|
predErr = err
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
predErr = checkPendingChannelNumHtlcs(forceClose, 0)
|
predErr = checkPendingChannelNumHtlcs(forceClose, numInvoices)
|
||||||
if predErr != nil {
|
if predErr != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if forceClose.LimboBalance != 0 {
|
predErr = checkPendingHtlcStageAndMaturity(
|
||||||
predErr = fmt.Errorf("expected 0 funds in limbo, "+
|
forceClose, 1, htlcExpiryHeight,
|
||||||
"found %d", forceClose.LimboBalance)
|
int32(htlcExpiryHeight)-curHeight,
|
||||||
|
)
|
||||||
|
if predErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if forceClose.LimboBalance == 0 {
|
||||||
|
predErr = fmt.Errorf("expected funds in limbo, found 0")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
158
rpcserver.go
158
rpcserver.go
@ -2063,57 +2063,26 @@ func (r *rpcServer) PendingChannels(ctx context.Context,
|
|||||||
ClosingTxid: closeTXID,
|
ClosingTxid: closeTXID,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for the maturity state for this force closed
|
// Fetch reports from both nursery and resolvers. At the
|
||||||
// channel. If we didn't have any time-locked outputs,
|
// moment this is not an atomic snapshot. This is
|
||||||
// then the nursery may not know of the contract.
|
// planned to be resolved when the nursery is removed
|
||||||
nurseryInfo, err := r.server.utxoNursery.NurseryReport(&chanPoint)
|
// and channel arbitrator will be the single source for
|
||||||
if err != nil && err != ErrContractNotFound {
|
// these kind of reports.
|
||||||
return nil, fmt.Errorf("unable to obtain "+
|
err := r.nurseryPopulateForceCloseResp(
|
||||||
"nursery report for ChannelPoint(%v): %v",
|
&chanPoint, currentHeight, forceClose,
|
||||||
chanPoint, err)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the nursery knows of this channel, then we can
|
err = r.arbitratorPopulateForceCloseResp(
|
||||||
// populate information detailing exactly how much
|
&chanPoint, currentHeight, forceClose,
|
||||||
// funds are time locked and also the height in which
|
)
|
||||||
// we can ultimately sweep the funds into the wallet.
|
if err != nil {
|
||||||
if nurseryInfo != nil {
|
return nil, err
|
||||||
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 {
|
resp.TotalLimboBalance += int64(forceClose.LimboBalance)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.PendingForceClosingChannels = append(
|
resp.PendingForceClosingChannels = append(
|
||||||
resp.PendingForceClosingChannels,
|
resp.PendingForceClosingChannels,
|
||||||
@ -2158,6 +2127,101 @@ func (r *rpcServer) PendingChannels(ctx context.Context,
|
|||||||
return resp, nil
|
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.
|
// ClosedChannels returns a list of all the channels have been closed.
|
||||||
// This does not include channels that are still in the process of closing.
|
// This does not include channels that are still in the process of closing.
|
||||||
func (r *rpcServer) ClosedChannels(ctx context.Context,
|
func (r *rpcServer) ClosedChannels(ctx context.Context,
|
||||||
|
@ -494,9 +494,7 @@ func (u *utxoNursery) NurseryReport(
|
|||||||
utxnLog.Infof("NurseryReport: building nursery report for channel %v",
|
utxnLog.Infof("NurseryReport: building nursery report for channel %v",
|
||||||
chanPoint)
|
chanPoint)
|
||||||
|
|
||||||
report := &contractMaturityReport{
|
report := &contractMaturityReport{}
|
||||||
chanPoint: *chanPoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := u.cfg.Store.ForChanOutputs(chanPoint, func(k, v []byte) error {
|
if err := u.cfg.Store.ForChanOutputs(chanPoint, func(k, v []byte) error {
|
||||||
switch {
|
switch {
|
||||||
@ -536,11 +534,18 @@ func (u *utxoNursery) NurseryReport(
|
|||||||
case input.CommitmentTimeLock:
|
case input.CommitmentTimeLock:
|
||||||
report.AddLimboCommitment(&kid)
|
report.AddLimboCommitment(&kid)
|
||||||
|
|
||||||
|
case input.HtlcAcceptedSuccessSecondLevel:
|
||||||
// An HTLC output on our commitment transaction
|
// An HTLC output on our commitment transaction
|
||||||
// where the second-layer transaction hasn't
|
// where the second-layer transaction hasn't
|
||||||
// yet confirmed.
|
// yet confirmed.
|
||||||
case input.HtlcAcceptedSuccessSecondLevel:
|
|
||||||
report.AddLimboStage1SuccessHtlc(&kid)
|
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):
|
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
|
// contractMaturityReport is a report that details the maturity progress of a
|
||||||
// particular force closed contract.
|
// particular force closed contract.
|
||||||
type contractMaturityReport struct {
|
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
|
// limboBalance is the total number of frozen coins within this
|
||||||
// contract.
|
// contract.
|
||||||
limboBalance btcutil.Amount
|
limboBalance btcutil.Amount
|
||||||
@ -1063,16 +1064,6 @@ type contractMaturityReport struct {
|
|||||||
// back to the user's wallet.
|
// back to the user's wallet.
|
||||||
recoveredBalance btcutil.Amount
|
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
|
// maturityHeight is the absolute block height that this output will
|
||||||
// mature at.
|
// mature at.
|
||||||
maturityHeight uint32
|
maturityHeight uint32
|
||||||
@ -1090,13 +1081,6 @@ type htlcMaturityReport struct {
|
|||||||
// amount is the final value that will be swept in back to the wallet.
|
// amount is the final value that will be swept in back to the wallet.
|
||||||
amount btcutil.Amount
|
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
|
// maturityHeight is the absolute block height that this output will
|
||||||
// mature at.
|
// mature at.
|
||||||
maturityHeight uint32
|
maturityHeight uint32
|
||||||
@ -1113,10 +1097,6 @@ type htlcMaturityReport struct {
|
|||||||
func (c *contractMaturityReport) AddLimboCommitment(kid *kidOutput) {
|
func (c *contractMaturityReport) AddLimboCommitment(kid *kidOutput) {
|
||||||
c.limboBalance += kid.Amount()
|
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
|
// If the confirmation height is set, then this means the contract has
|
||||||
// been confirmed, and we know the final maturity height.
|
// been confirmed, and we know the final maturity height.
|
||||||
if kid.ConfHeight() != 0 {
|
if kid.ConfHeight() != 0 {
|
||||||
@ -1129,9 +1109,6 @@ func (c *contractMaturityReport) AddLimboCommitment(kid *kidOutput) {
|
|||||||
func (c *contractMaturityReport) AddRecoveredCommitment(kid *kidOutput) {
|
func (c *contractMaturityReport) AddRecoveredCommitment(kid *kidOutput) {
|
||||||
c.recoveredBalance += kid.Amount()
|
c.recoveredBalance += kid.Amount()
|
||||||
|
|
||||||
c.localAmount += kid.Amount()
|
|
||||||
c.confHeight = kid.ConfHeight()
|
|
||||||
c.maturityRequirement = kid.BlocksToMaturity()
|
|
||||||
c.maturityHeight = kid.BlocksToMaturity() + kid.ConfHeight()
|
c.maturityHeight = kid.BlocksToMaturity() + kid.ConfHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1144,7 +1121,6 @@ func (c *contractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) {
|
|||||||
c.htlcs = append(c.htlcs, htlcMaturityReport{
|
c.htlcs = append(c.htlcs, htlcMaturityReport{
|
||||||
outpoint: *baby.OutPoint(),
|
outpoint: *baby.OutPoint(),
|
||||||
amount: baby.Amount(),
|
amount: baby.Amount(),
|
||||||
confHeight: baby.ConfHeight(),
|
|
||||||
maturityHeight: baby.expiry,
|
maturityHeight: baby.expiry,
|
||||||
stage: 1,
|
stage: 1,
|
||||||
})
|
})
|
||||||
@ -1152,14 +1128,13 @@ func (c *contractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) {
|
|||||||
|
|
||||||
// AddLimboDirectHtlc adds a direct HTLC on the commitment transaction of the
|
// AddLimboDirectHtlc adds a direct HTLC on the commitment transaction of the
|
||||||
// remote party to the maturity report. This a CLTV time-locked output that
|
// 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) {
|
func (c *contractMaturityReport) AddLimboDirectHtlc(kid *kidOutput) {
|
||||||
c.limboBalance += kid.Amount()
|
c.limboBalance += kid.Amount()
|
||||||
|
|
||||||
htlcReport := htlcMaturityReport{
|
htlcReport := htlcMaturityReport{
|
||||||
outpoint: *kid.OutPoint(),
|
outpoint: *kid.OutPoint(),
|
||||||
amount: kid.Amount(),
|
amount: kid.Amount(),
|
||||||
confHeight: kid.ConfHeight(),
|
|
||||||
maturityHeight: kid.absoluteMaturity,
|
maturityHeight: kid.absoluteMaturity,
|
||||||
stage: 2,
|
stage: 2,
|
||||||
}
|
}
|
||||||
@ -1176,8 +1151,6 @@ func (c *contractMaturityReport) AddLimboStage1SuccessHtlc(kid *kidOutput) {
|
|||||||
c.htlcs = append(c.htlcs, htlcMaturityReport{
|
c.htlcs = append(c.htlcs, htlcMaturityReport{
|
||||||
outpoint: *kid.OutPoint(),
|
outpoint: *kid.OutPoint(),
|
||||||
amount: kid.Amount(),
|
amount: kid.Amount(),
|
||||||
confHeight: kid.ConfHeight(),
|
|
||||||
maturityRequirement: kid.BlocksToMaturity(),
|
|
||||||
stage: 1,
|
stage: 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1190,8 +1163,6 @@ func (c *contractMaturityReport) AddLimboStage2Htlc(kid *kidOutput) {
|
|||||||
htlcReport := htlcMaturityReport{
|
htlcReport := htlcMaturityReport{
|
||||||
outpoint: *kid.OutPoint(),
|
outpoint: *kid.OutPoint(),
|
||||||
amount: kid.Amount(),
|
amount: kid.Amount(),
|
||||||
confHeight: kid.ConfHeight(),
|
|
||||||
maturityRequirement: kid.BlocksToMaturity(),
|
|
||||||
stage: 2,
|
stage: 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1213,8 +1184,6 @@ func (c *contractMaturityReport) AddRecoveredHtlc(kid *kidOutput) {
|
|||||||
c.htlcs = append(c.htlcs, htlcMaturityReport{
|
c.htlcs = append(c.htlcs, htlcMaturityReport{
|
||||||
outpoint: *kid.OutPoint(),
|
outpoint: *kid.OutPoint(),
|
||||||
amount: kid.Amount(),
|
amount: kid.Amount(),
|
||||||
confHeight: kid.ConfHeight(),
|
|
||||||
maturityRequirement: kid.BlocksToMaturity(),
|
|
||||||
maturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(),
|
maturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -664,12 +664,7 @@ func incubateTestOutput(t *testing.T, nursery *utxoNursery,
|
|||||||
if onLocalCommitment {
|
if onLocalCommitment {
|
||||||
expectedStage = 1
|
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
|
return outgoingRes
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user