mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-17 12:21:41 +02:00
Merge pull request #1635 from halseth/funding-broadcast-fail
Move funding tx broadcasting to Fundingmanager
This commit is contained in:
commit
d64bb5921e
@ -961,23 +961,42 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
|||||||
// Check number of pending channels to be smaller than maximum allowed
|
// Check number of pending channels to be smaller than maximum allowed
|
||||||
// number and send ErrorGeneric to remote peer if condition is
|
// number and send ErrorGeneric to remote peer if condition is
|
||||||
// violated.
|
// violated.
|
||||||
peerIDKey := newSerializedKey(fmsg.peer.IdentityKey())
|
peerPubKey := fmsg.peer.IdentityKey()
|
||||||
|
peerIDKey := newSerializedKey(peerPubKey)
|
||||||
|
|
||||||
msg := fmsg.msg
|
msg := fmsg.msg
|
||||||
amt := msg.FundingAmount
|
amt := msg.FundingAmount
|
||||||
|
|
||||||
|
// We count the number of pending channels for this peer. This is the
|
||||||
|
// sum of the active reservations and the channels pending open in the
|
||||||
|
// database.
|
||||||
|
f.resMtx.RLock()
|
||||||
|
numPending := len(f.activeReservations[peerIDKey])
|
||||||
|
f.resMtx.RUnlock()
|
||||||
|
|
||||||
|
channels, err := f.cfg.Wallet.Cfg.Database.FetchOpenChannels(peerPubKey)
|
||||||
|
if err != nil {
|
||||||
|
f.failFundingFlow(
|
||||||
|
fmsg.peer, fmsg.msg.PendingChannelID, err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range channels {
|
||||||
|
if c.IsPending {
|
||||||
|
numPending++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): modify to only accept a _single_ pending channel per
|
// TODO(roasbeef): modify to only accept a _single_ pending channel per
|
||||||
// block unless white listed
|
// block unless white listed
|
||||||
f.resMtx.RLock()
|
if numPending >= cfg.MaxPendingChannels {
|
||||||
if len(f.activeReservations[peerIDKey]) >= cfg.MaxPendingChannels {
|
|
||||||
f.resMtx.RUnlock()
|
|
||||||
f.failFundingFlow(
|
f.failFundingFlow(
|
||||||
fmsg.peer, fmsg.msg.PendingChannelID,
|
fmsg.peer, fmsg.msg.PendingChannelID,
|
||||||
lnwire.ErrMaxPendingChannels,
|
lnwire.ErrMaxPendingChannels,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.resMtx.RUnlock()
|
|
||||||
|
|
||||||
// We'll also reject any requests to create channels until we're fully
|
// We'll also reject any requests to create channels until we're fully
|
||||||
// synced to the network as we won't be able to properly validate the
|
// synced to the network as we won't be able to properly validate the
|
||||||
@ -1351,6 +1370,10 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The channel is marked IsPending in the database, and can be removed
|
||||||
|
// from the set of active reservations.
|
||||||
|
f.deleteReservationCtx(peerKey, fmsg.msg.PendingChannelID)
|
||||||
|
|
||||||
// If something goes wrong before the funding transaction is confirmed,
|
// If something goes wrong before the funding transaction is confirmed,
|
||||||
// we use this convenience method to delete the pending OpenChannel
|
// we use this convenience method to delete the pending OpenChannel
|
||||||
// from the database.
|
// from the database.
|
||||||
@ -1420,11 +1443,6 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
|
|||||||
|
|
||||||
// At this point we have sent our last funding message to the
|
// At this point we have sent our last funding message to the
|
||||||
// initiating peer before the funding transaction will be broadcast.
|
// initiating peer before the funding transaction will be broadcast.
|
||||||
// The only thing left to do before we can delete this reservation
|
|
||||||
// is wait for the funding transaction. Lock the reservation so it
|
|
||||||
// is not pruned by the zombie sweeper.
|
|
||||||
resCtx.lock()
|
|
||||||
|
|
||||||
// With this last message, our job as the responder is now complete.
|
// With this last message, our job as the responder is now complete.
|
||||||
// We'll wait for the funding transaction to reach the specified number
|
// We'll wait for the funding transaction to reach the specified number
|
||||||
// of confirmations, then start normal operations.
|
// of confirmations, then start normal operations.
|
||||||
@ -1473,8 +1491,6 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Success, funding transaction was confirmed.
|
// Success, funding transaction was confirmed.
|
||||||
f.deleteReservationCtx(peerKey, fmsg.msg.PendingChannelID)
|
|
||||||
|
|
||||||
err := f.handleFundingConfirmation(
|
err := f.handleFundingConfirmation(
|
||||||
fmsg.peer, completeChan, shortChanID,
|
fmsg.peer, completeChan, shortChanID,
|
||||||
)
|
)
|
||||||
@ -1542,13 +1558,36 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
|
|||||||
// transaction. We'll verify the signature for validity, then commit
|
// transaction. We'll verify the signature for validity, then commit
|
||||||
// the state to disk as we can now open the channel.
|
// the state to disk as we can now open the channel.
|
||||||
commitSig := fmsg.msg.CommitSig.ToSignatureBytes()
|
commitSig := fmsg.msg.CommitSig.ToSignatureBytes()
|
||||||
completeChan, err := resCtx.reservation.CompleteReservation(nil, commitSig)
|
completeChan, err := resCtx.reservation.CompleteReservation(
|
||||||
|
nil, commitSig,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fndgLog.Errorf("Unable to complete reservation sign complete: %v", err)
|
fndgLog.Errorf("Unable to complete reservation sign "+
|
||||||
|
"complete: %v", err)
|
||||||
f.failFundingFlow(fmsg.peer, pendingChanID, err)
|
f.failFundingFlow(fmsg.peer, pendingChanID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The channel is now marked IsPending in the database, and we can
|
||||||
|
// delete it from our set of active reservations.
|
||||||
|
f.deleteReservationCtx(peerKey, pendingChanID)
|
||||||
|
|
||||||
|
// Broadcast the finalized funding transaction to the network.
|
||||||
|
fundingTx := completeChan.FundingTxn
|
||||||
|
fndgLog.Infof("Broadcasting funding tx for ChannelPoint(%v): %v",
|
||||||
|
completeChan.FundingOutpoint, spew.Sdump(fundingTx))
|
||||||
|
|
||||||
|
err = f.cfg.PublishTransaction(fundingTx)
|
||||||
|
if err != nil {
|
||||||
|
fndgLog.Errorf("unable to broadcast funding "+
|
||||||
|
"txn: %v", err)
|
||||||
|
// We failed to broadcast the funding transaction, but watch
|
||||||
|
// the channel regardless, in case the transaction made it to
|
||||||
|
// the network. We will retry broadcast at startup.
|
||||||
|
// TODO(halseth): retry more often? Handle with CPFP? Just
|
||||||
|
// delete from the DB?
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we have a finalized reservation for this funding flow,
|
// Now that we have a finalized reservation for this funding flow,
|
||||||
// we'll send the to be active channel to the ChainArbitrator so it can
|
// we'll send the to be active channel to the ChainArbitrator so it can
|
||||||
// watch for any on-chin actions before the channel has fully
|
// watch for any on-chin actions before the channel has fully
|
||||||
@ -1576,11 +1615,7 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// At this point we have broadcast the funding transaction and done all
|
// At this point we have broadcast the funding transaction and done all
|
||||||
// necessary processing. The only thing left to do before we can delete
|
// necessary processing.
|
||||||
// this reservation is wait for the funding transaction. Lock the
|
|
||||||
// reservation so it is not pruned by the zombie sweeper.
|
|
||||||
resCtx.lock()
|
|
||||||
|
|
||||||
f.wg.Add(1)
|
f.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer f.wg.Done()
|
defer f.wg.Done()
|
||||||
@ -1659,8 +1694,6 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f.deleteReservationCtx(peerKey, pendingChanID)
|
|
||||||
|
|
||||||
err = f.annAfterSixConfs(completeChan, shortChanID)
|
err = f.annAfterSixConfs(completeChan, shortChanID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fndgLog.Errorf("failed sending channel announcement: %v",
|
fndgLog.Errorf("failed sending channel announcement: %v",
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -249,8 +250,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
shutdownChan := make(chan struct{})
|
shutdownChan := make(chan struct{})
|
||||||
|
|
||||||
wc := &mockWalletController{
|
wc := &mockWalletController{
|
||||||
rootKey: alicePrivKey,
|
rootKey: alicePrivKey,
|
||||||
publishedTransactions: publTxChan,
|
|
||||||
}
|
}
|
||||||
signer := &mockSigner{
|
signer := &mockSigner{
|
||||||
key: alicePrivKey,
|
key: alicePrivKey,
|
||||||
@ -350,6 +350,10 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
ReportShortChanID: func(wire.OutPoint) error {
|
ReportShortChanID: func(wire.OutPoint) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
PublishTransaction: func(txn *wire.MsgTx) error {
|
||||||
|
publTxChan <- txn
|
||||||
|
return nil
|
||||||
|
},
|
||||||
ZombieSweeperInterval: 1 * time.Hour,
|
ZombieSweeperInterval: 1 * time.Hour,
|
||||||
ReservationTimeout: 1 * time.Nanosecond,
|
ReservationTimeout: 1 * time.Nanosecond,
|
||||||
})
|
})
|
||||||
@ -445,11 +449,11 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupFundingManagers(t *testing.T) (*testNode, *testNode) {
|
func setupFundingManagers(t *testing.T, maxPendingChannels int) (*testNode, *testNode) {
|
||||||
// We need to set the global config, as fundingManager uses
|
// We need to set the global config, as fundingManager uses
|
||||||
// MaxPendingChannels, and it is usually set in lndMain().
|
// MaxPendingChannels, and it is usually set in lndMain().
|
||||||
cfg = &config{
|
cfg = &config{
|
||||||
MaxPendingChannels: defaultMaxPendingChannels,
|
MaxPendingChannels: maxPendingChannels,
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceTestDir, err := ioutil.TempDir("", "alicelnwallet")
|
aliceTestDir, err := ioutil.TempDir("", "alicelnwallet")
|
||||||
@ -566,6 +570,11 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
|||||||
t, bob.msgChan, "AcceptChannel",
|
t, bob.msgChan, "AcceptChannel",
|
||||||
).(*lnwire.AcceptChannel)
|
).(*lnwire.AcceptChannel)
|
||||||
|
|
||||||
|
// They now should both have pending reservations for this channel
|
||||||
|
// active.
|
||||||
|
assertNumPendingReservations(t, alice, bobPubKey, 1)
|
||||||
|
assertNumPendingReservations(t, bob, alicePubKey, 1)
|
||||||
|
|
||||||
// Forward the response to Alice.
|
// Forward the response to Alice.
|
||||||
alice.fundingMgr.processFundingAccept(acceptChannelResponse, bob)
|
alice.fundingMgr.processFundingAccept(acceptChannelResponse, bob)
|
||||||
|
|
||||||
@ -612,6 +621,12 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
|||||||
Hash: publ.TxHash(),
|
Hash: publ.TxHash(),
|
||||||
Index: 0,
|
Index: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, make sure neither have active reservation for the channel
|
||||||
|
// now pending open in the database.
|
||||||
|
assertNumPendingReservations(t, alice, bobPubKey, 0)
|
||||||
|
assertNumPendingReservations(t, bob, alicePubKey, 0)
|
||||||
|
|
||||||
return fundingOutPoint
|
return fundingOutPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,6 +675,8 @@ func assertFundingMsgSent(t *testing.T, msgChan chan lnwire.Message,
|
|||||||
sentMsg, ok = msg.(*lnwire.FundingSigned)
|
sentMsg, ok = msg.(*lnwire.FundingSigned)
|
||||||
case "FundingLocked":
|
case "FundingLocked":
|
||||||
sentMsg, ok = msg.(*lnwire.FundingLocked)
|
sentMsg, ok = msg.(*lnwire.FundingLocked)
|
||||||
|
case "Error":
|
||||||
|
sentMsg, ok = msg.(*lnwire.Error)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unknown message type: %s", msgType)
|
t.Fatalf("unknown message type: %s", msgType)
|
||||||
}
|
}
|
||||||
@ -670,8 +687,10 @@ func assertFundingMsgSent(t *testing.T, msgChan chan lnwire.Message,
|
|||||||
t.Fatalf("expected %s to be sent, instead got error: %v",
|
t.Fatalf("expected %s to be sent, instead got error: %v",
|
||||||
msgType, lnwire.ErrorCode(errorMsg.Data[0]))
|
msgType, lnwire.ErrorCode(errorMsg.Data[0]))
|
||||||
}
|
}
|
||||||
t.Fatalf("expected %s to be sent, instead got %T",
|
|
||||||
msgType, msg)
|
_, _, line, _ := runtime.Caller(1)
|
||||||
|
t.Fatalf("expected %s to be sent, instead got %T at %v",
|
||||||
|
msgType, msg, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sentMsg
|
return sentMsg
|
||||||
@ -937,7 +956,7 @@ func assertHandleFundingLocked(t *testing.T, alice, bob *testNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFundingManagerNormalWorkflow(t *testing.T) {
|
func TestFundingManagerNormalWorkflow(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -948,19 +967,10 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
|
|||||||
fundingOutPoint := openChannel(t, alice, bob, 500000, 0, 1, updateChan,
|
fundingOutPoint := openChannel(t, alice, bob, 500000, 0, 1, updateChan,
|
||||||
true)
|
true)
|
||||||
|
|
||||||
// Make sure both reservations time out and then run both zombie sweepers.
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
go alice.fundingMgr.pruneZombieReservations()
|
|
||||||
go bob.fundingMgr.pruneZombieReservations()
|
|
||||||
|
|
||||||
// Check that neither Alice nor Bob sent an error message.
|
// Check that neither Alice nor Bob sent an error message.
|
||||||
assertErrorNotSent(t, alice.msgChan)
|
assertErrorNotSent(t, alice.msgChan)
|
||||||
assertErrorNotSent(t, bob.msgChan)
|
assertErrorNotSent(t, bob.msgChan)
|
||||||
|
|
||||||
// Check that neither reservation has been pruned.
|
|
||||||
assertNumPendingReservations(t, alice, bobPubKey, 1)
|
|
||||||
assertNumPendingReservations(t, bob, alicePubKey, 1)
|
|
||||||
|
|
||||||
// Notify that transaction was mined.
|
// Notify that transaction was mined.
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{}
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{}
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{}
|
||||||
@ -1016,7 +1026,7 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFundingManagerRestartBehavior(t *testing.T) {
|
func TestFundingManagerRestartBehavior(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// Run through the process of opening the channel, up until the funding
|
// Run through the process of opening the channel, up until the funding
|
||||||
@ -1147,7 +1157,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
|
|||||||
// server to notify when the peer comes online, in case sending the
|
// server to notify when the peer comes online, in case sending the
|
||||||
// fundingLocked message fails the first time.
|
// fundingLocked message fails the first time.
|
||||||
func TestFundingManagerOfflinePeer(t *testing.T) {
|
func TestFundingManagerOfflinePeer(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// Run through the process of opening the channel, up until the funding
|
// Run through the process of opening the channel, up until the funding
|
||||||
@ -1278,7 +1288,7 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
|
|||||||
// will properly clean up a zombie reservation that times out after the
|
// will properly clean up a zombie reservation that times out after the
|
||||||
// initFundingMsg has been handled.
|
// initFundingMsg has been handled.
|
||||||
func TestFundingManagerPeerTimeoutAfterInitFunding(t *testing.T) {
|
func TestFundingManagerPeerTimeoutAfterInitFunding(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1338,7 +1348,7 @@ func TestFundingManagerPeerTimeoutAfterInitFunding(t *testing.T) {
|
|||||||
// will properly clean up a zombie reservation that times out after the
|
// will properly clean up a zombie reservation that times out after the
|
||||||
// fundingOpenMsg has been handled.
|
// fundingOpenMsg has been handled.
|
||||||
func TestFundingManagerPeerTimeoutAfterFundingOpen(t *testing.T) {
|
func TestFundingManagerPeerTimeoutAfterFundingOpen(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1407,7 +1417,7 @@ func TestFundingManagerPeerTimeoutAfterFundingOpen(t *testing.T) {
|
|||||||
// will properly clean up a zombie reservation that times out after the
|
// will properly clean up a zombie reservation that times out after the
|
||||||
// fundingAcceptMsg has been handled.
|
// fundingAcceptMsg has been handled.
|
||||||
func TestFundingManagerPeerTimeoutAfterFundingAccept(t *testing.T) {
|
func TestFundingManagerPeerTimeoutAfterFundingAccept(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1481,7 +1491,7 @@ func TestFundingManagerPeerTimeoutAfterFundingAccept(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFundingManagerFundingTimeout(t *testing.T) {
|
func TestFundingManagerFundingTimeout(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1526,7 +1536,7 @@ func TestFundingManagerFundingTimeout(t *testing.T) {
|
|||||||
// the channel initiator, that it does not timeout when the lnd restarts.
|
// the channel initiator, that it does not timeout when the lnd restarts.
|
||||||
func TestFundingManagerFundingNotTimeoutInitiator(t *testing.T) {
|
func TestFundingManagerFundingNotTimeoutInitiator(t *testing.T) {
|
||||||
|
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1593,7 +1603,7 @@ func TestFundingManagerFundingNotTimeoutInitiator(t *testing.T) {
|
|||||||
// continues to operate as expected in case we receive a duplicate fundingLocked
|
// continues to operate as expected in case we receive a duplicate fundingLocked
|
||||||
// message.
|
// message.
|
||||||
func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
|
func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1682,7 +1692,7 @@ func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
|
|||||||
// handles receiving a fundingLocked after the its own fundingLocked and channel
|
// handles receiving a fundingLocked after the its own fundingLocked and channel
|
||||||
// announcement is sent and gets restarted.
|
// announcement is sent and gets restarted.
|
||||||
func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
|
func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1756,7 +1766,7 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
|
|||||||
// fundingManager continues to operate as expected after it has received
|
// fundingManager continues to operate as expected after it has received
|
||||||
// fundingLocked and then gets restarted.
|
// fundingLocked and then gets restarted.
|
||||||
func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
|
func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1826,7 +1836,7 @@ func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
|
|||||||
// (a channel not supposed to be announced to the rest of the network),
|
// (a channel not supposed to be announced to the rest of the network),
|
||||||
// the announcementSignatures nor the nodeAnnouncement messages are sent.
|
// the announcementSignatures nor the nodeAnnouncement messages are sent.
|
||||||
func TestFundingManagerPrivateChannel(t *testing.T) {
|
func TestFundingManagerPrivateChannel(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1906,7 +1916,7 @@ func TestFundingManagerPrivateChannel(t *testing.T) {
|
|||||||
// announcement signatures nor the node announcement messages are sent upon
|
// announcement signatures nor the node announcement messages are sent upon
|
||||||
// restart.
|
// restart.
|
||||||
func TestFundingManagerPrivateRestart(t *testing.T) {
|
func TestFundingManagerPrivateRestart(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is needed.
|
// We will consume the channel updates as we go, so no buffering is needed.
|
||||||
@ -1996,7 +2006,7 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
|
|||||||
// TestFundingManagerCustomChannelParameters checks that custom requirements we
|
// TestFundingManagerCustomChannelParameters checks that custom requirements we
|
||||||
// specify during the channel funding flow is preserved correcly on both sides.
|
// specify during the channel funding flow is preserved correcly on both sides.
|
||||||
func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t, defaultMaxPendingChannels)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// This is the custom parameters we'll use.
|
// This is the custom parameters we'll use.
|
||||||
@ -2088,39 +2098,6 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
t, alice.msgChan, "FundingCreated",
|
t, alice.msgChan, "FundingCreated",
|
||||||
).(*lnwire.FundingCreated)
|
).(*lnwire.FundingCreated)
|
||||||
|
|
||||||
// Give the message to Bob.
|
|
||||||
bob.fundingMgr.processFundingCreated(fundingCreated, alice)
|
|
||||||
|
|
||||||
// Finally, Bob should send the FundingSigned message.
|
|
||||||
fundingSigned := assertFundingMsgSent(
|
|
||||||
t, bob.msgChan, "FundingSigned",
|
|
||||||
).(*lnwire.FundingSigned)
|
|
||||||
|
|
||||||
// Forward the signature to Alice.
|
|
||||||
alice.fundingMgr.processFundingSigned(fundingSigned, bob)
|
|
||||||
|
|
||||||
// After Alice processes the singleFundingSignComplete message, she will
|
|
||||||
// broadcast the funding transaction to the network. We expect to get a
|
|
||||||
// channel update saying the channel is pending.
|
|
||||||
var pendingUpdate *lnrpc.OpenStatusUpdate
|
|
||||||
select {
|
|
||||||
case pendingUpdate = <-updateChan:
|
|
||||||
case <-time.After(time.Second * 5):
|
|
||||||
t.Fatalf("alice did not send OpenStatusUpdate_ChanPending")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanPending")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for Alice to published the funding tx to the network.
|
|
||||||
select {
|
|
||||||
case <-alice.publTxChan:
|
|
||||||
case <-time.After(time.Second * 5):
|
|
||||||
t.Fatalf("alice did not publish funding tx")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method for checking the CSV delay stored for a reservation.
|
// Helper method for checking the CSV delay stored for a reservation.
|
||||||
assertDelay := func(resCtx *reservationWithCtx,
|
assertDelay := func(resCtx *reservationWithCtx,
|
||||||
ourDelay, theirDelay uint16) error {
|
ourDelay, theirDelay uint16) error {
|
||||||
@ -2190,4 +2167,198 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
if err := assertMinHtlc(resCtx, minHtlc, 5); err != nil {
|
if err := assertMinHtlc(resCtx, minHtlc, 5); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Give the message to Bob.
|
||||||
|
bob.fundingMgr.processFundingCreated(fundingCreated, alice)
|
||||||
|
|
||||||
|
// Finally, Bob should send the FundingSigned message.
|
||||||
|
fundingSigned := assertFundingMsgSent(
|
||||||
|
t, bob.msgChan, "FundingSigned",
|
||||||
|
).(*lnwire.FundingSigned)
|
||||||
|
|
||||||
|
// Forward the signature to Alice.
|
||||||
|
alice.fundingMgr.processFundingSigned(fundingSigned, bob)
|
||||||
|
|
||||||
|
// After Alice processes the singleFundingSignComplete message, she will
|
||||||
|
// broadcast the funding transaction to the network. We expect to get a
|
||||||
|
// channel update saying the channel is pending.
|
||||||
|
var pendingUpdate *lnrpc.OpenStatusUpdate
|
||||||
|
select {
|
||||||
|
case pendingUpdate = <-updateChan:
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("alice did not send OpenStatusUpdate_ChanPending")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanPending")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Alice to published the funding tx to the network.
|
||||||
|
select {
|
||||||
|
case <-alice.publTxChan:
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("alice did not publish funding tx")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFundingManagerMaxPendingChannels checks that trying to open another
|
||||||
|
// channel with the same peer when MaxPending channels are pending fails.
|
||||||
|
func TestFundingManagerMaxPendingChannels(t *testing.T) {
|
||||||
|
const maxPending = 4
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t, maxPending)
|
||||||
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
|
// Create openChanReqs for maxPending+1 channels.
|
||||||
|
var initReqs []*openChanReq
|
||||||
|
for i := 0; i < maxPending+1; i++ {
|
||||||
|
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
initReq := &openChanReq{
|
||||||
|
targetPubkey: bob.privKey.PubKey(),
|
||||||
|
chainHash: *activeNetParams.GenesisHash,
|
||||||
|
localFundingAmt: 5000000,
|
||||||
|
pushAmt: lnwire.NewMSatFromSatoshis(0),
|
||||||
|
private: false,
|
||||||
|
updates: updateChan,
|
||||||
|
err: errChan,
|
||||||
|
}
|
||||||
|
initReqs = append(initReqs, initReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kick of maxPending+1 funding workflows.
|
||||||
|
var accepts []*lnwire.AcceptChannel
|
||||||
|
var lastOpen *lnwire.OpenChannel
|
||||||
|
for i, initReq := range initReqs {
|
||||||
|
alice.fundingMgr.initFundingWorkflow(bob, initReq)
|
||||||
|
|
||||||
|
// Alice should have sent the OpenChannel message to Bob.
|
||||||
|
var aliceMsg lnwire.Message
|
||||||
|
select {
|
||||||
|
case aliceMsg = <-alice.msgChan:
|
||||||
|
case err := <-initReq.err:
|
||||||
|
t.Fatalf("error init funding workflow: %v", err)
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("alice did not send OpenChannel message")
|
||||||
|
}
|
||||||
|
|
||||||
|
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
|
||||||
|
if !ok {
|
||||||
|
errorMsg, gotError := aliceMsg.(*lnwire.Error)
|
||||||
|
if gotError {
|
||||||
|
t.Fatalf("expected OpenChannel to be sent "+
|
||||||
|
"from bob, instead got error: %v",
|
||||||
|
lnwire.ErrorCode(errorMsg.Data[0]))
|
||||||
|
}
|
||||||
|
t.Fatalf("expected OpenChannel to be sent from "+
|
||||||
|
"alice, instead got %T", aliceMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let Bob handle the init message.
|
||||||
|
bob.fundingMgr.processFundingOpen(openChannelReq, alice)
|
||||||
|
|
||||||
|
// Bob should answer with an AcceptChannel message for the
|
||||||
|
// first maxPending channels.
|
||||||
|
if i < maxPending {
|
||||||
|
acceptChannelResponse := assertFundingMsgSent(
|
||||||
|
t, bob.msgChan, "AcceptChannel",
|
||||||
|
).(*lnwire.AcceptChannel)
|
||||||
|
accepts = append(accepts, acceptChannelResponse)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the last channel, Bob should answer with an error.
|
||||||
|
lastOpen = openChannelReq
|
||||||
|
_ = assertFundingMsgSent(
|
||||||
|
t, bob.msgChan, "Error",
|
||||||
|
).(*lnwire.Error)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward the responses to Alice.
|
||||||
|
var signs []*lnwire.FundingSigned
|
||||||
|
for _, accept := range accepts {
|
||||||
|
alice.fundingMgr.processFundingAccept(accept, bob)
|
||||||
|
|
||||||
|
// Alice responds with a FundingCreated message.
|
||||||
|
fundingCreated := assertFundingMsgSent(
|
||||||
|
t, alice.msgChan, "FundingCreated",
|
||||||
|
).(*lnwire.FundingCreated)
|
||||||
|
|
||||||
|
// Give the message to Bob.
|
||||||
|
bob.fundingMgr.processFundingCreated(fundingCreated, alice)
|
||||||
|
|
||||||
|
// Finally, Bob should send the FundingSigned message.
|
||||||
|
fundingSigned := assertFundingMsgSent(
|
||||||
|
t, bob.msgChan, "FundingSigned",
|
||||||
|
).(*lnwire.FundingSigned)
|
||||||
|
|
||||||
|
signs = append(signs, fundingSigned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sending another init request from Alice should still make Bob
|
||||||
|
// respond with an error.
|
||||||
|
bob.fundingMgr.processFundingOpen(lastOpen, alice)
|
||||||
|
_ = assertFundingMsgSent(
|
||||||
|
t, bob.msgChan, "Error",
|
||||||
|
).(*lnwire.Error)
|
||||||
|
|
||||||
|
// Give the FundingSigned messages to Alice.
|
||||||
|
for i, sign := range signs {
|
||||||
|
alice.fundingMgr.processFundingSigned(sign, bob)
|
||||||
|
|
||||||
|
// Alice should send a status update for each channel, and
|
||||||
|
// publish a funding tx to the network.
|
||||||
|
var pendingUpdate *lnrpc.OpenStatusUpdate
|
||||||
|
select {
|
||||||
|
case pendingUpdate = <-initReqs[i].updates:
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("alice did not send OpenStatusUpdate_ChanPending")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanPending")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-alice.publTxChan:
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("alice did not publish funding tx")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sending another init request from Alice should still make Bob
|
||||||
|
// respond with an error, since the funding transactions are not
|
||||||
|
// confirmed yet,
|
||||||
|
bob.fundingMgr.processFundingOpen(lastOpen, alice)
|
||||||
|
_ = assertFundingMsgSent(
|
||||||
|
t, bob.msgChan, "Error",
|
||||||
|
).(*lnwire.Error)
|
||||||
|
|
||||||
|
// Notify that the transactions were mined.
|
||||||
|
for i := 0; i < maxPending; i++ {
|
||||||
|
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{}
|
||||||
|
|
||||||
|
// Expect both to be sending FundingLocked.
|
||||||
|
_ = assertFundingMsgSent(
|
||||||
|
t, alice.msgChan, "FundingLocked",
|
||||||
|
).(*lnwire.FundingLocked)
|
||||||
|
|
||||||
|
_ = assertFundingMsgSent(
|
||||||
|
t, bob.msgChan, "FundingLocked",
|
||||||
|
).(*lnwire.FundingLocked)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now opening another channel should work.
|
||||||
|
bob.fundingMgr.processFundingOpen(lastOpen, alice)
|
||||||
|
|
||||||
|
// Bob should answer with an AcceptChannel message.
|
||||||
|
_ = assertFundingMsgSent(
|
||||||
|
t, bob.msgChan, "AcceptChannel",
|
||||||
|
).(*lnwire.AcceptChannel)
|
||||||
}
|
}
|
||||||
|
@ -427,6 +427,11 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
|||||||
t.Fatalf("channel not detected as dual funder")
|
t.Fatalf("channel not detected as dual funder")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let Alice publish the funding transaction.
|
||||||
|
if err := alice.PublishTransaction(fundingTx); err != nil {
|
||||||
|
t.Fatalf("unable to publish funding tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Mine a single block, the funding transaction should be included
|
// Mine a single block, the funding transaction should be included
|
||||||
// within this block.
|
// within this block.
|
||||||
err = waitForMempoolTx(miner, &fundingSha)
|
err = waitForMempoolTx(miner, &fundingSha)
|
||||||
@ -843,6 +848,11 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
|||||||
channeldb.SingleFunder, bobChannels[0].ChanType)
|
channeldb.SingleFunder, bobChannels[0].ChanType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let Alice publish the funding transaction.
|
||||||
|
if err := alice.PublishTransaction(fundingTx); err != nil {
|
||||||
|
t.Fatalf("unable to publish funding tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Mine a single block, the funding transaction should be included
|
// Mine a single block, the funding transaction should be included
|
||||||
// within this block.
|
// within this block.
|
||||||
err = waitForMempoolTx(miner, &fundingSha)
|
err = waitForMempoolTx(miner, &fundingSha)
|
||||||
|
@ -434,11 +434,11 @@ func (r *ChannelReservation) OurSignatures() ([]*InputScript, []byte) {
|
|||||||
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
||||||
// Additionally, verification is performed in order to ensure that the
|
// Additionally, verification is performed in order to ensure that the
|
||||||
// counterparty supplied a valid signature to our version of the commitment
|
// counterparty supplied a valid signature to our version of the commitment
|
||||||
// transaction. Once this method returns, caller's should then call
|
// transaction. Once this method returns, caller's should broadcast the
|
||||||
// .WaitForChannelOpen() which will block until the funding transaction obtains
|
// created funding transaction, then call .WaitForChannelOpen() which will
|
||||||
// the configured number of confirmations. Once the method unblocks, a
|
// block until the funding transaction obtains the configured number of
|
||||||
// LightningChannel instance is returned, marking the channel available for
|
// confirmations. Once the method unblocks, a LightningChannel instance is
|
||||||
// updates.
|
// returned, marking the channel available for updates.
|
||||||
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScript,
|
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScript,
|
||||||
commitmentSig []byte) (*channeldb.OpenChannel, error) {
|
commitmentSig []byte) (*channeldb.OpenChannel, error) {
|
||||||
|
|
||||||
|
@ -1096,16 +1096,6 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
walletLog.Infof("Broadcasting funding tx for ChannelPoint(%v): %v",
|
|
||||||
res.partialState.FundingOutpoint, spew.Sdump(fundingTx))
|
|
||||||
|
|
||||||
// Broadcast the finalized funding transaction to the network.
|
|
||||||
if err := l.PublishTransaction(fundingTx); err != nil {
|
|
||||||
msg.err <- err
|
|
||||||
msg.completeChan <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.completeChan <- res.partialState
|
msg.completeChan <- res.partialState
|
||||||
msg.err <- nil
|
msg.err <- nil
|
||||||
}
|
}
|
||||||
|
7
mock.go
7
mock.go
@ -4,6 +4,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
@ -191,6 +192,7 @@ type mockWalletController struct {
|
|||||||
rootKey *btcec.PrivateKey
|
rootKey *btcec.PrivateKey
|
||||||
prevAddres btcutil.Address
|
prevAddres btcutil.Address
|
||||||
publishedTransactions chan *wire.MsgTx
|
publishedTransactions chan *wire.MsgTx
|
||||||
|
index uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackEnd returns "mock" to signify a mock wallet controller.
|
// BackEnd returns "mock" to signify a mock wallet controller.
|
||||||
@ -231,16 +233,17 @@ func (*mockWalletController) SendOutputs(outputs []*wire.TxOut,
|
|||||||
|
|
||||||
// ListUnspentWitness is called by the wallet when doing coin selection. We just
|
// ListUnspentWitness is called by the wallet when doing coin selection. We just
|
||||||
// need one unspent for the funding transaction.
|
// need one unspent for the funding transaction.
|
||||||
func (*mockWalletController) ListUnspentWitness(confirms int32) ([]*lnwallet.Utxo, error) {
|
func (m *mockWalletController) ListUnspentWitness(confirms int32) ([]*lnwallet.Utxo, error) {
|
||||||
utxo := &lnwallet.Utxo{
|
utxo := &lnwallet.Utxo{
|
||||||
AddressType: lnwallet.WitnessPubKey,
|
AddressType: lnwallet.WitnessPubKey,
|
||||||
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
|
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
|
||||||
PkScript: make([]byte, 22),
|
PkScript: make([]byte, 22),
|
||||||
OutPoint: wire.OutPoint{
|
OutPoint: wire.OutPoint{
|
||||||
Hash: chainhash.Hash{},
|
Hash: chainhash.Hash{},
|
||||||
Index: 0,
|
Index: m.index,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
atomic.AddUint32(&m.index, 1)
|
||||||
var ret []*lnwallet.Utxo
|
var ret []*lnwallet.Utxo
|
||||||
ret = append(ret, utxo)
|
ret = append(ret, utxo)
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
Loading…
x
Reference in New Issue
Block a user