You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
5.9 KiB
210 lines
5.9 KiB
package contractcourt |
|
|
|
import ( |
|
"errors" |
|
"io" |
|
"sync" |
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/lightningnetwork/lnd/channeldb" |
|
"github.com/lightningnetwork/lnd/input" |
|
"github.com/lightningnetwork/lnd/sweep" |
|
) |
|
|
|
// anchorResolver is a resolver that will attempt to sweep our anchor output. |
|
type anchorResolver struct { |
|
// anchorSignDescriptor contains the information that is required to |
|
// sweep the anchor. |
|
anchorSignDescriptor input.SignDescriptor |
|
|
|
// anchor is the outpoint on the commitment transaction. |
|
anchor wire.OutPoint |
|
|
|
// resolved reflects if the contract has been fully resolved or not. |
|
resolved bool |
|
|
|
// broadcastHeight is the height that the original contract was |
|
// broadcast to the main-chain at. We'll use this value to bound any |
|
// historical queries to the chain for spends/confirmations. |
|
broadcastHeight uint32 |
|
|
|
// chanPoint is the channel point of the original contract. |
|
chanPoint wire.OutPoint |
|
|
|
// currentReport stores the current state of the resolver for reporting |
|
// over the rpc interface. |
|
currentReport ContractReport |
|
|
|
// reportLock prevents concurrent access to the resolver report. |
|
reportLock sync.Mutex |
|
|
|
contractResolverKit |
|
} |
|
|
|
// newAnchorResolver instantiates a new anchor resolver. |
|
func newAnchorResolver(anchorSignDescriptor input.SignDescriptor, |
|
anchor wire.OutPoint, broadcastHeight uint32, |
|
chanPoint wire.OutPoint, resCfg ResolverConfig) *anchorResolver { |
|
|
|
amt := btcutil.Amount(anchorSignDescriptor.Output.Value) |
|
|
|
report := ContractReport{ |
|
Outpoint: anchor, |
|
Type: ReportOutputAnchor, |
|
Amount: amt, |
|
LimboBalance: amt, |
|
RecoveredBalance: 0, |
|
} |
|
|
|
r := &anchorResolver{ |
|
contractResolverKit: *newContractResolverKit(resCfg), |
|
anchorSignDescriptor: anchorSignDescriptor, |
|
anchor: anchor, |
|
broadcastHeight: broadcastHeight, |
|
chanPoint: chanPoint, |
|
currentReport: report, |
|
} |
|
|
|
r.initLogger(r) |
|
|
|
return r |
|
} |
|
|
|
// ResolverKey returns an identifier which should be globally unique for this |
|
// particular resolver within the chain the original contract resides within. |
|
func (c *anchorResolver) ResolverKey() []byte { |
|
// The anchor resolver is stateless and doesn't need a database key. |
|
return nil |
|
} |
|
|
|
// Resolve offers the anchor output to the sweeper and waits for it to be swept. |
|
func (c *anchorResolver) Resolve() (ContractResolver, error) { |
|
// Attempt to update the sweep parameters to the post-confirmation |
|
// situation. We don't want to force sweep anymore, because the anchor |
|
// lost its special purpose to get the commitment confirmed. It is just |
|
// an output that we want to sweep only if it is economical to do so. |
|
// |
|
// An exclusive group is not necessary anymore, because we know that |
|
// this is the only anchor that can be swept. |
|
// |
|
// We also clear the parent tx information for cpfp, because the |
|
// commitment tx is confirmed. |
|
// |
|
// After a restart or when the remote force closes, the sweeper is not |
|
// yet aware of the anchor. In that case, it will be added as new input |
|
// to the sweeper. |
|
relayFeeRate := c.Sweeper.RelayFeePerKW() |
|
|
|
anchorInput := input.MakeBaseInput( |
|
&c.anchor, |
|
input.CommitmentAnchor, |
|
&c.anchorSignDescriptor, |
|
c.broadcastHeight, |
|
nil, |
|
) |
|
|
|
resultChan, err := c.Sweeper.SweepInput( |
|
&anchorInput, |
|
sweep.Params{ |
|
Fee: sweep.FeePreference{ |
|
FeeRate: relayFeeRate, |
|
}, |
|
}, |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var ( |
|
outcome channeldb.ResolverOutcome |
|
spendTx *chainhash.Hash |
|
) |
|
|
|
select { |
|
case sweepRes := <-resultChan: |
|
switch sweepRes.Err { |
|
|
|
// Anchor was swept successfully. |
|
case nil: |
|
sweepTxID := sweepRes.Tx.TxHash() |
|
|
|
spendTx = &sweepTxID |
|
outcome = channeldb.ResolverOutcomeClaimed |
|
|
|
// Anchor was swept by someone else. This is possible after the |
|
// 16 block csv lock. |
|
case sweep.ErrRemoteSpend: |
|
c.log.Warnf("our anchor spent by someone else") |
|
outcome = channeldb.ResolverOutcomeUnclaimed |
|
|
|
// The sweeper gave up on sweeping the anchor. This happens |
|
// after the maximum number of sweep attempts has been reached. |
|
// See sweep.DefaultMaxSweepAttempts. Sweep attempts are |
|
// interspaced with random delays picked from a range that |
|
// increases exponentially. |
|
// |
|
// We consider the anchor as being lost. |
|
case sweep.ErrTooManyAttempts: |
|
c.log.Warnf("anchor sweep abandoned") |
|
outcome = channeldb.ResolverOutcomeUnclaimed |
|
|
|
// An unexpected error occurred. |
|
default: |
|
c.log.Errorf("unable to sweep anchor: %v", sweepRes.Err) |
|
|
|
return nil, sweepRes.Err |
|
} |
|
|
|
case <-c.quit: |
|
return nil, errResolverShuttingDown |
|
} |
|
|
|
// Update report to reflect that funds are no longer in limbo. |
|
c.reportLock.Lock() |
|
if outcome == channeldb.ResolverOutcomeClaimed { |
|
c.currentReport.RecoveredBalance = c.currentReport.LimboBalance |
|
} |
|
c.currentReport.LimboBalance = 0 |
|
report := c.currentReport.resolverReport( |
|
spendTx, channeldb.ResolverTypeAnchor, outcome, |
|
) |
|
c.reportLock.Unlock() |
|
|
|
c.resolved = true |
|
return nil, c.PutResolverReport(nil, report) |
|
} |
|
|
|
// Stop signals the resolver to cancel any current resolution processes, and |
|
// suspend. |
|
// |
|
// NOTE: Part of the ContractResolver interface. |
|
func (c *anchorResolver) Stop() { |
|
close(c.quit) |
|
} |
|
|
|
// IsResolved returns true if the stored state in the resolve is fully |
|
// resolved. In this case the target output can be forgotten. |
|
// |
|
// NOTE: Part of the ContractResolver interface. |
|
func (c *anchorResolver) IsResolved() bool { |
|
return c.resolved |
|
} |
|
|
|
// report returns a report on the resolution state of the contract. |
|
func (c *anchorResolver) report() *ContractReport { |
|
c.reportLock.Lock() |
|
defer c.reportLock.Unlock() |
|
|
|
reportCopy := c.currentReport |
|
return &reportCopy |
|
} |
|
|
|
func (c *anchorResolver) Encode(w io.Writer) error { |
|
return errors.New("serialization not supported") |
|
} |
|
|
|
// A compile time assertion to ensure anchorResolver meets the |
|
// ContractResolver interface. |
|
var _ ContractResolver = (*anchorResolver)(nil)
|
|
|