Merge pull request #3027 from Roasbeef/new-onion-structs
router+build: update to the latest version of lightning-onion
This commit is contained in:
14 files changed
+245
-90
No files matched your search
@@ -87,6 +87,13 @@ type ErrorEncrypter interface {
|
||||
// slightly, in that it computes a proper MAC over the error.
|
||||
EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason, error)
|
||||
|
||||
// EncryptMalformedError is similar to EncryptFirstHop (it adds the
|
||||
// MAC), but it accepts an opaque failure reason rather than a failure
|
||||
// message. This method is used when we receive an
|
||||
// UpdateFailMalformedHTLC from the remote peer and then need to
|
||||
// convert that into a proper error from only the raw bytes.
|
||||
EncryptMalformedError(lnwire.OpaqueReason) lnwire.OpaqueReason
|
||||
|
||||
// IntermediateEncrypt wraps an already encrypted opaque reason error
|
||||
// in an additional layer of onion encryption. This process repeats
|
||||
// until the error arrives at the source of the payment.
|
||||
@@ -153,6 +160,17 @@ func (s *SphinxErrorEncrypter) EncryptFirstHop(failure lnwire.FailureMessage) (l
|
||||
return s.EncryptError(true, b.Bytes()), nil
|
||||
}
|
||||
|
||||
// EncryptMalformedError is similar to EncryptFirstHop (it adds the MAC), but
|
||||
// it accepts an opaque failure reason rather than a failure message. This
|
||||
// method is used when we receive an UpdateFailMalformedHTLC from the remote
|
||||
// peer and then need to convert that into an proper error from only the raw
|
||||
// bytes.
|
||||
//
|
||||
// NOTE: Part of the ErrorEncrypter interface.
|
||||
func (s *SphinxErrorEncrypter) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
|
||||
return s.EncryptError(true, reason)
|
||||
}
|
||||
|
||||
// IntermediateEncrypt wraps an already encrypted opaque reason error in an
|
||||
// additional layer of onion encryption. This process repeats until the error
|
||||
// arrives at the source of the payment. We re-encrypt the message on the
|
||||
|
||||
+15
-1
@@ -2404,10 +2404,24 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg,
|
||||
outgoingHTLCID: pd.ParentIndex,
|
||||
destRef: pd.DestRef,
|
||||
htlc: &lnwire.UpdateFailHTLC{
|
||||
Reason: lnwire.OpaqueReason(pd.FailReason),
|
||||
Reason: lnwire.OpaqueReason(
|
||||
pd.FailReason,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
// If the failure message lacks an HMAC (but includes
|
||||
// the 4 bytes for encoding the message and padding
|
||||
// lengths, then this means that we received it as an
|
||||
// UpdateFailMalformedHTLC. As a result, we'll signal
|
||||
// that we need to convert this error within the switch
|
||||
// to an actual error, by encrypting it as if we were
|
||||
// the originating hop.
|
||||
convertedErrorSize := lnwire.FailureMessageLength + 4
|
||||
if len(pd.FailReason) == convertedErrorSize {
|
||||
failPacket.convertedError = true
|
||||
}
|
||||
|
||||
// Add the packet to the batch to be forwarded, and
|
||||
// notify the overflow queue that a spare spot has been
|
||||
// freed up within the commitment state.
|
||||
|
||||
@@ -368,7 +368,10 @@ func (o *mockObfuscator) EncryptFirstHop(failure lnwire.FailureMessage) (
|
||||
|
||||
func (o *mockObfuscator) IntermediateEncrypt(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
|
||||
return reason
|
||||
}
|
||||
|
||||
func (o *mockObfuscator) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
|
||||
return reason
|
||||
}
|
||||
|
||||
// mockDeobfuscator mock implementation of the failure deobfuscator which
|
||||
@@ -400,6 +403,8 @@ type mockIteratorDecoder struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
responses map[[32]byte][]DecodeHopIteratorResponse
|
||||
|
||||
decodeFail bool
|
||||
}
|
||||
|
||||
func newMockIteratorDecoder() *mockIteratorDecoder {
|
||||
@@ -451,6 +456,10 @@ func (p *mockIteratorDecoder) DecodeHopIterators(id []byte,
|
||||
req.OnionReader, req.RHash, req.IncomingCltv,
|
||||
)
|
||||
|
||||
if p.decodeFail {
|
||||
failcode = lnwire.CodeTemporaryChannelFailure
|
||||
}
|
||||
|
||||
resp := DecodeHopIteratorResponse{
|
||||
HopIterator: iterator,
|
||||
FailCode: failcode,
|
||||
|
||||
@@ -61,6 +61,14 @@ type htlcPacket struct {
|
||||
// encrypted with any shared secret.
|
||||
localFailure bool
|
||||
|
||||
// convertedError is set to true if this is an HTLC fail that was
|
||||
// created using an UpdateFailMalformedHTLC from the remote party. If
|
||||
// this is true, then when forwarding this failure packet, we'll need
|
||||
// to wrap it as if we were the first hop if it's a multi-hop HTLC. If
|
||||
// it's a direct HTLC, then we'll decode the error as no encryption has
|
||||
// taken place.
|
||||
convertedError bool
|
||||
|
||||
// hasSource is set to true if the incomingChanID and incomingHTLCID
|
||||
// fields of a forwarded fail packet are already set and do not need to
|
||||
// be looked up in the circuit map.
|
||||
|
||||
+23
-5
@@ -956,7 +956,7 @@ func (s *Switch) parseFailedPayment(payment *pendingPayment, pkt *htlcPacket,
|
||||
// The payment never cleared the link, so we don't need to
|
||||
// decrypt the error, simply decode it them report back to the
|
||||
// user.
|
||||
case pkt.localFailure:
|
||||
case pkt.localFailure || pkt.convertedError:
|
||||
var userErr string
|
||||
r := bytes.NewReader(htlc.Reason)
|
||||
failureMsg, err := lnwire.DecodeFailure(r, 0)
|
||||
@@ -1172,13 +1172,12 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
||||
fail, isFail := htlc.(*lnwire.UpdateFailHTLC)
|
||||
if isFail && !packet.hasSource {
|
||||
switch {
|
||||
// No message to encrypt, locally sourced payment.
|
||||
case circuit.ErrorEncrypter == nil:
|
||||
// No message to encrypt, locally sourced
|
||||
// payment.
|
||||
|
||||
// If this is a resolution message, then we'll need to
|
||||
// encrypt it as it's actually internally sourced.
|
||||
case packet.isResolution:
|
||||
// If this is a resolution message, then we'll need to encrypt
|
||||
// it as it's actually internally sourced.
|
||||
var err error
|
||||
// TODO(roasbeef): don't need to pass actually?
|
||||
failure := &lnwire.FailPermanentChannelFailure{}
|
||||
@@ -1191,6 +1190,25 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
// Alternatively, if the remote party send us an
|
||||
// UpdateFailMalformedHTLC, then we'll need to convert
|
||||
// this into a proper well formatted onion error as
|
||||
// there's no HMAC currently.
|
||||
case packet.convertedError:
|
||||
log.Infof("Converting malformed HTLC error "+
|
||||
"for circuit for Circuit(%x: "+
|
||||
"(%s, %d) <-> (%s, %d))", packet.circuit.PaymentHash,
|
||||
packet.incomingChanID, packet.incomingHTLCID,
|
||||
packet.outgoingChanID, packet.outgoingHTLCID)
|
||||
|
||||
fail.Reason = circuit.ErrorEncrypter.EncryptMalformedError(
|
||||
fail.Reason,
|
||||
)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to obfuscate "+
|
||||
"error: %v", err)
|
||||
log.Error(err)
|
||||
}
|
||||
default:
|
||||
// Otherwise, it's a forwarded error, so we'll perform a
|
||||
// wrapper encryption as normal.
|
||||
|
||||
@@ -2049,3 +2049,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.
|
||||
type threeHopNetwork struct {
|
||||
aliceServer *mockServer
|
||||
aliceChannelLink *channelLink
|
||||
aliceServer *mockServer
|
||||
aliceChannelLink *channelLink
|
||||
aliceOnionDecoder *mockIteratorDecoder
|
||||
|
||||
bobServer *mockServer
|
||||
firstBobChannelLink *channelLink
|
||||
secondBobChannelLink *channelLink
|
||||
bobOnionDecoder *mockIteratorDecoder
|
||||
|
||||
carolServer *mockServer
|
||||
carolChannelLink *channelLink
|
||||
carolServer *mockServer
|
||||
carolChannelLink *channelLink
|
||||
carolOnionDecoder *mockIteratorDecoder
|
||||
|
||||
hopNetwork
|
||||
}
|
||||
@@ -948,15 +951,18 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
|
||||
}
|
||||
|
||||
return &threeHopNetwork{
|
||||
aliceServer: aliceServer,
|
||||
aliceChannelLink: aliceChannelLink.(*channelLink),
|
||||
aliceServer: aliceServer,
|
||||
aliceChannelLink: aliceChannelLink.(*channelLink),
|
||||
aliceOnionDecoder: aliceDecoder,
|
||||
|
||||
bobServer: bobServer,
|
||||
firstBobChannelLink: firstBobChannelLink.(*channelLink),
|
||||
secondBobChannelLink: secondBobChannelLink.(*channelLink),
|
||||
bobOnionDecoder: bobDecoder,
|
||||
|
||||
carolServer: carolServer,
|
||||
carolChannelLink: carolChannelLink.(*channelLink),
|
||||
carolServer: carolServer,
|
||||
carolChannelLink: carolChannelLink.(*channelLink),
|
||||
carolOnionDecoder: carolDecoder,
|
||||
|
||||
hopNetwork: *hopNetwork,
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user