Merge pull request #1025 from Roasbeef/incoming-htlc-breach-retribution
lnwallet+test: properly generate the sender HTLC script in a contract breach scenario
This commit is contained in:
3 files changed
+215
-58
No files matched your search
+15
-5
@@ -1971,13 +1971,23 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
// With the commitment outputs located, we'll now generate all the
|
||||
// retribution structs for each of the HTLC transactions active on the
|
||||
// remote commitment transaction.
|
||||
htlcRetributions := make([]HtlcRetribution, len(revokedSnapshot.Htlcs))
|
||||
for i, htlc := range revokedSnapshot.Htlcs {
|
||||
htlcRetributions := make([]HtlcRetribution, 0, len(revokedSnapshot.Htlcs))
|
||||
for _, htlc := range revokedSnapshot.Htlcs {
|
||||
var (
|
||||
htlcScript []byte
|
||||
err error
|
||||
)
|
||||
|
||||
// If the HTLC is dust, then we'll skip it as it doesn't have
|
||||
// an output on the commitment transaction.
|
||||
if htlcIsDust(
|
||||
htlc.Incoming, false,
|
||||
SatPerKWeight(revokedSnapshot.FeePerKw),
|
||||
htlc.Amt.ToSatoshis(), chanState.RemoteChanCfg.DustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We'll generate the original second level witness script now,
|
||||
// as we'll need it if we're revoking an HTLC output on the
|
||||
// remote commitment transaction, and *they* go to the second
|
||||
@@ -1994,7 +2004,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
// re-generate the sender HTLC script.
|
||||
if htlc.Incoming {
|
||||
htlcScript, err = senderHTLCScript(
|
||||
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
||||
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, htlc.RHash[:],
|
||||
)
|
||||
if err != nil {
|
||||
@@ -2015,7 +2025,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
}
|
||||
}
|
||||
|
||||
htlcRetributions[i] = HtlcRetribution{
|
||||
htlcRetributions = append(htlcRetributions, HtlcRetribution{
|
||||
SignDesc: SignDescriptor{
|
||||
KeyDesc: chanState.LocalChanCfg.RevocationBasePoint,
|
||||
DoubleTweak: commitmentSecret,
|
||||
@@ -2031,7 +2041,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
},
|
||||
SecondLevelWitnessScript: secondLevelWitnessScript,
|
||||
IsIncoming: htlc.Incoming,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Finally, with all the necessary data constructed, we can create the
|
||||
|
||||
@@ -5012,3 +5012,98 @@ func TestMinHTLC(t *testing.T) {
|
||||
t.Fatalf("expected ErrBelowMinHTLC, instead received: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewBreachRetributionSkipsDustHtlcs ensures that in the case of a
|
||||
// contract breach, all dust HTLCs are ignored and not reflected in the
|
||||
// produced BreachRetribution struct. We ignore these HTLCs as they aren't
|
||||
// actually manifested on the commitment transaction, as a result we can't
|
||||
// actually revoked them.
|
||||
func TestNewBreachRetributionSkipsDustHtlcs(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()
|
||||
|
||||
var fakeOnionBlob [lnwire.OnionPacketSize]byte
|
||||
copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize))
|
||||
|
||||
// We'll modify the dust settings on both channels to be a predictable
|
||||
// value for the prurpose of the test.
|
||||
dustValue := btcutil.Amount(200)
|
||||
aliceChannel.channelState.LocalChanCfg.DustLimit = dustValue
|
||||
aliceChannel.channelState.RemoteChanCfg.DustLimit = dustValue
|
||||
bobChannel.channelState.LocalChanCfg.DustLimit = dustValue
|
||||
bobChannel.channelState.RemoteChanCfg.DustLimit = dustValue
|
||||
|
||||
// We'll now create a series of dust HTLC's, and send then from Alice
|
||||
// to Bob, finally locking both of them in.
|
||||
var bobPreimage [32]byte
|
||||
copy(bobPreimage[:], bytes.Repeat([]byte{0xbb}, 32))
|
||||
for i := 0; i < 3; i++ {
|
||||
rHash := sha256.Sum256(bobPreimage[:])
|
||||
h := &lnwire.UpdateAddHTLC{
|
||||
PaymentHash: rHash,
|
||||
Amount: lnwire.NewMSatFromSatoshis(dustValue),
|
||||
Expiry: uint32(10),
|
||||
OnionBlob: fakeOnionBlob,
|
||||
}
|
||||
|
||||
htlcIndex, err := aliceChannel.AddHTLC(h, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add bob's htlc: %v", err)
|
||||
}
|
||||
|
||||
h.ID = htlcIndex
|
||||
if _, err := bobChannel.ReceiveHTLC(h); err != nil {
|
||||
t.Fatalf("unable to recv bob's htlc: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// With the HTLC's applied to both update logs, we'll initiate a state
|
||||
// transition from Alice.
|
||||
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
||||
t.Fatalf("unable to complete bob's state transition: %v", err)
|
||||
}
|
||||
|
||||
// At this point, we'll capture the current state number, as well as
|
||||
// the current commitment.
|
||||
revokedCommit := bobChannel.channelState.LocalCommitment.CommitTx
|
||||
revokedStateNum := aliceChannel.channelState.LocalCommitment.CommitHeight
|
||||
|
||||
// We'll now have Bob settle those HTLC's to Alice and then advance
|
||||
// forward to a new state.
|
||||
for i := 0; i < 3; i++ {
|
||||
err := bobChannel.SettleHTLC(bobPreimage, uint64(i), nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle htlc: %v", err)
|
||||
}
|
||||
err = aliceChannel.ReceiveHTLCSettle(bobPreimage, uint64(i))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle htlc: %v", err)
|
||||
}
|
||||
}
|
||||
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
||||
t.Fatalf("unable to complete bob's state transition: %v", err)
|
||||
}
|
||||
|
||||
// At this point, we'll now simulate a contract breach by Bob using the
|
||||
// NewBreachRetribution method.
|
||||
breachRet, err := NewBreachRetribution(
|
||||
aliceChannel.channelState, revokedStateNum, revokedCommit, 100,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create breach retribution: %v", err)
|
||||
}
|
||||
|
||||
// The retribution shouldn't have any HTLCs set as they were all below
|
||||
// dust for both parties.
|
||||
if len(breachRet.HtlcRetributions) != 0 {
|
||||
t.Fatalf("zero HTLC retributions should have been created, "+
|
||||
"instead %v were", len(breachRet.HtlcRetributions))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user