Merge pull request #3165 from halseth/autopilot-spend-coins
[funding+autopilot] Make the funding manager and autopilot fee aware
This commit is contained in:
commit
111cbeaa99
@ -584,6 +584,7 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
|
|||||||
|
|
||||||
// Use the heuristic to calculate a score for each node in the
|
// Use the heuristic to calculate a score for each node in the
|
||||||
// graph.
|
// graph.
|
||||||
|
log.Debugf("Scoring %d nodes for chan_size=%v", len(nodes), chanSize)
|
||||||
scores, err := a.cfg.Heuristic.NodeScores(
|
scores, err := a.cfg.Heuristic.NodeScores(
|
||||||
a.cfg.Graph, totalChans, chanSize, nodes,
|
a.cfg.Graph, totalChans, chanSize, nodes,
|
||||||
)
|
)
|
||||||
|
@ -1196,7 +1196,7 @@ func TestAgentChannelSizeAllocation(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Total number of nodes in our mock graph.
|
// Total number of nodes in our mock graph.
|
||||||
const numNodes = 10
|
const numNodes = 20
|
||||||
|
|
||||||
testCtx, cleanup := setup(t, nil)
|
testCtx, cleanup := setup(t, nil)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
@ -1240,6 +1240,8 @@ func TestAgentChannelSizeAllocation(t *testing.T) {
|
|||||||
numNewChannels, nodeScores,
|
numNewChannels, nodeScores,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// We expect the autopilot to have allocated all funds towards
|
||||||
|
// channels.
|
||||||
expectedAllocation := testCtx.constraints.MaxChanSize() * btcutil.Amount(numNewChannels)
|
expectedAllocation := testCtx.constraints.MaxChanSize() * btcutil.Amount(numNewChannels)
|
||||||
nodes := checkChannelOpens(
|
nodes := checkChannelOpens(
|
||||||
t, testCtx, expectedAllocation, numNewChannels,
|
t, testCtx, expectedAllocation, numNewChannels,
|
||||||
@ -1259,34 +1261,40 @@ func TestAgentChannelSizeAllocation(t *testing.T) {
|
|||||||
waitForNumChans := func(expChans int) {
|
waitForNumChans := func(expChans int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
var (
|
||||||
|
numChans int
|
||||||
|
balance btcutil.Amount
|
||||||
|
)
|
||||||
|
|
||||||
Loop:
|
Loop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case arg := <-testCtx.constraints.moreChanArgs:
|
case arg := <-testCtx.constraints.moreChanArgs:
|
||||||
|
numChans = len(arg.chans)
|
||||||
|
balance = arg.balance
|
||||||
|
|
||||||
// As long as the number of existing channels
|
// As long as the number of existing channels
|
||||||
// is below our expected number of channels,
|
// is below our expected number of channels,
|
||||||
// we'll keep responding with "no more
|
// and the balance is not what we expect, we'll
|
||||||
// channels".
|
// keep responding with "no more channels".
|
||||||
if len(arg.chans) != expChans {
|
if numChans == expChans &&
|
||||||
|
balance == testCtx.walletBalance {
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case testCtx.constraints.moreChansResps <- moreChansResp{0, 0}:
|
case testCtx.constraints.moreChansResps <- moreChansResp{0, 0}:
|
||||||
case <-time.After(time.Second * 3):
|
case <-time.After(time.Second * 3):
|
||||||
t.Fatalf("heuristic wasn't " +
|
t.Fatalf("heuristic wasn't queried " +
|
||||||
"queried in time")
|
"in time")
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if arg.balance != testCtx.walletBalance {
|
|
||||||
t.Fatalf("expectd agent to have %v "+
|
|
||||||
"balance, had %v",
|
|
||||||
testCtx.walletBalance,
|
|
||||||
arg.balance)
|
|
||||||
}
|
|
||||||
break Loop
|
|
||||||
|
|
||||||
case <-time.After(time.Second * 3):
|
case <-time.After(time.Second * 3):
|
||||||
t.Fatalf("heuristic wasn't queried in time")
|
t.Fatalf("did not receive expected "+
|
||||||
|
"channels(%d) and balance(%d), "+
|
||||||
|
"instead got %d and %d", expChans,
|
||||||
|
testCtx.walletBalance, numChans,
|
||||||
|
balance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1308,5 +1316,30 @@ func TestAgentChannelSizeAllocation(t *testing.T) {
|
|||||||
|
|
||||||
// To stay within the budget, we expect the autopilot to open 2
|
// To stay within the budget, we expect the autopilot to open 2
|
||||||
// channels.
|
// channels.
|
||||||
checkChannelOpens(t, testCtx, channelBudget, 2)
|
expectedAllocation = channelBudget
|
||||||
|
nodes = checkChannelOpens(t, testCtx, expectedAllocation, 2)
|
||||||
|
numExistingChannels = 7
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
delete(nodeScores, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForNumChans(numExistingChannels)
|
||||||
|
|
||||||
|
// Finally check that we make maximum channels if we are well within
|
||||||
|
// our budget.
|
||||||
|
channelBudget = btcutil.SatoshiPerBitcoin * 5
|
||||||
|
numNewChannels = 2
|
||||||
|
respondWithScores(
|
||||||
|
t, testCtx, channelBudget, numExistingChannels,
|
||||||
|
numNewChannels, nodeScores,
|
||||||
|
)
|
||||||
|
|
||||||
|
// We now expect the autopilot to open 2 channels, and since it has
|
||||||
|
// more than enough balance within the budget, they should both be of
|
||||||
|
// maximum size.
|
||||||
|
expectedAllocation = testCtx.constraints.MaxChanSize() *
|
||||||
|
btcutil.Amount(numNewChannels)
|
||||||
|
|
||||||
|
checkChannelOpens(t, testCtx, expectedAllocation, numNewChannels)
|
||||||
}
|
}
|
||||||
|
@ -188,10 +188,11 @@ func init() {
|
|||||||
// open a channel within the graph to a target peer, close targeted channels,
|
// open a channel within the graph to a target peer, close targeted channels,
|
||||||
// or add/remove funds from existing channels via a splice in/out mechanisms.
|
// or add/remove funds from existing channels via a splice in/out mechanisms.
|
||||||
type ChannelController interface {
|
type ChannelController interface {
|
||||||
// OpenChannel opens a channel to a target peer, with a capacity of the
|
// OpenChannel opens a channel to a target peer, using at most amt
|
||||||
// specified amount. This function should un-block immediately after
|
// funds. This means that the resulting channel capacity might be
|
||||||
// the funding transaction that marks the channel open has been
|
// slightly less to account for fees. This function should un-block
|
||||||
// broadcast.
|
// immediately after the funding transaction that marks the channel
|
||||||
|
// open has been broadcast.
|
||||||
OpenChannel(target *btcec.PublicKey, amt btcutil.Amount) error
|
OpenChannel(target *btcec.PublicKey, amt btcutil.Amount) error
|
||||||
|
|
||||||
// CloseChannel attempts to close out the target channel.
|
// CloseChannel attempts to close out the target channel.
|
||||||
|
@ -337,6 +337,14 @@ type fundingConfig struct {
|
|||||||
// due to fees.
|
// due to fees.
|
||||||
MinChanSize btcutil.Amount
|
MinChanSize btcutil.Amount
|
||||||
|
|
||||||
|
// MaxPendingChannels is the maximum number of pending channels we
|
||||||
|
// allow for each peer.
|
||||||
|
MaxPendingChannels int
|
||||||
|
|
||||||
|
// RejectPush is set true if the fundingmanager should reject any
|
||||||
|
// incoming channels having a non-zero push amount.
|
||||||
|
RejectPush bool
|
||||||
|
|
||||||
// NotifyOpenChannelEvent informs the ChannelNotifier when channels
|
// NotifyOpenChannelEvent informs the ChannelNotifier when channels
|
||||||
// transition from pending open to open.
|
// transition from pending open to open.
|
||||||
NotifyOpenChannelEvent func(wire.OutPoint)
|
NotifyOpenChannelEvent func(wire.OutPoint)
|
||||||
@ -1012,7 +1020,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
|||||||
|
|
||||||
// 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
|
||||||
if numPending >= cfg.MaxPendingChannels {
|
if numPending >= f.cfg.MaxPendingChannels {
|
||||||
f.failFundingFlow(
|
f.failFundingFlow(
|
||||||
fmsg.peer, fmsg.msg.PendingChannelID,
|
fmsg.peer, fmsg.msg.PendingChannelID,
|
||||||
lnwire.ErrMaxPendingChannels,
|
lnwire.ErrMaxPendingChannels,
|
||||||
@ -1057,7 +1065,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
|||||||
|
|
||||||
// If request specifies non-zero push amount and 'rejectpush' is set,
|
// If request specifies non-zero push amount and 'rejectpush' is set,
|
||||||
// signal an error.
|
// signal an error.
|
||||||
if cfg.RejectPush && msg.PushAmount > 0 {
|
if f.cfg.RejectPush && msg.PushAmount > 0 {
|
||||||
f.failFundingFlow(
|
f.failFundingFlow(
|
||||||
fmsg.peer, fmsg.msg.PendingChannelID,
|
fmsg.peer, fmsg.msg.PendingChannelID,
|
||||||
lnwallet.ErrNonZeroPushAmount())
|
lnwallet.ErrNonZeroPushAmount())
|
||||||
@ -1079,8 +1087,8 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
|||||||
ChainHash: &chainHash,
|
ChainHash: &chainHash,
|
||||||
NodeID: fmsg.peer.IdentityKey(),
|
NodeID: fmsg.peer.IdentityKey(),
|
||||||
NodeAddr: fmsg.peer.Address(),
|
NodeAddr: fmsg.peer.Address(),
|
||||||
FundingAmount: 0,
|
LocalFundingAmt: 0,
|
||||||
Capacity: amt,
|
RemoteFundingAmt: amt,
|
||||||
CommitFeePerKw: lnwallet.SatPerKWeight(msg.FeePerKiloWeight),
|
CommitFeePerKw: lnwallet.SatPerKWeight(msg.FeePerKiloWeight),
|
||||||
FundingFeePerKw: 0,
|
FundingFeePerKw: 0,
|
||||||
PushMSat: msg.PushAmount,
|
PushMSat: msg.PushAmount,
|
||||||
@ -1714,8 +1722,8 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
|
|||||||
return
|
return
|
||||||
case shortChanID, ok = <-confChan:
|
case shortChanID, ok = <-confChan:
|
||||||
if !ok {
|
if !ok {
|
||||||
fndgLog.Errorf("waiting for funding confirmation" +
|
fndgLog.Errorf("waiting for funding " +
|
||||||
" failed")
|
"confirmation failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1885,8 +1893,9 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) {
|
|||||||
// when a channel has become active for lightning transactions.
|
// when a channel has become active for lightning transactions.
|
||||||
// The wait can be canceled by closing the cancelChan. In case of success,
|
// The wait can be canceled by closing the cancelChan. In case of success,
|
||||||
// a *lnwire.ShortChannelID will be passed to confChan.
|
// a *lnwire.ShortChannelID will be passed to confChan.
|
||||||
func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.OpenChannel,
|
func (f *fundingManager) waitForFundingConfirmation(
|
||||||
cancelChan <-chan struct{}, confChan chan<- *lnwire.ShortChannelID) {
|
completeChan *channeldb.OpenChannel, cancelChan <-chan struct{},
|
||||||
|
confChan chan<- *lnwire.ShortChannelID) {
|
||||||
|
|
||||||
defer close(confChan)
|
defer close(confChan)
|
||||||
|
|
||||||
@ -1896,16 +1905,19 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open
|
|||||||
fundingScript, err := makeFundingScript(completeChan)
|
fundingScript, err := makeFundingScript(completeChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fndgLog.Errorf("unable to create funding script for "+
|
fndgLog.Errorf("unable to create funding script for "+
|
||||||
"ChannelPoint(%v): %v", completeChan.FundingOutpoint, err)
|
"ChannelPoint(%v): %v", completeChan.FundingOutpoint,
|
||||||
|
err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
numConfs := uint32(completeChan.NumConfsRequired)
|
numConfs := uint32(completeChan.NumConfsRequired)
|
||||||
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(
|
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(
|
||||||
&txid, fundingScript, numConfs, completeChan.FundingBroadcastHeight,
|
&txid, fundingScript, numConfs,
|
||||||
|
completeChan.FundingBroadcastHeight,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fndgLog.Errorf("Unable to register for confirmation of "+
|
fndgLog.Errorf("Unable to register for confirmation of "+
|
||||||
"ChannelPoint(%v): %v", completeChan.FundingOutpoint, err)
|
"ChannelPoint(%v): %v", completeChan.FundingOutpoint,
|
||||||
|
err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1920,14 +1932,17 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open
|
|||||||
select {
|
select {
|
||||||
case confDetails, ok = <-confNtfn.Confirmed:
|
case confDetails, ok = <-confNtfn.Confirmed:
|
||||||
// fallthrough
|
// fallthrough
|
||||||
|
|
||||||
case <-cancelChan:
|
case <-cancelChan:
|
||||||
fndgLog.Warnf("canceled waiting for funding confirmation, "+
|
fndgLog.Warnf("canceled waiting for funding confirmation, "+
|
||||||
"stopping funding flow for ChannelPoint(%v)",
|
"stopping funding flow for ChannelPoint(%v)",
|
||||||
completeChan.FundingOutpoint)
|
completeChan.FundingOutpoint)
|
||||||
return
|
return
|
||||||
|
|
||||||
case <-f.quit:
|
case <-f.quit:
|
||||||
fndgLog.Warnf("fundingManager shutting down, stopping funding "+
|
fndgLog.Warnf("fundingManager shutting down, stopping funding "+
|
||||||
"flow for ChannelPoint(%v)", completeChan.FundingOutpoint)
|
"flow for ChannelPoint(%v)",
|
||||||
|
completeChan.FundingOutpoint)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1940,6 +1955,18 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open
|
|||||||
|
|
||||||
fundingPoint := completeChan.FundingOutpoint
|
fundingPoint := completeChan.FundingOutpoint
|
||||||
chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint)
|
chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint)
|
||||||
|
if int(fundingPoint.Index) >= len(confDetails.Tx.TxOut) {
|
||||||
|
fndgLog.Warnf("Funding point index does not exist for "+
|
||||||
|
"ChannelPoint(%v)", completeChan.FundingOutpoint)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outputAmt := btcutil.Amount(confDetails.Tx.TxOut[fundingPoint.Index].Value)
|
||||||
|
if outputAmt != completeChan.Capacity {
|
||||||
|
fndgLog.Warnf("Invalid output value for ChannelPoint(%v)",
|
||||||
|
completeChan.FundingOutpoint)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fndgLog.Infof("ChannelPoint(%v) is now active: ChannelID(%x)",
|
fndgLog.Infof("ChannelPoint(%v) is now active: ChannelID(%x)",
|
||||||
fundingPoint, chanID[:])
|
fundingPoint, chanID[:])
|
||||||
@ -2734,8 +2761,6 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
|||||||
var (
|
var (
|
||||||
peerKey = msg.peer.IdentityKey()
|
peerKey = msg.peer.IdentityKey()
|
||||||
localAmt = msg.localFundingAmt
|
localAmt = msg.localFundingAmt
|
||||||
remoteAmt = msg.remoteFundingAmt
|
|
||||||
capacity = localAmt + remoteAmt
|
|
||||||
minHtlc = msg.minHtlc
|
minHtlc = msg.minHtlc
|
||||||
remoteCsvDelay = msg.remoteCsvDelay
|
remoteCsvDelay = msg.remoteCsvDelay
|
||||||
)
|
)
|
||||||
@ -2749,10 +2774,11 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
|||||||
ourDustLimit = defaultLitecoinDustLimit
|
ourDustLimit = defaultLitecoinDustLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
fndgLog.Infof("Initiating fundingRequest(localAmt=%v, remoteAmt=%v, "+
|
fndgLog.Infof("Initiating fundingRequest(local_amt=%v "+
|
||||||
"capacity=%v, chainhash=%v, peer=%x, dustLimit=%v, min_confs=%v)",
|
"(subtract_fees=%v), push_amt=%v, chain_hash=%v, peer=%x, "+
|
||||||
localAmt, msg.pushAmt, capacity, msg.chainHash,
|
"dust_limit=%v, min_confs=%v)", localAmt, msg.subtractFees,
|
||||||
peerKey.SerializeCompressed(), ourDustLimit, msg.minConfs)
|
msg.pushAmt, msg.chainHash, peerKey.SerializeCompressed(),
|
||||||
|
ourDustLimit, msg.minConfs)
|
||||||
|
|
||||||
// First, we'll query the fee estimator for a fee that should get the
|
// First, we'll query the fee estimator for a fee that should get the
|
||||||
// commitment transaction confirmed by the next few blocks (conf target
|
// commitment transaction confirmed by the next few blocks (conf target
|
||||||
@ -2779,8 +2805,9 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
|||||||
ChainHash: &msg.chainHash,
|
ChainHash: &msg.chainHash,
|
||||||
NodeID: peerKey,
|
NodeID: peerKey,
|
||||||
NodeAddr: msg.peer.Address(),
|
NodeAddr: msg.peer.Address(),
|
||||||
FundingAmount: localAmt,
|
SubtractFees: msg.subtractFees,
|
||||||
Capacity: capacity,
|
LocalFundingAmt: localAmt,
|
||||||
|
RemoteFundingAmt: 0,
|
||||||
CommitFeePerKw: commitFeePerKw,
|
CommitFeePerKw: commitFeePerKw,
|
||||||
FundingFeePerKw: msg.fundingFeePerKw,
|
FundingFeePerKw: msg.fundingFeePerKw,
|
||||||
PushMSat: msg.pushAmt,
|
PushMSat: msg.pushAmt,
|
||||||
@ -2794,6 +2821,12 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that we have successfully reserved funds for this channel in the
|
||||||
|
// wallet, we can fetch the final channel capacity. This is done at
|
||||||
|
// this point since the final capacity might change in case of
|
||||||
|
// SubtractFees=true.
|
||||||
|
capacity := reservation.Capacity()
|
||||||
|
|
||||||
// Obtain a new pending channel ID which is used to track this
|
// Obtain a new pending channel ID which is used to track this
|
||||||
// reservation throughout its lifetime.
|
// reservation throughout its lifetime.
|
||||||
chanID := f.nextPendingChanID()
|
chanID := f.nextPendingChanID()
|
||||||
|
@ -236,7 +236,8 @@ func createTestWallet(cdb *channeldb.DB, netParams *chaincfg.Params,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||||
addr *lnwire.NetAddress, tempTestDir string) (*testNode, error) {
|
addr *lnwire.NetAddress, tempTestDir string,
|
||||||
|
options ...cfgOption) (*testNode, error) {
|
||||||
|
|
||||||
netParams := activeNetParams.Params
|
netParams := activeNetParams.Params
|
||||||
estimator := lnwallet.NewStaticFeeEstimator(62500, 0)
|
estimator := lnwallet.NewStaticFeeEstimator(62500, 0)
|
||||||
@ -282,7 +283,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
|
|
||||||
var chanIDSeed [32]byte
|
var chanIDSeed [32]byte
|
||||||
|
|
||||||
f, err := newFundingManager(fundingConfig{
|
fundingCfg := fundingConfig{
|
||||||
IDKey: privKey.PubKey(),
|
IDKey: privKey.PubKey(),
|
||||||
Wallet: lnw,
|
Wallet: lnw,
|
||||||
Notifier: chainNotifier,
|
Notifier: chainNotifier,
|
||||||
@ -363,8 +364,15 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
},
|
},
|
||||||
ZombieSweeperInterval: 1 * time.Hour,
|
ZombieSweeperInterval: 1 * time.Hour,
|
||||||
ReservationTimeout: 1 * time.Nanosecond,
|
ReservationTimeout: 1 * time.Nanosecond,
|
||||||
|
MaxPendingChannels: DefaultMaxPendingChannels,
|
||||||
NotifyOpenChannelEvent: func(wire.OutPoint) {},
|
NotifyOpenChannelEvent: func(wire.OutPoint) {},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
for _, op := range options {
|
||||||
|
op(&fundingCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := newFundingManager(fundingCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed creating fundingManager: %v", err)
|
t.Fatalf("failed creating fundingManager: %v", err)
|
||||||
}
|
}
|
||||||
@ -468,12 +476,10 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupFundingManagers(t *testing.T, maxPendingChannels int) (*testNode, *testNode) {
|
type cfgOption func(*fundingConfig)
|
||||||
// We need to set the global config, as fundingManager uses
|
|
||||||
// MaxPendingChannels, and it is usually set in lndMain().
|
func setupFundingManagers(t *testing.T,
|
||||||
cfg = &config{
|
options ...cfgOption) (*testNode, *testNode) {
|
||||||
MaxPendingChannels: maxPendingChannels,
|
|
||||||
}
|
|
||||||
|
|
||||||
aliceTestDir, err := ioutil.TempDir("", "alicelnwallet")
|
aliceTestDir, err := ioutil.TempDir("", "alicelnwallet")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -481,7 +487,7 @@ func setupFundingManagers(t *testing.T, maxPendingChannels int) (*testNode, *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
alice, err := createTestFundingManager(
|
alice, err := createTestFundingManager(
|
||||||
t, alicePrivKey, aliceAddr, aliceTestDir,
|
t, alicePrivKey, aliceAddr, aliceTestDir, options...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed creating fundingManager: %v", err)
|
t.Fatalf("failed creating fundingManager: %v", err)
|
||||||
@ -492,7 +498,9 @@ func setupFundingManagers(t *testing.T, maxPendingChannels int) (*testNode, *tes
|
|||||||
t.Fatalf("unable to create temp directory: %v", err)
|
t.Fatalf("unable to create temp directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bob, err := createTestFundingManager(t, bobPrivKey, bobAddr, bobTestDir)
|
bob, err := createTestFundingManager(
|
||||||
|
t, bobPrivKey, bobAddr, bobTestDir, options...,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed creating fundingManager: %v", err)
|
t.Fatalf("failed creating fundingManager: %v", err)
|
||||||
}
|
}
|
||||||
@ -544,14 +552,35 @@ func tearDownFundingManagers(t *testing.T, a, b *testNode) {
|
|||||||
// transaction is confirmed on-chain. Returns the funding out point.
|
// transaction is confirmed on-chain. Returns the funding out point.
|
||||||
func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
||||||
pushAmt btcutil.Amount, numConfs uint32,
|
pushAmt btcutil.Amount, numConfs uint32,
|
||||||
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool) *wire.OutPoint {
|
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool) (
|
||||||
|
*wire.OutPoint, *wire.MsgTx) {
|
||||||
|
|
||||||
|
publ := fundChannel(
|
||||||
|
t, alice, bob, localFundingAmt, pushAmt, false, numConfs,
|
||||||
|
updateChan, announceChan,
|
||||||
|
)
|
||||||
|
fundingOutPoint := &wire.OutPoint{
|
||||||
|
Hash: publ.TxHash(),
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
return fundingOutPoint, publ
|
||||||
|
}
|
||||||
|
|
||||||
|
// fundChannel takes the funding process to the point where the funding
|
||||||
|
// transaction is confirmed on-chain. Returns the funding tx.
|
||||||
|
func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
||||||
|
pushAmt btcutil.Amount, subtractFees bool, numConfs uint32,
|
||||||
|
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool) *wire.MsgTx {
|
||||||
|
|
||||||
// Create a funding request and start the workflow.
|
// Create a funding request and start the workflow.
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
initReq := &openChanReq{
|
initReq := &openChanReq{
|
||||||
targetPubkey: bob.privKey.PubKey(),
|
targetPubkey: bob.privKey.PubKey(),
|
||||||
chainHash: *activeNetParams.GenesisHash,
|
chainHash: *activeNetParams.GenesisHash,
|
||||||
|
subtractFees: subtractFees,
|
||||||
localFundingAmt: localFundingAmt,
|
localFundingAmt: localFundingAmt,
|
||||||
pushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
|
pushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
|
||||||
|
fundingFeePerKw: 1000,
|
||||||
private: !announceChan,
|
private: !announceChan,
|
||||||
updates: updateChan,
|
updates: updateChan,
|
||||||
err: errChan,
|
err: errChan,
|
||||||
@ -636,17 +665,12 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
|||||||
t.Fatalf("alice did not publish funding tx")
|
t.Fatalf("alice did not publish funding tx")
|
||||||
}
|
}
|
||||||
|
|
||||||
fundingOutPoint := &wire.OutPoint{
|
|
||||||
Hash: publ.TxHash(),
|
|
||||||
Index: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, make sure neither have active reservation for the channel
|
// Finally, make sure neither have active reservation for the channel
|
||||||
// now pending open in the database.
|
// now pending open in the database.
|
||||||
assertNumPendingReservations(t, alice, bobPubKey, 0)
|
assertNumPendingReservations(t, alice, bobPubKey, 0)
|
||||||
assertNumPendingReservations(t, bob, alicePubKey, 0)
|
assertNumPendingReservations(t, bob, alicePubKey, 0)
|
||||||
|
|
||||||
return fundingOutPoint
|
return publ
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertErrorNotSent(t *testing.T, msgChan chan lnwire.Message) {
|
func assertErrorNotSent(t *testing.T, msgChan chan lnwire.Message) {
|
||||||
@ -674,6 +698,8 @@ func assertErrorSent(t *testing.T, msgChan chan lnwire.Message) {
|
|||||||
|
|
||||||
func assertFundingMsgSent(t *testing.T, msgChan chan lnwire.Message,
|
func assertFundingMsgSent(t *testing.T, msgChan chan lnwire.Message,
|
||||||
msgType string) lnwire.Message {
|
msgType string) lnwire.Message {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
var msg lnwire.Message
|
var msg lnwire.Message
|
||||||
select {
|
select {
|
||||||
case msg = <-msgChan:
|
case msg = <-msgChan:
|
||||||
@ -1029,7 +1055,9 @@ func assertHandleFundingLocked(t *testing.T, alice, bob *testNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFundingManagerNormalWorkflow(t *testing.T) {
|
func TestFundingManagerNormalWorkflow(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1040,16 +1068,21 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
|
|||||||
localAmt := btcutil.Amount(500000)
|
localAmt := btcutil.Amount(500000)
|
||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
capacity := localAmt + pushAmt
|
capacity := localAmt + pushAmt
|
||||||
fundingOutPoint := openChannel(t, alice, bob, localAmt, pushAmt, 1,
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
updateChan, true)
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
|
)
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@ -1090,8 +1123,12 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
|
|||||||
assertHandleFundingLocked(t, alice, bob)
|
assertHandleFundingLocked(t, alice, bob)
|
||||||
|
|
||||||
// Notify that six confirmations has been reached on funding transaction.
|
// Notify that six confirmations has been reached on funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the fundingManagers exchange announcement signatures.
|
// Make sure the fundingManagers exchange announcement signatures.
|
||||||
assertAnnouncementSignatures(t, alice, bob)
|
assertAnnouncementSignatures(t, alice, bob)
|
||||||
@ -1102,7 +1139,9 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFundingManagerRestartBehavior(t *testing.T) {
|
func TestFundingManagerRestartBehavior(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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
|
||||||
@ -1111,8 +1150,9 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
|
|||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
capacity := localAmt + pushAmt
|
capacity := localAmt + pushAmt
|
||||||
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
||||||
fundingOutPoint := openChannel(t, alice, bob, localAmt, pushAmt, 1,
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
updateChan, true)
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
|
)
|
||||||
|
|
||||||
// After the funding transaction gets mined, both nodes will send the
|
// After the funding transaction gets mined, both nodes will send the
|
||||||
// fundingLocked message to the other peer. If the funding node fails
|
// fundingLocked message to the other peer. If the funding node fails
|
||||||
@ -1133,8 +1173,12 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@ -1225,8 +1269,12 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
|
|||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
|
||||||
// Notify that six confirmations has been reached on funding transaction.
|
// Notify that six confirmations has been reached on funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the fundingManagers exchange announcement signatures.
|
// Make sure the fundingManagers exchange announcement signatures.
|
||||||
assertAnnouncementSignatures(t, alice, bob)
|
assertAnnouncementSignatures(t, alice, bob)
|
||||||
@ -1240,7 +1288,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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
|
||||||
@ -1249,8 +1299,9 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
|
|||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
capacity := localAmt + pushAmt
|
capacity := localAmt + pushAmt
|
||||||
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
||||||
fundingOutPoint := openChannel(t, alice, bob, localAmt, pushAmt, 1,
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
updateChan, true)
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
|
)
|
||||||
|
|
||||||
// After the funding transaction gets mined, both nodes will send the
|
// After the funding transaction gets mined, both nodes will send the
|
||||||
// fundingLocked message to the other peer. If the funding node fails
|
// fundingLocked message to the other peer. If the funding node fails
|
||||||
@ -1272,8 +1323,12 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@ -1358,8 +1413,12 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
|
|||||||
assertHandleFundingLocked(t, alice, bob)
|
assertHandleFundingLocked(t, alice, bob)
|
||||||
|
|
||||||
// Notify that six confirmations has been reached on funding transaction.
|
// Notify that six confirmations has been reached on funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure both fundingManagers send the expected announcement
|
// Make sure both fundingManagers send the expected announcement
|
||||||
// signatures.
|
// signatures.
|
||||||
@ -1374,7 +1433,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1434,7 +1495,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1503,7 +1566,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1577,7 +1642,9 @@ func TestFundingManagerPeerTimeoutAfterFundingAccept(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFundingManagerFundingTimeout(t *testing.T) {
|
func TestFundingManagerFundingTimeout(t *testing.T) {
|
||||||
alice, bob := setupFundingManagers(t, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1585,7 +1652,7 @@ func TestFundingManagerFundingTimeout(t *testing.T) {
|
|||||||
|
|
||||||
// Run through the process of opening the channel, up until the funding
|
// Run through the process of opening the channel, up until the funding
|
||||||
// transaction is broadcasted.
|
// transaction is broadcasted.
|
||||||
_ = openChannel(t, alice, bob, 500000, 0, 1, updateChan, true)
|
_, _ = openChannel(t, alice, bob, 500000, 0, 1, updateChan, true)
|
||||||
|
|
||||||
// Bob will at this point be waiting for the funding transaction to be
|
// Bob will at this point be waiting for the funding transaction to be
|
||||||
// confirmed, so the channel should be considered pending.
|
// confirmed, so the channel should be considered pending.
|
||||||
@ -1621,8 +1688,9 @@ func TestFundingManagerFundingTimeout(t *testing.T) {
|
|||||||
// TestFundingManagerFundingNotTimeoutInitiator checks that if the user was
|
// TestFundingManagerFundingNotTimeoutInitiator checks that if the user was
|
||||||
// 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) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
alice, bob := setupFundingManagers(t, DefaultMaxPendingChannels)
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1630,7 +1698,7 @@ func TestFundingManagerFundingNotTimeoutInitiator(t *testing.T) {
|
|||||||
|
|
||||||
// Run through the process of opening the channel, up until the funding
|
// Run through the process of opening the channel, up until the funding
|
||||||
// transaction is broadcasted.
|
// transaction is broadcasted.
|
||||||
_ = openChannel(t, alice, bob, 500000, 0, 1, updateChan, true)
|
_, _ = openChannel(t, alice, bob, 500000, 0, 1, updateChan, true)
|
||||||
|
|
||||||
// Alice will at this point be waiting for the funding transaction to be
|
// Alice will at this point be waiting for the funding transaction to be
|
||||||
// confirmed, so the channel should be considered pending.
|
// confirmed, so the channel should be considered pending.
|
||||||
@ -1689,7 +1757,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1700,12 +1770,17 @@ func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
|
|||||||
localAmt := btcutil.Amount(500000)
|
localAmt := btcutil.Amount(500000)
|
||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
capacity := localAmt + pushAmt
|
capacity := localAmt + pushAmt
|
||||||
fundingOutPoint := openChannel(t, alice, bob, localAmt, pushAmt, 1,
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
updateChan, true)
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
|
)
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@ -1766,8 +1841,12 @@ func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Notify that six confirmations has been reached on funding transaction.
|
// Notify that six confirmations has been reached on funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the fundingManagers exchange announcement signatures.
|
// Make sure the fundingManagers exchange announcement signatures.
|
||||||
assertAnnouncementSignatures(t, alice, bob)
|
assertAnnouncementSignatures(t, alice, bob)
|
||||||
@ -1781,7 +1860,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1792,12 +1873,17 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
|
|||||||
localAmt := btcutil.Amount(500000)
|
localAmt := btcutil.Amount(500000)
|
||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
capacity := localAmt + pushAmt
|
capacity := localAmt + pushAmt
|
||||||
fundingOutPoint := openChannel(t, alice, bob, localAmt, pushAmt, 1,
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
updateChan, true)
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
|
)
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@ -1843,8 +1929,12 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
|
|||||||
assertHandleFundingLocked(t, alice, bob)
|
assertHandleFundingLocked(t, alice, bob)
|
||||||
|
|
||||||
// Notify that six confirmations has been reached on funding transaction.
|
// Notify that six confirmations has been reached on funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel announcements.
|
// Make sure both fundingManagers send the expected channel announcements.
|
||||||
assertAnnouncementSignatures(t, alice, bob)
|
assertAnnouncementSignatures(t, alice, bob)
|
||||||
@ -1858,7 +1948,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1869,12 +1961,17 @@ func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
|
|||||||
localAmt := btcutil.Amount(500000)
|
localAmt := btcutil.Amount(500000)
|
||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
capacity := localAmt + pushAmt
|
capacity := localAmt + pushAmt
|
||||||
fundingOutPoint := openChannel(t, alice, bob, localAmt, pushAmt, 1,
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
updateChan, true)
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
|
)
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@ -1916,8 +2013,12 @@ func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
|
|||||||
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
// Notify that six confirmations has been reached on funding transaction.
|
// Notify that six confirmations has been reached on funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel announcements.
|
// Make sure both fundingManagers send the expected channel announcements.
|
||||||
assertAnnouncementSignatures(t, alice, bob)
|
assertAnnouncementSignatures(t, alice, bob)
|
||||||
@ -1931,7 +2032,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -1942,12 +2045,17 @@ func TestFundingManagerPrivateChannel(t *testing.T) {
|
|||||||
localAmt := btcutil.Amount(500000)
|
localAmt := btcutil.Amount(500000)
|
||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
capacity := localAmt + pushAmt
|
capacity := localAmt + pushAmt
|
||||||
fundingOutPoint := openChannel(t, alice, bob, localAmt, pushAmt, 1,
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
updateChan, false)
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, false,
|
||||||
|
)
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@ -1985,8 +2093,12 @@ func TestFundingManagerPrivateChannel(t *testing.T) {
|
|||||||
assertHandleFundingLocked(t, alice, bob)
|
assertHandleFundingLocked(t, alice, bob)
|
||||||
|
|
||||||
// Notify that six confirmations has been reached on funding transaction.
|
// Notify that six confirmations has been reached on funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// Since this is a private channel, we shouldn't receive the
|
// Since this is a private channel, we shouldn't receive the
|
||||||
// announcement signatures.
|
// announcement signatures.
|
||||||
@ -2033,7 +2145,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -2044,12 +2158,17 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
|
|||||||
localAmt := btcutil.Amount(500000)
|
localAmt := btcutil.Amount(500000)
|
||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
capacity := localAmt + pushAmt
|
capacity := localAmt + pushAmt
|
||||||
fundingOutPoint := openChannel(t, alice, bob, localAmt, pushAmt, 1,
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
updateChan, false)
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, false,
|
||||||
|
)
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@ -2092,8 +2211,12 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
|
|||||||
assertHandleFundingLocked(t, alice, bob)
|
assertHandleFundingLocked(t, alice, bob)
|
||||||
|
|
||||||
// Notify that six confirmations has been reached on funding transaction.
|
// Notify that six confirmations has been reached on funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// Since this is a private channel, we shouldn't receive the public
|
// Since this is a private channel, we shouldn't receive the public
|
||||||
// channel announcement messages.
|
// channel announcement messages.
|
||||||
@ -2155,7 +2278,9 @@ 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, DefaultMaxPendingChannels)
|
t.Parallel()
|
||||||
|
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
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.
|
||||||
@ -2348,15 +2473,20 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Alice to published the funding tx to the network.
|
// Wait for Alice to published the funding tx to the network.
|
||||||
|
var fundingTx *wire.MsgTx
|
||||||
select {
|
select {
|
||||||
case <-alice.publTxChan:
|
case fundingTx = <-alice.publTxChan:
|
||||||
case <-time.After(time.Second * 5):
|
case <-time.After(time.Second * 5):
|
||||||
t.Fatalf("alice did not publish funding tx")
|
t.Fatalf("alice did not publish funding tx")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{}
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: fundingTx,
|
||||||
|
}
|
||||||
|
|
||||||
// After the funding transaction is mined, Alice will send
|
// After the funding transaction is mined, Alice will send
|
||||||
// fundingLocked to Bob.
|
// fundingLocked to Bob.
|
||||||
@ -2383,9 +2513,15 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
// TestFundingManagerMaxPendingChannels checks that trying to open another
|
// TestFundingManagerMaxPendingChannels checks that trying to open another
|
||||||
// channel with the same peer when MaxPending channels are pending fails.
|
// channel with the same peer when MaxPending channels are pending fails.
|
||||||
func TestFundingManagerMaxPendingChannels(t *testing.T) {
|
func TestFundingManagerMaxPendingChannels(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
const maxPending = 4
|
const maxPending = 4
|
||||||
|
|
||||||
alice, bob := setupFundingManagers(t, maxPending)
|
alice, bob := setupFundingManagers(
|
||||||
|
t, func(cfg *fundingConfig) {
|
||||||
|
cfg.MaxPendingChannels = maxPending
|
||||||
|
},
|
||||||
|
)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// Create openChanReqs for maxPending+1 channels.
|
// Create openChanReqs for maxPending+1 channels.
|
||||||
@ -2483,6 +2619,7 @@ func TestFundingManagerMaxPendingChannels(t *testing.T) {
|
|||||||
).(*lnwire.Error)
|
).(*lnwire.Error)
|
||||||
|
|
||||||
// Give the FundingSigned messages to Alice.
|
// Give the FundingSigned messages to Alice.
|
||||||
|
var txs []*wire.MsgTx
|
||||||
for i, sign := range signs {
|
for i, sign := range signs {
|
||||||
alice.fundingMgr.processFundingSigned(sign, bob)
|
alice.fundingMgr.processFundingSigned(sign, bob)
|
||||||
|
|
||||||
@ -2501,7 +2638,8 @@ func TestFundingManagerMaxPendingChannels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-alice.publTxChan:
|
case tx := <-alice.publTxChan:
|
||||||
|
txs = append(txs, tx)
|
||||||
case <-time.After(time.Second * 5):
|
case <-time.After(time.Second * 5):
|
||||||
t.Fatalf("alice did not publish funding tx")
|
t.Fatalf("alice did not publish funding tx")
|
||||||
}
|
}
|
||||||
@ -2518,8 +2656,12 @@ func TestFundingManagerMaxPendingChannels(t *testing.T) {
|
|||||||
|
|
||||||
// Notify that the transactions were mined.
|
// Notify that the transactions were mined.
|
||||||
for i := 0; i < maxPending; i++ {
|
for i := 0; i < maxPending; i++ {
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{}
|
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{}
|
Tx: txs[i],
|
||||||
|
}
|
||||||
|
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
|
Tx: txs[i],
|
||||||
|
}
|
||||||
|
|
||||||
// Expect both to be sending FundingLocked.
|
// Expect both to be sending FundingLocked.
|
||||||
_ = assertFundingMsgSent(
|
_ = assertFundingMsgSent(
|
||||||
@ -2544,14 +2686,15 @@ func TestFundingManagerMaxPendingChannels(t *testing.T) {
|
|||||||
// TestFundingManagerRejectPush checks behaviour of 'rejectpush'
|
// TestFundingManagerRejectPush checks behaviour of 'rejectpush'
|
||||||
// option, namely that non-zero incoming push amounts are disabled.
|
// option, namely that non-zero incoming push amounts are disabled.
|
||||||
func TestFundingManagerRejectPush(t *testing.T) {
|
func TestFundingManagerRejectPush(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Enable 'rejectpush' option and initialize funding managers.
|
// Enable 'rejectpush' option and initialize funding managers.
|
||||||
alice, bob := setupFundingManagers(t, DefaultMaxPendingChannels)
|
alice, bob := setupFundingManagers(
|
||||||
rejectPush := cfg.RejectPush
|
t, func(cfg *fundingConfig) {
|
||||||
cfg.RejectPush = true
|
cfg.RejectPush = true
|
||||||
defer func() {
|
},
|
||||||
tearDownFundingManagers(t, alice, bob)
|
)
|
||||||
cfg.RejectPush = rejectPush
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
}()
|
|
||||||
|
|
||||||
// Create a funding request and start the workflow.
|
// Create a funding request and start the workflow.
|
||||||
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
||||||
@ -2607,7 +2750,7 @@ func TestFundingManagerRejectPush(t *testing.T) {
|
|||||||
func TestFundingManagerMaxConfs(t *testing.T) {
|
func TestFundingManagerMaxConfs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
alice, bob := setupFundingManagers(t, DefaultMaxPendingChannels)
|
alice, bob := setupFundingManagers(t)
|
||||||
defer tearDownFundingManagers(t, alice, bob)
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
// Create a funding request and start the workflow.
|
// Create a funding request and start the workflow.
|
||||||
@ -2669,3 +2812,102 @@ func TestFundingManagerMaxConfs(t *testing.T) {
|
|||||||
string(err.Data))
|
string(err.Data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFundingManagerFundAll tests that we can initiate a funding request to
|
||||||
|
// use the funds remaining in the wallet. This should produce a funding tx with
|
||||||
|
// no change output.
|
||||||
|
func TestFundingManagerFundAll(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// We set up our mock wallet to control a list of UTXOs that sum to
|
||||||
|
// less than the max channel size.
|
||||||
|
allCoins := []*lnwallet.Utxo{
|
||||||
|
{
|
||||||
|
AddressType: lnwallet.WitnessPubKey,
|
||||||
|
Value: btcutil.Amount(
|
||||||
|
0.05 * btcutil.SatoshiPerBitcoin,
|
||||||
|
),
|
||||||
|
PkScript: make([]byte, 22),
|
||||||
|
OutPoint: wire.OutPoint{
|
||||||
|
Hash: chainhash.Hash{},
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AddressType: lnwallet.WitnessPubKey,
|
||||||
|
Value: btcutil.Amount(
|
||||||
|
0.06 * btcutil.SatoshiPerBitcoin,
|
||||||
|
),
|
||||||
|
PkScript: make([]byte, 22),
|
||||||
|
OutPoint: wire.OutPoint{
|
||||||
|
Hash: chainhash.Hash{},
|
||||||
|
Index: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
spendAmt btcutil.Amount
|
||||||
|
change bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// We will spend all the funds in the wallet, and
|
||||||
|
// expects no change output.
|
||||||
|
spendAmt: btcutil.Amount(
|
||||||
|
0.11 * btcutil.SatoshiPerBitcoin,
|
||||||
|
),
|
||||||
|
change: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We spend a little less than the funds in the wallet,
|
||||||
|
// so a change output should be created.
|
||||||
|
spendAmt: btcutil.Amount(
|
||||||
|
0.10 * btcutil.SatoshiPerBitcoin,
|
||||||
|
),
|
||||||
|
change: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
alice, bob := setupFundingManagers(t)
|
||||||
|
defer tearDownFundingManagers(t, alice, bob)
|
||||||
|
|
||||||
|
alice.fundingMgr.cfg.Wallet.WalletController.(*mockWalletController).utxos = allCoins
|
||||||
|
|
||||||
|
// We will consume the channel updates as we go, so no
|
||||||
|
// buffering is needed.
|
||||||
|
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
||||||
|
|
||||||
|
// Initiate a fund channel, and inspect the funding tx.
|
||||||
|
pushAmt := btcutil.Amount(0)
|
||||||
|
fundingTx := fundChannel(
|
||||||
|
t, alice, bob, test.spendAmt, pushAmt, true, 1,
|
||||||
|
updateChan, true,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check whether the expected change output is present.
|
||||||
|
if test.change && len(fundingTx.TxOut) != 2 {
|
||||||
|
t.Fatalf("expected 2 outputs, had %v",
|
||||||
|
len(fundingTx.TxOut))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !test.change && len(fundingTx.TxOut) != 1 {
|
||||||
|
t.Fatalf("expected 1 output, had %v",
|
||||||
|
len(fundingTx.TxOut))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inputs should be all funds in the wallet.
|
||||||
|
if len(fundingTx.TxIn) != len(allCoins) {
|
||||||
|
t.Fatalf("Had %d inputs, expected %d",
|
||||||
|
len(fundingTx.TxIn), len(allCoins))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, txIn := range fundingTx.TxIn {
|
||||||
|
if txIn.PreviousOutPoint != allCoins[i].OutPoint {
|
||||||
|
t.Fatalf("expected outpoint to be %v, was %v",
|
||||||
|
allCoins[i].OutPoint,
|
||||||
|
txIn.PreviousOutPoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -418,8 +418,8 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
|||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
NodeID: bobPub,
|
NodeID: bobPub,
|
||||||
NodeAddr: bobAddr,
|
NodeAddr: bobAddr,
|
||||||
FundingAmount: fundingAmount,
|
LocalFundingAmt: fundingAmount,
|
||||||
Capacity: fundingAmount * 2,
|
RemoteFundingAmt: fundingAmount,
|
||||||
CommitFeePerKw: feePerKw,
|
CommitFeePerKw: feePerKw,
|
||||||
FundingFeePerKw: feePerKw,
|
FundingFeePerKw: feePerKw,
|
||||||
PushMSat: 0,
|
PushMSat: 0,
|
||||||
@ -461,8 +461,8 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
|||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
NodeID: alicePub,
|
NodeID: alicePub,
|
||||||
NodeAddr: aliceAddr,
|
NodeAddr: aliceAddr,
|
||||||
FundingAmount: fundingAmount,
|
LocalFundingAmt: fundingAmount,
|
||||||
Capacity: fundingAmount * 2,
|
RemoteFundingAmt: fundingAmount,
|
||||||
CommitFeePerKw: feePerKw,
|
CommitFeePerKw: feePerKw,
|
||||||
FundingFeePerKw: feePerKw,
|
FundingFeePerKw: feePerKw,
|
||||||
PushMSat: 0,
|
PushMSat: 0,
|
||||||
@ -621,8 +621,8 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
|
|||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
NodeID: bobPub,
|
NodeID: bobPub,
|
||||||
NodeAddr: bobAddr,
|
NodeAddr: bobAddr,
|
||||||
FundingAmount: fundingAmount,
|
LocalFundingAmt: fundingAmount,
|
||||||
Capacity: fundingAmount,
|
RemoteFundingAmt: 0,
|
||||||
CommitFeePerKw: feePerKw,
|
CommitFeePerKw: feePerKw,
|
||||||
FundingFeePerKw: feePerKw,
|
FundingFeePerKw: feePerKw,
|
||||||
PushMSat: 0,
|
PushMSat: 0,
|
||||||
@ -643,8 +643,8 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
|
|||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
NodeID: bobPub,
|
NodeID: bobPub,
|
||||||
NodeAddr: bobAddr,
|
NodeAddr: bobAddr,
|
||||||
FundingAmount: amt,
|
LocalFundingAmt: amt,
|
||||||
Capacity: amt,
|
RemoteFundingAmt: 0,
|
||||||
CommitFeePerKw: feePerKw,
|
CommitFeePerKw: feePerKw,
|
||||||
FundingFeePerKw: feePerKw,
|
FundingFeePerKw: feePerKw,
|
||||||
PushMSat: 0,
|
PushMSat: 0,
|
||||||
@ -679,8 +679,8 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
|
|||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
NodeID: bobPub,
|
NodeID: bobPub,
|
||||||
NodeAddr: bobAddr,
|
NodeAddr: bobAddr,
|
||||||
FundingAmount: fundingAmount,
|
LocalFundingAmt: fundingAmount,
|
||||||
Capacity: fundingAmount,
|
RemoteFundingAmt: 0,
|
||||||
CommitFeePerKw: feePerKw,
|
CommitFeePerKw: feePerKw,
|
||||||
FundingFeePerKw: feePerKw,
|
FundingFeePerKw: feePerKw,
|
||||||
PushMSat: 0,
|
PushMSat: 0,
|
||||||
@ -753,9 +753,9 @@ func testCancelNonExistentReservation(miner *rpctest.Harness,
|
|||||||
func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
|
func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
|
||||||
alice, _ *lnwallet.LightningWallet, t *testing.T) {
|
alice, _ *lnwallet.LightningWallet, t *testing.T) {
|
||||||
|
|
||||||
// We'll attempt to create a new reservation with an extremely high fee
|
// We'll attempt to create a new reservation with an extremely high
|
||||||
// rate. This should push our balance into the negative and result in a
|
// commitment fee rate. This should push our balance into the negative
|
||||||
// failure to create the reservation.
|
// and result in a failure to create the reservation.
|
||||||
const numBTC = 4
|
const numBTC = 4
|
||||||
fundingAmount, err := btcutil.NewAmount(numBTC)
|
fundingAmount, err := btcutil.NewAmount(numBTC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -769,10 +769,10 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
|
|||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
NodeID: bobPub,
|
NodeID: bobPub,
|
||||||
NodeAddr: bobAddr,
|
NodeAddr: bobAddr,
|
||||||
FundingAmount: fundingAmount,
|
LocalFundingAmt: fundingAmount,
|
||||||
Capacity: fundingAmount,
|
RemoteFundingAmt: 0,
|
||||||
CommitFeePerKw: feePerKw,
|
CommitFeePerKw: feePerKw,
|
||||||
FundingFeePerKw: feePerKw,
|
FundingFeePerKw: 1000,
|
||||||
PushMSat: 0,
|
PushMSat: 0,
|
||||||
Flags: lnwire.FFAnnounceChannel,
|
Flags: lnwire.FFAnnounceChannel,
|
||||||
}
|
}
|
||||||
@ -850,8 +850,8 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
|||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
NodeID: bobPub,
|
NodeID: bobPub,
|
||||||
NodeAddr: bobAddr,
|
NodeAddr: bobAddr,
|
||||||
FundingAmount: fundingAmt,
|
LocalFundingAmt: fundingAmt,
|
||||||
Capacity: fundingAmt,
|
RemoteFundingAmt: 0,
|
||||||
CommitFeePerKw: feePerKw,
|
CommitFeePerKw: feePerKw,
|
||||||
FundingFeePerKw: feePerKw,
|
FundingFeePerKw: feePerKw,
|
||||||
PushMSat: pushAmt,
|
PushMSat: pushAmt,
|
||||||
@ -893,8 +893,8 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
|||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
NodeID: alicePub,
|
NodeID: alicePub,
|
||||||
NodeAddr: aliceAddr,
|
NodeAddr: aliceAddr,
|
||||||
FundingAmount: 0,
|
LocalFundingAmt: 0,
|
||||||
Capacity: fundingAmt,
|
RemoteFundingAmt: fundingAmt,
|
||||||
CommitFeePerKw: feePerKw,
|
CommitFeePerKw: feePerKw,
|
||||||
FundingFeePerKw: feePerKw,
|
FundingFeePerKw: feePerKw,
|
||||||
PushMSat: pushAmt,
|
PushMSat: pushAmt,
|
||||||
|
@ -127,7 +127,7 @@ type ChannelReservation struct {
|
|||||||
// used only internally by lnwallet. In order to concurrent safety, the
|
// used only internally by lnwallet. In order to concurrent safety, the
|
||||||
// creation of all channel reservations should be carried out via the
|
// creation of all channel reservations should be carried out via the
|
||||||
// lnwallet.InitChannelReservation interface.
|
// lnwallet.InitChannelReservation interface.
|
||||||
func NewChannelReservation(capacity, fundingAmt btcutil.Amount,
|
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||||
commitFeePerKw SatPerKWeight, wallet *LightningWallet,
|
commitFeePerKw SatPerKWeight, wallet *LightningWallet,
|
||||||
id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash,
|
id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash,
|
||||||
flags lnwire.FundingFlag) (*ChannelReservation, error) {
|
flags lnwire.FundingFlag) (*ChannelReservation, error) {
|
||||||
@ -139,14 +139,16 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount,
|
|||||||
)
|
)
|
||||||
|
|
||||||
commitFee := commitFeePerKw.FeeForWeight(input.CommitWeight)
|
commitFee := commitFeePerKw.FeeForWeight(input.CommitWeight)
|
||||||
fundingMSat := lnwire.NewMSatFromSatoshis(fundingAmt)
|
localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
|
||||||
|
// TODO(halseth): make method take remote funding amount direcly
|
||||||
|
// instead of inferring it from capacity and local amt.
|
||||||
capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
|
capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
|
||||||
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
|
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
|
||||||
|
|
||||||
// If we're the responder to a single-funder reservation, then we have
|
// If we're the responder to a single-funder reservation, then we have
|
||||||
// no initial balance in the channel unless the remote party is pushing
|
// no initial balance in the channel unless the remote party is pushing
|
||||||
// some funds to us within the first commitment state.
|
// some funds to us within the first commitment state.
|
||||||
if fundingAmt == 0 {
|
if localFundingAmt == 0 {
|
||||||
ourBalance = pushMSat
|
ourBalance = pushMSat
|
||||||
theirBalance = capacityMSat - feeMSat - pushMSat
|
theirBalance = capacityMSat - feeMSat - pushMSat
|
||||||
initiator = false
|
initiator = false
|
||||||
@ -163,7 +165,7 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount,
|
|||||||
// TODO(roasbeef): need to rework fee structure in general and
|
// TODO(roasbeef): need to rework fee structure in general and
|
||||||
// also when we "unlock" dual funder within the daemon
|
// also when we "unlock" dual funder within the daemon
|
||||||
|
|
||||||
if capacity == fundingAmt {
|
if capacity == localFundingAmt {
|
||||||
// If we're initiating a single funder workflow, then
|
// If we're initiating a single funder workflow, then
|
||||||
// we pay all the initial fees within the commitment
|
// we pay all the initial fees within the commitment
|
||||||
// transaction. We also deduct our balance by the
|
// transaction. We also deduct our balance by the
|
||||||
@ -174,8 +176,8 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount,
|
|||||||
// Otherwise, this is a dual funder workflow where both
|
// Otherwise, this is a dual funder workflow where both
|
||||||
// slides split the amount funded and the commitment
|
// slides split the amount funded and the commitment
|
||||||
// fee.
|
// fee.
|
||||||
ourBalance = fundingMSat - (feeMSat / 2)
|
ourBalance = localFundingMSat - (feeMSat / 2)
|
||||||
theirBalance = capacityMSat - fundingMSat - (feeMSat / 2) + pushMSat
|
theirBalance = capacityMSat - localFundingMSat - (feeMSat / 2) + pushMSat
|
||||||
}
|
}
|
||||||
|
|
||||||
initiator = true
|
initiator = true
|
||||||
@ -513,6 +515,13 @@ func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
|
|||||||
return &r.partialState.FundingOutpoint
|
return &r.partialState.FundingOutpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capacity returns the channel capacity for this reservation.
|
||||||
|
func (r *ChannelReservation) Capacity() btcutil.Amount {
|
||||||
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
return r.partialState.Capacity
|
||||||
|
}
|
||||||
|
|
||||||
// Cancel abandons this channel reservation. This method should be called in
|
// Cancel abandons this channel reservation. This method should be called in
|
||||||
// the scenario that communications with the counterparty break down. Upon
|
// the scenario that communications with the counterparty break down. Upon
|
||||||
// cancellation, all resources previously reserved for this pending payment
|
// cancellation, all resources previously reserved for this pending payment
|
||||||
|
@ -69,12 +69,20 @@ type InitFundingReserveMsg struct {
|
|||||||
// workflow.
|
// workflow.
|
||||||
NodeAddr net.Addr
|
NodeAddr net.Addr
|
||||||
|
|
||||||
// FundingAmount is the amount of funds requested for this channel.
|
// SubtractFees should be set if we intend to spend exactly
|
||||||
FundingAmount btcutil.Amount
|
// LocalFundingAmt when opening the channel, subtracting the fees from
|
||||||
|
// the funding output. This can be used for instance to use all our
|
||||||
|
// remaining funds to open the channel, since it will take fees into
|
||||||
|
// account.
|
||||||
|
SubtractFees bool
|
||||||
|
|
||||||
// Capacity is the total capacity of the channel which includes the
|
// LocalFundingAmt is the amount of funds requested from us for this
|
||||||
// amount of funds the remote party contributes (if any).
|
// channel.
|
||||||
Capacity btcutil.Amount
|
LocalFundingAmt btcutil.Amount
|
||||||
|
|
||||||
|
// RemoteFundingAmnt is the amount of funds the remote will contribute
|
||||||
|
// to this channel.
|
||||||
|
RemoteFundingAmt btcutil.Amount
|
||||||
|
|
||||||
// CommitFeePerKw is the starting accepted satoshis/Kw fee for the set
|
// CommitFeePerKw is the starting accepted satoshis/Kw fee for the set
|
||||||
// of initial commitment transactions. In order to ensure timely
|
// of initial commitment transactions. In order to ensure timely
|
||||||
@ -431,7 +439,7 @@ func (l *LightningWallet) InitChannelReservation(
|
|||||||
// validate a funding reservation request.
|
// validate a funding reservation request.
|
||||||
func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg) {
|
func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg) {
|
||||||
// It isn't possible to create a channel with zero funds committed.
|
// It isn't possible to create a channel with zero funds committed.
|
||||||
if req.FundingAmount+req.Capacity == 0 {
|
if req.LocalFundingAmt+req.RemoteFundingAmt == 0 {
|
||||||
err := ErrZeroCapacity()
|
err := ErrZeroCapacity()
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
@ -449,128 +457,59 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localFundingAmt := req.LocalFundingAmt
|
||||||
|
|
||||||
|
var (
|
||||||
|
selected *coinSelection
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// If we're on the receiving end of a single funder channel then we
|
||||||
|
// don't need to perform any coin selection, and the remote contributes
|
||||||
|
// all funds. Otherwise, attempt to obtain enough coins to meet the
|
||||||
|
// required funding amount.
|
||||||
|
if req.LocalFundingAmt != 0 {
|
||||||
|
// Coin selection is done on the basis of sat/kw, so we'll use
|
||||||
|
// the fee rate passed in to perform coin selection.
|
||||||
|
var err error
|
||||||
|
selected, err = l.selectCoinsAndChange(
|
||||||
|
req.FundingFeePerKw, req.LocalFundingAmt, req.MinConfs,
|
||||||
|
req.SubtractFees,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
req.err <- err
|
||||||
|
req.resp <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
localFundingAmt = selected.fundingAmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// The total channel capacity will be the size of the funding output we
|
||||||
|
// created plus the remote contribution.
|
||||||
|
capacity := localFundingAmt + req.RemoteFundingAmt
|
||||||
|
|
||||||
id := atomic.AddUint64(&l.nextFundingID, 1)
|
id := atomic.AddUint64(&l.nextFundingID, 1)
|
||||||
reservation, err := NewChannelReservation(
|
reservation, err := NewChannelReservation(
|
||||||
req.Capacity, req.FundingAmount, req.CommitFeePerKw, l, id,
|
capacity, localFundingAmt, req.CommitFeePerKw, l, id,
|
||||||
req.PushMSat, l.Cfg.NetParams.GenesisHash, req.Flags,
|
req.PushMSat, l.Cfg.NetParams.GenesisHash, req.Flags,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
selected.unlockCoins()
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab the mutex on the ChannelReservation to ensure thread-safety
|
err = l.initOurContribution(
|
||||||
reservation.Lock()
|
reservation, selected, req.NodeAddr, req.NodeID,
|
||||||
defer reservation.Unlock()
|
|
||||||
|
|
||||||
reservation.nodeAddr = req.NodeAddr
|
|
||||||
reservation.partialState.IdentityPub = req.NodeID
|
|
||||||
|
|
||||||
// If we're on the receiving end of a single funder channel then we
|
|
||||||
// don't need to perform any coin selection. Otherwise, attempt to
|
|
||||||
// obtain enough coins to meet the required funding amount.
|
|
||||||
if req.FundingAmount != 0 {
|
|
||||||
// Coin selection is done on the basis of sat/kw, so we'll use
|
|
||||||
// the fee rate passed in to perform coin selection.
|
|
||||||
err := l.selectCoinsAndChange(
|
|
||||||
req.FundingFeePerKw, req.FundingAmount, req.MinConfs,
|
|
||||||
reservation.ourContribution,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
selected.unlockCoins()
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Next, we'll grab a series of keys from the wallet which will be used
|
|
||||||
// for the duration of the channel. The keys include: our multi-sig
|
|
||||||
// key, the base revocation key, the base htlc key,the base payment
|
|
||||||
// key, and the delayed payment key.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): "salt" each key as well?
|
|
||||||
reservation.ourContribution.MultiSigKey, err = l.DeriveNextKey(
|
|
||||||
keychain.KeyFamilyMultiSig,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reservation.ourContribution.RevocationBasePoint, err = l.DeriveNextKey(
|
|
||||||
keychain.KeyFamilyRevocationBase,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reservation.ourContribution.HtlcBasePoint, err = l.DeriveNextKey(
|
|
||||||
keychain.KeyFamilyHtlcBase,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reservation.ourContribution.PaymentBasePoint, err = l.DeriveNextKey(
|
|
||||||
keychain.KeyFamilyPaymentBase,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reservation.ourContribution.DelayBasePoint, err = l.DeriveNextKey(
|
|
||||||
keychain.KeyFamilyDelayBase,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the above keys created, we'll also need to initialization our
|
|
||||||
// initial revocation tree state.
|
|
||||||
nextRevocationKeyDesc, err := l.DeriveNextKey(
|
|
||||||
keychain.KeyFamilyRevocationRoot,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
revocationRoot, err := l.DerivePrivKey(nextRevocationKeyDesc)
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we have the root, we can then generate our shachain producer
|
|
||||||
// and from that generate the per-commitment point.
|
|
||||||
revRoot, err := chainhash.NewHash(revocationRoot.Serialize())
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
producer := shachain.NewRevocationProducer(*revRoot)
|
|
||||||
firstPreimage, err := producer.AtIndex(0)
|
|
||||||
if err != nil {
|
|
||||||
req.err <- err
|
|
||||||
req.resp <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reservation.ourContribution.FirstCommitmentPoint = input.ComputeCommitmentPoint(
|
|
||||||
firstPreimage[:],
|
|
||||||
)
|
|
||||||
|
|
||||||
reservation.partialState.RevocationProducer = producer
|
|
||||||
reservation.ourContribution.ChannelConstraints = l.Cfg.DefaultConstraints
|
|
||||||
|
|
||||||
// TODO(roasbeef): turn above into: initContribution()
|
|
||||||
|
|
||||||
// Create a limbo and record entry for this newly pending funding
|
// Create a limbo and record entry for this newly pending funding
|
||||||
// request.
|
// request.
|
||||||
@ -585,6 +524,96 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
|||||||
req.err <- nil
|
req.err <- nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initOurContribution initializes the given ChannelReservation with our coins
|
||||||
|
// and change reserved for the channel, and derives the keys to use for this
|
||||||
|
// channel.
|
||||||
|
func (l *LightningWallet) initOurContribution(reservation *ChannelReservation,
|
||||||
|
selected *coinSelection, nodeAddr net.Addr, nodeID *btcec.PublicKey) error {
|
||||||
|
|
||||||
|
// Grab the mutex on the ChannelReservation to ensure thread-safety
|
||||||
|
reservation.Lock()
|
||||||
|
defer reservation.Unlock()
|
||||||
|
|
||||||
|
if selected != nil {
|
||||||
|
reservation.ourContribution.Inputs = selected.coins
|
||||||
|
reservation.ourContribution.ChangeOutputs = selected.change
|
||||||
|
}
|
||||||
|
|
||||||
|
reservation.nodeAddr = nodeAddr
|
||||||
|
reservation.partialState.IdentityPub = nodeID
|
||||||
|
|
||||||
|
// Next, we'll grab a series of keys from the wallet which will be used
|
||||||
|
// for the duration of the channel. The keys include: our multi-sig
|
||||||
|
// key, the base revocation key, the base htlc key,the base payment
|
||||||
|
// key, and the delayed payment key.
|
||||||
|
//
|
||||||
|
// TODO(roasbeef): "salt" each key as well?
|
||||||
|
var err error
|
||||||
|
reservation.ourContribution.MultiSigKey, err = l.DeriveNextKey(
|
||||||
|
keychain.KeyFamilyMultiSig,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reservation.ourContribution.RevocationBasePoint, err = l.DeriveNextKey(
|
||||||
|
keychain.KeyFamilyRevocationBase,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reservation.ourContribution.HtlcBasePoint, err = l.DeriveNextKey(
|
||||||
|
keychain.KeyFamilyHtlcBase,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reservation.ourContribution.PaymentBasePoint, err = l.DeriveNextKey(
|
||||||
|
keychain.KeyFamilyPaymentBase,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reservation.ourContribution.DelayBasePoint, err = l.DeriveNextKey(
|
||||||
|
keychain.KeyFamilyDelayBase,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the above keys created, we'll also need to initialization our
|
||||||
|
// initial revocation tree state.
|
||||||
|
nextRevocationKeyDesc, err := l.DeriveNextKey(
|
||||||
|
keychain.KeyFamilyRevocationRoot,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
revocationRoot, err := l.DerivePrivKey(nextRevocationKeyDesc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we have the root, we can then generate our shachain producer
|
||||||
|
// and from that generate the per-commitment point.
|
||||||
|
revRoot, err := chainhash.NewHash(revocationRoot.Serialize())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
producer := shachain.NewRevocationProducer(*revRoot)
|
||||||
|
firstPreimage, err := producer.AtIndex(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reservation.ourContribution.FirstCommitmentPoint = input.ComputeCommitmentPoint(
|
||||||
|
firstPreimage[:],
|
||||||
|
)
|
||||||
|
|
||||||
|
reservation.partialState.RevocationProducer = producer
|
||||||
|
reservation.ourContribution.ChannelConstraints = l.Cfg.DefaultConstraints
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleFundingReserveCancel cancels an existing channel reservation. As part
|
// handleFundingReserveCancel cancels an existing channel reservation. As part
|
||||||
// of the cancellation, outputs previously selected as inputs for the funding
|
// of the cancellation, outputs previously selected as inputs for the funding
|
||||||
// transaction via coin selection are freed allowing future reservations to
|
// transaction via coin selection are freed allowing future reservations to
|
||||||
@ -1269,14 +1298,24 @@ func (l *LightningWallet) WithCoinSelectLock(f func() error) error {
|
|||||||
return f()
|
return f()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// coinSelection holds the result from selectCoinsAndChange.
|
||||||
|
type coinSelection struct {
|
||||||
|
coins []*wire.TxIn
|
||||||
|
change []*wire.TxOut
|
||||||
|
fundingAmt btcutil.Amount
|
||||||
|
unlockCoins func()
|
||||||
|
}
|
||||||
|
|
||||||
// selectCoinsAndChange performs coin selection in order to obtain witness
|
// selectCoinsAndChange performs coin selection in order to obtain witness
|
||||||
// outputs which sum to at least 'numCoins' amount of satoshis. If coin
|
// outputs which sum to at least 'amt' amount of satoshis. If necessary,
|
||||||
// selection is successful/possible, then the selected coins are available
|
// a change address will also be generated. If coin selection is
|
||||||
// within the passed contribution's inputs. If necessary, a change address will
|
// successful/possible, then the selected coins and change outputs are
|
||||||
// also be generated.
|
// returned, and the value of the resulting funding output. This method locks
|
||||||
|
// the selected outputs, and a function closure to unlock them in case of an
|
||||||
|
// error is returned.
|
||||||
func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerKWeight,
|
func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerKWeight,
|
||||||
amt btcutil.Amount, minConfs int32,
|
amt btcutil.Amount, minConfs int32, subtractFees bool) (
|
||||||
contribution *ChannelContribution) error {
|
*coinSelection, error) {
|
||||||
|
|
||||||
// We hold the coin select mutex while querying for outputs, and
|
// We hold the coin select mutex while querying for outputs, and
|
||||||
// performing coin selection in order to avoid inadvertent double
|
// performing coin selection in order to avoid inadvertent double
|
||||||
@ -1291,21 +1330,66 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerKWeight,
|
|||||||
// number of confirmations required.
|
// number of confirmations required.
|
||||||
coins, err := l.ListUnspentWitness(minConfs, math.MaxInt32)
|
coins, err := l.ListUnspentWitness(minConfs, math.MaxInt32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
selectedCoins []*Utxo
|
||||||
|
fundingAmt btcutil.Amount
|
||||||
|
changeAmt btcutil.Amount
|
||||||
|
)
|
||||||
|
|
||||||
// Perform coin selection over our available, unlocked unspent outputs
|
// Perform coin selection over our available, unlocked unspent outputs
|
||||||
// in order to find enough coins to meet the funding amount
|
// in order to find enough coins to meet the funding amount
|
||||||
// requirements.
|
// requirements.
|
||||||
selectedCoins, changeAmt, err := coinSelect(feeRate, amt, coins)
|
switch {
|
||||||
|
// In case this request want the fees subtracted from the local amount,
|
||||||
|
// we'll call the specialized method for that. This ensures that we
|
||||||
|
// won't deduct more that the specified balance from our wallet.
|
||||||
|
case subtractFees:
|
||||||
|
dustLimit := l.Cfg.DefaultConstraints.DustLimit
|
||||||
|
selectedCoins, fundingAmt, changeAmt, err = coinSelectSubtractFees(
|
||||||
|
feeRate, amt, dustLimit, coins,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ótherwise do a normal coin selection where we target a given funding
|
||||||
|
// amount.
|
||||||
|
default:
|
||||||
|
fundingAmt = amt
|
||||||
|
selectedCoins, changeAmt, err = coinSelect(feeRate, amt, coins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record any change output(s) generated as a result of the coin
|
||||||
|
// selection, but only if the addition of the output won't lead to the
|
||||||
|
// creation of dust.
|
||||||
|
var changeOutputs []*wire.TxOut
|
||||||
|
if changeAmt != 0 && changeAmt > DefaultDustLimit() {
|
||||||
|
changeAddr, err := l.NewAddress(WitnessPubKey, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
changeScript, err := txscript.PayToAddrScript(changeAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
changeOutputs = make([]*wire.TxOut, 1)
|
||||||
|
changeOutputs[0] = &wire.TxOut{
|
||||||
|
Value: int64(changeAmt),
|
||||||
|
PkScript: changeScript,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the selected coins. These coins are now "reserved", this
|
// Lock the selected coins. These coins are now "reserved", this
|
||||||
// prevents concurrent funding requests from referring to and this
|
// prevents concurrent funding requests from referring to and this
|
||||||
// double-spending the same set of coins.
|
// double-spending the same set of coins.
|
||||||
contribution.Inputs = make([]*wire.TxIn, len(selectedCoins))
|
inputs := make([]*wire.TxIn, len(selectedCoins))
|
||||||
for i, coin := range selectedCoins {
|
for i, coin := range selectedCoins {
|
||||||
outpoint := &coin.OutPoint
|
outpoint := &coin.OutPoint
|
||||||
l.lockedOutPoints[*outpoint] = struct{}{}
|
l.lockedOutPoints[*outpoint] = struct{}{}
|
||||||
@ -1313,30 +1397,26 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerKWeight,
|
|||||||
|
|
||||||
// Empty sig script, we'll actually sign if this reservation is
|
// Empty sig script, we'll actually sign if this reservation is
|
||||||
// queued up to be completed (the other side accepts).
|
// queued up to be completed (the other side accepts).
|
||||||
contribution.Inputs[i] = wire.NewTxIn(outpoint, nil, nil)
|
inputs[i] = wire.NewTxIn(outpoint, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record any change output(s) generated as a result of the coin
|
unlock := func() {
|
||||||
// selection, but only if the addition of the output won't lead to the
|
l.coinSelectMtx.Lock()
|
||||||
// creation of dust.
|
defer l.coinSelectMtx.Unlock()
|
||||||
if changeAmt != 0 && changeAmt > DefaultDustLimit() {
|
|
||||||
changeAddr, err := l.NewAddress(WitnessPubKey, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
changeScript, err := txscript.PayToAddrScript(changeAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
contribution.ChangeOutputs = make([]*wire.TxOut, 1)
|
for _, coin := range selectedCoins {
|
||||||
contribution.ChangeOutputs[0] = &wire.TxOut{
|
outpoint := &coin.OutPoint
|
||||||
Value: int64(changeAmt),
|
delete(l.lockedOutPoints, *outpoint)
|
||||||
PkScript: changeScript,
|
l.UnlockOutpoint(*outpoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return &coinSelection{
|
||||||
|
coins: inputs,
|
||||||
|
change: changeOutputs,
|
||||||
|
fundingAmt: fundingAmt,
|
||||||
|
unlockCoins: unlock,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeriveStateHintObfuscator derives the bytes to be used for obfuscating the
|
// DeriveStateHintObfuscator derives the bytes to be used for obfuscating the
|
||||||
@ -1416,7 +1496,7 @@ func coinSelect(feeRate SatPerKWeight, amt btcutil.Amount,
|
|||||||
case NestedWitnessPubKey:
|
case NestedWitnessPubKey:
|
||||||
weightEstimate.AddNestedP2WKHInput()
|
weightEstimate.AddNestedP2WKHInput()
|
||||||
default:
|
default:
|
||||||
return nil, 0, fmt.Errorf("Unsupported address type: %v",
|
return nil, 0, fmt.Errorf("unsupported address type: %v",
|
||||||
utxo.AddressType)
|
utxo.AddressType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1428,6 +1508,8 @@ func coinSelect(feeRate SatPerKWeight, amt btcutil.Amount,
|
|||||||
//
|
//
|
||||||
// TODO: Handle wallets that generate non-witness change
|
// TODO: Handle wallets that generate non-witness change
|
||||||
// addresses.
|
// addresses.
|
||||||
|
// TODO(halseth): make coinSelect not estimate change output
|
||||||
|
// for dust change.
|
||||||
weightEstimate.AddP2WKHOutput()
|
weightEstimate.AddP2WKHOutput()
|
||||||
|
|
||||||
// The difference between the selected amount and the amount
|
// The difference between the selected amount and the amount
|
||||||
@ -1453,3 +1535,95 @@ func coinSelect(feeRate SatPerKWeight, amt btcutil.Amount,
|
|||||||
return selectedUtxos, changeAmt, nil
|
return selectedUtxos, changeAmt, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// coinSelectSubtractFees attempts to select coins such that we'll spend up to
|
||||||
|
// amt in total after fees, adhering to the specified fee rate. The selected
|
||||||
|
// coins, the final output and change values are returned.
|
||||||
|
func coinSelectSubtractFees(feeRate SatPerKWeight, amt,
|
||||||
|
dustLimit btcutil.Amount, coins []*Utxo) ([]*Utxo, btcutil.Amount,
|
||||||
|
btcutil.Amount, error) {
|
||||||
|
|
||||||
|
// First perform an initial round of coin selection to estimate
|
||||||
|
// the required fee.
|
||||||
|
totalSat, selectedUtxos, err := selectInputs(amt, coins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var weightEstimate input.TxWeightEstimator
|
||||||
|
for _, utxo := range selectedUtxos {
|
||||||
|
switch utxo.AddressType {
|
||||||
|
case WitnessPubKey:
|
||||||
|
weightEstimate.AddP2WKHInput()
|
||||||
|
case NestedWitnessPubKey:
|
||||||
|
weightEstimate.AddNestedP2WKHInput()
|
||||||
|
default:
|
||||||
|
return nil, 0, 0, fmt.Errorf("unsupported "+
|
||||||
|
"address type: %v", utxo.AddressType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel funding multisig output is P2WSH.
|
||||||
|
weightEstimate.AddP2WSHOutput()
|
||||||
|
|
||||||
|
// At this point we've got two possibilities, either create a
|
||||||
|
// change output, or not. We'll first try without creating a
|
||||||
|
// change output.
|
||||||
|
//
|
||||||
|
// Estimate the fee required for a transaction without a change
|
||||||
|
// output.
|
||||||
|
totalWeight := int64(weightEstimate.Weight())
|
||||||
|
requiredFee := feeRate.FeeForWeight(totalWeight)
|
||||||
|
|
||||||
|
// For a transaction without a change output, we'll let everything go
|
||||||
|
// to our multi-sig output after subtracting fees.
|
||||||
|
outputAmt := totalSat - requiredFee
|
||||||
|
changeAmt := btcutil.Amount(0)
|
||||||
|
|
||||||
|
// If the the output is too small after subtracting the fee, the coin
|
||||||
|
// selection cannot be performed with an amount this small.
|
||||||
|
if outputAmt <= dustLimit {
|
||||||
|
return nil, 0, 0, fmt.Errorf("output amount(%v) after "+
|
||||||
|
"subtracting fees(%v) below dust limit(%v)", outputAmt,
|
||||||
|
requiredFee, dustLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We were able to create a transaction with no change from the
|
||||||
|
// selected inputs. We'll remember the resulting values for
|
||||||
|
// now, while we try to add a change output. Assume that change output
|
||||||
|
// is a P2WKH output.
|
||||||
|
weightEstimate.AddP2WKHOutput()
|
||||||
|
|
||||||
|
// Now that we have added the change output, redo the fee
|
||||||
|
// estimate.
|
||||||
|
totalWeight = int64(weightEstimate.Weight())
|
||||||
|
requiredFee = feeRate.FeeForWeight(totalWeight)
|
||||||
|
|
||||||
|
// For a transaction with a change output, everything we don't spend
|
||||||
|
// will go to change.
|
||||||
|
newChange := totalSat - amt
|
||||||
|
newOutput := amt - requiredFee
|
||||||
|
|
||||||
|
// If adding a change output leads to both outputs being above
|
||||||
|
// the dust limit, we'll add the change output. Otherwise we'll
|
||||||
|
// go with the no change tx we originally found.
|
||||||
|
if newChange > dustLimit && newOutput > dustLimit {
|
||||||
|
outputAmt = newOutput
|
||||||
|
changeAmt = newChange
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check the resulting output values to make sure we
|
||||||
|
// don't burn a great part to fees.
|
||||||
|
totalOut := outputAmt + changeAmt
|
||||||
|
fee := totalSat - totalOut
|
||||||
|
|
||||||
|
// Fail if more than 20% goes to fees.
|
||||||
|
// TODO(halseth): smarter fee limit. Make configurable or dynamic wrt
|
||||||
|
// total funding size?
|
||||||
|
if fee > totalOut/5 {
|
||||||
|
return nil, 0, 0, fmt.Errorf("fee %v on total output"+
|
||||||
|
"value %v", fee, totalOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedUtxos, outputAmt, changeAmt, nil
|
||||||
|
}
|
||||||
|
350
lnwallet/wallet_test.go
Normal file
350
lnwallet/wallet_test.go
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
package lnwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fundingFee is a helper method that returns the fee estimate used for a tx
|
||||||
|
// with the given number of inputs and the optional change output. This matches
|
||||||
|
// the estimate done by the wallet.
|
||||||
|
func fundingFee(feeRate SatPerKWeight, numInput int, change bool) btcutil.Amount {
|
||||||
|
var weightEstimate input.TxWeightEstimator
|
||||||
|
|
||||||
|
// All inputs.
|
||||||
|
for i := 0; i < numInput; i++ {
|
||||||
|
weightEstimate.AddP2WKHInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The multisig funding output.
|
||||||
|
weightEstimate.AddP2WSHOutput()
|
||||||
|
|
||||||
|
// Optionally count a change output.
|
||||||
|
if change {
|
||||||
|
weightEstimate.AddP2WKHOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
totalWeight := int64(weightEstimate.Weight())
|
||||||
|
return feeRate.FeeForWeight(totalWeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCoinSelect tests that we pick coins adding up to the expected amount
|
||||||
|
// when creating a funding transaction, and that the calculated change is the
|
||||||
|
// expected amount.
|
||||||
|
//
|
||||||
|
// NOTE: coinSelect will always attempt to add a change output, so we must
|
||||||
|
// account for this in the tests.
|
||||||
|
func TestCoinSelect(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const feeRate = SatPerKWeight(100)
|
||||||
|
const dust = btcutil.Amount(100)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
outputValue btcutil.Amount
|
||||||
|
coins []*Utxo
|
||||||
|
|
||||||
|
expectedInput []btcutil.Amount
|
||||||
|
expectedChange btcutil.Amount
|
||||||
|
expectErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
// We have 1.0 BTC available, and wants to send 0.5.
|
||||||
|
// This will obviously lead to a change output of
|
||||||
|
// almost 0.5 BTC.
|
||||||
|
name: "big change",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: 1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputValue: 0.5 * btcutil.SatoshiPerBitcoin,
|
||||||
|
|
||||||
|
// The one and only input will be selected.
|
||||||
|
expectedInput: []btcutil.Amount{
|
||||||
|
1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
// Change will be what's left minus the fee.
|
||||||
|
expectedChange: 0.5*btcutil.SatoshiPerBitcoin - fundingFee(feeRate, 1, true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We have 1 BTC available, and we want to send 1 BTC.
|
||||||
|
// This should lead to an error, as we don't have
|
||||||
|
// enough funds to pay the fee.
|
||||||
|
name: "nothing left for fees",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: 1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputValue: 1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We have a 1 BTC input, and want to create an output
|
||||||
|
// as big as possible, such that the remaining change
|
||||||
|
// will be dust.
|
||||||
|
name: "dust change",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: 1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// We tune the output value by subtracting the expected
|
||||||
|
// fee and a small dust amount.
|
||||||
|
outputValue: 1*btcutil.SatoshiPerBitcoin - fundingFee(feeRate, 1, true) - dust,
|
||||||
|
|
||||||
|
expectedInput: []btcutil.Amount{
|
||||||
|
1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Change will the dust.
|
||||||
|
expectedChange: dust,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We have a 1 BTC input, and want to create an output
|
||||||
|
// as big as possible, such that there is nothing left
|
||||||
|
// for change.
|
||||||
|
name: "no change",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: 1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// We tune the output value to be the maximum amount
|
||||||
|
// possible, leaving just enough for fees.
|
||||||
|
outputValue: 1*btcutil.SatoshiPerBitcoin - fundingFee(feeRate, 1, true),
|
||||||
|
|
||||||
|
expectedInput: []btcutil.Amount{
|
||||||
|
1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
// We have just enough left to pay the fee, so there is
|
||||||
|
// nothing left for change.
|
||||||
|
// TODO(halseth): currently coinselect estimates fees
|
||||||
|
// assuming a change output.
|
||||||
|
expectedChange: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
selected, changeAmt, err := coinSelect(
|
||||||
|
feeRate, test.outputValue, test.coins,
|
||||||
|
)
|
||||||
|
if !test.expectErr && err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.expectErr && err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got an expected error, there is nothing more to test.
|
||||||
|
if test.expectErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the selected inputs match what we expect.
|
||||||
|
if len(selected) != len(test.expectedInput) {
|
||||||
|
t.Fatalf("expected %v inputs, got %v",
|
||||||
|
len(test.expectedInput), len(selected))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, coin := range selected {
|
||||||
|
if coin.Value != test.expectedInput[i] {
|
||||||
|
t.Fatalf("expected input %v to have value %v, "+
|
||||||
|
"had %v", i, test.expectedInput[i],
|
||||||
|
coin.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert we got the expected change amount.
|
||||||
|
if changeAmt != test.expectedChange {
|
||||||
|
t.Fatalf("expected %v change amt, got %v",
|
||||||
|
test.expectedChange, changeAmt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCoinSelectSubtractFees tests that we pick coins adding up to the
|
||||||
|
// expected amount when creating a funding transaction, and that a change
|
||||||
|
// output is created only when necessary.
|
||||||
|
func TestCoinSelectSubtractFees(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const feeRate = SatPerKWeight(100)
|
||||||
|
const dustLimit = btcutil.Amount(1000)
|
||||||
|
const dust = btcutil.Amount(100)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
spendValue btcutil.Amount
|
||||||
|
coins []*Utxo
|
||||||
|
|
||||||
|
expectedInput []btcutil.Amount
|
||||||
|
expectedFundingAmt btcutil.Amount
|
||||||
|
expectedChange btcutil.Amount
|
||||||
|
expectErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
// We have 1.0 BTC available, spend them all. This
|
||||||
|
// should lead to a funding TX with one output, the
|
||||||
|
// rest goes to fees.
|
||||||
|
name: "spend all",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: 1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spendValue: 1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
|
||||||
|
// The one and only input will be selected.
|
||||||
|
expectedInput: []btcutil.Amount{
|
||||||
|
1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
expectedFundingAmt: 1*btcutil.SatoshiPerBitcoin - fundingFee(feeRate, 1, false),
|
||||||
|
expectedChange: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The total funds available is below the dust limit
|
||||||
|
// after paying fees.
|
||||||
|
name: "dust output",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: fundingFee(feeRate, 1, false) + dust,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spendValue: fundingFee(feeRate, 1, false) + dust,
|
||||||
|
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// After subtracting fees, the resulting change output
|
||||||
|
// is below the dust limit. The remainder should go
|
||||||
|
// towards the funding output.
|
||||||
|
name: "dust change",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: 1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spendValue: 1*btcutil.SatoshiPerBitcoin - dust,
|
||||||
|
|
||||||
|
expectedInput: []btcutil.Amount{
|
||||||
|
1 * btcutil.SatoshiPerBitcoin,
|
||||||
|
},
|
||||||
|
expectedFundingAmt: 1*btcutil.SatoshiPerBitcoin - fundingFee(feeRate, 1, false),
|
||||||
|
expectedChange: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We got just enough funds to create an output above the dust limit.
|
||||||
|
name: "output right above dustlimit",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: fundingFee(feeRate, 1, false) + dustLimit + 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spendValue: fundingFee(feeRate, 1, false) + dustLimit + 1,
|
||||||
|
|
||||||
|
expectedInput: []btcutil.Amount{
|
||||||
|
fundingFee(feeRate, 1, false) + dustLimit + 1,
|
||||||
|
},
|
||||||
|
expectedFundingAmt: dustLimit + 1,
|
||||||
|
expectedChange: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Amount left is below dust limit after paying fee for
|
||||||
|
// a change output, resulting in a no-change tx.
|
||||||
|
name: "no amount to pay fee for change",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: fundingFee(feeRate, 1, false) + 2*(dustLimit+1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spendValue: fundingFee(feeRate, 1, false) + dustLimit + 1,
|
||||||
|
|
||||||
|
expectedInput: []btcutil.Amount{
|
||||||
|
fundingFee(feeRate, 1, false) + 2*(dustLimit+1),
|
||||||
|
},
|
||||||
|
expectedFundingAmt: 2 * (dustLimit + 1),
|
||||||
|
expectedChange: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// If more than 20% of funds goes to fees, it should fail.
|
||||||
|
name: "high fee",
|
||||||
|
coins: []*Utxo{
|
||||||
|
{
|
||||||
|
AddressType: WitnessPubKey,
|
||||||
|
Value: 5 * fundingFee(feeRate, 1, false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spendValue: 5 * fundingFee(feeRate, 1, false),
|
||||||
|
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
selected, localFundingAmt, changeAmt, err := coinSelectSubtractFees(
|
||||||
|
feeRate, test.spendValue, dustLimit, test.coins,
|
||||||
|
)
|
||||||
|
if !test.expectErr && err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.expectErr && err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got an expected error, there is nothing more to test.
|
||||||
|
if test.expectErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the selected inputs match what we expect.
|
||||||
|
if len(selected) != len(test.expectedInput) {
|
||||||
|
t.Fatalf("expected %v inputs, got %v",
|
||||||
|
len(test.expectedInput), len(selected))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, coin := range selected {
|
||||||
|
if coin.Value != test.expectedInput[i] {
|
||||||
|
t.Fatalf("expected input %v to have value %v, "+
|
||||||
|
"had %v", i, test.expectedInput[i],
|
||||||
|
coin.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert we got the expected change amount.
|
||||||
|
if localFundingAmt != test.expectedFundingAmt {
|
||||||
|
t.Fatalf("expected %v local funding amt, got %v",
|
||||||
|
test.expectedFundingAmt, localFundingAmt)
|
||||||
|
}
|
||||||
|
if changeAmt != test.expectedChange {
|
||||||
|
t.Fatalf("expected %v change amt, got %v",
|
||||||
|
test.expectedChange, changeAmt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
8
mock.go
8
mock.go
@ -231,6 +231,7 @@ type mockWalletController struct {
|
|||||||
prevAddres btcutil.Address
|
prevAddres btcutil.Address
|
||||||
publishedTransactions chan *wire.MsgTx
|
publishedTransactions chan *wire.MsgTx
|
||||||
index uint32
|
index uint32
|
||||||
|
utxos []*lnwallet.Utxo
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackEnd returns "mock" to signify a mock wallet controller.
|
// BackEnd returns "mock" to signify a mock wallet controller.
|
||||||
@ -284,6 +285,13 @@ func (*mockWalletController) CreateSimpleTx(outputs []*wire.TxOut,
|
|||||||
// need one unspent for the funding transaction.
|
// need one unspent for the funding transaction.
|
||||||
func (m *mockWalletController) ListUnspentWitness(minconfirms,
|
func (m *mockWalletController) ListUnspentWitness(minconfirms,
|
||||||
maxconfirms int32) ([]*lnwallet.Utxo, error) {
|
maxconfirms int32) ([]*lnwallet.Utxo, error) {
|
||||||
|
|
||||||
|
// If the mock already has a list of utxos, return it.
|
||||||
|
if m.utxos != nil {
|
||||||
|
return m.utxos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create one to return.
|
||||||
utxo := &lnwallet.Utxo{
|
utxo := &lnwallet.Utxo{
|
||||||
AddressType: lnwallet.WitnessPubKey,
|
AddressType: lnwallet.WitnessPubKey,
|
||||||
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
|
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
|
||||||
|
1
pilot.go
1
pilot.go
@ -100,6 +100,7 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey,
|
|||||||
req := &openChanReq{
|
req := &openChanReq{
|
||||||
targetPubkey: target,
|
targetPubkey: target,
|
||||||
chainHash: *activeNetParams.GenesisHash,
|
chainHash: *activeNetParams.GenesisHash,
|
||||||
|
subtractFees: true,
|
||||||
localFundingAmt: amt,
|
localFundingAmt: amt,
|
||||||
pushAmt: 0,
|
pushAmt: 0,
|
||||||
minHtlc: minHtlc,
|
minHtlc: minHtlc,
|
||||||
|
@ -1056,6 +1056,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
|
|||||||
ZombieSweeperInterval: 1 * time.Minute,
|
ZombieSweeperInterval: 1 * time.Minute,
|
||||||
ReservationTimeout: 10 * time.Minute,
|
ReservationTimeout: 10 * time.Minute,
|
||||||
MinChanSize: btcutil.Amount(cfg.MinChanSize),
|
MinChanSize: btcutil.Amount(cfg.MinChanSize),
|
||||||
|
MaxPendingChannels: cfg.MaxPendingChannels,
|
||||||
|
RejectPush: cfg.RejectPush,
|
||||||
NotifyOpenChannelEvent: s.channelNotifier.NotifyOpenChannelEvent,
|
NotifyOpenChannelEvent: s.channelNotifier.NotifyOpenChannelEvent,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2982,8 +2984,8 @@ type openChanReq struct {
|
|||||||
|
|
||||||
chainHash chainhash.Hash
|
chainHash chainhash.Hash
|
||||||
|
|
||||||
|
subtractFees bool
|
||||||
localFundingAmt btcutil.Amount
|
localFundingAmt btcutil.Amount
|
||||||
remoteFundingAmt btcutil.Amount
|
|
||||||
|
|
||||||
pushAmt lnwire.MilliSatoshi
|
pushAmt lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user