github.com/decred/dcrlnd@v0.7.6/funding/batch_test.go (about) 1 package funding 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "errors" 8 "fmt" 9 "testing" 10 11 "github.com/decred/dcrd/chaincfg/chainhash" 12 "github.com/decred/dcrd/dcrec/secp256k1/v4" 13 "github.com/decred/dcrd/dcrutil/v4" 14 "github.com/decred/dcrd/wire" 15 "github.com/decred/dcrlnd/internal/psbt" 16 "github.com/decred/dcrlnd/lnrpc" 17 "github.com/decred/dcrlnd/lnrpc/walletrpc" 18 "github.com/decred/dcrlnd/lnwallet/chainfee" 19 "github.com/decred/dcrlnd/lnwire" 20 "github.com/stretchr/testify/require" 21 ) 22 23 var ( 24 errFundingFailed = errors.New("funding failed") 25 26 testPubKey1Hex = "02e1ce77dfdda9fd1cf5e9d796faf57d1cedef9803aec84a6d7" + 27 "f8487d32781341e" 28 testPubKey1Bytes, _ = hex.DecodeString(testPubKey1Hex) 29 30 testPubKey2Hex = "039ddfc912035417b24aefe8da155267d71c3cf9e35405fc390" + 31 "df8357c5da7a5eb" 32 testPubKey2Bytes, _ = hex.DecodeString(testPubKey2Hex) 33 34 testOutPoint = wire.OutPoint{ 35 Hash: [32]byte{1, 2, 3}, 36 Index: 2, 37 } 38 ) 39 40 type fundingIntent struct { 41 chanIndex uint32 42 updateChan chan *lnrpc.OpenStatusUpdate 43 errChan chan error 44 } 45 46 type testHarness struct { 47 t *testing.T 48 batcher *Batcher 49 50 failUpdate1 bool 51 failUpdate2 bool 52 failPublish bool 53 54 intentsCreated map[[32]byte]*fundingIntent 55 intentsCanceled map[[32]byte]struct{} 56 abandonedChannels map[wire.OutPoint]struct{} 57 releasedUTXOs map[wire.OutPoint]struct{} 58 59 pendingPacket *psbt.Packet 60 pendingTx *wire.MsgTx 61 62 txPublished bool 63 } 64 65 func newTestHarness(t *testing.T, failUpdate1, failUpdate2, 66 failPublish bool) *testHarness { 67 68 h := &testHarness{ 69 t: t, 70 failUpdate1: failUpdate1, 71 failUpdate2: failUpdate2, 72 failPublish: failPublish, 73 intentsCreated: make(map[[32]byte]*fundingIntent), 74 intentsCanceled: make(map[[32]byte]struct{}), 75 abandonedChannels: make(map[wire.OutPoint]struct{}), 76 releasedUTXOs: make(map[wire.OutPoint]struct{}), 77 pendingTx: &wire.MsgTx{ 78 Version: 2, 79 TxIn: []*wire.TxIn{{ 80 // Our one input that pays for everything. 81 PreviousOutPoint: testOutPoint, 82 }}, 83 TxOut: []*wire.TxOut{{ 84 // Our static change output. 85 PkScript: []byte{1, 2, 3}, 86 Value: 99, 87 }}, 88 }, 89 } 90 h.batcher = NewBatcher(&BatchConfig{ 91 RequestParser: h.parseRequest, 92 ChannelOpener: h.openChannel, 93 ChannelAbandoner: h.abandonChannel, 94 WalletKitServer: h, 95 Wallet: h, 96 Quit: make(chan struct{}), 97 }) 98 return h 99 } 100 101 func (h *testHarness) parseRequest( 102 in *lnrpc.OpenChannelRequest) (*InitFundingMsg, error) { 103 104 pubKey, err := secp256k1.ParsePubKey(in.NodePubkey) 105 if err != nil { 106 return nil, err 107 } 108 109 return &InitFundingMsg{ 110 TargetPubkey: pubKey, 111 LocalFundingAmt: dcrutil.Amount(in.LocalFundingAmount), 112 PushAmt: lnwire.NewMAtomsFromAtoms( 113 dcrutil.Amount(in.PushAtoms), 114 ), 115 FundingFeePerKB: chainfee.AtomPerKByte( 116 in.AtomsPerByte * 1000, 117 ), 118 Private: in.Private, 119 RemoteCsvDelay: uint16(in.RemoteCsvDelay), 120 MinConfs: in.MinConfs, 121 MaxLocalCsv: uint16(in.MaxLocalCsv), 122 }, nil 123 } 124 125 func (h *testHarness) openChannel( 126 req *InitFundingMsg) (chan *lnrpc.OpenStatusUpdate, chan error) { 127 128 updateChan := make(chan *lnrpc.OpenStatusUpdate, 2) 129 errChan := make(chan error, 1) 130 131 // The change output is always index 0. 132 chanIndex := uint32(len(h.intentsCreated) + 1) 133 134 h.intentsCreated[req.PendingChanID] = &fundingIntent{ 135 chanIndex: chanIndex, 136 updateChan: updateChan, 137 errChan: errChan, 138 } 139 h.pendingTx.TxOut = append(h.pendingTx.TxOut, &wire.TxOut{ 140 PkScript: []byte{1, 2, 3, byte(chanIndex)}, 141 Value: int64(req.LocalFundingAmt), 142 }) 143 144 if h.failUpdate1 { 145 errChan <- errFundingFailed 146 147 // Once we fail we don't send any more updates. 148 return updateChan, errChan 149 } 150 151 updateChan <- &lnrpc.OpenStatusUpdate{ 152 PendingChanId: req.PendingChanID[:], 153 Update: &lnrpc.OpenStatusUpdate_PsbtFund{ 154 PsbtFund: &lnrpc.ReadyForPsbtFunding{ 155 FundingAmount: int64( 156 req.LocalFundingAmt, 157 ), 158 FundingAddress: fmt.Sprintf("foo%d", chanIndex), 159 }, 160 }, 161 } 162 163 return updateChan, errChan 164 } 165 166 func (h *testHarness) abandonChannel(op *wire.OutPoint) error { 167 h.abandonedChannels[*op] = struct{}{} 168 169 return nil 170 } 171 172 func (h *testHarness) FundPsbt(context.Context, 173 *walletrpc.FundPsbtRequest) (*walletrpc.FundPsbtResponse, error) { 174 175 packet, err := psbt.NewFromUnsignedTx(h.pendingTx) 176 if err != nil { 177 return nil, err 178 } 179 h.pendingPacket = packet 180 181 var buf bytes.Buffer 182 if err := packet.Serialize(&buf); err != nil { 183 return nil, err 184 } 185 186 return &walletrpc.FundPsbtResponse{ 187 FundedPsbt: buf.Bytes(), 188 LockedUtxos: []*walletrpc.UtxoLease{{ 189 Id: []byte{1, 2, 3}, 190 Outpoint: &lnrpc.OutPoint{ 191 TxidBytes: testOutPoint.Hash[:], 192 OutputIndex: testOutPoint.Index, 193 }, 194 }}, 195 }, nil 196 } 197 198 func (h *testHarness) FinalizePsbt(context.Context, 199 *walletrpc.FinalizePsbtRequest) (*walletrpc.FinalizePsbtResponse, 200 error) { 201 202 var psbtBuf bytes.Buffer 203 if err := h.pendingPacket.Serialize(&psbtBuf); err != nil { 204 return nil, err 205 } 206 207 var txBuf bytes.Buffer 208 if err := h.pendingTx.Serialize(&txBuf); err != nil { 209 return nil, err 210 } 211 212 return &walletrpc.FinalizePsbtResponse{ 213 SignedPsbt: psbtBuf.Bytes(), 214 RawFinalTx: txBuf.Bytes(), 215 }, nil 216 } 217 218 func (h *testHarness) ReleaseOutput(_ context.Context, 219 r *walletrpc.ReleaseOutputRequest) (*walletrpc.ReleaseOutputResponse, 220 error) { 221 222 hash, err := chainhash.NewHash(r.Outpoint.TxidBytes) 223 if err != nil { 224 return nil, err 225 } 226 op := wire.OutPoint{ 227 Hash: *hash, 228 Index: r.Outpoint.OutputIndex, 229 } 230 231 h.releasedUTXOs[op] = struct{}{} 232 233 return &walletrpc.ReleaseOutputResponse{}, nil 234 } 235 236 func (h *testHarness) PsbtFundingVerify([32]byte, *psbt.Packet, bool) error { 237 return nil 238 } 239 240 func (h *testHarness) PsbtFundingFinalize(pid [32]byte, _ *psbt.Packet, 241 _ *wire.MsgTx) error { 242 243 // During the finalize phase we can now prepare the next update to send. 244 // For this we first need to find the intent that has the channels we 245 // need to send on. 246 intent, ok := h.intentsCreated[pid] 247 if !ok { 248 return fmt.Errorf("intent %x not found", pid) 249 } 250 251 // We should now also have the final TX, let's get its hash. 252 hash := h.pendingTx.TxHash() 253 254 // For the second update we fail on the second channel only so the first 255 // is actually pending. 256 if h.failUpdate2 && intent.chanIndex == 2 { 257 intent.errChan <- errFundingFailed 258 } else { 259 intent.updateChan <- &lnrpc.OpenStatusUpdate{ 260 PendingChanId: pid[:], 261 Update: &lnrpc.OpenStatusUpdate_ChanPending{ 262 ChanPending: &lnrpc.PendingUpdate{ 263 Txid: hash[:], 264 OutputIndex: intent.chanIndex, 265 }, 266 }, 267 } 268 } 269 270 return nil 271 } 272 273 func (h *testHarness) PublishTransaction(*wire.MsgTx, string) error { 274 if h.failPublish { 275 return errFundingFailed 276 } 277 278 h.txPublished = true 279 280 return nil 281 } 282 283 func (h *testHarness) CancelFundingIntent(pid [32]byte) error { 284 h.intentsCanceled[pid] = struct{}{} 285 286 return nil 287 } 288 289 // TestBatchFund tests different success and error scenarios of the atomic batch 290 // channel funding. 291 func TestBatchFund(t *testing.T) { 292 t.Skip("PSBT not implemented") 293 t.Parallel() 294 295 testCases := []struct { 296 name string 297 failUpdate1 bool 298 failUpdate2 bool 299 failPublish bool 300 channels []*lnrpc.BatchOpenChannel 301 expectedErr string 302 }{{ 303 name: "happy path", 304 channels: []*lnrpc.BatchOpenChannel{{ 305 NodePubkey: testPubKey1Bytes, 306 LocalFundingAmount: 1234, 307 }, { 308 NodePubkey: testPubKey2Bytes, 309 LocalFundingAmount: 4321, 310 }}, 311 }, { 312 name: "initial negotiation failure", 313 failUpdate1: true, 314 channels: []*lnrpc.BatchOpenChannel{{ 315 NodePubkey: testPubKey1Bytes, 316 LocalFundingAmount: 1234, 317 }, { 318 NodePubkey: testPubKey2Bytes, 319 LocalFundingAmount: 4321, 320 }}, 321 expectedErr: "initial negotiation failed", 322 }, { 323 name: "final negotiation failure", 324 failUpdate2: true, 325 channels: []*lnrpc.BatchOpenChannel{{ 326 NodePubkey: testPubKey1Bytes, 327 LocalFundingAmount: 1234, 328 }, { 329 NodePubkey: testPubKey2Bytes, 330 LocalFundingAmount: 4321, 331 }}, 332 expectedErr: "final negotiation failed", 333 }, { 334 name: "publish failure", 335 failPublish: true, 336 channels: []*lnrpc.BatchOpenChannel{{ 337 NodePubkey: testPubKey1Bytes, 338 LocalFundingAmount: 1234, 339 }, { 340 NodePubkey: testPubKey2Bytes, 341 LocalFundingAmount: 4321, 342 }}, 343 expectedErr: "error publishing final batch transaction", 344 }} 345 346 for _, tc := range testCases { 347 tc := tc 348 349 t.Run(tc.name, func(t *testing.T) { 350 t.Parallel() 351 352 h := newTestHarness( 353 t, tc.failUpdate1, tc.failUpdate2, 354 tc.failPublish, 355 ) 356 357 req := &lnrpc.BatchOpenChannelRequest{ 358 Channels: tc.channels, 359 AtomsPerByte: 5, 360 MinConfs: 1, 361 } 362 updates, err := h.batcher.BatchFund( 363 context.Background(), req, 364 ) 365 366 if tc.failUpdate1 || tc.failUpdate2 || tc.failPublish { 367 require.Error(t, err) 368 require.Contains(t, err.Error(), tc.expectedErr) 369 } else { 370 require.NoError(t, err) 371 require.Len(t, updates, len(tc.channels)) 372 } 373 374 if tc.failUpdate1 { 375 require.Len(t, h.releasedUTXOs, 0) 376 require.Len(t, h.intentsCreated, 2) 377 for pid := range h.intentsCreated { 378 require.Contains( 379 t, h.intentsCanceled, pid, 380 ) 381 } 382 } 383 384 hash := h.pendingTx.TxHash() 385 if tc.failUpdate2 { 386 require.Len(t, h.releasedUTXOs, 1) 387 require.Len(t, h.intentsCreated, 2) 388 389 // If we fail on update 2 we do so on the second 390 // channel so one will be pending and one not 391 // yet. 392 require.Len(t, h.intentsCanceled, 1) 393 require.Len(t, h.abandonedChannels, 1) 394 require.Contains( 395 t, h.abandonedChannels, wire.OutPoint{ 396 Hash: hash, 397 Index: 1, 398 }, 399 ) 400 } 401 402 if tc.failPublish { 403 require.Len(t, h.releasedUTXOs, 1) 404 require.Len(t, h.intentsCreated, 2) 405 406 require.Len(t, h.intentsCanceled, 0) 407 require.Len(t, h.abandonedChannels, 2) 408 require.Contains( 409 t, h.abandonedChannels, wire.OutPoint{ 410 Hash: hash, 411 Index: 1, 412 }, 413 ) 414 require.Contains( 415 t, h.abandonedChannels, wire.OutPoint{ 416 Hash: hash, 417 Index: 2, 418 }, 419 ) 420 } 421 }) 422 } 423 }