Merge pull request #443 from halseth/chanConstraints
Validate channel constraints
This commit is contained in:
commit
d61212cf26
@ -1268,8 +1268,8 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: aliceDustLimit,
|
||||
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
||||
ChanReserve: btcutil.Amount(rand.Int63()),
|
||||
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
||||
ChanReserve: 0,
|
||||
MinHTLC: 0,
|
||||
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
||||
},
|
||||
CsvDelay: uint16(csvTimeoutAlice),
|
||||
@ -1283,8 +1283,8 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: bobDustLimit,
|
||||
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
||||
ChanReserve: btcutil.Amount(rand.Int63()),
|
||||
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
||||
ChanReserve: 0,
|
||||
MinHTLC: 0,
|
||||
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
||||
},
|
||||
CsvDelay: uint16(csvTimeoutBob),
|
||||
|
@ -109,7 +109,7 @@ const (
|
||||
)
|
||||
|
||||
// ChannelConstraints represents a set of constraints meant to allow a node to
|
||||
// limit their exposure, enact flow control and ensure that all HTLC's are
|
||||
// limit their exposure, enact flow control and ensure that all HTLCs are
|
||||
// economically relevant This struct will be mirrored for both sides of the
|
||||
// channel, as each side will enforce various constraints that MUST be adhered
|
||||
// to for the life time of the channel. The parameters for each of these
|
||||
@ -121,27 +121,28 @@ type ChannelConstraints struct {
|
||||
// as an actual output, but is instead burned to miner's fees.
|
||||
DustLimit btcutil.Amount
|
||||
|
||||
// MaxPendingAmount is the maximum pending HTLC value that can be
|
||||
// present within the channel at a particular time. This value is set
|
||||
// by the initiator of the channel and must be upheld at all times.
|
||||
MaxPendingAmount lnwire.MilliSatoshi
|
||||
|
||||
// ChanReserve is an absolute reservation on the channel for this
|
||||
// particular node. This means that the current settled balance for
|
||||
// this node CANNOT dip below the reservation amount. This acts as a
|
||||
// defense against costless attacks when either side no longer has any
|
||||
// skin in the game.
|
||||
// ChanReserve is an absolute reservation on the channel for the
|
||||
// owner of this set of constraints. This means that the current
|
||||
// settled balance for this node CANNOT dip below the reservation
|
||||
// amount. This acts as a defense against costless attacks when
|
||||
// either side no longer has any skin in the game.
|
||||
ChanReserve btcutil.Amount
|
||||
|
||||
// MinHTLC is the minimum HTLC accepted for a direction of the channel.
|
||||
// If any HTLC's below this amount are offered, then the HTLC will be
|
||||
// rejected. This, in tandem with the dust limit allows a node to
|
||||
// regulate the smallest HTLC that it deems economically relevant.
|
||||
// MaxPendingAmount is the maximum pending HTLC value that the
|
||||
// owner of these constraints can offer the remote node at a
|
||||
// particular time.
|
||||
MaxPendingAmount lnwire.MilliSatoshi
|
||||
|
||||
// MinHTLC is the minimum HTLC value that the the owner of these
|
||||
// constraints can offer the remote node. If any HTLCs below this
|
||||
// amount are offered, then the HTLC will be rejected. This, in
|
||||
// tandem with the dust limit allows a node to regulate the
|
||||
// smallest HTLC that it deems economically relevant.
|
||||
MinHTLC lnwire.MilliSatoshi
|
||||
|
||||
// MaxAcceptedHtlcs is the maximum amount of HTLC's that are to be
|
||||
// accepted by the owner of this set of constraints. This allows each
|
||||
// node to limit their over all exposure to HTLC's that may need to be
|
||||
// MaxAcceptedHtlcs is the maximum number of HTLCs that the owner of
|
||||
// this set of constraints can offer the remote node. This allows each
|
||||
// node to limit their over all exposure to HTLCs that may need to be
|
||||
// acted upon in the case of a unilateral channel closure or a contract
|
||||
// breach.
|
||||
MaxAcceptedHtlcs uint16
|
||||
|
@ -236,6 +236,22 @@ type fundingConfig struct {
|
||||
// contract breach.
|
||||
RequiredRemoteDelay func(btcutil.Amount) uint16
|
||||
|
||||
// RequiredRemoteChanReserve is a function closure that, given the
|
||||
// channel capacity, will return an appropriate amount for the remote
|
||||
// peer's required channel reserve that is to be adhered to at all
|
||||
// times.
|
||||
RequiredRemoteChanReserve func(btcutil.Amount) btcutil.Amount
|
||||
|
||||
// RequiredRemoteMaxValue is a function closure that, given the
|
||||
// channel capacity, returns the amount of MilliSatoshis that our
|
||||
// remote peer can have in total outstanding HTLCs with us.
|
||||
RequiredRemoteMaxValue func(btcutil.Amount) lnwire.MilliSatoshi
|
||||
|
||||
// RequiredRemoteMaxHTLCs is a function closure that, given the
|
||||
// channel capacity, returns the number of maximum HTLCs the remote
|
||||
// peer can offer us.
|
||||
RequiredRemoteMaxHTLCs func(btcutil.Amount) uint16
|
||||
|
||||
// WatchNewChannel is to be called once a new channel enters the final
|
||||
// funding stage: waiting for on-chain confirmation. This method sends
|
||||
// the channel to the ChainArbitrator so it can watch for any on-chain
|
||||
@ -868,7 +884,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
||||
// party is attempting to dictate for our commitment transaction.
|
||||
err = reservation.CommitConstraints(
|
||||
uint16(msg.CsvDelay), msg.MaxAcceptedHTLCs,
|
||||
msg.MaxValueInFlight, msg.ChannelReserve,
|
||||
msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve,
|
||||
)
|
||||
if err != nil {
|
||||
f.failFundingFlow(
|
||||
@ -904,7 +920,9 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
||||
remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt)
|
||||
|
||||
// We'll also generate our required constraints for the remote party,
|
||||
chanReserve, maxValue, maxHtlcs := reservation.RemoteChanConstraints()
|
||||
chanReserve := f.cfg.RequiredRemoteChanReserve(amt)
|
||||
maxValue := f.cfg.RequiredRemoteMaxValue(amt)
|
||||
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(amt)
|
||||
|
||||
// With our parameters set, we'll now process their contribution so we
|
||||
// can move the funding workflow ahead.
|
||||
@ -1004,7 +1022,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
|
||||
resCtx.reservation.SetNumConfsRequired(uint16(msg.MinAcceptDepth))
|
||||
err = resCtx.reservation.CommitConstraints(
|
||||
uint16(msg.CsvDelay), msg.MaxAcceptedHTLCs,
|
||||
msg.MaxValueInFlight, msg.ChannelReserve,
|
||||
msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve,
|
||||
)
|
||||
if err != nil {
|
||||
f.failFundingFlow(
|
||||
@ -1018,7 +1036,9 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
|
||||
// As they've accepted our channel constraints, we'll regenerate them
|
||||
// here so we can properly commit their accepted constraints to the
|
||||
// reservation.
|
||||
chanReserve, maxValue, maxHtlcs := resCtx.reservation.RemoteChanConstraints()
|
||||
chanReserve := f.cfg.RequiredRemoteChanReserve(resCtx.chanAmt)
|
||||
maxValue := f.cfg.RequiredRemoteMaxValue(resCtx.chanAmt)
|
||||
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(resCtx.chanAmt)
|
||||
|
||||
// The remote node has responded with their portion of the channel
|
||||
// contribution. At this point, we can process their contribution which
|
||||
@ -2389,7 +2409,9 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
||||
// Finally, we'll use the current value of the channels and our default
|
||||
// policy to determine of required commitment constraints for the
|
||||
// remote party.
|
||||
chanReserve, maxValue, maxHtlcs := reservation.RemoteChanConstraints()
|
||||
chanReserve := f.cfg.RequiredRemoteChanReserve(capacity)
|
||||
maxValue := f.cfg.RequiredRemoteMaxValue(capacity)
|
||||
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(capacity)
|
||||
|
||||
fndgLog.Infof("Starting funding workflow with %v for pendingID(%x)",
|
||||
msg.peerAddress.Address, chanID)
|
||||
|
@ -154,13 +154,14 @@ func createTestWallet(cdb *channeldb.DB, netParams *chaincfg.Params,
|
||||
estimator lnwallet.FeeEstimator) (*lnwallet.LightningWallet, error) {
|
||||
|
||||
wallet, err := lnwallet.NewLightningWallet(lnwallet.Config{
|
||||
Database: cdb,
|
||||
Notifier: notifier,
|
||||
WalletController: wc,
|
||||
Signer: signer,
|
||||
ChainIO: bio,
|
||||
FeeEstimator: estimator,
|
||||
NetParams: *netParams,
|
||||
Database: cdb,
|
||||
Notifier: notifier,
|
||||
WalletController: wc,
|
||||
Signer: signer,
|
||||
ChainIO: bio,
|
||||
FeeEstimator: estimator,
|
||||
NetParams: *netParams,
|
||||
DefaultConstraints: defaultChannelConstraints,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -277,6 +278,16 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
RequiredRemoteDelay: func(amt btcutil.Amount) uint16 {
|
||||
return 4
|
||||
},
|
||||
RequiredRemoteChanReserve: func(chanAmt btcutil.Amount) btcutil.Amount {
|
||||
return chanAmt / 100
|
||||
},
|
||||
RequiredRemoteMaxValue: func(chanAmt btcutil.Amount) lnwire.MilliSatoshi {
|
||||
reserve := lnwire.NewMSatFromSatoshis(chanAmt / 100)
|
||||
return lnwire.NewMSatFromSatoshis(chanAmt) - reserve
|
||||
},
|
||||
RequiredRemoteMaxHTLCs: func(chanAmt btcutil.Amount) uint16 {
|
||||
return uint16(lnwallet.MaxHTLCNumber / 2)
|
||||
},
|
||||
ArbiterChan: arbiterChan,
|
||||
WatchNewChannel: func(*channeldb.OpenChannel) error {
|
||||
return nil
|
||||
|
@ -1261,11 +1261,21 @@ type getBandwidthCmd struct {
|
||||
//
|
||||
// NOTE: Part of the ChannelLink interface.
|
||||
func (l *channelLink) Bandwidth() lnwire.MilliSatoshi {
|
||||
// TODO(roasbeef): subtract reserve
|
||||
channelBandwidth := l.channel.AvailableBalance()
|
||||
overflowBandwidth := l.overflowQueue.TotalHtlcAmount()
|
||||
linkBandwidth := channelBandwidth - overflowBandwidth
|
||||
reserve := lnwire.NewMSatFromSatoshis(l.channel.LocalChanReserve())
|
||||
|
||||
return channelBandwidth - overflowBandwidth
|
||||
// If the channel reserve is greater than the total available
|
||||
// balance of the link, just return 0.
|
||||
if linkBandwidth < reserve {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Else the amount that is available to flow through the link at
|
||||
// this point is the available balance minus the reserve amount
|
||||
// we are required to keep as collateral.
|
||||
return linkBandwidth - reserve
|
||||
}
|
||||
|
||||
// policyUpdate is a message sent to a channel link when an outside sub-system
|
||||
|
@ -1405,8 +1405,8 @@ func (m *mockPeer) Disconnect(reason error) {
|
||||
|
||||
var _ Peer = (*mockPeer)(nil)
|
||||
|
||||
func newSingleLinkTestHarness(chanAmt btcutil.Amount) (ChannelLink,
|
||||
*lnwallet.LightningChannel, chan time.Time, func(), error) {
|
||||
func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) (
|
||||
ChannelLink, *lnwallet.LightningChannel, chan time.Time, func(), error) {
|
||||
globalEpoch := &chainntnfs.BlockEpochEvent{
|
||||
Epochs: make(chan *chainntnfs.BlockEpoch),
|
||||
Cancel: func() {
|
||||
@ -1415,7 +1415,8 @@ func newSingleLinkTestHarness(chanAmt btcutil.Amount) (ChannelLink,
|
||||
|
||||
chanID := lnwire.NewShortChanIDFromInt(4)
|
||||
aliceChannel, bobChannel, fCleanUp, _, err := createTestChannel(
|
||||
alicePrivKey, bobPrivKey, chanAmt, chanAmt, chanID,
|
||||
alicePrivKey, bobPrivKey, chanAmt, chanAmt,
|
||||
chanReserve, chanReserve, chanID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
@ -1659,7 +1660,7 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
|
||||
|
||||
// We'll start the test by creating a single instance of
|
||||
const chanAmt = btcutil.SatoshiPerBitcoin * 5
|
||||
link, bobChannel, tmr, cleanUp, err := newSingleLinkTestHarness(chanAmt)
|
||||
link, bobChannel, tmr, cleanUp, err := newSingleLinkTestHarness(chanAmt, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create link: %v", err)
|
||||
}
|
||||
@ -1992,7 +1993,8 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) {
|
||||
var mockBlob [lnwire.OnionPacketSize]byte
|
||||
|
||||
const chanAmt = btcutil.SatoshiPerBitcoin * 5
|
||||
aliceLink, bobChannel, batchTick, cleanUp, err := newSingleLinkTestHarness(chanAmt)
|
||||
aliceLink, bobChannel, batchTick, cleanUp, err :=
|
||||
newSingleLinkTestHarness(chanAmt, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create link: %v", err)
|
||||
}
|
||||
@ -2191,6 +2193,134 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestChannelLinkBandwidthChanReserve checks that the bandwidth available
|
||||
// on the channel link reflects the channel reserve that must be kept
|
||||
// at all times.
|
||||
func TestChannelLinkBandwidthChanReserve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// First start a link that has a balance greater than it's
|
||||
// channel reserve.
|
||||
const chanAmt = btcutil.SatoshiPerBitcoin * 5
|
||||
const chanReserve = btcutil.SatoshiPerBitcoin * 1
|
||||
aliceLink, bobChannel, batchTimer, cleanUp, err :=
|
||||
newSingleLinkTestHarness(chanAmt, chanReserve)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create link: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
var (
|
||||
mockBlob [lnwire.OnionPacketSize]byte
|
||||
coreLink = aliceLink.(*channelLink)
|
||||
coreChan = coreLink.channel
|
||||
defaultCommitFee = coreChan.StateSnapshot().CommitFee
|
||||
aliceStartingBandwidth = aliceLink.Bandwidth()
|
||||
aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs
|
||||
)
|
||||
|
||||
estimator := &lnwallet.StaticFeeEstimator{
|
||||
FeeRate: 24,
|
||||
}
|
||||
feePerWeight, err := estimator.EstimateFeePerWeight(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query fee estimator: %v", err)
|
||||
}
|
||||
feePerKw := feePerWeight * 1000
|
||||
htlcFee := lnwire.NewMSatFromSatoshis(
|
||||
btcutil.Amount((int64(feePerKw) * lnwallet.HtlcWeight) / 1000),
|
||||
)
|
||||
|
||||
// The starting bandwidth of the channel should be exactly the amount
|
||||
// that we created the channel between her and Bob, minus the channel
|
||||
// reserve.
|
||||
expectedBandwidth := lnwire.NewMSatFromSatoshis(
|
||||
chanAmt - defaultCommitFee - chanReserve)
|
||||
assertLinkBandwidth(t, aliceLink, expectedBandwidth)
|
||||
|
||||
// Next, we'll create an HTLC worth 3 BTC, and send it into the link as
|
||||
// a switch initiated payment. The resulting bandwidth should
|
||||
// now be decremented to reflect the new HTLC.
|
||||
htlcAmt := lnwire.NewMSatFromSatoshis(3 * btcutil.SatoshiPerBitcoin)
|
||||
invoice, htlc, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create payment: %v", err)
|
||||
}
|
||||
addPkt := htlcPacket{
|
||||
htlc: htlc,
|
||||
}
|
||||
aliceLink.HandleSwitchPacket(&addPkt)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee)
|
||||
|
||||
// Alice should send the HTLC to Bob.
|
||||
var msg lnwire.Message
|
||||
select {
|
||||
case msg = <-aliceMsgs:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatalf("did not receive message")
|
||||
}
|
||||
|
||||
addHtlc, ok := msg.(*lnwire.UpdateAddHTLC)
|
||||
if !ok {
|
||||
t.Fatalf("expected UpdateAddHTLC, got %T", msg)
|
||||
}
|
||||
|
||||
bobIndex, err := bobChannel.ReceiveHTLC(addHtlc)
|
||||
if err != nil {
|
||||
t.Fatalf("bob failed receiving htlc: %v", err)
|
||||
}
|
||||
|
||||
// Lock in the HTLC.
|
||||
if err := updateState(batchTimer, coreLink, bobChannel, true); err != nil {
|
||||
t.Fatalf("unable to update state: %v", err)
|
||||
}
|
||||
|
||||
assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee)
|
||||
|
||||
// If we now send in a valid HTLC settle for the prior HTLC we added,
|
||||
// then the bandwidth should remain unchanged as the remote party will
|
||||
// gain additional channel balance.
|
||||
err = bobChannel.SettleHTLC(invoice.Terms.PaymentPreimage, bobIndex)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle htlc: %v", err)
|
||||
}
|
||||
htlcSettle := &lnwire.UpdateFulfillHTLC{
|
||||
ID: bobIndex,
|
||||
PaymentPreimage: invoice.Terms.PaymentPreimage,
|
||||
}
|
||||
aliceLink.HandleChannelUpdate(htlcSettle)
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
|
||||
// Since the settle is not locked in yet, Alice's bandwidth should still
|
||||
// reflect that she has to pay the fee.
|
||||
assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee)
|
||||
|
||||
// Lock in the settle.
|
||||
if err := updateState(batchTimer, coreLink, bobChannel, false); err != nil {
|
||||
t.Fatalf("unable to update state: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt)
|
||||
|
||||
// Now we create a channel that has a channel reserve that is
|
||||
// greater than it's balance. In these case only payments can
|
||||
// be received on this channel, not sent. The available bandwidth
|
||||
// should therefore be 0.
|
||||
const bobChanAmt = btcutil.SatoshiPerBitcoin * 1
|
||||
const bobChanReserve = btcutil.SatoshiPerBitcoin * 1.5
|
||||
bobLink, _, _, bobCleanUp, err := newSingleLinkTestHarness(bobChanAmt,
|
||||
bobChanReserve)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create link: %v", err)
|
||||
}
|
||||
defer bobCleanUp()
|
||||
|
||||
// Make sure bandwidth is reported as 0.
|
||||
assertLinkBandwidth(t, bobLink, 0)
|
||||
}
|
||||
|
||||
// TestChannelRetransmission tests the ability of the channel links to
|
||||
// synchronize theirs states after abrupt disconnect.
|
||||
func TestChannelRetransmission(t *testing.T) {
|
||||
|
@ -88,7 +88,7 @@ func generateRandomBytes(n int) ([]byte, error) {
|
||||
//
|
||||
// TODO(roasbeef): need to factor out, similar func re-used in many parts of codebase
|
||||
func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
||||
aliceAmount, bobAmount btcutil.Amount,
|
||||
aliceAmount, bobAmount, aliceReserve, bobReserve btcutil.Amount,
|
||||
chanID lnwire.ShortChannelID) (*lnwallet.LightningChannel, *lnwallet.LightningChannel, func(),
|
||||
func() (*lnwallet.LightningChannel, *lnwallet.LightningChannel,
|
||||
error), error) {
|
||||
@ -97,11 +97,27 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
||||
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobPrivKey)
|
||||
|
||||
channelCapacity := aliceAmount + bobAmount
|
||||
aliceDustLimit := btcutil.Amount(200)
|
||||
bobDustLimit := btcutil.Amount(800)
|
||||
csvTimeoutAlice := uint32(5)
|
||||
csvTimeoutBob := uint32(4)
|
||||
|
||||
aliceConstraints := &channeldb.ChannelConstraints{
|
||||
DustLimit: btcutil.Amount(200),
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(
|
||||
channelCapacity),
|
||||
ChanReserve: aliceReserve,
|
||||
MinHTLC: 0,
|
||||
MaxAcceptedHtlcs: lnwallet.MaxHTLCNumber / 2,
|
||||
}
|
||||
|
||||
bobConstraints := &channeldb.ChannelConstraints{
|
||||
DustLimit: btcutil.Amount(800),
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(
|
||||
channelCapacity),
|
||||
ChanReserve: bobReserve,
|
||||
MinHTLC: 0,
|
||||
MaxAcceptedHtlcs: lnwallet.MaxHTLCNumber / 2,
|
||||
}
|
||||
|
||||
var hash [sha256.Size]byte
|
||||
randomSeed, err := generateRandomBytes(sha256.Size)
|
||||
if err != nil {
|
||||
@ -116,9 +132,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
||||
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
||||
|
||||
aliceCfg := channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: aliceDustLimit,
|
||||
},
|
||||
ChannelConstraints: *aliceConstraints,
|
||||
CsvDelay: uint16(csvTimeoutAlice),
|
||||
MultiSigKey: aliceKeyPub,
|
||||
RevocationBasePoint: aliceKeyPub,
|
||||
@ -127,9 +141,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
||||
HtlcBasePoint: aliceKeyPub,
|
||||
}
|
||||
bobCfg := channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: bobDustLimit,
|
||||
},
|
||||
ChannelConstraints: *bobConstraints,
|
||||
CsvDelay: uint16(csvTimeoutBob),
|
||||
MultiSigKey: bobKeyPub,
|
||||
RevocationBasePoint: bobKeyPub,
|
||||
@ -656,15 +668,17 @@ func createClusterChannels(aliceToBob, bobToCarol btcutil.Amount) (
|
||||
secondChanID := lnwire.NewShortChanIDFromInt(5)
|
||||
|
||||
// Create lightning channels between Alice<->Bob and Bob<->Carol
|
||||
aliceChannel, firstBobChannel, cleanAliceBob, restoreAliceBob, err := createTestChannel(
|
||||
alicePrivKey, bobPrivKey, aliceToBob, aliceToBob, firstChanID)
|
||||
aliceChannel, firstBobChannel, cleanAliceBob, restoreAliceBob, err :=
|
||||
createTestChannel(alicePrivKey, bobPrivKey, aliceToBob,
|
||||
aliceToBob, 0, 0, firstChanID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Errorf("unable to create "+
|
||||
"alice<->bob channel: %v", err)
|
||||
}
|
||||
|
||||
secondBobChannel, carolChannel, cleanBobCarol, restoreBobCarol, err := createTestChannel(
|
||||
bobPrivKey, carolPrivKey, bobToCarol, bobToCarol, secondChanID)
|
||||
secondBobChannel, carolChannel, cleanBobCarol, restoreBobCarol, err :=
|
||||
createTestChannel(bobPrivKey, carolPrivKey, bobToCarol,
|
||||
bobToCarol, 0, 0, secondChanID)
|
||||
if err != nil {
|
||||
cleanAliceBob()
|
||||
return nil, nil, nil, errors.Errorf("unable to create "+
|
||||
|
18
lnd.go
18
lnd.go
@ -388,6 +388,24 @@ func lndMain() error {
|
||||
cid := lnwire.NewChanIDFromOutPoint(&chanPoint)
|
||||
return server.htlcSwitch.UpdateShortChanID(cid, sid)
|
||||
},
|
||||
RequiredRemoteChanReserve: func(chanAmt btcutil.Amount) btcutil.Amount {
|
||||
// By default, we'll require the remote peer to maintain
|
||||
// at least 1% of the total channel capacity at all
|
||||
// times.
|
||||
return chanAmt / 100
|
||||
},
|
||||
RequiredRemoteMaxValue: func(chanAmt btcutil.Amount) lnwire.MilliSatoshi {
|
||||
// By default, we'll allow the remote peer to fully
|
||||
// utilize the full bandwidth of the channel, minus our
|
||||
// required reserve.
|
||||
reserve := lnwire.NewMSatFromSatoshis(chanAmt / 100)
|
||||
return lnwire.NewMSatFromSatoshis(chanAmt) - reserve
|
||||
},
|
||||
RequiredRemoteMaxHTLCs: func(chanAmt btcutil.Amount) uint16 {
|
||||
// By default, we'll permit them to utilize the full
|
||||
// channel bandwidth.
|
||||
return uint16(lnwallet.MaxHTLCNumber / 2)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -4103,7 +4103,8 @@ out:
|
||||
// Alice's side, leaving on 10k satoshis of available balance for bob.
|
||||
// There's a max payment amount, so we'll have to do this
|
||||
// incrementally.
|
||||
amtToSend := int64(chanAmt) - 20000
|
||||
chanReserve := int64(chanAmt / 100)
|
||||
amtToSend := int64(chanAmt) - chanReserve - 20000
|
||||
amtSent := int64(0)
|
||||
for amtSent != amtToSend {
|
||||
// We'll send in chunks of the max payment amount. If we're
|
||||
@ -4642,8 +4643,10 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
t.Fatalf("unable to get alice channel info: %v", err)
|
||||
}
|
||||
|
||||
// Calculate the number of invoices.
|
||||
numInvoices := int(info.LocalBalance / paymentAmt)
|
||||
// Calculate the number of invoices. We will deplete the channel
|
||||
// all the way down to the channel reserve.
|
||||
chanReserve := info.LocalBalance / 100
|
||||
numInvoices := int((info.LocalBalance - chanReserve) / paymentAmt)
|
||||
bobAmt := int64(numInvoices * paymentAmt)
|
||||
aliceAmt := info.LocalBalance - bobAmt
|
||||
|
||||
|
@ -47,9 +47,22 @@ var (
|
||||
ErrMaxHTLCNumber = fmt.Errorf("commitment transaction exceed max " +
|
||||
"htlc number")
|
||||
|
||||
// ErrInsufficientBalance is returned when a proposed HTLC would
|
||||
// exceed the available balance.
|
||||
ErrInsufficientBalance = fmt.Errorf("insufficient local balance")
|
||||
// ErrMaxPendingAmount is returned when a proposed HTLC would exceed
|
||||
// the overall maximum pending value of all HTLCs if committed in a
|
||||
// state transition.
|
||||
ErrMaxPendingAmount = fmt.Errorf("commitment transaction exceed max" +
|
||||
"overall pending htlc value")
|
||||
|
||||
// ErrBelowChanReserve is returned when a proposed HTLC would cause
|
||||
// one of the peer's funds to dip below the channel reserve limit.
|
||||
ErrBelowChanReserve = fmt.Errorf("commitment transaction dips peer " +
|
||||
"below chan reserve")
|
||||
|
||||
// ErrBelowMinHTLC is returned when a proposed HTLC has a value that
|
||||
// is below the minimum HTLC value constraint for either us or our
|
||||
// peer depending on which flags are set.
|
||||
ErrBelowMinHTLC = fmt.Errorf("proposed HTLC value is below minimum " +
|
||||
"allowed HTLC value")
|
||||
|
||||
// ErrCannotSyncCommitChains is returned if, upon receiving a ChanSync
|
||||
// message, the state machine deems that is unable to properly
|
||||
@ -324,6 +337,8 @@ type commitment struct {
|
||||
// within the commitment chain. This balance is computed by properly
|
||||
// evaluating all the add/remove/settle log entries before the listed
|
||||
// indexes.
|
||||
//
|
||||
// NOTE: This is the balance *before* subtracting any commitment fee.
|
||||
ourBalance lnwire.MilliSatoshi
|
||||
theirBalance lnwire.MilliSatoshi
|
||||
|
||||
@ -1928,7 +1943,7 @@ func htlcSuccessFee(feePerKw btcutil.Amount) btcutil.Amount {
|
||||
// htlcIsDust determines if an HTLC output is dust or not depending on two
|
||||
// bits: if the HTLC is incoming and if the HTLC will be placed on our
|
||||
// commitment transaction, or theirs. These two pieces of information are
|
||||
// require as we currently used second-level HTLC transactions ass off-chain
|
||||
// require as we currently used second-level HTLC transactions as off-chain
|
||||
// covenants. Depending on the two bits, we'll either be using a timeout or
|
||||
// success transaction which have different weights.
|
||||
func htlcIsDust(incoming, ourCommit bool,
|
||||
@ -2023,62 +2038,14 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
|
||||
commitChain = lc.remoteCommitChain
|
||||
}
|
||||
|
||||
ourBalance := commitChain.tip().ourBalance
|
||||
theirBalance := commitChain.tip().theirBalance
|
||||
|
||||
// Add the fee from the previous commitment state back to the
|
||||
// initiator's balance, so that the fee can be recalculated and
|
||||
// re-applied in case fee estimation parameters have changed or the
|
||||
// number of outstanding HTLCs has changed.
|
||||
if lc.channelState.IsInitiator {
|
||||
ourBalance += lnwire.NewMSatFromSatoshis(commitChain.tip().fee)
|
||||
} else if !lc.channelState.IsInitiator {
|
||||
theirBalance += lnwire.NewMSatFromSatoshis(commitChain.tip().fee)
|
||||
}
|
||||
|
||||
nextHeight := commitChain.tip().height + 1
|
||||
|
||||
// Run through all the HTLCs that will be covered by this transaction
|
||||
// in order to update their commitment addition height, and to adjust
|
||||
// the balances on the commitment transaction accordingly.
|
||||
htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex)
|
||||
filteredHTLCView := lc.evaluateHTLCView(htlcView, &ourBalance,
|
||||
&theirBalance, nextHeight, remoteChain)
|
||||
|
||||
// Initiate feePerKw to the last committed fee for this chain as we'll
|
||||
// need this to determine which HTLC's are dust, and also the final fee
|
||||
// rate.
|
||||
feePerKw := commitChain.tail().feePerKw
|
||||
|
||||
// Check if any fee updates have taken place since that last
|
||||
// commitment.
|
||||
if lc.channelState.IsInitiator {
|
||||
switch {
|
||||
// We've sent an update_fee message since our last commitment,
|
||||
// and now are now creating a commitment that reflects the new
|
||||
// fee update.
|
||||
case remoteChain && lc.pendingFeeUpdate != nil:
|
||||
feePerKw = *lc.pendingFeeUpdate
|
||||
|
||||
// We've created a new commitment for the remote chain that
|
||||
// includes a fee update, and have not received a commitment
|
||||
// after the fee update has been ACKed.
|
||||
case !remoteChain && lc.pendingAckFeeUpdate != nil:
|
||||
feePerKw = *lc.pendingAckFeeUpdate
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
// We've received a fee update since the last local commitment,
|
||||
// so we'll include the fee update in the current view.
|
||||
case !remoteChain && lc.pendingFeeUpdate != nil:
|
||||
feePerKw = *lc.pendingFeeUpdate
|
||||
|
||||
// Earlier we received a commitment that signed an earlier fee
|
||||
// update, and now we must ACK that update.
|
||||
case remoteChain && lc.pendingAckFeeUpdate != nil:
|
||||
feePerKw = *lc.pendingAckFeeUpdate
|
||||
}
|
||||
}
|
||||
ourBalance, theirBalance, _, filteredHTLCView, feePerKw :=
|
||||
lc.computeView(htlcView, remoteChain, true)
|
||||
|
||||
// Determine how many current HTLCs are over the dust limit, and should
|
||||
// be counted for the purpose of fee calculation.
|
||||
@ -2253,8 +2220,16 @@ func (lc *LightningChannel) createCommitmentTx(c *commitment,
|
||||
// settles, and timeouts found in both logs. The resulting view returned
|
||||
// reflects the current state of HTLCs within the remote or local commitment
|
||||
// chain.
|
||||
//
|
||||
// If mutateState is set to true, then the add height of all added HTLCs
|
||||
// will be set to nextHeight, and the remove height of all removed HTLCs
|
||||
// will be set to nextHeight. This should therefore only be set to true
|
||||
// once for each height, and only in concert with signing a new commitment.
|
||||
// TODO(halseth): return htlcs to mutate instead of mutating inside
|
||||
// method.
|
||||
func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
||||
theirBalance *lnwire.MilliSatoshi, nextHeight uint64, remoteChain bool) *htlcView {
|
||||
theirBalance *lnwire.MilliSatoshi, nextHeight uint64,
|
||||
remoteChain, mutateState bool) *htlcView {
|
||||
|
||||
newView := &htlcView{}
|
||||
|
||||
@ -2276,7 +2251,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
||||
// If we're settling an inbound HTLC, and it hasn't been
|
||||
// processed yet, then increment our state tracking the total
|
||||
// number of satoshis we've received within the channel.
|
||||
if entry.EntryType == Settle && !remoteChain &&
|
||||
if mutateState && entry.EntryType == Settle && !remoteChain &&
|
||||
entry.removeCommitHeightLocal == 0 {
|
||||
lc.channelState.TotalMSatReceived += entry.Amount
|
||||
}
|
||||
@ -2285,7 +2260,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
||||
|
||||
skipThem[addEntry.HtlcIndex] = struct{}{}
|
||||
processRemoveEntry(entry, ourBalance, theirBalance,
|
||||
nextHeight, remoteChain, true)
|
||||
nextHeight, remoteChain, true, mutateState)
|
||||
}
|
||||
for _, entry := range view.theirUpdates {
|
||||
if entry.EntryType == Add {
|
||||
@ -2296,7 +2271,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
||||
// and it hasn't been processed, yet, the increment our state
|
||||
// tracking the total number of satoshis we've sent within the
|
||||
// channel.
|
||||
if entry.EntryType == Settle && !remoteChain &&
|
||||
if mutateState && entry.EntryType == Settle && !remoteChain &&
|
||||
entry.removeCommitHeightLocal == 0 {
|
||||
lc.channelState.TotalMSatSent += entry.Amount
|
||||
}
|
||||
@ -2305,7 +2280,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
||||
|
||||
skipUs[addEntry.HtlcIndex] = struct{}{}
|
||||
processRemoveEntry(entry, ourBalance, theirBalance,
|
||||
nextHeight, remoteChain, false)
|
||||
nextHeight, remoteChain, false, mutateState)
|
||||
}
|
||||
|
||||
// Next we take a second pass through all the log entries, skipping any
|
||||
@ -2318,7 +2293,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
||||
}
|
||||
|
||||
processAddEntry(entry, ourBalance, theirBalance, nextHeight,
|
||||
remoteChain, false)
|
||||
remoteChain, false, mutateState)
|
||||
newView.ourUpdates = append(newView.ourUpdates, entry)
|
||||
}
|
||||
for _, entry := range view.theirUpdates {
|
||||
@ -2328,7 +2303,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
||||
}
|
||||
|
||||
processAddEntry(entry, ourBalance, theirBalance, nextHeight,
|
||||
remoteChain, true)
|
||||
remoteChain, true, mutateState)
|
||||
newView.theirUpdates = append(newView.theirUpdates, entry)
|
||||
}
|
||||
|
||||
@ -2340,7 +2315,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
||||
// was committed is updated. Keeping track of this inclusion height allows us to
|
||||
// later compact the log once the change is fully committed in both chains.
|
||||
func processAddEntry(htlc *PaymentDescriptor, ourBalance, theirBalance *lnwire.MilliSatoshi,
|
||||
nextHeight uint64, remoteChain bool, isIncoming bool) {
|
||||
nextHeight uint64, remoteChain bool, isIncoming, mutateState bool) {
|
||||
|
||||
// If we're evaluating this entry for the remote chain (to create/view
|
||||
// a new commitment), then we'll may be updating the height this entry
|
||||
@ -2368,7 +2343,9 @@ func processAddEntry(htlc *PaymentDescriptor, ourBalance, theirBalance *lnwire.M
|
||||
*ourBalance -= htlc.Amount
|
||||
}
|
||||
|
||||
*addHeight = nextHeight
|
||||
if mutateState {
|
||||
*addHeight = nextHeight
|
||||
}
|
||||
}
|
||||
|
||||
// processRemoveEntry processes a log entry which settles or times out a
|
||||
@ -2376,7 +2353,7 @@ func processAddEntry(htlc *PaymentDescriptor, ourBalance, theirBalance *lnwire.M
|
||||
// is skipped.
|
||||
func processRemoveEntry(htlc *PaymentDescriptor, ourBalance,
|
||||
theirBalance *lnwire.MilliSatoshi, nextHeight uint64,
|
||||
remoteChain bool, isIncoming bool) {
|
||||
remoteChain bool, isIncoming, mutateState bool) {
|
||||
|
||||
var removeHeight *uint64
|
||||
if remoteChain {
|
||||
@ -2416,7 +2393,9 @@ func processRemoveEntry(htlc *PaymentDescriptor, ourBalance,
|
||||
*ourBalance += htlc.Amount
|
||||
}
|
||||
|
||||
*removeHeight = nextHeight
|
||||
if mutateState {
|
||||
*removeHeight = nextHeight
|
||||
}
|
||||
}
|
||||
|
||||
// generateRemoteHtlcSigJobs generates a series of HTLC signature jobs for the
|
||||
@ -2701,7 +2680,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, erro
|
||||
// party set up when we initially set up the channel. If we are, then
|
||||
// we'll abort this state transition.
|
||||
err := lc.validateCommitmentSanity(remoteACKedIndex,
|
||||
lc.localUpdateLog.logIndex, false, true, true)
|
||||
lc.localUpdateLog.logIndex, true, nil)
|
||||
if err != nil {
|
||||
return sig, htlcSigs, err
|
||||
}
|
||||
@ -3060,69 +3039,223 @@ func (lc *LightningChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validateCommitmentSanity is used to validate that on current state the commitment
|
||||
// transaction is valid in terms of propagating it over Bitcoin network, and
|
||||
// also that all outputs are meet Bitcoin spec requirements and they are
|
||||
// spendable.
|
||||
func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
|
||||
ourLogCounter uint64, prediction bool, local bool, remote bool) error {
|
||||
// computeView takes the given htlcView, and calculates the balances,
|
||||
// filtered view (settling unsettled HTLCs), commitment weight and
|
||||
// feePerKw, after applying the HTLCs to the latest commitment. The
|
||||
// returned balanced are the balances *before* subtracting the
|
||||
// commitment fee from the initiator's balance.
|
||||
//
|
||||
// If the updateState boolean is set true, the add and remove heights
|
||||
// of the HTLCs will be set to the next commitment height.
|
||||
func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
||||
updateState bool) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, int64,
|
||||
*htlcView, btcutil.Amount) {
|
||||
|
||||
// TODO(roasbeef): verify remaining sanity requirements
|
||||
htlcCount := 0
|
||||
|
||||
// If we adding or receiving the htlc we increase the number of htlcs
|
||||
// by one in order to not overflow the commitment transaction by
|
||||
// insertion.
|
||||
if prediction {
|
||||
htlcCount++
|
||||
commitChain := lc.localCommitChain
|
||||
dustLimit := lc.localChanCfg.DustLimit
|
||||
if remoteChain {
|
||||
commitChain = lc.remoteCommitChain
|
||||
dustLimit = lc.remoteChanCfg.DustLimit
|
||||
}
|
||||
|
||||
// TODO(roasbeef): call availableBalance in here re-using htlcView
|
||||
// Since the fetched htlc view will include all updates added
|
||||
// after the last committed state, we start with the balances
|
||||
// reflecting that state.
|
||||
ourBalance := commitChain.tip().ourBalance
|
||||
theirBalance := commitChain.tip().theirBalance
|
||||
|
||||
// Run through all the HTLCs that will be covered by this transaction
|
||||
// in order to calculate theirs count.
|
||||
// Add the fee from the previous commitment state back to the
|
||||
// initiator's balance, so that the fee can be recalculated and
|
||||
// re-applied in case fee estimation parameters have changed or
|
||||
// the number of outstanding HTLCs has changed.
|
||||
if lc.channelState.IsInitiator {
|
||||
ourBalance += lnwire.NewMSatFromSatoshis(
|
||||
commitChain.tip().fee)
|
||||
} else if !lc.channelState.IsInitiator {
|
||||
theirBalance += lnwire.NewMSatFromSatoshis(
|
||||
commitChain.tip().fee)
|
||||
}
|
||||
nextHeight := commitChain.tip().height + 1
|
||||
|
||||
// We evaluate the view at this stage, meaning settled and
|
||||
// failed HTLCs will remove their corresponding added HTLCs.
|
||||
// The resulting filtered view will only have Add entries left,
|
||||
// making it easy to compare the channel constraints to the
|
||||
// final commitment state.
|
||||
filteredHTLCView := lc.evaluateHTLCView(view, &ourBalance,
|
||||
&theirBalance, nextHeight, remoteChain, updateState)
|
||||
|
||||
// Initiate feePerKw to the last committed fee for this chain as we'll
|
||||
// need this to determine which HTLCs are dust, and also the final fee
|
||||
// rate.
|
||||
feePerKw := commitChain.tip().feePerKw
|
||||
|
||||
// Check if any fee updates have taken place since that last
|
||||
// commitment.
|
||||
if lc.channelState.IsInitiator {
|
||||
switch {
|
||||
// We've sent an update_fee message since our last commitment,
|
||||
// and now are now creating a commitment that reflects the new
|
||||
// fee update.
|
||||
case remoteChain && lc.pendingFeeUpdate != nil:
|
||||
feePerKw = *lc.pendingFeeUpdate
|
||||
|
||||
// We've created a new commitment for the remote chain that
|
||||
// includes a fee update, and have not received a commitment
|
||||
// after the fee update has been ACKed.
|
||||
case !remoteChain && lc.pendingAckFeeUpdate != nil:
|
||||
feePerKw = *lc.pendingAckFeeUpdate
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
// We've received a fee update since the last local commitment,
|
||||
// so we'll include the fee update in the current view.
|
||||
case !remoteChain && lc.pendingFeeUpdate != nil:
|
||||
feePerKw = *lc.pendingFeeUpdate
|
||||
|
||||
// Earlier we received a commitment that signed an earlier fee
|
||||
// update, and now we must ACK that update.
|
||||
case remoteChain && lc.pendingAckFeeUpdate != nil:
|
||||
feePerKw = *lc.pendingAckFeeUpdate
|
||||
}
|
||||
}
|
||||
|
||||
// Now go through all HTLCs at this stage, to calculate the total
|
||||
// weight, needed to calculate the transaction fee.
|
||||
var totalHtlcWeight int64
|
||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
||||
if htlcIsDust(remoteChain, !remoteChain, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalHtlcWeight += HtlcWeight
|
||||
}
|
||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
||||
if htlcIsDust(!remoteChain, !remoteChain, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalHtlcWeight += HtlcWeight
|
||||
}
|
||||
|
||||
totalCommitWeight := CommitWeight + totalHtlcWeight
|
||||
return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView, feePerKw
|
||||
}
|
||||
|
||||
// validateCommitmentSanity is used to validate the current state of the
|
||||
// commitment transaction in terms of the ChannelConstraints that we and our
|
||||
// remote peer agreed upon during the funding workflow. The predictAdded
|
||||
// parameter should be set to a valid PaymentDescriptor if we are validating
|
||||
// in the state when adding a new HTLC, or nil otherwise.
|
||||
func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
|
||||
ourLogCounter uint64, remoteChain bool,
|
||||
predictAdded *PaymentDescriptor) error {
|
||||
// Fetch all updates not committed.
|
||||
view := lc.fetchHTLCView(theirLogCounter, ourLogCounter)
|
||||
|
||||
if remote {
|
||||
for _, entry := range view.theirUpdates {
|
||||
if entry.EntryType == Add {
|
||||
htlcCount++
|
||||
}
|
||||
}
|
||||
for _, entry := range view.ourUpdates {
|
||||
if entry.EntryType != Add {
|
||||
htlcCount--
|
||||
}
|
||||
}
|
||||
// If we are checking if we can add a new HTLC, we add this to the
|
||||
// update log, in order to validate the sanity of the commitment
|
||||
// resulting from _actually adding_ this HTLC to the state.
|
||||
if predictAdded != nil {
|
||||
// If we are adding an HTLC, this will be an Add to the
|
||||
// local update log.
|
||||
view.ourUpdates = append(view.ourUpdates, predictAdded)
|
||||
}
|
||||
|
||||
if local {
|
||||
for _, entry := range view.ourUpdates {
|
||||
if entry.EntryType == Add {
|
||||
htlcCount++
|
||||
}
|
||||
}
|
||||
for _, entry := range view.theirUpdates {
|
||||
if entry.EntryType != Add {
|
||||
htlcCount--
|
||||
}
|
||||
}
|
||||
commitChain := lc.localCommitChain
|
||||
if remoteChain {
|
||||
commitChain = lc.remoteCommitChain
|
||||
}
|
||||
ourInitialBalance := commitChain.tip().ourBalance
|
||||
theirInitialBalance := commitChain.tip().theirBalance
|
||||
|
||||
// If we're validating the commitment sanity for HTLC _log_ update by a
|
||||
// particular side, then we'll only consider half of the available HTLC
|
||||
// bandwidth. However, if we're validating the _creation_ of a new
|
||||
// commitment state, then we'll use the full value as the sum of the
|
||||
// contribution of both sides shouldn't exceed the max number.
|
||||
var maxHTLCNumber int
|
||||
if local && remote {
|
||||
maxHTLCNumber = MaxHTLCNumber
|
||||
ourBalance, theirBalance, commitWeight, filteredView, feePerKw :=
|
||||
lc.computeView(view, remoteChain, false)
|
||||
|
||||
// Calculate the commitment fee, and subtract it from the
|
||||
// initiator's balance.
|
||||
commitFee := btcutil.Amount((int64(feePerKw) * commitWeight) / 1000)
|
||||
if lc.channelState.IsInitiator {
|
||||
ourBalance -= lnwire.NewMSatFromSatoshis(commitFee)
|
||||
} else {
|
||||
maxHTLCNumber = MaxHTLCNumber / 2
|
||||
theirBalance -= lnwire.NewMSatFromSatoshis(commitFee)
|
||||
}
|
||||
|
||||
if htlcCount > maxHTLCNumber {
|
||||
return ErrMaxHTLCNumber
|
||||
// If the added HTLCs will decrease the balance, make sure
|
||||
// they won't dip the local and remote balances below the
|
||||
// channel reserves.
|
||||
if ourBalance < ourInitialBalance &&
|
||||
ourBalance < lnwire.NewMSatFromSatoshis(
|
||||
lc.localChanCfg.ChanReserve) {
|
||||
return ErrBelowChanReserve
|
||||
}
|
||||
|
||||
if theirBalance < theirInitialBalance &&
|
||||
theirBalance < lnwire.NewMSatFromSatoshis(
|
||||
lc.remoteChanCfg.ChanReserve) {
|
||||
return ErrBelowChanReserve
|
||||
}
|
||||
|
||||
// validateUpdates take a set of updates, and validates them
|
||||
// against the passed channel constraints.
|
||||
validateUpdates := func(updates []*PaymentDescriptor,
|
||||
constraints *channeldb.ChannelConfig) error {
|
||||
|
||||
// We keep track of the number of HTLCs in flight for
|
||||
// the commitment, and the amount in flight.
|
||||
var numInFlight uint16
|
||||
var amtInFlight lnwire.MilliSatoshi
|
||||
|
||||
// Go through all updates, checking that they don't
|
||||
// violate the channel constraints.
|
||||
for _, entry := range updates {
|
||||
if entry.EntryType == Add {
|
||||
// An HTLC is being added, this will
|
||||
// add to the number and amount in
|
||||
// flight.
|
||||
amtInFlight += entry.Amount
|
||||
numInFlight++
|
||||
|
||||
// Check that the value of the HTLC they
|
||||
// added is above our minimum.
|
||||
if entry.Amount < constraints.MinHTLC {
|
||||
return ErrBelowMinHTLC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know the total value of added HTLCs,
|
||||
// we check that this satisfy the MaxPendingAmont
|
||||
// contraint.
|
||||
if amtInFlight > constraints.MaxPendingAmount {
|
||||
return ErrMaxPendingAmount
|
||||
}
|
||||
|
||||
// In this step, we verify that the total number of
|
||||
// active HTLCs does not exceed the constraint of the
|
||||
// maximum number of HTLCs in flight.
|
||||
if numInFlight > constraints.MaxAcceptedHtlcs {
|
||||
return ErrMaxHTLCNumber
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// First check that the remote updates won't violate it's
|
||||
// channel constraints.
|
||||
err := validateUpdates(filteredView.theirUpdates,
|
||||
lc.remoteChanCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Secondly check that our updates won't violate our
|
||||
// channel constraints.
|
||||
err = validateUpdates(filteredView.ourUpdates,
|
||||
lc.localChanCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -3323,7 +3456,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig,
|
||||
// the constraints we specified during initial channel setup. If not,
|
||||
// then we'll abort the channel as they've violated our constraints.
|
||||
err := lc.validateCommitmentSanity(lc.remoteUpdateLog.logIndex,
|
||||
localACKedIndex, false, true, true)
|
||||
localACKedIndex, false, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -3342,8 +3475,9 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig,
|
||||
lc.remoteChanCfg)
|
||||
|
||||
// With the current commitment point re-calculated, construct the new
|
||||
// commitment view which includes all the entries we know of in their
|
||||
// HTLC log, and up to ourLogIndex in our HTLC log.
|
||||
// commitment view which includes all the entries (pending or committed)
|
||||
// we know of in the remote node's HTLC log, but only our local changes
|
||||
// up to the last change the remote node has ACK'd.
|
||||
localCommitmentView, err := lc.fetchCommitmentView(
|
||||
false, localACKedIndex, localHtlcIndex,
|
||||
lc.remoteUpdateLog.logIndex, lc.remoteUpdateLog.htlcCounter,
|
||||
@ -3668,43 +3802,6 @@ func (lc *LightningChannel) AddHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, error)
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
if err := lc.validateCommitmentSanity(lc.remoteUpdateLog.logIndex,
|
||||
lc.localUpdateLog.logIndex, true, true, false); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// To ensure that we can actually fully accept this new HTLC, we'll
|
||||
// calculate the current available bandwidth, and subtract the value of
|
||||
// the HTLC from it.
|
||||
initialBalance, _ := lc.availableBalance()
|
||||
availableBalance := initialBalance
|
||||
availableBalance -= htlc.Amount
|
||||
|
||||
feePerKw := lc.channelState.LocalCommitment.FeePerKw
|
||||
dustLimit := lc.channelState.LocalChanCfg.DustLimit
|
||||
htlcIsDust := htlcIsDust(
|
||||
false, true, feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
|
||||
)
|
||||
|
||||
// If this HTLC is not dust, and we're the initiator, then we'll also
|
||||
// subtract the amount we'll need to pay in fees for this HTLC.
|
||||
if !htlcIsDust && lc.channelState.IsInitiator {
|
||||
htlcFee := lnwire.NewMSatFromSatoshis(
|
||||
btcutil.Amount((int64(feePerKw) * HtlcWeight) / 1000),
|
||||
)
|
||||
availableBalance -= htlcFee
|
||||
}
|
||||
|
||||
// If this value is negative, then we can't accept the HTLC, so we'll
|
||||
// reject it with an error.
|
||||
if availableBalance < 0 {
|
||||
// TODO(roasbeef): also needs to respect reservation
|
||||
// * expand to add context err msg
|
||||
walletLog.Errorf("Unable to carry added HTLC: amt=%v, bal=%v",
|
||||
htlc.Amount, availableBalance)
|
||||
return 0, ErrInsufficientBalance
|
||||
}
|
||||
|
||||
pd := &PaymentDescriptor{
|
||||
EntryType: Add,
|
||||
RHash: PaymentHash(htlc.PaymentHash),
|
||||
@ -3715,6 +3812,14 @@ func (lc *LightningChannel) AddHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, error)
|
||||
OnionBlob: htlc.OnionBlob[:],
|
||||
}
|
||||
|
||||
// Make sure adding this HTLC won't violate any of the constrainst
|
||||
// we must keep on our commitment transaction.
|
||||
remoteACKedIndex := lc.localCommitChain.tail().theirMessageIndex
|
||||
if err := lc.validateCommitmentSanity(remoteACKedIndex,
|
||||
lc.localUpdateLog.logIndex, true, pd); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
lc.localUpdateLog.appendHtlc(pd)
|
||||
|
||||
return pd.HtlcIndex, nil
|
||||
@ -3732,11 +3837,6 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, err
|
||||
"ID %d", htlc.ID, lc.remoteUpdateLog.htlcCounter)
|
||||
}
|
||||
|
||||
if err := lc.validateCommitmentSanity(lc.remoteUpdateLog.logIndex,
|
||||
lc.localUpdateLog.logIndex, true, false, true); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
pd := &PaymentDescriptor{
|
||||
EntryType: Add,
|
||||
RHash: PaymentHash(htlc.PaymentHash),
|
||||
@ -4963,124 +5063,24 @@ func (lc *LightningChannel) AvailableBalance() lnwire.MilliSatoshi {
|
||||
// this method. Additionally, the total weight of the next to be created
|
||||
// commitment is returned for accounting purposes.
|
||||
func (lc *LightningChannel) availableBalance() (lnwire.MilliSatoshi, int64) {
|
||||
// First, we'll grab the current local balance. If we're the initiator
|
||||
// of the channel then we paid the fees on the last commitment state,
|
||||
// so we'll re-apply those.
|
||||
settledBalance := lc.channelState.LocalCommitment.LocalBalance
|
||||
if lc.channelState.IsInitiator {
|
||||
settledBalance += lnwire.NewMSatFromSatoshis(
|
||||
lc.localCommitChain.tip().fee,
|
||||
)
|
||||
}
|
||||
|
||||
// Next we'll grab the current set of log updates that are still active
|
||||
// and haven't been garbage collected.
|
||||
// We'll grab the current set of log updates that the remote has
|
||||
// ACKed.
|
||||
remoteACKedIndex := lc.localCommitChain.tip().theirMessageIndex
|
||||
htlcView := lc.fetchHTLCView(remoteACKedIndex,
|
||||
lc.localUpdateLog.logIndex)
|
||||
feePerKw := lc.channelState.LocalCommitment.FeePerKw
|
||||
dustLimit := lc.channelState.LocalChanCfg.DustLimit
|
||||
|
||||
// We'll now re-compute the current weight of all the active HTLC's. We
|
||||
// make sure to skip any HTLC's that would be dust on our version of
|
||||
// the commitment transaction.
|
||||
var totalHtlcWeight int64
|
||||
for _, htlc := range lc.channelState.LocalCommitment.Htlcs {
|
||||
if htlcIsDust(false, true, feePerKw, htlc.Amt.ToSatoshis(),
|
||||
dustLimit) {
|
||||
continue
|
||||
}
|
||||
// Then compute our current balance for that view.
|
||||
ourBalance, _, commitWeight, _, feePerKw :=
|
||||
lc.computeView(htlcView, false, false)
|
||||
|
||||
totalHtlcWeight += HtlcWeight
|
||||
}
|
||||
|
||||
// Next we'll run through our set of updates and modify the
|
||||
// settledBalance and totalHtlcWeight fields accordingly.
|
||||
for _, entry := range htlcView.ourUpdates {
|
||||
switch {
|
||||
|
||||
// For any new HTLC's added as a part of this state, we'll
|
||||
// subtract the total balance, and tally the weight increase if
|
||||
// it isn't dust.
|
||||
case entry.EntryType == Add && entry.addCommitHeightLocal == 0:
|
||||
settledBalance -= entry.Amount
|
||||
|
||||
if htlcIsDust(false, true, feePerKw, entry.Amount.ToSatoshis(),
|
||||
dustLimit) {
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
totalHtlcWeight += HtlcWeight
|
||||
|
||||
// For any new HTLC's we newly settled as part of this state,
|
||||
// we'll subtract the HTLC weight and increase our balance
|
||||
// accordingly.
|
||||
case entry.EntryType == Settle && entry.removeCommitHeightLocal == 0:
|
||||
totalHtlcWeight -= HtlcWeight
|
||||
|
||||
settledBalance += entry.Amount
|
||||
|
||||
// For any new fails added as a part of this state, we'll
|
||||
// subtract the weight of the HTLC we're failing.
|
||||
case entry.EntryType == Fail && entry.removeCommitHeightLocal == 0:
|
||||
fallthrough
|
||||
case entry.EntryType == MalformedFail && entry.removeCommitHeightLocal == 0:
|
||||
totalHtlcWeight -= HtlcWeight
|
||||
}
|
||||
}
|
||||
for _, entry := range htlcView.theirUpdates {
|
||||
switch {
|
||||
// If the remote party has an HTLC that will be included as
|
||||
// part of this state, then we'll account for the additional
|
||||
// weight of the HTLC.
|
||||
case entry.EntryType == Add && entry.addCommitHeightLocal == 0:
|
||||
if htlcIsDust(true, true, feePerKw, entry.Amount.ToSatoshis(),
|
||||
dustLimit) {
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
totalHtlcWeight += HtlcWeight
|
||||
|
||||
// If the remote party is settling one of our HTLC's for the
|
||||
// first time as part of this state, then we'll subtract the
|
||||
// weight of the HTLC.
|
||||
case entry.EntryType == Settle && entry.removeCommitHeightLocal == 0:
|
||||
totalHtlcWeight -= HtlcWeight
|
||||
|
||||
// For any HTLC's that they're failing as a part of the next,
|
||||
// state, we'll subtract the weight of the HTLC and also credit
|
||||
// ourselves back the value of the HTLC.
|
||||
case entry.EntryType == Fail && entry.removeCommitHeightLocal == 0:
|
||||
fallthrough
|
||||
case entry.EntryType == MalformedFail && entry.removeCommitHeightLocal == 0:
|
||||
totalHtlcWeight -= HtlcWeight
|
||||
|
||||
settledBalance += entry.Amount
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If we subtracted dust HTLC's, then we'll need to reset the weight of
|
||||
// the HTLCs back to zero.
|
||||
if totalHtlcWeight < 0 {
|
||||
totalHtlcWeight = 0
|
||||
}
|
||||
|
||||
// If we're the initiator then we need to pay fees for this state, so
|
||||
// taking into account the number of active HTLC's we'll calculate the
|
||||
// fee that must be paid.
|
||||
totalCommitWeight := CommitWeight + totalHtlcWeight
|
||||
// If we are the channel initiator, we must remember to subtract the
|
||||
// commitment fee from our available balance.
|
||||
commitFee := btcutil.Amount((int64(feePerKw) * commitWeight) / 1000)
|
||||
if lc.channelState.IsInitiator {
|
||||
additionalFee := lnwire.NewMSatFromSatoshis(
|
||||
btcutil.Amount((int64(feePerKw) * totalCommitWeight) / 1000),
|
||||
)
|
||||
|
||||
settledBalance -= additionalFee
|
||||
ourBalance -= lnwire.NewMSatFromSatoshis(commitFee)
|
||||
}
|
||||
|
||||
return settledBalance, totalCommitWeight
|
||||
return ourBalance, commitWeight
|
||||
}
|
||||
|
||||
// StateSnapshot returns a snapshot of the current fully committed state within
|
||||
@ -5374,3 +5374,8 @@ func (lc *LightningChannel) ActiveHtlcs() []channeldb.HTLC {
|
||||
|
||||
return activeHtlcs
|
||||
}
|
||||
|
||||
// LocalChanReserve returns our local ChanReserve requirement for the remote party.
|
||||
func (lc *LightningChannel) LocalChanReserve() btcutil.Amount {
|
||||
return lc.localChanCfg.ChanReserve
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@ -163,10 +163,10 @@ func createTestChannels(revocationWindow int) (*LightningChannel,
|
||||
aliceCfg := channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: aliceDustLimit,
|
||||
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity),
|
||||
ChanReserve: channelCapacity / 100,
|
||||
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
||||
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
||||
MinHTLC: 0,
|
||||
MaxAcceptedHtlcs: MaxHTLCNumber / 2,
|
||||
},
|
||||
CsvDelay: uint16(csvTimeoutAlice),
|
||||
MultiSigKey: aliceKeys[0].PubKey(),
|
||||
@ -178,10 +178,10 @@ func createTestChannels(revocationWindow int) (*LightningChannel,
|
||||
bobCfg := channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: bobDustLimit,
|
||||
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity),
|
||||
ChanReserve: channelCapacity / 100,
|
||||
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
||||
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
||||
MinHTLC: 0,
|
||||
MaxAcceptedHtlcs: MaxHTLCNumber / 2,
|
||||
},
|
||||
CsvDelay: uint16(csvTimeoutBob),
|
||||
MultiSigKey: bobKeys[0].PubKey(),
|
||||
@ -1100,6 +1100,14 @@ func TestForceCloseDustOutput(t *testing.T) {
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
// We set both node's channel reserves to 0, to make sure
|
||||
// they can create small dust ouputs without going under
|
||||
// their channel reserves.
|
||||
aliceChannel.localChanCfg.ChanReserve = 0
|
||||
bobChannel.localChanCfg.ChanReserve = 0
|
||||
aliceChannel.remoteChanCfg.ChanReserve = 0
|
||||
bobChannel.remoteChanCfg.ChanReserve = 0
|
||||
|
||||
htlcAmount := lnwire.NewMSatFromSatoshis(500)
|
||||
|
||||
aliceAmount := aliceChannel.channelState.LocalCommitment.LocalBalance
|
||||
@ -1372,6 +1380,11 @@ func TestChannelBalanceDustLimit(t *testing.T) {
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
// To allow Alice's balance to get beneath her dust limit, set the
|
||||
// channel reserve to be 0.
|
||||
aliceChannel.localChanCfg.ChanReserve = 0
|
||||
bobChannel.remoteChanCfg.ChanReserve = 0
|
||||
|
||||
// This amount should leave an amount larger than Alice's dust limit
|
||||
// once fees have been subtracted, but smaller than Bob's dust limit.
|
||||
// We account in fees for the HTLC we will be adding.
|
||||
@ -2426,8 +2439,12 @@ func TestAddHTLCNegativeBalance(t *testing.T) {
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
// First, we'll add 5 HTLCs of 1 BTC each to Alice's commitment.
|
||||
const numHTLCs = 4
|
||||
// We set the channel reserve to 0, such that we can add HTLCs
|
||||
// all the way to a negative balance.
|
||||
aliceChannel.localChanCfg.ChanReserve = 0
|
||||
|
||||
// First, we'll add 3 HTLCs of 1 BTC each to Alice's commitment.
|
||||
const numHTLCs = 3
|
||||
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||
for i := 0; i < numHTLCs; i++ {
|
||||
htlc, _ := createHTLC(i, htlcAmt)
|
||||
@ -2436,13 +2453,14 @@ func TestAddHTLCNegativeBalance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// We'll then craft another HTLC with 2 BTC to add to Alice's channel.
|
||||
// This attempt should put Alice in the negative, meaning she should
|
||||
// reject the HTLC.
|
||||
htlc, _ := createHTLC(numHTLCs+1, htlcAmt*2)
|
||||
// Alice now has an available balance of 2 BTC. We'll add a new HTLC
|
||||
// of value 2 BTC, which should make Alice's balance negative (since
|
||||
// (she has to pay a commitment fee).
|
||||
htlcAmt = lnwire.NewMSatFromSatoshis(2 * btcutil.SatoshiPerBitcoin)
|
||||
htlc, _ := createHTLC(numHTLCs+1, htlcAmt)
|
||||
_, err = aliceChannel.AddHTLC(htlc)
|
||||
if err != ErrInsufficientBalance {
|
||||
t.Fatalf("expected insufficient balance, instead got: %v", err)
|
||||
if err != ErrBelowChanReserve {
|
||||
t.Fatalf("expected balance below channel reserve, instead got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4344,7 +4362,7 @@ func TestDesyncHTLCs(t *testing.T) {
|
||||
// because the balance is unavailable.
|
||||
htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin)
|
||||
htlc, _ = createHTLC(1, htlcAmt)
|
||||
if _, err = aliceChannel.AddHTLC(htlc); err != ErrInsufficientBalance {
|
||||
if _, err = aliceChannel.AddHTLC(htlc); err != ErrBelowChanReserve {
|
||||
t.Fatalf("expected ErrInsufficientBalance, instead received: %v",
|
||||
err)
|
||||
}
|
||||
@ -4360,3 +4378,359 @@ func TestDesyncHTLCs(t *testing.T) {
|
||||
}
|
||||
|
||||
// TODO(roasbeef): testing.Quick test case for retrans!!!
|
||||
|
||||
// TestMaxAcceptedHTLCs tests that the correct error message (ErrMaxHTLCNumber)
|
||||
// is thrown when a node tries to accept more than MaxAcceptedHTLCs in a channel.
|
||||
func TestMaxAcceptedHTLCs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
// One over the maximum number of HTLCs that either can accept.
|
||||
const numHTLCs = 20
|
||||
const numHTLCsReceived = 12
|
||||
|
||||
// Set the remote's required MaxAcceptedHtlcs. This means that alice
|
||||
// can only offer the remote up to numHTLCs HTLCs.
|
||||
aliceChannel.localChanCfg.MaxAcceptedHtlcs = numHTLCs
|
||||
bobChannel.remoteChanCfg.MaxAcceptedHtlcs = numHTLCs
|
||||
|
||||
// Similarly, set the remote config's MaxAcceptedHtlcs. This means
|
||||
// that the remote will be aware that Alice will only accept up to
|
||||
// numHTLCsRecevied at a time.
|
||||
aliceChannel.remoteChanCfg.MaxAcceptedHtlcs = numHTLCsReceived
|
||||
bobChannel.localChanCfg.MaxAcceptedHtlcs = numHTLCsReceived
|
||||
|
||||
// Each HTLC amount is 0.1 BTC.
|
||||
htlcAmt := lnwire.NewMSatFromSatoshis(0.1 * btcutil.SatoshiPerBitcoin)
|
||||
|
||||
// Send the maximum allowed number of HTLCs.
|
||||
for i := 0; i < numHTLCs; i++ {
|
||||
htlc, _ := createHTLC(i, htlcAmt)
|
||||
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to add htlc: %v", err)
|
||||
}
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// The next HTLC should fail with ErrMaxHTLCNumber.
|
||||
htlc, _ := createHTLC(numHTLCs, htlcAmt)
|
||||
_, err = aliceChannel.AddHTLC(htlc)
|
||||
if err != ErrMaxHTLCNumber {
|
||||
t.Fatalf("expected ErrMaxHTLCNumber, instead received: %v", err)
|
||||
}
|
||||
|
||||
// After receiving the next HTLC, next state transition should fail
|
||||
// with ErrMaxHTLCNumber.
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
err = forceStateTransition(aliceChannel, bobChannel)
|
||||
if err != ErrMaxHTLCNumber {
|
||||
t.Fatalf("expected ErrMaxHTLCNumber, instead received: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMaxPendingAmount tests that the maximum overall pending HTLC value is met
|
||||
// given several HTLCs that, combined, exceed this value. An ErrMaxPendingAmount
|
||||
// error should be returned.
|
||||
func TestMaxPendingAmount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
// We set the remote required MaxPendingAmount to 3 BTC. We will
|
||||
// attempt to overflow this value and see if it gives us the
|
||||
// ErrMaxPendingAmount error.
|
||||
maxPending := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin * 3)
|
||||
|
||||
// We set the max pending amount of Alice's config. This mean that she
|
||||
// cannot offer Bob HTLCs with a total value above this limit at a given
|
||||
// time.
|
||||
aliceChannel.localChanCfg.MaxPendingAmount = maxPending
|
||||
bobChannel.remoteChanCfg.MaxPendingAmount = maxPending
|
||||
|
||||
// First, we'll add 2 HTLCs of 1.5 BTC each to Alice's commitment.
|
||||
// This won't trigger Alice's ErrMaxPendingAmount error.
|
||||
const numHTLCs = 2
|
||||
htlcAmt := lnwire.NewMSatFromSatoshis(1.5 * btcutil.SatoshiPerBitcoin)
|
||||
for i := 0; i < numHTLCs; i++ {
|
||||
htlc, _ := createHTLC(i, htlcAmt)
|
||||
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to add htlc: %v", err)
|
||||
}
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We finally add one more HTLC of 0.1 BTC to Alice's commitment. This
|
||||
// SHOULD trigger Alice's ErrMaxPendingAmount error.
|
||||
htlcAmt = lnwire.NewMSatFromSatoshis(0.1 * btcutil.SatoshiPerBitcoin)
|
||||
htlc, _ := createHTLC(numHTLCs, htlcAmt)
|
||||
_, err = aliceChannel.AddHTLC(htlc)
|
||||
if err != ErrMaxPendingAmount {
|
||||
t.Fatalf("expected ErrMaxPendingAmount, instead received: %v", err)
|
||||
}
|
||||
|
||||
// And also Bob shouldn't be accepting this HTLC in the next state
|
||||
// transition.
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
err = forceStateTransition(aliceChannel, bobChannel)
|
||||
if err != ErrMaxPendingAmount {
|
||||
t.Fatalf("expected ErrMaxPendingAmount, instead received: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChanReserve tests that the ErrBelowChanReserve error is thrown when
|
||||
// an HTLC is added that causes a node's balance to dip below its channel
|
||||
// reserve limit.
|
||||
func TestChanReserve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
setupChannels := func() (*LightningChannel, *LightningChannel, func()) {
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
|
||||
// We set the remote required ChanReserve to 0.5 BTC. We will
|
||||
// attempt to cause Alice's balance to dip below this amount and test
|
||||
// whether it triggers the ErrBelowChanReserve error.
|
||||
aliceMinReserve := btcutil.Amount(0.5 * btcutil.SatoshiPerBitcoin)
|
||||
|
||||
// Alice will need to keep her reserve above aliceMinReserve, so
|
||||
// set this limit to here local config.
|
||||
aliceChannel.localChanCfg.ChanReserve = aliceMinReserve
|
||||
|
||||
// During channel opening Bob will also get to know Alice's minimum
|
||||
// reserve, and this will be found in his remote config.
|
||||
bobChannel.remoteChanCfg.ChanReserve = aliceMinReserve
|
||||
|
||||
// We set Bob's channel reserve to a value that is larger than his
|
||||
// current balance in the channel. This will ensure that after a
|
||||
// channel is first opened, Bob can still receive HTLCs
|
||||
// even though his balance is less than his channel reserve.
|
||||
bobMinReserve := btcutil.Amount(6 * btcutil.SatoshiPerBitcoin)
|
||||
bobChannel.localChanCfg.ChanReserve = bobMinReserve
|
||||
aliceChannel.remoteChanCfg.ChanReserve = bobMinReserve
|
||||
|
||||
return aliceChannel, bobChannel, cleanUp
|
||||
}
|
||||
aliceChannel, bobChannel, cleanUp := setupChannels()
|
||||
defer cleanUp()
|
||||
|
||||
aliceIndex := 0
|
||||
bobIndex := 0
|
||||
|
||||
// Add an HTLC that will increase Bob's balance. This should
|
||||
// succeed, since Alice stays above her channel reserve, and
|
||||
// Bob increases his balance (while still being below his
|
||||
// channel reserve).
|
||||
// Resulting balances:
|
||||
// Alice: 4.5
|
||||
// Bob: 5.5
|
||||
htlcAmt := lnwire.NewMSatFromSatoshis(0.5 * btcutil.SatoshiPerBitcoin)
|
||||
htlc, _ := createHTLC(aliceIndex, htlcAmt)
|
||||
aliceIndex++
|
||||
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to add htlc: %v", err)
|
||||
}
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
|
||||
// Force a state transation, making sure this HTLC is considered
|
||||
// valid even though the channel reserves are not met.
|
||||
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
||||
t.Fatalf("unable to complete state update: %v", err)
|
||||
}
|
||||
|
||||
// Now let Bob try to add an HTLC. This should fail, since it
|
||||
// will decrease his balance, which is already below the channel
|
||||
// reserve.
|
||||
// Resulting balances:
|
||||
// Alice: 4.5
|
||||
// Bob: 5.5
|
||||
htlc, _ = createHTLC(bobIndex, htlcAmt)
|
||||
bobIndex++
|
||||
_, err := bobChannel.AddHTLC(htlc)
|
||||
if err != ErrBelowChanReserve {
|
||||
t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err)
|
||||
}
|
||||
|
||||
// Alice will reject this htlc when a state transition is attempted.
|
||||
if _, err := aliceChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
err = forceStateTransition(aliceChannel, bobChannel)
|
||||
if err != ErrBelowChanReserve {
|
||||
t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err)
|
||||
}
|
||||
|
||||
// We must setup the channels again, since a violation of the channel
|
||||
// constraints leads to channel shutdown.
|
||||
aliceChannel, bobChannel, cleanUp = setupChannels()
|
||||
defer cleanUp()
|
||||
|
||||
aliceIndex = 0
|
||||
bobIndex = 0
|
||||
|
||||
// Now we'll add HTLC of 3.5 BTC to Alice's commitment, this should
|
||||
// put Alice's balance at 1.5 BTC.
|
||||
// Resulting balances:
|
||||
// Alice: 1.5
|
||||
// Bob: 9.5
|
||||
htlcAmt = lnwire.NewMSatFromSatoshis(3.5 * btcutil.SatoshiPerBitcoin)
|
||||
|
||||
// The first HTLC should successfully be sent.
|
||||
htlc, _ = createHTLC(aliceIndex, htlcAmt)
|
||||
aliceIndex++
|
||||
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to add htlc: %v", err)
|
||||
}
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
|
||||
// Add a second HTLC of 1 BTC. This should fail because it will take
|
||||
// Alice's balance all the way down to her channel reserve, but
|
||||
// since she is the initiator the additional transaction fee makes
|
||||
// her balance dip below.
|
||||
htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin)
|
||||
htlc, _ = createHTLC(aliceIndex, htlcAmt)
|
||||
aliceIndex++
|
||||
_, err = aliceChannel.AddHTLC(htlc)
|
||||
if err != ErrBelowChanReserve {
|
||||
t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err)
|
||||
}
|
||||
|
||||
// Likewise, Bob will reject a state transition after this htlc is
|
||||
// received, of the same reason.
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
err = forceStateTransition(aliceChannel, bobChannel)
|
||||
if err != ErrBelowChanReserve {
|
||||
t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err)
|
||||
}
|
||||
|
||||
// We must setup the channels again, since a violation of the channel
|
||||
// constraints leads to channel shutdown.
|
||||
aliceChannel, bobChannel, cleanUp = setupChannels()
|
||||
defer cleanUp()
|
||||
|
||||
aliceIndex = 0
|
||||
bobIndex = 0
|
||||
|
||||
// Add a HTLC of 2 BTC to Alice, and the settle it.
|
||||
// Resulting balances:
|
||||
// Alice: 3.0
|
||||
// Bob: 7.0
|
||||
htlcAmt = lnwire.NewMSatFromSatoshis(2 * btcutil.SatoshiPerBitcoin)
|
||||
htlc, preimage := createHTLC(aliceIndex, htlcAmt)
|
||||
aliceIndex++
|
||||
aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add htlc: %v", err)
|
||||
}
|
||||
bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
if err := bobChannel.SettleHTLC(preimage, bobHtlcIndex); err != nil {
|
||||
t.Fatalf("bob unable to settle inbound htlc: %v", err)
|
||||
}
|
||||
if err := aliceChannel.ReceiveHTLCSettle(preimage, aliceHtlcIndex); err != nil {
|
||||
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
|
||||
}
|
||||
|
||||
// And now let Bob add an HTLC of 1 BTC. This will take Bob's balance
|
||||
// all the way down to his channel reserve, but since he is not paying the
|
||||
// fee this is okay.
|
||||
htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin)
|
||||
htlc, _ = createHTLC(bobIndex, htlcAmt)
|
||||
bobIndex++
|
||||
if _, err := bobChannel.AddHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to add htlc: %v", err)
|
||||
}
|
||||
if _, err := aliceChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
// Do a last state transition, which should succeed.
|
||||
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
||||
t.Fatalf("unable to complete state update: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMinHTLC tests that the ErrBelowMinHTLC error is thrown if an HTLC is added
|
||||
// that is below the minimm allowed value for HTLCs.
|
||||
func TestMinHTLC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
// We set Alice's MinHTLC to 0.1 BTC. We will attempt to send an
|
||||
// HTLC BELOW this value to trigger the ErrBelowMinHTLC error.
|
||||
minValue := lnwire.NewMSatFromSatoshis(0.1 * btcutil.SatoshiPerBitcoin)
|
||||
|
||||
// Setting the min value in Alice's local config means that the
|
||||
// remote will not accept any HTLCs of value less than specified.
|
||||
aliceChannel.localChanCfg.MinHTLC = minValue
|
||||
bobChannel.remoteChanCfg.MinHTLC = minValue
|
||||
|
||||
// First, we will add an HTLC of 0.5 BTC. This will not trigger
|
||||
// ErrBelowMinHTLC.
|
||||
htlcAmt := lnwire.NewMSatFromSatoshis(0.5 * btcutil.SatoshiPerBitcoin)
|
||||
htlc, _ := createHTLC(0, htlcAmt)
|
||||
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to add htlc: %v", err)
|
||||
}
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to recv htlc: %v", err)
|
||||
}
|
||||
|
||||
// We add an HTLC below the min value, this should result in
|
||||
// an ErrBelowMinHTLC error.
|
||||
amt := minValue - 100
|
||||
htlc, _ = createHTLC(1, amt)
|
||||
_, err = aliceChannel.AddHTLC(htlc)
|
||||
if err != ErrBelowMinHTLC {
|
||||
t.Fatalf("expected ErrBelowMinHTLC, instead received: %v", err)
|
||||
}
|
||||
|
||||
// Bob will receive this HTLC, but reject the next state update, since
|
||||
// the htlc is too small.
|
||||
_, err = bobChannel.ReceiveHTLC(htlc)
|
||||
if err != nil {
|
||||
t.Fatalf("error receiving htlc: %v", err)
|
||||
}
|
||||
err = forceStateTransition(aliceChannel, bobChannel)
|
||||
if err != ErrBelowMinHTLC {
|
||||
t.Fatalf("expected ErrBelowMinHTLC, instead received: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +296,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
}
|
||||
aliceChanReservation.SetNumConfsRequired(numReqConfs)
|
||||
aliceChanReservation.CommitConstraints(csvDelay, lnwallet.MaxHTLCNumber/2,
|
||||
lnwire.NewMSatFromSatoshis(fundingAmount), 10)
|
||||
lnwire.NewMSatFromSatoshis(fundingAmount), 1, 10)
|
||||
|
||||
// The channel reservation should now be populated with a multi-sig key
|
||||
// from our HD chain, a change output with 3 BTC, and 2 outputs
|
||||
@ -319,7 +319,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
t.Fatalf("bob unable to init channel reservation: %v", err)
|
||||
}
|
||||
bobChanReservation.CommitConstraints(csvDelay, lnwallet.MaxHTLCNumber/2,
|
||||
lnwire.NewMSatFromSatoshis(fundingAmount), 10)
|
||||
lnwire.NewMSatFromSatoshis(fundingAmount), 1, 10)
|
||||
bobChanReservation.SetNumConfsRequired(numReqConfs)
|
||||
|
||||
assertContributionInitPopulated(t, bobChanReservation.OurContribution())
|
||||
@ -649,7 +649,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
}
|
||||
aliceChanReservation.SetNumConfsRequired(numReqConfs)
|
||||
aliceChanReservation.CommitConstraints(csvDelay, lnwallet.MaxHTLCNumber/2,
|
||||
lnwire.NewMSatFromSatoshis(fundingAmt), 10)
|
||||
lnwire.NewMSatFromSatoshis(fundingAmt), 1, 10)
|
||||
|
||||
// Verify all contribution fields have been set properly.
|
||||
aliceContribution := aliceChanReservation.OurContribution()
|
||||
@ -661,7 +661,6 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
t.Fatalf("coin selection failed, should have one change outputs, "+
|
||||
"instead have: %v", len(aliceContribution.ChangeOutputs))
|
||||
}
|
||||
aliceContribution.CsvDelay = csvDelay
|
||||
assertContributionInitPopulated(t, aliceContribution)
|
||||
|
||||
// Next, Bob receives the initial request, generates a corresponding
|
||||
@ -673,12 +672,11 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create bob reservation: %v", err)
|
||||
}
|
||||
bobChanReservation.CommitConstraints(csvDelay, lnwallet.MaxHTLCNumber/2,
|
||||
lnwire.NewMSatFromSatoshis(fundingAmt), 10)
|
||||
lnwire.NewMSatFromSatoshis(fundingAmt), 1, 10)
|
||||
bobChanReservation.SetNumConfsRequired(numReqConfs)
|
||||
|
||||
// We'll ensure that Bob's contribution also gets generated properly.
|
||||
bobContribution := bobChanReservation.OurContribution()
|
||||
bobContribution.CsvDelay = csvDelay
|
||||
assertContributionInitPopulated(t, bobContribution)
|
||||
|
||||
// With his contribution generated, he can now process Alice's
|
||||
|
@ -277,44 +277,68 @@ func (r *ChannelReservation) RegisterMinHTLC(minHTLC lnwire.MilliSatoshi) {
|
||||
// will also attempt to verify the constraints for sanity, returning an error
|
||||
// if the parameters are seemed unsound.
|
||||
func (r *ChannelReservation) CommitConstraints(csvDelay, maxHtlcs uint16,
|
||||
maxValueInFlight lnwire.MilliSatoshi, chanReserve btcutil.Amount) error {
|
||||
maxValueInFlight, minHtlc lnwire.MilliSatoshi,
|
||||
chanReserve btcutil.Amount) error {
|
||||
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
// Fail if we consider csvDelay excessively large.
|
||||
// TODO(halseth): find a more scientific choice of value.
|
||||
if csvDelay > 10000 {
|
||||
return fmt.Errorf("csvDelay is too large: %d", csvDelay)
|
||||
}
|
||||
|
||||
// Fail if we consider the channel reserve to be too large.
|
||||
// We currently fail if it is greater than 20% of the
|
||||
// channel capacity.
|
||||
maxChanReserve := r.partialState.Capacity / 5
|
||||
if chanReserve > maxChanReserve {
|
||||
return fmt.Errorf("chanReserve is too large: %g",
|
||||
chanReserve.ToBTC())
|
||||
}
|
||||
|
||||
// Fail if the minimum HTLC value is too large. If this is
|
||||
// too large, the channel won't be useful for sending small
|
||||
// payments. This limit is currently set to maxValueInFlight,
|
||||
// effictively letting the remote setting this as large as
|
||||
// it wants.
|
||||
// TODO(halseth): set a reasonable/dynamic value.
|
||||
if minHtlc > maxValueInFlight {
|
||||
return fmt.Errorf("minimum HTLC value is too large: %g",
|
||||
r.ourContribution.MinHTLC.ToBTC())
|
||||
}
|
||||
|
||||
// Fail if maxHtlcs is above the maximum allowed number of 483.
|
||||
// This number is specified in BOLT-02.
|
||||
if maxHtlcs > uint16(MaxHTLCNumber/2) {
|
||||
return fmt.Errorf("maxHtlcs is too large: %d", maxHtlcs)
|
||||
}
|
||||
|
||||
// Fail if we consider maxHtlcs too small. If this is too small
|
||||
// we cannot offer many HTLCs to the remote.
|
||||
const minNumHtlc = 5
|
||||
if maxHtlcs < minNumHtlc {
|
||||
return fmt.Errorf("maxHtlcs is too small: %d", maxHtlcs)
|
||||
}
|
||||
|
||||
// Fail if we consider maxValueInFlight too small. We currently
|
||||
// require the remote to at least allow minNumHtlc * minHtlc
|
||||
// in flight.
|
||||
if maxValueInFlight < minNumHtlc*minHtlc {
|
||||
return fmt.Errorf("maxValueInFlight is too small: %g",
|
||||
maxValueInFlight.ToBTC())
|
||||
}
|
||||
|
||||
r.ourContribution.ChannelConfig.CsvDelay = csvDelay
|
||||
r.ourContribution.ChannelConfig.ChanReserve = chanReserve
|
||||
r.ourContribution.ChannelConfig.MaxAcceptedHtlcs = maxHtlcs
|
||||
r.ourContribution.ChannelConfig.MaxPendingAmount = maxValueInFlight
|
||||
r.ourContribution.ChannelConfig.MinHTLC = minHtlc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoteChanConstraints returns our desired parameters which constraint the
|
||||
// type of commitment transactions that the remote party can extend for our
|
||||
// current state. In order to ensure that we only accept sane states, we'll
|
||||
// specify: the required reserve the remote party must uphold, the max value in
|
||||
// flight, and the maximum number of HTLC's that can propose in a state.
|
||||
func (r *ChannelReservation) RemoteChanConstraints() (btcutil.Amount, lnwire.MilliSatoshi, uint16) {
|
||||
chanCapacity := r.partialState.Capacity
|
||||
|
||||
// TODO(roasbeef): move csv delay calculation into func?
|
||||
|
||||
// By default, we'll require them to maintain at least 1% of the total
|
||||
// channel capacity at all times. This is the absolute amount the
|
||||
// settled balance of the remote party must be above at *all* times.
|
||||
chanReserve := (chanCapacity) / 100
|
||||
|
||||
// We'll allow them to fully utilize the full bandwidth of the channel,
|
||||
// minus our required reserve.
|
||||
maxValue := lnwire.NewMSatFromSatoshis(chanCapacity - chanReserve)
|
||||
|
||||
// Finally, we'll permit them to utilize the full channel bandwidth
|
||||
maxHTLCs := uint16(MaxHTLCNumber / 2)
|
||||
|
||||
return chanReserve, maxValue, maxHTLCs
|
||||
}
|
||||
|
||||
// OurContribution returns the wallet's fully populated contribution to the
|
||||
// pending payment channel. See 'ChannelContribution' for further details
|
||||
// regarding the contents of a contribution.
|
||||
|
Loading…
Reference in New Issue
Block a user