fundingmanager+lnwallet: add HTLC dust limit logic

This commit is contained in:
Andrey Samokhvalov authored and Olaoluwa Osuntokun committed 2016-12-13 11:01:57 -08:00
1 parent c731156ac8
commit d01f1b5ff4
9 files changed
+188 -35

No files matched your search

+11 -1
View File
@@ -881,19 +881,21 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
var selfKey *btcec.PublicKey
var remoteKey *btcec.PublicKey
var delay uint32
var delayBalance, p2wkhBalance btcutil.Amount
var delayBalance, p2wkhBalance, dustLimit btcutil.Amount
if remoteChain {
selfKey = lc.channelState.TheirCommitKey
remoteKey = lc.channelState.OurCommitKey
delay = lc.channelState.RemoteCsvDelay
delayBalance = theirBalance
p2wkhBalance = ourBalance
dustLimit = lc.channelState.TheirDustLimit
} else {
selfKey = lc.channelState.OurCommitKey
remoteKey = lc.channelState.TheirCommitKey
delay = lc.channelState.LocalCsvDelay
delayBalance = ourBalance
p2wkhBalance = theirBalance
dustLimit = lc.channelState.OurDustLimit
}
// Generate a new commitment transaction with all the latest
@@ -905,6 +907,10 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
return nil, err
}
for _, htlc := range filteredHTLCView.ourUpdates {
if htlc.Amount < dustLimit {
continue
}
err := lc.addHTLC(commitTx, ourCommitTx, htlc,
revocationHash, delay, false)
if err != nil {
@@ -912,6 +918,10 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
}
}
for _, htlc := range filteredHTLCView.theirUpdates {
if htlc.Amount < dustLimit {
continue
}
err := lc.addHTLC(commitTx, ourCommitTx, htlc,
revocationHash, delay, true)
if err != nil {
+122
View File
@@ -188,6 +188,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
channelCapacity := btcutil.Amount(10 * 1e8)
channelBal := channelCapacity / 2
aliceDustLimit := btcutil.Amount(200)
bobDustLimit := btcutil.Amount(800)
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
@@ -265,6 +267,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
TheirCurrentRevocation: bobRevokeKey,
LocalElkrem: aliceElkrem,
RemoteElkrem: &elkrem.ElkremReceiver{},
TheirDustLimit: bobDustLimit,
OurDustLimit: aliceDustLimit,
Db: dbAlice,
}
bobChannelState := &channeldb.OpenChannel{
@@ -289,6 +293,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
TheirCurrentRevocation: aliceRevokeKey,
LocalElkrem: bobElkrem,
RemoteElkrem: &elkrem.ElkremReceiver{},
TheirDustLimit: aliceDustLimit,
OurDustLimit: bobDustLimit,
Db: dbBob,
}
@@ -833,6 +839,122 @@ func TestCheckHTLCNumberConstraint(t *testing.T) {
}
// TestCheckDustLimit checks that unsettled HTLC with dust limit not included in
// commitment transaction as output, but sender balance is decreased (thereby all
// unsettled dust HTLCs will go to miners fee).
func TestCheckDustLimit(t *testing.T) {
createHTLC := func(data, amount btcutil.Amount) (*lnwire.HTLCAddRequest,
[32]byte) {
preimage := bytes.Repeat([]byte{byte(data)}, 32)
paymentHash := fastsha256.Sum256(preimage)
var returnPreimage [32]byte
copy(returnPreimage[:], preimage)
return &lnwire.HTLCAddRequest{
RedemptionHashes: [][32]byte{paymentHash},
Amount: amount,
Expiry: uint32(5),
}, returnPreimage
}
// Create a test channel which will be used for the duration of this
// unittest. The channel will be funded evenly with Alice having 5 BTC,
// and Bob having 5 BTC.
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
aliceDustLimit := aliceChannel.channelState.OurDustLimit
bobDustLimit := bobChannel.channelState.OurDustLimit
htlcAmount := btcutil.Amount(500)
if !((htlcAmount > aliceDustLimit) && (bobDustLimit > htlcAmount)) {
t.Fatal("htlc amount needs to be above Alice's dust limit, but " +
"below Bob's dust limit .")
}
aliceAmount := aliceChannel.channelState.OurBalance
bobAmount := bobChannel.channelState.OurBalance
htlc, preimage := createHTLC(0, htlcAmount)
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("alice unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("bob unable to receive htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}
// First two outputs are payment to them and to us. If we encounter
// third output it means that dust HTLC was included. Their channel
// balance shouldn't change because, it will be changed only after
// HTLC will be settled.
// From Alice point of view HTLC's amount is bigger then dust limit.
commitment := aliceChannel.localCommitChain.tip()
if len(commitment.txn.TxOut) != 3 {
t.Fatal("htlc wasn't added")
}
if commitment.ourBalance != aliceAmount-htlcAmount {
t.Fatal("our balance wasn't updated")
}
if commitment.theirBalance != bobAmount {
t.Fatal("their balance was updated")
}
// From Bob point of view HTLC's amount is lower then dust limit.
commitment = bobChannel.localCommitChain.tip()
if len(commitment.txn.TxOut) != 2 {
t.Fatal("HTLC with dust amount was added")
}
if commitment.theirBalance != aliceAmount-htlcAmount {
t.Fatal("their balance wasn't updated")
}
if commitment.ourBalance != bobAmount {
t.Fatal("our balance was updated")
}
// Settle HTLC and sign new commitment.
settleIndex, err := bobChannel.SettleHTLC(preimage)
if err != nil {
t.Fatalf("bob unable to settle inbound htlc: %v", err)
}
err = aliceChannel.ReceiveHTLCSettle(preimage, settleIndex)
if err != nil {
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}
commitment = aliceChannel.localCommitChain.tip()
if len(commitment.txn.TxOut) != 2 {
t.Fatal("HTLC wasn't settled")
}
if commitment.ourBalance != aliceAmount-htlcAmount {
t.Fatal("our balance wasn't updated")
}
if commitment.theirBalance != bobAmount+htlcAmount {
t.Fatal("their balance wasn't updated")
}
commitment = bobChannel.localCommitChain.tip()
if len(commitment.txn.TxOut) != 2 {
t.Fatal("HTLC with dust amount wasn't settled")
}
if commitment.ourBalance != bobAmount+htlcAmount {
t.Fatal("our balance wasn't updated")
}
if commitment.theirBalance != aliceAmount-htlcAmount {
t.Fatal("their balance wasn't updated")
}
}
func TestStateUpdatePersistence(t *testing.T) {
// Create a test channel which will be used for the duration of this
// unittest. The channel will be funded evenly with Alice having 5 BTC,
+8 -8
View File
@@ -363,7 +363,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet
// Bob initiates a channel funded with 5 BTC for each side, so 10
// BTC total. He also generates 2 BTC in change.
chanReservation, err := wallet.InitChannelReservation(fundingAmount*2,
fundingAmount, bobNode.id, bobAddr, numReqConfs, 4)
fundingAmount, bobNode.id, bobAddr, numReqConfs, 4, 540)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
}
@@ -522,7 +522,7 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
// Create a single channel asking for 16 BTC total.
fundingAmount := btcutil.Amount(8 * 1e8)
_, err := wallet.InitChannelReservation(fundingAmount, fundingAmount,
testPub, bobAddr, numReqConfs, 4)
testPub, bobAddr, numReqConfs, 4, 540)
if err != nil {
t.Fatalf("unable to initialize funding reservation 1: %v", err)
}
@@ -532,7 +532,7 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
// that aren't locked, so this should fail.
amt := btcutil.Amount(900 * 1e8)
failedReservation, err := wallet.InitChannelReservation(amt, amt,
testPub, bobAddr, numReqConfs, 4)
testPub, bobAddr, numReqConfs, 4, 540)
if err == nil {
t.Fatalf("not error returned, should fail on coin selection")
}
@@ -552,14 +552,14 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
// Create a reservation for 44 BTC.
fundingAmount := btcutil.Amount(44 * 1e8)
chanReservation, err := wallet.InitChannelReservation(fundingAmount,
fundingAmount, testPub, bobAddr, numReqConfs, 4)
fundingAmount, testPub, bobAddr, numReqConfs, 4, 540)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
}
// Attempt to create another channel with 44 BTC, this should fail.
_, err = wallet.InitChannelReservation(fundingAmount,
fundingAmount, testPub, bobAddr, numReqConfs, 4)
fundingAmount, testPub, bobAddr, numReqConfs, 4, 540)
if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok {
t.Fatalf("coin selection succeded should have insufficient funds: %v",
err)
@@ -589,7 +589,7 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
// Request to fund a new channel should now succeed.
_, err = wallet.InitChannelReservation(fundingAmount, fundingAmount,
testPub, bobAddr, numReqConfs, 4)
testPub, bobAddr, numReqConfs, 4, 540)
if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
}
@@ -628,7 +628,7 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness,
// Initialize a reservation for a channel with 4 BTC funded solely by us.
fundingAmt := btcutil.Amount(4 * 1e8)
chanReservation, err := lnwallet.InitChannelReservation(fundingAmt,
fundingAmt, bobNode.id, bobAddr, numReqConfs, 4)
fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540)
if err != nil {
t.Fatalf("unable to init channel reservation: %v", err)
}
@@ -769,7 +769,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness,
// contribution and the necessary resources.
fundingAmt := btcutil.Amount(0)
chanReservation, err := wallet.InitChannelReservation(capacity,
fundingAmt, bobNode.id, bobAddr, numReqConfs, 4)
fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540)
if err != nil {
t.Fatalf("unable to init channel reservation: %v", err)
}
+2 -2
View File
@@ -1,12 +1,12 @@
package lnwallet
import (
"github.com/roasbeef/btcwallet/wallet/txrules"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcwallet/wallet/txrules"
)
// DefaultDustLimit is used to calculate the dust HTLC amount which will be
// proposed to other node during channel creation.
// send to other node during funding process.
func DefaultDustLimit() btcutil.Amount {
return txrules.GetDustThreshold(P2WSHSize, txrules.DefaultRelayFeePerKb)
}
+8
View File
@@ -382,6 +382,14 @@ func (r *ChannelReservation) LocalCommitTx() *wire.MsgTx {
return r.partialState.OurCommitTx
}
// SetDustLimit set dust limit of the remote party.
func (r *ChannelReservation) SetTheirDustLimit(dustLimit btcutil.Amount) {
r.Lock()
defer r.Unlock()
r.partialState.TheirDustLimit = dustLimit
}
// FundingOutpoint returns the outpoint of the funding transaction.
//
// NOTE: The pointer returned will only be set once the .ProcesContribution()
+13 -4
View File
@@ -100,6 +100,11 @@ type initFundingReserveMsg struct {
// TODO(roasbeef): integrate fee estimation project...
minFeeRate btcutil.Amount
// ourDustLimit is the threshold below which no HTLC output should be
// generated for our commitment transaction; ie. HTLCs below
// this amount are not enforceable onchain from our point of view.
ourDustLimit btcutil.Amount
// The delay on the "pay-to-self" output(s) of the commitment transaction.
csvDelay uint32
@@ -484,7 +489,7 @@ out:
func (l *LightningWallet) InitChannelReservation(capacity,
ourFundAmt btcutil.Amount, theirID *btcec.PublicKey,
theirAddr *net.TCPAddr, numConfs uint16,
csvDelay uint32) (*ChannelReservation, error) {
csvDelay uint32, ourDustLimit btcutil.Amount) (*ChannelReservation, error) {
errChan := make(chan error, 1)
respChan := make(chan *ChannelReservation, 1)
@@ -494,6 +499,7 @@ func (l *LightningWallet) InitChannelReservation(capacity,
numConfs: numConfs,
fundingAmount: ourFundAmt,
csvDelay: csvDelay,
ourDustLimit: ourDustLimit,
nodeID: theirID,
err: errChan,
resp: respChan,
@@ -522,11 +528,14 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
reservation.Lock()
defer reservation.Unlock()
reservation.partialState.IdentityPub = req.nodeID
reservation.nodeAddr = req.nodeAddr
ourContribution := reservation.ourContribution
ourContribution.CsvDelay = req.csvDelay
reservation.ourContribution.CsvDelay = req.csvDelay
reservation.partialState.IdentityPub = req.nodeID
reservation.partialState.LocalCsvDelay = req.csvDelay
reservation.partialState.OurDustLimit = req.ourDustLimit
ourContribution := reservation.ourContribution
// 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