package contractcourt import ( "encoding/binary" "fmt" "io" "github.com/lightningnetwork/lnd/input" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnwallet" ) // commitSweepResolver is a resolver that will attempt to sweep the commitment // output paying to us, in the case that the remote party broadcasts their // version of the commitment transaction. We can sweep this output immediately, // as it doesn't have a time-lock delay. type commitSweepResolver struct { // commitResolution contains all data required to successfully sweep // this HTLC on-chain. commitResolution lnwallet.CommitOutputResolution // 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 ResolverKit } // ResolverKey returns an identifier which should be globally unique for this // particular resolver within the chain the original contract resides within. func (c *commitSweepResolver) ResolverKey() []byte { key := newResolverID(c.commitResolution.SelfOutPoint) return key[:] } // Resolve instructs the contract resolver to resolve the output on-chain. Once // the output has been *fully* resolved, the function should return immediately // with a nil ContractResolver value for the first return value. In the case // that the contract requires further resolution, then another resolve is // returned. // // NOTE: This function MUST be run as a goroutine. func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // If we're already resolved, then we can exit early. if c.resolved { return nil, nil } // First, we'll register for a notification once the commitment output // itself has been confirmed. // // TODO(roasbeef): instead sweep asap if remote commit? yeh commitTXID := c.commitResolution.SelfOutPoint.Hash sweepScript := c.commitResolution.SelfOutputSignDesc.Output.PkScript confNtfn, err := c.Notifier.RegisterConfirmationsNtfn( &commitTXID, sweepScript, 1, c.broadcastHeight, ) if err != nil { return nil, err } log.Debugf("%T(%v): waiting for commit tx to confirm", c, c.chanPoint) select { case _, ok := <-confNtfn.Confirmed: if !ok { return nil, fmt.Errorf("quitting") } case <-c.Quit: return nil, fmt.Errorf("quitting") } // We're dealing with our commitment transaction if the delay on the // resolution isn't zero. isLocalCommitTx := c.commitResolution.MaturityDelay != 0 if !isLocalCommitTx { // We'll craft an input with all the information required for // the sweeper to create a fully valid sweeping transaction to // recover these coins. inp := input.MakeBaseInput( &c.commitResolution.SelfOutPoint, input.CommitmentNoDelay, &c.commitResolution.SelfOutputSignDesc, c.broadcastHeight, ) // With our input constructed, we'll now offer it to the // sweeper. log.Infof("%T(%v): sweeping commit output", c, c.chanPoint) resultChan, err := c.Sweeper.SweepInput(&inp) if err != nil { log.Errorf("%T(%v): unable to sweep input: %v", c, c.chanPoint, err) return nil, err } // Sweeper is going to join this input with other inputs if // possible and publish the sweep tx. When the sweep tx // confirms, it signals us through the result channel with the // outcome. Wait for this to happen. select { case sweepResult := <-resultChan: if sweepResult.Err != nil { log.Errorf("%T(%v): unable to sweep input: %v", c, c.chanPoint, sweepResult.Err) return nil, err } log.Infof("ChannelPoint(%v) commit tx is fully resolved by "+ "sweep tx: %v", c.chanPoint, sweepResult.Tx.TxHash()) case <-c.Quit: return nil, fmt.Errorf("quitting") } c.resolved = true return nil, c.Checkpoint(c) } // Otherwise we are dealing with a local commitment transaction and the // output we need to sweep has been sent to the nursery for incubation. // In this case, we'll wait until the commitment output has been spent. spendNtfn, err := c.Notifier.RegisterSpendNtfn( &c.commitResolution.SelfOutPoint, c.commitResolution.SelfOutputSignDesc.Output.PkScript, c.broadcastHeight, ) if err != nil { return nil, err } log.Infof("%T(%v): waiting for commit output to be swept", c, c.chanPoint) var sweepTx *wire.MsgTx select { case commitSpend, ok := <-spendNtfn.Spend: if !ok { return nil, fmt.Errorf("quitting") } // Once we detect the commitment output has been spent, // we'll extract the spending transaction itself, as we // now consider this to be our sweep transaction. sweepTx = commitSpend.SpendingTx log.Infof("%T(%v): commit output swept by txid=%v", c, c.chanPoint, sweepTx.TxHash()) if err := c.Checkpoint(c); err != nil { log.Errorf("unable to Checkpoint: %v", err) return nil, err } case <-c.Quit: return nil, fmt.Errorf("quitting") } log.Infof("%T(%v): waiting for commit sweep txid=%v conf", c, c.chanPoint, sweepTx.TxHash()) // Now we'll wait until the sweeping transaction has been fully // confirmed. Once it's confirmed, we can mark this contract resolved. sweepTXID := sweepTx.TxHash() sweepingScript := sweepTx.TxOut[0].PkScript confNtfn, err = c.Notifier.RegisterConfirmationsNtfn( &sweepTXID, sweepingScript, 1, c.broadcastHeight, ) if err != nil { return nil, err } select { case confInfo, ok := <-confNtfn.Confirmed: if !ok { return nil, fmt.Errorf("quitting") } log.Infof("ChannelPoint(%v) commit tx is fully resolved, at height: %v", c.chanPoint, confInfo.BlockHeight) case <-c.Quit: return nil, fmt.Errorf("quitting") } // Once the transaction has received a sufficient number of // confirmations, we'll mark ourselves as fully resolved and exit. c.resolved = true return nil, c.Checkpoint(c) } // Stop signals the resolver to cancel any current resolution processes, and // suspend. // // NOTE: Part of the ContractResolver interface. func (c *commitSweepResolver) 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 *commitSweepResolver) IsResolved() bool { return c.resolved } // Encode writes an encoded version of the ContractResolver into the passed // Writer. // // NOTE: Part of the ContractResolver interface. func (c *commitSweepResolver) Encode(w io.Writer) error { if err := encodeCommitResolution(w, &c.commitResolution); err != nil { return err } if err := binary.Write(w, endian, c.resolved); err != nil { return err } if err := binary.Write(w, endian, c.broadcastHeight); err != nil { return err } if _, err := w.Write(c.chanPoint.Hash[:]); err != nil { return err } err := binary.Write(w, endian, c.chanPoint.Index) if err != nil { return err } // Previously a sweep tx was serialized at this point. Refactoring // removed this, but keep in mind that this data may still be present in // the database. return nil } // Decode attempts to decode an encoded ContractResolver from the passed Reader // instance, returning an active ContractResolver instance. // // NOTE: Part of the ContractResolver interface. func (c *commitSweepResolver) Decode(r io.Reader) error { if err := decodeCommitResolution(r, &c.commitResolution); err != nil { return err } if err := binary.Read(r, endian, &c.resolved); err != nil { return err } if err := binary.Read(r, endian, &c.broadcastHeight); err != nil { return err } _, err := io.ReadFull(r, c.chanPoint.Hash[:]) if err != nil { return err } err = binary.Read(r, endian, &c.chanPoint.Index) if err != nil { return err } // Previously a sweep tx was deserialized at this point. Refactoring // removed this, but keep in mind that this data may still be present in // the database. return nil } // AttachResolverKit should be called once a resolved is successfully decoded // from its stored format. This struct delivers a generic tool kit that // resolvers need to complete their duty. // // NOTE: Part of the ContractResolver interface. func (c *commitSweepResolver) AttachResolverKit(r ResolverKit) { c.ResolverKit = r } // A compile time assertion to ensure commitSweepResolver meets the // ContractResolver interface. var _ ContractResolver = (*commitSweepResolver)(nil)