itest: move helper functions into one file
This commit creates the file utils.go to hold the commonly used functions for tests.
This commit is contained in:
parent
27b9273e2f
commit
73a2211205
@ -1251,3 +1251,15 @@ func copyPorts(oldNode *lntest.HarnessNode) lntest.NodeOption {
|
||||
cfg.ProfilePort = oldNode.Cfg.ProfilePort
|
||||
}
|
||||
}
|
||||
|
||||
func rpcPointToWirePoint(t *harnessTest, chanPoint *lnrpc.ChannelPoint) wire.OutPoint {
|
||||
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get txid: %v", err)
|
||||
}
|
||||
|
||||
return wire.OutPoint{
|
||||
Hash: *txid,
|
||||
Index: chanPoint.OutputIndex,
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
@ -20,10 +19,8 @@ import (
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/funding"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
@ -31,7 +28,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -102,343 +98,6 @@ func getTestCaseSplitTranche() ([]*testCase, uint, uint) {
|
||||
return allTestCases[trancheOffset:trancheEnd], threadID, trancheOffset
|
||||
}
|
||||
|
||||
func rpcPointToWirePoint(t *harnessTest, chanPoint *lnrpc.ChannelPoint) wire.OutPoint {
|
||||
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get txid: %v", err)
|
||||
}
|
||||
|
||||
return wire.OutPoint{
|
||||
Hash: *txid,
|
||||
Index: chanPoint.OutputIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// completePaymentRequests sends payments from a lightning node to complete all
|
||||
// payment requests. If the awaitResponse parameter is true, this function
|
||||
// does not return until all payments successfully complete without errors.
|
||||
func completePaymentRequests(ctx context.Context, client lnrpc.LightningClient,
|
||||
routerClient routerrpc.RouterClient, paymentRequests []string,
|
||||
awaitResponse bool) error {
|
||||
|
||||
// We start by getting the current state of the client's channels. This
|
||||
// is needed to ensure the payments actually have been committed before
|
||||
// we return.
|
||||
ctxt, _ := context.WithTimeout(ctx, defaultTimeout)
|
||||
req := &lnrpc.ListChannelsRequest{}
|
||||
listResp, err := client.ListChannels(ctxt, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// send sends a payment and returns an error if it doesn't succeeded.
|
||||
send := func(payReq string) error {
|
||||
ctxc, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
payStream, err := routerClient.SendPaymentV2(
|
||||
ctxc,
|
||||
&routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: payReq,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := getPaymentResult(payStream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Status != lnrpc.Payment_SUCCEEDED {
|
||||
return errors.New(resp.FailureReason)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Launch all payments simultaneously.
|
||||
results := make(chan error)
|
||||
for _, payReq := range paymentRequests {
|
||||
payReqCopy := payReq
|
||||
go func() {
|
||||
err := send(payReqCopy)
|
||||
if awaitResponse {
|
||||
results <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// If awaiting a response, verify that all payments succeeded.
|
||||
if awaitResponse {
|
||||
for range paymentRequests {
|
||||
err := <-results
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// We are not waiting for feedback in the form of a response, but we
|
||||
// should still wait long enough for the server to receive and handle
|
||||
// the send before cancelling the request. We wait for the number of
|
||||
// updates to one of our channels has increased before we return.
|
||||
err = wait.Predicate(func() bool {
|
||||
ctxt, _ = context.WithTimeout(ctx, defaultTimeout)
|
||||
newListResp, err := client.ListChannels(ctxt, req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the number of open channels is now lower than before
|
||||
// attempting the payments, it means one of the payments
|
||||
// triggered a force closure (for example, due to an incorrect
|
||||
// preimage). Return early since it's clear the payment was
|
||||
// attempted.
|
||||
if len(newListResp.Channels) < len(listResp.Channels) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, c1 := range listResp.Channels {
|
||||
for _, c2 := range newListResp.Channels {
|
||||
if c1.ChannelPoint != c2.ChannelPoint {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this channel has an increased numbr of
|
||||
// updates, we assume the payments are
|
||||
// committed, and we can return.
|
||||
if c2.NumUpdates > c1.NumUpdates {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}, defaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeFakePayHash creates random pre image hash
|
||||
func makeFakePayHash(t *harnessTest) []byte {
|
||||
randBuf := make([]byte, 32)
|
||||
|
||||
if _, err := rand.Read(randBuf); err != nil {
|
||||
t.Fatalf("internal error, cannot generate random string: %v", err)
|
||||
}
|
||||
|
||||
return randBuf
|
||||
}
|
||||
|
||||
// createPayReqs is a helper method that will create a slice of payment
|
||||
// requests for the given node.
|
||||
func createPayReqs(node *lntest.HarnessNode, paymentAmt btcutil.Amount,
|
||||
numInvoices int) ([]string, [][]byte, []*lnrpc.Invoice, error) {
|
||||
|
||||
payReqs := make([]string, numInvoices)
|
||||
rHashes := make([][]byte, numInvoices)
|
||||
invoices := make([]*lnrpc.Invoice, numInvoices)
|
||||
for i := 0; i < numInvoices; i++ {
|
||||
preimage := make([]byte, 32)
|
||||
_, err := rand.Read(preimage)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("unable to generate "+
|
||||
"preimage: %v", err)
|
||||
}
|
||||
invoice := &lnrpc.Invoice{
|
||||
Memo: "testing",
|
||||
RPreimage: preimage,
|
||||
Value: int64(paymentAmt),
|
||||
}
|
||||
ctxt, _ := context.WithTimeout(
|
||||
context.Background(), defaultTimeout,
|
||||
)
|
||||
resp, err := node.AddInvoice(ctxt, invoice)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("unable to add "+
|
||||
"invoice: %v", err)
|
||||
}
|
||||
|
||||
// Set the payment address in the invoice so the caller can
|
||||
// properly use it.
|
||||
invoice.PaymentAddr = resp.PaymentAddr
|
||||
|
||||
payReqs[i] = resp.PaymentRequest
|
||||
rHashes[i] = resp.RHash
|
||||
invoices[i] = invoice
|
||||
}
|
||||
return payReqs, rHashes, invoices, nil
|
||||
}
|
||||
|
||||
// getChanInfo is a helper method for getting channel info for a node's sole
|
||||
// channel.
|
||||
func getChanInfo(ctx context.Context, node *lntest.HarnessNode) (
|
||||
*lnrpc.Channel, error) {
|
||||
|
||||
req := &lnrpc.ListChannelsRequest{}
|
||||
channelInfo, err := node.ListChannels(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(channelInfo.Channels) != 1 {
|
||||
return nil, fmt.Errorf("node should only have a single "+
|
||||
"channel, instead it has %v", len(channelInfo.Channels))
|
||||
}
|
||||
|
||||
return channelInfo.Channels[0], nil
|
||||
}
|
||||
|
||||
// commitType is a simple enum used to run though the basic funding flow with
|
||||
// different commitment formats.
|
||||
type commitType byte
|
||||
|
||||
const (
|
||||
// commitTypeLegacy is the old school commitment type.
|
||||
commitTypeLegacy commitType = iota
|
||||
|
||||
// commiTypeTweakless is the commitment type where the remote key is
|
||||
// static (non-tweaked).
|
||||
commitTypeTweakless
|
||||
|
||||
// commitTypeAnchors is the kind of commitment that has extra outputs
|
||||
// used for anchoring down to commitment using CPFP.
|
||||
commitTypeAnchors
|
||||
)
|
||||
|
||||
// String returns that name of the commitment type.
|
||||
func (c commitType) String() string {
|
||||
switch c {
|
||||
case commitTypeLegacy:
|
||||
return "legacy"
|
||||
case commitTypeTweakless:
|
||||
return "tweakless"
|
||||
case commitTypeAnchors:
|
||||
return "anchors"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// Args returns the command line flag to supply to enable this commitment type.
|
||||
func (c commitType) Args() []string {
|
||||
switch c {
|
||||
case commitTypeLegacy:
|
||||
return []string{"--protocol.legacy.committweak"}
|
||||
case commitTypeTweakless:
|
||||
return []string{}
|
||||
case commitTypeAnchors:
|
||||
return []string{"--protocol.anchors"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcStaticFee calculates appropriate fees for commitment transactions. This
|
||||
// function provides a simple way to allow test balance assertions to take fee
|
||||
// calculations into account.
|
||||
func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount {
|
||||
const htlcWeight = input.HTLCWeight
|
||||
var (
|
||||
feePerKw = chainfee.SatPerKVByte(50000).FeePerKWeight()
|
||||
commitWeight = input.CommitWeight
|
||||
anchors = btcutil.Amount(0)
|
||||
)
|
||||
|
||||
// The anchor commitment type is slightly heavier, and we must also add
|
||||
// the value of the two anchors to the resulting fee the initiator
|
||||
// pays. In addition the fee rate is capped at 10 sat/vbyte for anchor
|
||||
// channels.
|
||||
if c == commitTypeAnchors {
|
||||
feePerKw = chainfee.SatPerKVByte(
|
||||
lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte * 1000,
|
||||
).FeePerKWeight()
|
||||
commitWeight = input.AnchorCommitWeight
|
||||
anchors = 2 * anchorSize
|
||||
}
|
||||
|
||||
return feePerKw.FeeForWeight(int64(commitWeight+htlcWeight*numHTLCs)) +
|
||||
anchors
|
||||
}
|
||||
|
||||
// channelCommitType retrieves the active channel commitment type for the given
|
||||
// chan point.
|
||||
func channelCommitType(node *lntest.HarnessNode,
|
||||
chanPoint *lnrpc.ChannelPoint) (commitType, error) {
|
||||
|
||||
ctxb := context.Background()
|
||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||
|
||||
req := &lnrpc.ListChannelsRequest{}
|
||||
channels, err := node.ListChannels(ctxt, req)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("listchannels failed: %v", err)
|
||||
}
|
||||
|
||||
for _, c := range channels.Channels {
|
||||
if c.ChannelPoint == txStr(chanPoint) {
|
||||
switch c.CommitmentType {
|
||||
|
||||
// If the anchor output size is non-zero, we are
|
||||
// dealing with the anchor type.
|
||||
case lnrpc.CommitmentType_ANCHORS:
|
||||
return commitTypeAnchors, nil
|
||||
|
||||
// StaticRemoteKey means it is tweakless,
|
||||
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
|
||||
return commitTypeTweakless, nil
|
||||
|
||||
// Otherwise legacy.
|
||||
default:
|
||||
return commitTypeLegacy, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("channel point %v not found", chanPoint)
|
||||
}
|
||||
|
||||
// calculateMaxHtlc re-implements the RequiredRemoteChannelReserve of the
|
||||
// funding manager's config, which corresponds to the maximum MaxHTLC value we
|
||||
// allow users to set when updating a channel policy.
|
||||
func calculateMaxHtlc(chanCap btcutil.Amount) uint64 {
|
||||
reserve := lnwire.NewMSatFromSatoshis(chanCap / 100)
|
||||
max := lnwire.NewMSatFromSatoshis(chanCap) - reserve
|
||||
return uint64(max)
|
||||
}
|
||||
|
||||
// waitForNodeBlockHeight queries the node for its current block height until
|
||||
// it reaches the passed height.
|
||||
func waitForNodeBlockHeight(ctx context.Context, node *lntest.HarnessNode,
|
||||
height int32) error {
|
||||
var predErr error
|
||||
err := wait.Predicate(func() bool {
|
||||
ctxt, _ := context.WithTimeout(ctx, defaultTimeout)
|
||||
info, err := node.GetInfo(ctxt, &lnrpc.GetInfoRequest{})
|
||||
if err != nil {
|
||||
predErr = err
|
||||
return false
|
||||
}
|
||||
|
||||
if int32(info.BlockHeight) != height {
|
||||
predErr = fmt.Errorf("expected block height to "+
|
||||
"be %v, was %v", height, info.BlockHeight)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, defaultTimeout)
|
||||
if err != nil {
|
||||
return predErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// testDisconnectingTargetPeer performs a test which disconnects Alice-peer from
|
||||
// Bob-peer and then re-connects them again. We expect Alice to be able to
|
||||
// disconnect at any point.
|
||||
@ -589,29 +248,6 @@ func testDisconnectingTargetPeer(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
cleanupForceClose(t, net, alice, chanPoint)
|
||||
}
|
||||
|
||||
// findTxAtHeight gets all of the transactions that a node's wallet has a record
|
||||
// of at the target height, and finds and returns the tx with the target txid,
|
||||
// failing if it is not found.
|
||||
func findTxAtHeight(ctx context.Context, t *harnessTest, height int32,
|
||||
target string, node *lntest.HarnessNode) *lnrpc.Transaction {
|
||||
|
||||
txns, err := node.LightningClient.GetTransactions(
|
||||
ctx, &lnrpc.GetTransactionsRequest{
|
||||
StartHeight: height,
|
||||
EndHeight: height,
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err, "could not get transactions")
|
||||
|
||||
for _, tx := range txns.Transactions {
|
||||
if tx.TxHash == target {
|
||||
return tx
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// testSphinxReplayPersistence verifies that replayed onion packets are rejected
|
||||
// by a remote peer after a restart. We use a combination of unsafe
|
||||
// configuration arguments to force Carol to replay the same sphinx packet after
|
||||
@ -933,75 +569,6 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
|
||||
}
|
||||
|
||||
// channelSubscription houses the proxied update and error chans for a node's
|
||||
// channel subscriptions.
|
||||
type channelSubscription struct {
|
||||
updateChan chan *lnrpc.ChannelEventUpdate
|
||||
errChan chan error
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// subscribeChannelNotifications subscribes to channel updates and launches a
|
||||
// goroutine that forwards these to the returned channel.
|
||||
func subscribeChannelNotifications(ctxb context.Context, t *harnessTest,
|
||||
node *lntest.HarnessNode) channelSubscription {
|
||||
|
||||
// We'll first start by establishing a notification client which will
|
||||
// send us notifications upon channels becoming active, inactive or
|
||||
// closed.
|
||||
req := &lnrpc.ChannelEventSubscription{}
|
||||
ctx, cancelFunc := context.WithCancel(ctxb)
|
||||
|
||||
chanUpdateClient, err := node.SubscribeChannelEvents(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create channel update client: %v", err)
|
||||
}
|
||||
|
||||
// We'll launch a goroutine that will be responsible for proxying all
|
||||
// notifications recv'd from the client into the channel below.
|
||||
errChan := make(chan error, 1)
|
||||
quit := make(chan struct{})
|
||||
chanUpdates := make(chan *lnrpc.ChannelEventUpdate, 20)
|
||||
go func() {
|
||||
defer cancelFunc()
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
chanUpdate, err := chanUpdateClient.Recv()
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
select {
|
||||
case errChan <- err:
|
||||
case <-quit:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case chanUpdates <- chanUpdate:
|
||||
case <-quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return channelSubscription{
|
||||
updateChan: chanUpdates,
|
||||
errChan: errChan,
|
||||
quit: quit,
|
||||
}
|
||||
}
|
||||
|
||||
// testMaxPendingChannels checks that error is returned from remote peer if
|
||||
// max pending channel number was exceeded and that '--maxpendingchannels' flag
|
||||
// exists and works properly.
|
||||
@ -1114,49 +681,6 @@ func testMaxPendingChannels(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// getNTxsFromMempool polls until finding the desired number of transactions in
|
||||
// the provided miner's mempool and returns the full transactions to the caller.
|
||||
func getNTxsFromMempool(miner *rpcclient.Client, n int,
|
||||
timeout time.Duration) ([]*wire.MsgTx, error) {
|
||||
|
||||
txids, err := waitForNTxsInMempool(miner, n, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var txes []*wire.MsgTx
|
||||
for _, txid := range txids {
|
||||
tx, err := miner.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txes = append(txes, tx.MsgTx())
|
||||
}
|
||||
return txes, nil
|
||||
}
|
||||
|
||||
// getTxFee retrieves parent transactions and reconstructs the fee paid.
|
||||
func getTxFee(miner *rpcclient.Client, tx *wire.MsgTx) (btcutil.Amount, error) {
|
||||
var balance btcutil.Amount
|
||||
for _, in := range tx.TxIn {
|
||||
parentHash := in.PreviousOutPoint.Hash
|
||||
rawTx, err := miner.GetRawTransaction(&parentHash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
parent := rawTx.MsgTx()
|
||||
balance += btcutil.Amount(
|
||||
parent.TxOut[in.PreviousOutPoint.Index].Value,
|
||||
)
|
||||
}
|
||||
|
||||
for _, out := range tx.TxOut {
|
||||
balance -= btcutil.Amount(out.Value)
|
||||
}
|
||||
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
// testGarbageCollectLinkNodes tests that we properly garbase collect link nodes
|
||||
// from the database and the set of persistent connections within the server.
|
||||
func testGarbageCollectLinkNodes(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
|
483
lntest/itest/utils.go
Normal file
483
lntest/itest/utils.go
Normal file
@ -0,0 +1,483 @@
|
||||
package itest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// completePaymentRequests sends payments from a lightning node to complete all
|
||||
// payment requests. If the awaitResponse parameter is true, this function
|
||||
// does not return until all payments successfully complete without errors.
|
||||
func completePaymentRequests(ctx context.Context, client lnrpc.LightningClient,
|
||||
routerClient routerrpc.RouterClient, paymentRequests []string,
|
||||
awaitResponse bool) error {
|
||||
|
||||
// We start by getting the current state of the client's channels. This
|
||||
// is needed to ensure the payments actually have been committed before
|
||||
// we return.
|
||||
ctxt, _ := context.WithTimeout(ctx, defaultTimeout)
|
||||
req := &lnrpc.ListChannelsRequest{}
|
||||
listResp, err := client.ListChannels(ctxt, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// send sends a payment and returns an error if it doesn't succeeded.
|
||||
send := func(payReq string) error {
|
||||
ctxc, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
payStream, err := routerClient.SendPaymentV2(
|
||||
ctxc,
|
||||
&routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: payReq,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := getPaymentResult(payStream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Status != lnrpc.Payment_SUCCEEDED {
|
||||
return errors.New(resp.FailureReason)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Launch all payments simultaneously.
|
||||
results := make(chan error)
|
||||
for _, payReq := range paymentRequests {
|
||||
payReqCopy := payReq
|
||||
go func() {
|
||||
err := send(payReqCopy)
|
||||
if awaitResponse {
|
||||
results <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// If awaiting a response, verify that all payments succeeded.
|
||||
if awaitResponse {
|
||||
for range paymentRequests {
|
||||
err := <-results
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// We are not waiting for feedback in the form of a response, but we
|
||||
// should still wait long enough for the server to receive and handle
|
||||
// the send before cancelling the request. We wait for the number of
|
||||
// updates to one of our channels has increased before we return.
|
||||
err = wait.Predicate(func() bool {
|
||||
ctxt, _ = context.WithTimeout(ctx, defaultTimeout)
|
||||
newListResp, err := client.ListChannels(ctxt, req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the number of open channels is now lower than before
|
||||
// attempting the payments, it means one of the payments
|
||||
// triggered a force closure (for example, due to an incorrect
|
||||
// preimage). Return early since it's clear the payment was
|
||||
// attempted.
|
||||
if len(newListResp.Channels) < len(listResp.Channels) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, c1 := range listResp.Channels {
|
||||
for _, c2 := range newListResp.Channels {
|
||||
if c1.ChannelPoint != c2.ChannelPoint {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this channel has an increased numbr of
|
||||
// updates, we assume the payments are
|
||||
// committed, and we can return.
|
||||
if c2.NumUpdates > c1.NumUpdates {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}, defaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeFakePayHash creates random pre image hash
|
||||
func makeFakePayHash(t *harnessTest) []byte {
|
||||
randBuf := make([]byte, 32)
|
||||
|
||||
if _, err := rand.Read(randBuf); err != nil {
|
||||
t.Fatalf("internal error, cannot generate random string: %v", err)
|
||||
}
|
||||
|
||||
return randBuf
|
||||
}
|
||||
|
||||
// createPayReqs is a helper method that will create a slice of payment
|
||||
// requests for the given node.
|
||||
func createPayReqs(node *lntest.HarnessNode, paymentAmt btcutil.Amount,
|
||||
numInvoices int) ([]string, [][]byte, []*lnrpc.Invoice, error) {
|
||||
|
||||
payReqs := make([]string, numInvoices)
|
||||
rHashes := make([][]byte, numInvoices)
|
||||
invoices := make([]*lnrpc.Invoice, numInvoices)
|
||||
for i := 0; i < numInvoices; i++ {
|
||||
preimage := make([]byte, 32)
|
||||
_, err := rand.Read(preimage)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("unable to generate "+
|
||||
"preimage: %v", err)
|
||||
}
|
||||
invoice := &lnrpc.Invoice{
|
||||
Memo: "testing",
|
||||
RPreimage: preimage,
|
||||
Value: int64(paymentAmt),
|
||||
}
|
||||
ctxt, _ := context.WithTimeout(
|
||||
context.Background(), defaultTimeout,
|
||||
)
|
||||
resp, err := node.AddInvoice(ctxt, invoice)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("unable to add "+
|
||||
"invoice: %v", err)
|
||||
}
|
||||
|
||||
// Set the payment address in the invoice so the caller can
|
||||
// properly use it.
|
||||
invoice.PaymentAddr = resp.PaymentAddr
|
||||
|
||||
payReqs[i] = resp.PaymentRequest
|
||||
rHashes[i] = resp.RHash
|
||||
invoices[i] = invoice
|
||||
}
|
||||
return payReqs, rHashes, invoices, nil
|
||||
}
|
||||
|
||||
// getChanInfo is a helper method for getting channel info for a node's sole
|
||||
// channel.
|
||||
func getChanInfo(ctx context.Context, node *lntest.HarnessNode) (
|
||||
*lnrpc.Channel, error) {
|
||||
|
||||
req := &lnrpc.ListChannelsRequest{}
|
||||
channelInfo, err := node.ListChannels(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(channelInfo.Channels) != 1 {
|
||||
return nil, fmt.Errorf("node should only have a single "+
|
||||
"channel, instead it has %v", len(channelInfo.Channels))
|
||||
}
|
||||
|
||||
return channelInfo.Channels[0], nil
|
||||
}
|
||||
|
||||
// commitType is a simple enum used to run though the basic funding flow with
|
||||
// different commitment formats.
|
||||
type commitType byte
|
||||
|
||||
const (
|
||||
// commitTypeLegacy is the old school commitment type.
|
||||
commitTypeLegacy commitType = iota
|
||||
|
||||
// commiTypeTweakless is the commitment type where the remote key is
|
||||
// static (non-tweaked).
|
||||
commitTypeTweakless
|
||||
|
||||
// commitTypeAnchors is the kind of commitment that has extra outputs
|
||||
// used for anchoring down to commitment using CPFP.
|
||||
commitTypeAnchors
|
||||
)
|
||||
|
||||
// String returns that name of the commitment type.
|
||||
func (c commitType) String() string {
|
||||
switch c {
|
||||
case commitTypeLegacy:
|
||||
return "legacy"
|
||||
case commitTypeTweakless:
|
||||
return "tweakless"
|
||||
case commitTypeAnchors:
|
||||
return "anchors"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// Args returns the command line flag to supply to enable this commitment type.
|
||||
func (c commitType) Args() []string {
|
||||
switch c {
|
||||
case commitTypeLegacy:
|
||||
return []string{"--protocol.legacy.committweak"}
|
||||
case commitTypeTweakless:
|
||||
return []string{}
|
||||
case commitTypeAnchors:
|
||||
return []string{"--protocol.anchors"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcStaticFee calculates appropriate fees for commitment transactions. This
|
||||
// function provides a simple way to allow test balance assertions to take fee
|
||||
// calculations into account.
|
||||
func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount {
|
||||
const htlcWeight = input.HTLCWeight
|
||||
var (
|
||||
feePerKw = chainfee.SatPerKVByte(50000).FeePerKWeight()
|
||||
commitWeight = input.CommitWeight
|
||||
anchors = btcutil.Amount(0)
|
||||
)
|
||||
|
||||
// The anchor commitment type is slightly heavier, and we must also add
|
||||
// the value of the two anchors to the resulting fee the initiator
|
||||
// pays. In addition the fee rate is capped at 10 sat/vbyte for anchor
|
||||
// channels.
|
||||
if c == commitTypeAnchors {
|
||||
feePerKw = chainfee.SatPerKVByte(
|
||||
lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte * 1000,
|
||||
).FeePerKWeight()
|
||||
commitWeight = input.AnchorCommitWeight
|
||||
anchors = 2 * anchorSize
|
||||
}
|
||||
|
||||
return feePerKw.FeeForWeight(int64(commitWeight+htlcWeight*numHTLCs)) +
|
||||
anchors
|
||||
}
|
||||
|
||||
// channelCommitType retrieves the active channel commitment type for the given
|
||||
// chan point.
|
||||
func channelCommitType(node *lntest.HarnessNode,
|
||||
chanPoint *lnrpc.ChannelPoint) (commitType, error) {
|
||||
|
||||
ctxb := context.Background()
|
||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||
|
||||
req := &lnrpc.ListChannelsRequest{}
|
||||
channels, err := node.ListChannels(ctxt, req)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("listchannels failed: %v", err)
|
||||
}
|
||||
|
||||
for _, c := range channels.Channels {
|
||||
if c.ChannelPoint == txStr(chanPoint) {
|
||||
switch c.CommitmentType {
|
||||
|
||||
// If the anchor output size is non-zero, we are
|
||||
// dealing with the anchor type.
|
||||
case lnrpc.CommitmentType_ANCHORS:
|
||||
return commitTypeAnchors, nil
|
||||
|
||||
// StaticRemoteKey means it is tweakless,
|
||||
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
|
||||
return commitTypeTweakless, nil
|
||||
|
||||
// Otherwise legacy.
|
||||
default:
|
||||
return commitTypeLegacy, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("channel point %v not found", chanPoint)
|
||||
}
|
||||
|
||||
// calculateMaxHtlc re-implements the RequiredRemoteChannelReserve of the
|
||||
// funding manager's config, which corresponds to the maximum MaxHTLC value we
|
||||
// allow users to set when updating a channel policy.
|
||||
func calculateMaxHtlc(chanCap btcutil.Amount) uint64 {
|
||||
reserve := lnwire.NewMSatFromSatoshis(chanCap / 100)
|
||||
max := lnwire.NewMSatFromSatoshis(chanCap) - reserve
|
||||
return uint64(max)
|
||||
}
|
||||
|
||||
// waitForNodeBlockHeight queries the node for its current block height until
|
||||
// it reaches the passed height.
|
||||
func waitForNodeBlockHeight(ctx context.Context, node *lntest.HarnessNode,
|
||||
height int32) error {
|
||||
var predErr error
|
||||
err := wait.Predicate(func() bool {
|
||||
ctxt, _ := context.WithTimeout(ctx, defaultTimeout)
|
||||
info, err := node.GetInfo(ctxt, &lnrpc.GetInfoRequest{})
|
||||
if err != nil {
|
||||
predErr = err
|
||||
return false
|
||||
}
|
||||
|
||||
if int32(info.BlockHeight) != height {
|
||||
predErr = fmt.Errorf("expected block height to "+
|
||||
"be %v, was %v", height, info.BlockHeight)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, defaultTimeout)
|
||||
if err != nil {
|
||||
return predErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNTxsFromMempool polls until finding the desired number of transactions in
|
||||
// the provided miner's mempool and returns the full transactions to the caller.
|
||||
func getNTxsFromMempool(miner *rpcclient.Client, n int,
|
||||
timeout time.Duration) ([]*wire.MsgTx, error) {
|
||||
|
||||
txids, err := waitForNTxsInMempool(miner, n, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var txes []*wire.MsgTx
|
||||
for _, txid := range txids {
|
||||
tx, err := miner.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txes = append(txes, tx.MsgTx())
|
||||
}
|
||||
return txes, nil
|
||||
}
|
||||
|
||||
// getTxFee retrieves parent transactions and reconstructs the fee paid.
|
||||
func getTxFee(miner *rpcclient.Client, tx *wire.MsgTx) (btcutil.Amount, error) {
|
||||
var balance btcutil.Amount
|
||||
for _, in := range tx.TxIn {
|
||||
parentHash := in.PreviousOutPoint.Hash
|
||||
rawTx, err := miner.GetRawTransaction(&parentHash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
parent := rawTx.MsgTx()
|
||||
balance += btcutil.Amount(
|
||||
parent.TxOut[in.PreviousOutPoint.Index].Value,
|
||||
)
|
||||
}
|
||||
|
||||
for _, out := range tx.TxOut {
|
||||
balance -= btcutil.Amount(out.Value)
|
||||
}
|
||||
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
// channelSubscription houses the proxied update and error chans for a node's
|
||||
// channel subscriptions.
|
||||
type channelSubscription struct {
|
||||
updateChan chan *lnrpc.ChannelEventUpdate
|
||||
errChan chan error
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// subscribeChannelNotifications subscribes to channel updates and launches a
|
||||
// goroutine that forwards these to the returned channel.
|
||||
func subscribeChannelNotifications(ctxb context.Context, t *harnessTest,
|
||||
node *lntest.HarnessNode) channelSubscription {
|
||||
|
||||
// We'll first start by establishing a notification client which will
|
||||
// send us notifications upon channels becoming active, inactive or
|
||||
// closed.
|
||||
req := &lnrpc.ChannelEventSubscription{}
|
||||
ctx, cancelFunc := context.WithCancel(ctxb)
|
||||
|
||||
chanUpdateClient, err := node.SubscribeChannelEvents(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create channel update client: %v", err)
|
||||
}
|
||||
|
||||
// We'll launch a goroutine that will be responsible for proxying all
|
||||
// notifications recv'd from the client into the channel below.
|
||||
errChan := make(chan error, 1)
|
||||
quit := make(chan struct{})
|
||||
chanUpdates := make(chan *lnrpc.ChannelEventUpdate, 20)
|
||||
go func() {
|
||||
defer cancelFunc()
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
chanUpdate, err := chanUpdateClient.Recv()
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
select {
|
||||
case errChan <- err:
|
||||
case <-quit:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case chanUpdates <- chanUpdate:
|
||||
case <-quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return channelSubscription{
|
||||
updateChan: chanUpdates,
|
||||
errChan: errChan,
|
||||
quit: quit,
|
||||
}
|
||||
}
|
||||
|
||||
// findTxAtHeight gets all of the transactions that a node's wallet has a record
|
||||
// of at the target height, and finds and returns the tx with the target txid,
|
||||
// failing if it is not found.
|
||||
func findTxAtHeight(ctx context.Context, t *harnessTest, height int32,
|
||||
target string, node *lntest.HarnessNode) *lnrpc.Transaction {
|
||||
|
||||
txns, err := node.LightningClient.GetTransactions(
|
||||
ctx, &lnrpc.GetTransactionsRequest{
|
||||
StartHeight: height,
|
||||
EndHeight: height,
|
||||
},
|
||||
)
|
||||
require.NoError(t.t, err, "could not get transactions")
|
||||
|
||||
for _, tx := range txns.Transactions {
|
||||
if tx.TxHash == target {
|
||||
return tx
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user