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  }