htlcswitch: add new TestUpdateFailMalformedHTLCErrorConversion test

In this commit, we add a new test to ensure that we're able to properly
convert malformed HTLC errors that are sourced from multiple hops away,
or our direct channel peers. In order to test this effectively, we force
the onion decryptors of various peers to always fail which will trigger
the malformed HTLC logic.
This commit is contained in:
Olaoluwa Osuntokun 2019-04-30 18:28:39 -07:00
parent be63c7d286
commit 56c969c911
No known key found for this signature in database
GPG Key ID: CE58F7F8E20FD9A2
3 changed files with 96 additions and 8 deletions

@ -368,7 +368,10 @@ func (o *mockObfuscator) EncryptFirstHop(failure lnwire.FailureMessage) (
func (o *mockObfuscator) IntermediateEncrypt(reason lnwire.OpaqueReason) lnwire.OpaqueReason { func (o *mockObfuscator) IntermediateEncrypt(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return reason return reason
}
func (o *mockObfuscator) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return reason
} }
// mockDeobfuscator mock implementation of the failure deobfuscator which // mockDeobfuscator mock implementation of the failure deobfuscator which
@ -400,6 +403,8 @@ type mockIteratorDecoder struct {
mu sync.RWMutex mu sync.RWMutex
responses map[[32]byte][]DecodeHopIteratorResponse responses map[[32]byte][]DecodeHopIteratorResponse
decodeFail bool
} }
func newMockIteratorDecoder() *mockIteratorDecoder { func newMockIteratorDecoder() *mockIteratorDecoder {
@ -451,6 +456,10 @@ func (p *mockIteratorDecoder) DecodeHopIterators(id []byte,
req.OnionReader, req.RHash, req.IncomingCltv, req.OnionReader, req.RHash, req.IncomingCltv,
) )
if p.decodeFail {
failcode = lnwire.CodeTemporaryChannelFailure
}
resp := DecodeHopIteratorResponse{ resp := DecodeHopIteratorResponse{
HopIterator: iterator, HopIterator: iterator,
FailCode: failcode, FailCode: failcode,

@ -2033,3 +2033,76 @@ func TestMultiHopPaymentForwardingEvents(t *testing.T) {
} }
} }
} }
// TestUpdateFailMalformedHTLCErrorConversion tests that we're able to properly
// convert malformed HTLC errors that originate at the direct link, as well as
// during multi-hop HTLC forwarding.
func TestUpdateFailMalformedHTLCErrorConversion(t *testing.T) {
t.Parallel()
// First, we'll create our traditional three hop network.
channels, cleanUp, _, err := createClusterChannels(
btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5,
)
if err != nil {
t.Fatalf("unable to create channel: %v", err)
}
defer cleanUp()
n := newThreeHopNetwork(
t, channels.aliceToBob, channels.bobToAlice,
channels.bobToCarol, channels.carolToBob, testStartingHeight,
)
if err := n.start(); err != nil {
t.Fatalf("unable to start three hop network: %v", err)
}
assertPaymentFailure := func(t *testing.T) {
// With the decoder modified, we'll now attempt to send a
// payment from Alice to carol.
finalAmt := lnwire.NewMSatFromSatoshis(100000)
htlcAmt, totalTimelock, hops := generateHops(
finalAmt, testStartingHeight, n.firstBobChannelLink,
n.carolChannelLink,
)
firstHop := n.firstBobChannelLink.ShortChanID()
_, err = makePayment(
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
htlcAmt, totalTimelock,
).Wait(30 * time.Second)
// The payment should fail as Carol is unable to decode the
// onion blob sent to her.
if err == nil {
t.Fatalf("unable to send payment: %v", err)
}
fwdingErr := err.(*ForwardingError)
failureMsg := fwdingErr.FailureMessage
if _, ok := failureMsg.(*lnwire.FailTemporaryChannelFailure); !ok {
t.Fatalf("expected temp chan failure instead got: %v",
fwdingErr.FailureMessage)
}
}
t.Run("multi-hop error conversion", func(t *testing.T) {
// Now that we have our network up, we'll modify the hop
// iterator for the Bob <-> Carol channel to fail to decode in
// order to simulate either a replay attack or an issue
// decoding the onion.
n.carolOnionDecoder.decodeFail = true
assertPaymentFailure(t)
})
t.Run("direct channel error conversion", func(t *testing.T) {
// Similar to the above test case, we'll now make the Alice <->
// Bob link always fail to decode an onion. This differs from
// the above test case in that there's no encryption on the
// error at all since Alice will directly receive a
// UpdateFailMalformedHTLC message.
n.bobOnionDecoder.decodeFail = true
assertPaymentFailure(t)
})
}

@ -589,15 +589,18 @@ func generateRoute(hops ...ForwardingInfo) ([lnwire.OnionPacketSize]byte, error)
// threeHopNetwork is used for managing the created cluster of 3 hops. // threeHopNetwork is used for managing the created cluster of 3 hops.
type threeHopNetwork struct { type threeHopNetwork struct {
aliceServer *mockServer aliceServer *mockServer
aliceChannelLink *channelLink aliceChannelLink *channelLink
aliceOnionDecoder *mockIteratorDecoder
bobServer *mockServer bobServer *mockServer
firstBobChannelLink *channelLink firstBobChannelLink *channelLink
secondBobChannelLink *channelLink secondBobChannelLink *channelLink
bobOnionDecoder *mockIteratorDecoder
carolServer *mockServer carolServer *mockServer
carolChannelLink *channelLink carolChannelLink *channelLink
carolOnionDecoder *mockIteratorDecoder
hopNetwork hopNetwork
} }
@ -948,15 +951,18 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
} }
return &threeHopNetwork{ return &threeHopNetwork{
aliceServer: aliceServer, aliceServer: aliceServer,
aliceChannelLink: aliceChannelLink.(*channelLink), aliceChannelLink: aliceChannelLink.(*channelLink),
aliceOnionDecoder: aliceDecoder,
bobServer: bobServer, bobServer: bobServer,
firstBobChannelLink: firstBobChannelLink.(*channelLink), firstBobChannelLink: firstBobChannelLink.(*channelLink),
secondBobChannelLink: secondBobChannelLink.(*channelLink), secondBobChannelLink: secondBobChannelLink.(*channelLink),
bobOnionDecoder: bobDecoder,
carolServer: carolServer, carolServer: carolServer,
carolChannelLink: carolChannelLink.(*channelLink), carolChannelLink: carolChannelLink.(*channelLink),
carolOnionDecoder: carolDecoder,
hopNetwork: *hopNetwork, hopNetwork: *hopNetwork,
} }