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)