lnwallet: add send/recv of HTLC cancellations to state machine
This commit adds the ability to send/recv HTLC cancellation to the commitment state machine. Previously this feature had been unimplemented within the state machine, with only adds/settles working. With this change, there’s now now no concept of “timing” out HTLC’s, only the cancellation of HTLC’s which may be triggered for various reasons.
This commit is contained in:
parent
4e53cf8b49
commit
fd0c0574e6
@ -89,7 +89,7 @@ type updateType uint8
|
||||
|
||||
const (
|
||||
Add updateType = iota
|
||||
Timeout
|
||||
Cancel
|
||||
Settle
|
||||
)
|
||||
|
||||
@ -1100,9 +1100,9 @@ func processRemoveEntry(htlc *PaymentDescriptor, ourBalance,
|
||||
// the HTLC amount.
|
||||
case isIncoming && htlc.EntryType == Settle:
|
||||
*ourBalance += htlc.Amount
|
||||
// Otherwise, this HTLC is being timed out, therefore the value of the
|
||||
// Otherwise, this HTLC is being cancelled, therefore the value of the
|
||||
// HTLC should return to the remote party.
|
||||
case isIncoming && htlc.EntryType == Timeout:
|
||||
case isIncoming && htlc.EntryType == Cancel:
|
||||
*theirBalance += htlc.Amount
|
||||
// If an outgoing HTLC is being settled, then this means that the
|
||||
// downstream party resented the preimage or learned of it via a
|
||||
@ -1110,9 +1110,9 @@ func processRemoveEntry(htlc *PaymentDescriptor, ourBalance,
|
||||
// the value of the HTLC.
|
||||
case !isIncoming && htlc.EntryType == Settle:
|
||||
*theirBalance += htlc.Amount
|
||||
// Otherwise, one of our outgoing HTLC's has timed out, so the value of
|
||||
// the HTLC should be returned to our settled balance.
|
||||
case !isIncoming && htlc.EntryType == Timeout:
|
||||
// Otherwise, one of our outgoing HTLC's has been cancelled, so the
|
||||
// value of the HTLC should be returned to our settled balance.
|
||||
case !isIncoming && htlc.EntryType == Cancel:
|
||||
*ourBalance += htlc.Amount
|
||||
}
|
||||
|
||||
@ -1715,8 +1715,58 @@ func (lc *LightningChannel) ReceiveHTLCSettle(preimage [32]byte, logIndex uint32
|
||||
return nil
|
||||
}
|
||||
|
||||
// TimeoutHTLC...
|
||||
func (lc *LightningChannel) TimeoutHTLC() error {
|
||||
// CancelHTLC attempts to cancel a targeted HTLC by its log index, inserting an
|
||||
// entry which will remove the target log entry within the next commitment
|
||||
// update. This method is intended to be called in order to cancel in
|
||||
// _incoming_ HTLC.
|
||||
func (lc *LightningChannel) CancelHTLC(logIndex uint32) error {
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
addEntry, ok := lc.theirLogIndex[logIndex]
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to find HTLC to cancel")
|
||||
}
|
||||
|
||||
htlc := addEntry.Value.(*PaymentDescriptor)
|
||||
|
||||
pd := &PaymentDescriptor{
|
||||
Amount: htlc.Amount,
|
||||
Index: lc.ourLogCounter,
|
||||
ParentIndex: htlc.Index,
|
||||
EntryType: Cancel,
|
||||
}
|
||||
|
||||
lc.ourUpdateLog.PushBack(pd)
|
||||
lc.ourLogCounter++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReceiveCancelHTLC attempts to cancel a targeted HTLC by its log index,
|
||||
// inserting an entry which will remove the target log entry within the next
|
||||
// commitment update. This method should be called in response to the upstream
|
||||
// party cancelling an outgoing HTLC.
|
||||
func (lc *LightningChannel) ReceiveCancelHTLC(logIndex uint32) error {
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
addEntry, ok := lc.ourLogIndex[logIndex]
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to find HTLC to cancel")
|
||||
}
|
||||
|
||||
htlc := addEntry.Value.(*PaymentDescriptor)
|
||||
pd := &PaymentDescriptor{
|
||||
Amount: htlc.Amount,
|
||||
ParentIndex: htlc.Index,
|
||||
Index: lc.theirLogCounter,
|
||||
EntryType: Cancel,
|
||||
}
|
||||
|
||||
lc.theirUpdateLog.PushBack(pd)
|
||||
lc.theirLogCounter++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1158,3 +1158,89 @@ func TestStateUpdatePersistence(t *testing.T) {
|
||||
3000, bobChannel.channelState.TotalSatoshisSent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelHTLC(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,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(5)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
// Add a new HTLC from Alice to Bob, then trigger a new state
|
||||
// transition in order to include it in the latest state.
|
||||
const htlcAmt = btcutil.SatoshiPerBitcoin
|
||||
|
||||
var preImage [32]byte
|
||||
copy(preImage[:], bytes.Repeat([]byte{0xaa}, 32))
|
||||
htlc := &lnwire.HTLCAddRequest{
|
||||
RedemptionHashes: [][32]byte{fastsha256.Sum256(preImage[:])},
|
||||
Amount: htlcAmt,
|
||||
Expiry: 10,
|
||||
}
|
||||
|
||||
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
|
||||
t.Fatalf("unable to add alice htlc: %v", err)
|
||||
}
|
||||
bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add bob htlc: %v", err)
|
||||
}
|
||||
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
||||
t.Fatalf("unable to create new commitment state: %v", err)
|
||||
}
|
||||
|
||||
// With the HTLC committed, Alice's balance should reflect the clearing
|
||||
// of the new HTLC.
|
||||
aliceExpectedBalance := btcutil.Amount(btcutil.SatoshiPerBitcoin * 4)
|
||||
if aliceChannel.channelState.OurBalance != aliceExpectedBalance {
|
||||
t.Fatalf("Alice's balance is wrong: expected %v, got %v",
|
||||
aliceExpectedBalance, aliceChannel.channelState.OurBalance)
|
||||
}
|
||||
|
||||
// Now, with the HTLC committed on both sides, trigger a cancellation
|
||||
// from Bob to Alice, removing the HTLC.
|
||||
if err := bobChannel.CancelHTLC(bobHtlcIndex); err != nil {
|
||||
t.Fatalf("unable to cancel HTLC: %v", err)
|
||||
}
|
||||
if err := aliceChannel.ReceiveCancelHTLC(bobHtlcIndex); err != nil {
|
||||
t.Fatalf("unable to recv htlc cancel: %v", err)
|
||||
}
|
||||
|
||||
// Now trigger another state transition, the HTLC should now be removed
|
||||
// from both sides, with balances reflected.
|
||||
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
||||
t.Fatalf("unable to create new commitment: %v", err)
|
||||
}
|
||||
|
||||
// Now HTLC's should be present on the commitment transaction for
|
||||
// either side.
|
||||
if len(aliceChannel.localCommitChain.tip().outgoingHTLCs) != 0 ||
|
||||
len(aliceChannel.remoteCommitChain.tip().outgoingHTLCs) != 0 {
|
||||
t.Fatalf("htlc's still active from alice's POV")
|
||||
}
|
||||
if len(bobChannel.localCommitChain.tip().outgoingHTLCs) != 0 ||
|
||||
len(bobChannel.remoteCommitChain.tip().outgoingHTLCs) != 0 {
|
||||
t.Fatalf("htlc's still active from bob's POV")
|
||||
}
|
||||
|
||||
expectedBalance := btcutil.Amount(btcutil.SatoshiPerBitcoin * 5)
|
||||
if aliceChannel.channelState.OurBalance != expectedBalance {
|
||||
t.Fatalf("balance is wrong: expected %v, got %v",
|
||||
aliceChannel.channelState.OurBalance, expectedBalance)
|
||||
}
|
||||
if aliceChannel.channelState.TheirBalance != expectedBalance {
|
||||
t.Fatalf("balance is wrong: expected %v, got %v",
|
||||
aliceChannel.channelState.TheirBalance, expectedBalance)
|
||||
}
|
||||
if bobChannel.channelState.OurBalance != expectedBalance {
|
||||
t.Fatalf("balance is wrong: expected %v, got %v",
|
||||
bobChannel.channelState.OurBalance, expectedBalance)
|
||||
}
|
||||
if bobChannel.channelState.TheirBalance != expectedBalance {
|
||||
t.Fatalf("balance is wrong: expected %v, got %v",
|
||||
bobChannel.channelState.TheirBalance, expectedBalance)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user