github.com/decred/dcrlnd@v0.7.6/lnwallet/chanfunding/canned_assembler.go (about)

     1  package chanfunding
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
     7  	"github.com/decred/dcrd/dcrutil/v4"
     8  	"github.com/decred/dcrd/wire"
     9  	"github.com/decred/dcrlnd/input"
    10  	"github.com/decred/dcrlnd/keychain"
    11  )
    12  
    13  // ShimIntent is an intent created by the CannedAssembler which represents a
    14  // funding output to be created that was constructed outside the wallet. This
    15  // might be used when a hardware wallet, or a channel factory is the entity
    16  // crafting the funding transaction, and not lnd.
    17  type ShimIntent struct {
    18  	// localFundingAmt is the final amount we put into the funding output.
    19  	localFundingAmt dcrutil.Amount
    20  
    21  	// remoteFundingAmt is the final amount the remote party put into the
    22  	// funding output.
    23  	remoteFundingAmt dcrutil.Amount
    24  
    25  	// localKey is our multi-sig key.
    26  	localKey *keychain.KeyDescriptor
    27  
    28  	// remoteKey is the remote party's multi-sig key.
    29  	remoteKey *secp256k1.PublicKey
    30  
    31  	// chanPoint is the final channel point for the to be created channel.
    32  	chanPoint *wire.OutPoint
    33  
    34  	// thawHeight, if non-zero is the height where this channel will become
    35  	// a normal channel. Until this height, it's considered frozen, so it
    36  	// can only be cooperatively closed by the responding party.
    37  	thawHeight uint32
    38  }
    39  
    40  // FundingOutput returns the witness script, and the output that creates the
    41  // funding output.
    42  //
    43  // NOTE: This method satisfies the chanfunding.Intent interface.
    44  func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) {
    45  	if s.localKey == nil || s.remoteKey == nil {
    46  		return nil, nil, fmt.Errorf("unable to create witness " +
    47  			"script, no funding keys")
    48  	}
    49  
    50  	totalAmt := s.localFundingAmt + s.remoteFundingAmt
    51  	return input.GenFundingPkScript(
    52  		s.localKey.PubKey.SerializeCompressed(),
    53  		s.remoteKey.SerializeCompressed(),
    54  		int64(totalAmt),
    55  	)
    56  }
    57  
    58  // Cancel allows the caller to cancel a funding Intent at any time.  This will
    59  // return any resources such as coins back to the eligible pool to be used in
    60  // order channel fundings.
    61  //
    62  // NOTE: This method satisfies the chanfunding.Intent interface.
    63  func (s *ShimIntent) Cancel() {
    64  }
    65  
    66  // LocalFundingAmt is the amount we put into the channel. This may differ from
    67  // the local amount requested, as depending on coin selection, we may bleed
    68  // from of that LocalAmt into fees to minimize change.
    69  //
    70  // NOTE: This method satisfies the chanfunding.Intent interface.
    71  func (s *ShimIntent) LocalFundingAmt() dcrutil.Amount {
    72  	return s.localFundingAmt
    73  }
    74  
    75  // RemoteFundingAmt is the amount the remote party put into the channel.
    76  //
    77  // NOTE: This method satisfies the chanfunding.Intent interface.
    78  func (s *ShimIntent) RemoteFundingAmt() dcrutil.Amount {
    79  	return s.remoteFundingAmt
    80  }
    81  
    82  // ChanPoint returns the final outpoint that will create the funding output
    83  // described above.
    84  //
    85  // NOTE: This method satisfies the chanfunding.Intent interface.
    86  func (s *ShimIntent) ChanPoint() (*wire.OutPoint, error) {
    87  	if s.chanPoint == nil {
    88  		return nil, fmt.Errorf("chan point unknown, funding output " +
    89  			"not constructed")
    90  	}
    91  
    92  	return s.chanPoint, nil
    93  }
    94  
    95  // ThawHeight returns the height where this channel goes back to being a normal
    96  // channel.
    97  func (s *ShimIntent) ThawHeight() uint32 {
    98  	return s.thawHeight
    99  }
   100  
   101  // Inputs returns all inputs to the final funding transaction that we
   102  // know about. For the ShimIntent this will always be none, since it is funded
   103  // externally.
   104  func (s *ShimIntent) Inputs() []wire.OutPoint {
   105  	return nil
   106  }
   107  
   108  // Outputs returns all outputs of the final funding transaction that we
   109  // know about. Since this is an externally funded channel, the channel output
   110  // is the only known one.
   111  func (s *ShimIntent) Outputs() []*wire.TxOut {
   112  	_, txOut, err := s.FundingOutput()
   113  	if err != nil {
   114  		log.Warnf("Unable to find funding output for shim intent: %v",
   115  			err)
   116  
   117  		// Failed finding funding output, return empty list of known
   118  		// outputs.
   119  		return nil
   120  	}
   121  
   122  	return []*wire.TxOut{txOut}
   123  }
   124  
   125  // FundingKeys couples our multi-sig key along with the remote party's key.
   126  type FundingKeys struct {
   127  	// LocalKey is our multi-sig key.
   128  	LocalKey *keychain.KeyDescriptor
   129  
   130  	// RemoteKey is the multi-sig key of the remote party.
   131  	RemoteKey *secp256k1.PublicKey
   132  }
   133  
   134  // MultiSigKeys returns the committed multi-sig keys, but only if they've been
   135  // specified/provided.
   136  func (s *ShimIntent) MultiSigKeys() (*FundingKeys, error) {
   137  	if s.localKey == nil || s.remoteKey == nil {
   138  		return nil, fmt.Errorf("unknown funding keys")
   139  	}
   140  
   141  	return &FundingKeys{
   142  		LocalKey:  s.localKey,
   143  		RemoteKey: s.remoteKey,
   144  	}, nil
   145  }
   146  
   147  // A compile-time check to ensure ShimIntent adheres to the Intent interface.
   148  var _ Intent = (*ShimIntent)(nil)
   149  
   150  // CannedAssembler is a type of chanfunding.Assembler wherein the funding
   151  // transaction is constructed outside of lnd, and may already exist. This
   152  // Assembler serves as a shim which gives the funding flow the only thing it
   153  // actually needs to proceed: the channel point.
   154  type CannedAssembler struct {
   155  	// fundingAmt is the total amount of coins in the funding output.
   156  	fundingAmt dcrutil.Amount
   157  
   158  	// localKey is our multi-sig key.
   159  	localKey *keychain.KeyDescriptor
   160  
   161  	// remoteKey is the remote party's multi-sig key.
   162  	remoteKey *secp256k1.PublicKey
   163  
   164  	// chanPoint is the final channel point for the to be created channel.
   165  	chanPoint wire.OutPoint
   166  
   167  	// initiator indicates if we're the initiator or the channel or not.
   168  	initiator bool
   169  
   170  	// thawHeight, if non-zero is the height where this channel will become
   171  	// a normal channel. Until this height, it's considered frozen, so it
   172  	// can only be cooperatively closed by the responding party.
   173  	thawHeight uint32
   174  }
   175  
   176  // NewCannedAssembler creates a new CannedAssembler from the material required
   177  // to construct a funding output and channel point.
   178  func NewCannedAssembler(thawHeight uint32, chanPoint wire.OutPoint,
   179  	fundingAmt dcrutil.Amount, localKey *keychain.KeyDescriptor,
   180  	remoteKey *secp256k1.PublicKey, initiator bool) *CannedAssembler {
   181  
   182  	return &CannedAssembler{
   183  		initiator:  initiator,
   184  		localKey:   localKey,
   185  		remoteKey:  remoteKey,
   186  		fundingAmt: fundingAmt,
   187  		chanPoint:  chanPoint,
   188  		thawHeight: thawHeight,
   189  	}
   190  }
   191  
   192  // ProvisionChannel creates a new ShimIntent given the passed funding Request.
   193  // The returned intent is immediately able to provide the channel point and
   194  // funding output as they've already been created outside lnd.
   195  //
   196  // NOTE: This method satisfies the chanfunding.Assembler interface.
   197  func (c *CannedAssembler) ProvisionChannel(req *Request) (Intent, error) {
   198  	// We'll exit out if this field is set as the funding transaction has
   199  	// already been assembled, so we don't influence coin selection..
   200  	if req.SubtractFees {
   201  		return nil, fmt.Errorf("SubtractFees ignored, funding " +
   202  			"transaction is frozen")
   203  	}
   204  
   205  	intent := &ShimIntent{
   206  		localKey:   c.localKey,
   207  		remoteKey:  c.remoteKey,
   208  		chanPoint:  &c.chanPoint,
   209  		thawHeight: c.thawHeight,
   210  	}
   211  
   212  	if c.initiator {
   213  		intent.localFundingAmt = c.fundingAmt
   214  	} else {
   215  		intent.remoteFundingAmt = c.fundingAmt
   216  	}
   217  
   218  	// A simple sanity check to ensure the provisioned request matches the
   219  	// re-made shim intent.
   220  	if req.LocalAmt+req.RemoteAmt != c.fundingAmt {
   221  		return nil, fmt.Errorf("intent doesn't match canned "+
   222  			"assembler: local_amt=%v, remote_amt=%v, funding_amt=%v",
   223  			req.LocalAmt, req.RemoteAmt, c.fundingAmt)
   224  	}
   225  
   226  	return intent, nil
   227  }
   228  
   229  // A compile-time assertion to ensure CannedAssembler meets the Assembler
   230  // interface.
   231  var _ Assembler = (*CannedAssembler)(nil)