Merge pull request #6913 from bottlepay/relax-failure-len-check
htlcswitch: relax failure message length check
This commit is contained in:
12 files changed
+318
-27
No files matched your search
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
@@ -298,7 +299,7 @@ func deserializeHTLCFailInfo(r io.Reader) (*HTLCFailInfo, error) {
|
|||||||
|
|
||||||
// Read failure.
|
// Read failure.
|
||||||
failureBytes, err := wire.ReadVarBytes(
|
failureBytes, err := wire.ReadVarBytes(
|
||||||
r, 0, lnwire.FailureMessageLength, "failure",
|
r, 0, math.MaxUint16, "failure",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
* Warning messages from peers are now recognized and
|
* Warning messages from peers are now recognized and
|
||||||
[logged](https://github.com/lightningnetwork/lnd/pull/6546) by lnd.
|
[logged](https://github.com/lightningnetwork/lnd/pull/6546) by lnd.
|
||||||
|
|
||||||
|
* Decrypt onion failure messages with a [length greater than 256
|
||||||
|
bytes](https://github.com/lightningnetwork/lnd/pull/6913). This moves LND
|
||||||
|
closer to being spec compliant.
|
||||||
|
|
||||||
## RPC
|
## RPC
|
||||||
|
|
||||||
* The `RegisterConfirmationsNtfn` call of the `chainnotifier` RPC sub-server
|
* The `RegisterConfirmationsNtfn` call of the `chainnotifier` RPC sub-server
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ require (
|
|||||||
github.com/kkdai/bstream v1.0.0
|
github.com/kkdai/bstream v1.0.0
|
||||||
github.com/lightninglabs/neutrino v0.14.2
|
github.com/lightninglabs/neutrino v0.14.2
|
||||||
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display
|
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display
|
||||||
github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5
|
github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1
|
||||||
github.com/lightningnetwork/lnd/cert v1.1.1
|
github.com/lightningnetwork/lnd/cert v1.1.1
|
||||||
github.com/lightningnetwork/lnd/clock v1.1.0
|
github.com/lightningnetwork/lnd/clock v1.1.0
|
||||||
github.com/lightningnetwork/lnd/healthcheck v1.2.2
|
github.com/lightningnetwork/lnd/healthcheck v1.2.2
|
||||||
|
|||||||
@@ -440,8 +440,8 @@ github.com/lightninglabs/neutrino v0.14.2 h1:yrnZUCYMZ5ECtXhgDrzqPq2oX8awoAN2D/c
|
|||||||
github.com/lightninglabs/neutrino v0.14.2/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc=
|
github.com/lightninglabs/neutrino v0.14.2/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc=
|
||||||
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display h1:RZJ8H4ueU/aQ9pFtx5wqsuD3B/DezrewJeVwDKKYY8E=
|
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display h1:RZJ8H4ueU/aQ9pFtx5wqsuD3B/DezrewJeVwDKKYY8E=
|
||||||
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display/go.mod h1:2oKOBU042GKFHrdbgGiKax4xVrFiZu51lhacUZQ9MnE=
|
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display/go.mod h1:2oKOBU042GKFHrdbgGiKax4xVrFiZu51lhacUZQ9MnE=
|
||||||
github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 h1:TkKwqFcQTGYoI+VEqyxA8rxpCin8qDaYX0AfVRinT3k=
|
github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 h1:Wm0g70gkcAu2pGpNZwfWPSVOY21j8IyYsNewwK4OkT4=
|
||||||
github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5/go.mod h1:7dDx73ApjEZA0kcknI799m2O5kkpfg4/gr7N092ojNo=
|
github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1/go.mod h1:7dDx73ApjEZA0kcknI799m2O5kkpfg4/gr7N092ojNo=
|
||||||
github.com/lightningnetwork/lnd/cert v1.1.1 h1:Nsav0RlIDRbOnzz2Yu69SQlK939IKya3Q2S0mDviIN8=
|
github.com/lightningnetwork/lnd/cert v1.1.1 h1:Nsav0RlIDRbOnzz2Yu69SQlK939IKya3Q2S0mDviIN8=
|
||||||
github.com/lightningnetwork/lnd/cert v1.1.1/go.mod h1:1P46svkkd73oSoeI4zjkVKgZNwGq8bkGuPR8z+5vQUs=
|
github.com/lightningnetwork/lnd/cert v1.1.1/go.mod h1:1P46svkkd73oSoeI4zjkVKgZNwGq8bkGuPR8z+5vQUs=
|
||||||
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
|
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package htlcswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/tlv"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestLongFailureMessage tests that longer failure messages can be interpreted
|
||||||
|
// correctly.
|
||||||
|
func TestLongFailureMessage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var testData struct {
|
||||||
|
SessionKey string
|
||||||
|
Path []string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use long 1024-byte test vector from BOLT 04.
|
||||||
|
testDataBytes, err := os.ReadFile("testdata/long_failure_msg.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, json.Unmarshal(testDataBytes, &testData))
|
||||||
|
|
||||||
|
sessionKeyBytes, _ := hex.DecodeString(testData.SessionKey)
|
||||||
|
|
||||||
|
reason, _ := hex.DecodeString(testData.Reason)
|
||||||
|
|
||||||
|
sphinxPath := make([]*btcec.PublicKey, len(testData.Path))
|
||||||
|
for i, sKey := range testData.Path {
|
||||||
|
bKey, err := hex.DecodeString(sKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
key, err := btcec.ParsePubKey(bKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sphinxPath[i] = key
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionKey, _ := btcec.PrivKeyFromBytes(sessionKeyBytes)
|
||||||
|
|
||||||
|
circuit := &sphinx.Circuit{
|
||||||
|
SessionKey: sessionKey,
|
||||||
|
PaymentPath: sphinxPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
errorDecryptor := &SphinxErrorDecrypter{
|
||||||
|
OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the failure message can still be extracted.
|
||||||
|
failure, err := errorDecryptor.DecryptError(reason)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
incorrectDetails, ok := failure.msg.(*lnwire.FailIncorrectDetails)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
var value varBytesRecordProducer
|
||||||
|
|
||||||
|
extraData := incorrectDetails.ExtraOpaqueData()
|
||||||
|
typeMap, err := extraData.ExtractRecords(&value)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, typeMap, 1)
|
||||||
|
|
||||||
|
expectedValue := make([]byte, 300)
|
||||||
|
for i := range expectedValue {
|
||||||
|
expectedValue[i] = 128
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, expectedValue, value.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
type varBytesRecordProducer struct {
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *varBytesRecordProducer) Record() tlv.Record {
|
||||||
|
return tlv.MakePrimitiveRecord(34001, &v.data)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package htlcswitch
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
crand "crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
prand "math/rand"
|
prand "math/rand"
|
||||||
@@ -1850,6 +1851,35 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case *lnwire.UpdateFailHTLC:
|
case *lnwire.UpdateFailHTLC:
|
||||||
|
// Verify that the failure reason is at least 256 bytes plus
|
||||||
|
// overhead.
|
||||||
|
const minimumFailReasonLength = lnwire.FailureMessageLength +
|
||||||
|
2 + 2 + 32
|
||||||
|
|
||||||
|
if len(msg.Reason) < minimumFailReasonLength {
|
||||||
|
// We've received a reason with a non-compliant length.
|
||||||
|
// Older nodes happily relay back these failures that
|
||||||
|
// may originate from a node further downstream.
|
||||||
|
// Therefore we can't just fail the channel.
|
||||||
|
//
|
||||||
|
// We want to be compliant ourselves, so we also can't
|
||||||
|
// pass back the reason unmodified. And we must make
|
||||||
|
// sure that we don't hit the magic length check of 260
|
||||||
|
// bytes in processRemoteSettleFails either.
|
||||||
|
//
|
||||||
|
// Because the reason is unreadable for the payer
|
||||||
|
// anyway, we just replace it by a compliant-length
|
||||||
|
// series of random bytes.
|
||||||
|
msg.Reason = make([]byte, minimumFailReasonLength)
|
||||||
|
_, err := crand.Read(msg.Reason[:])
|
||||||
|
if err != nil {
|
||||||
|
l.log.Errorf("Random generation error: %v", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add fail to the update log.
|
||||||
idx := msg.ID
|
idx := msg.ID
|
||||||
err := l.channel.ReceiveFailHTLC(idx, msg.Reason[:])
|
err := l.channel.ReceiveFailHTLC(idx, msg.Reason[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+108
-3
@@ -2255,11 +2255,14 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
|
|||||||
// With that processed, we'll now generate an HTLC fail (sent by the
|
// With that processed, we'll now generate an HTLC fail (sent by the
|
||||||
// remote peer) to cancel the HTLC we just added. This should return us
|
// remote peer) to cancel the HTLC we just added. This should return us
|
||||||
// back to the bandwidth of the link right before the HTLC was sent.
|
// back to the bandwidth of the link right before the HTLC was sent.
|
||||||
err = bobChannel.FailHTLC(bobIndex, []byte("nop"), nil, nil, nil)
|
reason := make([]byte, 292)
|
||||||
|
copy(reason, []byte("nop"))
|
||||||
|
|
||||||
|
err = bobChannel.FailHTLC(bobIndex, reason, nil, nil, nil)
|
||||||
require.NoError(t, err, "unable to fail htlc")
|
require.NoError(t, err, "unable to fail htlc")
|
||||||
failMsg := &lnwire.UpdateFailHTLC{
|
failMsg := &lnwire.UpdateFailHTLC{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Reason: lnwire.OpaqueReason([]byte("nop")),
|
Reason: lnwire.OpaqueReason(reason),
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceLink.HandleChannelUpdate(failMsg)
|
aliceLink.HandleChannelUpdate(failMsg)
|
||||||
@@ -2431,7 +2434,8 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
|
|||||||
outgoingChanID: addPkt.outgoingChanID,
|
outgoingChanID: addPkt.outgoingChanID,
|
||||||
outgoingHTLCID: addPkt.outgoingHTLCID,
|
outgoingHTLCID: addPkt.outgoingHTLCID,
|
||||||
htlc: &lnwire.UpdateFailHTLC{
|
htlc: &lnwire.UpdateFailHTLC{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
|
Reason: reason,
|
||||||
},
|
},
|
||||||
obfuscator: NewMockObfuscator(),
|
obfuscator: NewMockObfuscator(),
|
||||||
}
|
}
|
||||||
@@ -6606,3 +6610,104 @@ func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) {
|
|||||||
code, rtErr.WireMessage().Code())
|
code, rtErr.WireMessage().Code())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkShortFailureRelay tests that failure reasons that are too
|
||||||
|
// short are replaced by a spec-compliant length failure message and relayed
|
||||||
|
// back.
|
||||||
|
func TestChannelLinkShortFailureRelay(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
defer timeout()()
|
||||||
|
|
||||||
|
const chanAmt = btcutil.SatoshiPerBitcoin * 5
|
||||||
|
|
||||||
|
aliceLink, bobChannel, batchTicker, start, _, err :=
|
||||||
|
newSingleLinkTestHarness(t, chanAmt, 0)
|
||||||
|
require.NoError(t, err, "unable to create link")
|
||||||
|
|
||||||
|
require.NoError(t, start())
|
||||||
|
|
||||||
|
coreLink, ok := aliceLink.(*channelLink)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
mockPeer, ok := coreLink.cfg.Peer.(*mockPeer)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
aliceMsgs := mockPeer.sentMsgs
|
||||||
|
switchChan := make(chan *htlcPacket)
|
||||||
|
|
||||||
|
coreLink.cfg.ForwardPackets = func(linkQuit chan struct{}, _ bool,
|
||||||
|
packets ...*htlcPacket) error {
|
||||||
|
|
||||||
|
for _, p := range packets {
|
||||||
|
switchChan <- p
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := linkTestContext{
|
||||||
|
t: t,
|
||||||
|
aliceLink: aliceLink,
|
||||||
|
aliceMsgs: aliceMsgs,
|
||||||
|
bobChannel: bobChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send and lock in htlc from Alice to Bob.
|
||||||
|
const htlcID = 0
|
||||||
|
|
||||||
|
htlc, _ := generateHtlcAndInvoice(t, htlcID)
|
||||||
|
ctx.sendHtlcAliceToBob(htlcID, htlc)
|
||||||
|
ctx.receiveHtlcAliceToBob()
|
||||||
|
|
||||||
|
batchTicker <- time.Now()
|
||||||
|
|
||||||
|
ctx.receiveCommitSigAliceToBob(1)
|
||||||
|
ctx.sendRevAndAckBobToAlice()
|
||||||
|
|
||||||
|
ctx.sendCommitSigBobToAlice(1)
|
||||||
|
ctx.receiveRevAndAckAliceToBob()
|
||||||
|
|
||||||
|
// Return a short htlc failure from Bob to Alice and lock in.
|
||||||
|
shortReason := make([]byte, 260)
|
||||||
|
|
||||||
|
err = bobChannel.FailHTLC(0, shortReason, nil, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
aliceLink.HandleChannelUpdate(&lnwire.UpdateFailHTLC{
|
||||||
|
ID: htlcID,
|
||||||
|
Reason: shortReason,
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.sendCommitSigBobToAlice(0)
|
||||||
|
ctx.receiveRevAndAckAliceToBob()
|
||||||
|
ctx.receiveCommitSigAliceToBob(0)
|
||||||
|
ctx.sendRevAndAckBobToAlice()
|
||||||
|
|
||||||
|
// Assert that switch gets the fail message.
|
||||||
|
msg := <-switchChan
|
||||||
|
|
||||||
|
htlcFailMsg, ok := msg.htlc.(*lnwire.UpdateFailHTLC)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
// Assert that it is not a converted error.
|
||||||
|
require.False(t, msg.convertedError)
|
||||||
|
|
||||||
|
// Assert that the length is corrected to the spec-compliant length of
|
||||||
|
// 256 bytes plus overhead.
|
||||||
|
require.Len(t, htlcFailMsg.Reason, 292)
|
||||||
|
|
||||||
|
// Stop the link
|
||||||
|
aliceLink.Stop()
|
||||||
|
|
||||||
|
// Check that no unexpected messages were sent.
|
||||||
|
select {
|
||||||
|
case msg := <-aliceMsgs:
|
||||||
|
require.Fail(t, "did not expect message %T", msg)
|
||||||
|
|
||||||
|
case msg := <-switchChan:
|
||||||
|
require.Fail(t, "did not expect switch message %T", msg)
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-2
@@ -419,12 +419,16 @@ func (o *mockObfuscator) Reextract(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fakeHmac = []byte("hmachmachmachmachmachmachmachmac")
|
||||||
|
|
||||||
func (o *mockObfuscator) EncryptFirstHop(failure lnwire.FailureMessage) (
|
func (o *mockObfuscator) EncryptFirstHop(failure lnwire.FailureMessage) (
|
||||||
lnwire.OpaqueReason, error) {
|
lnwire.OpaqueReason, error) {
|
||||||
|
|
||||||
o.failure = failure
|
o.failure = failure
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
b.Write(fakeHmac)
|
||||||
|
|
||||||
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
|
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -436,7 +440,12 @@ func (o *mockObfuscator) IntermediateEncrypt(reason lnwire.OpaqueReason) lnwire.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *mockObfuscator) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
|
func (o *mockObfuscator) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
|
||||||
return reason
|
var b bytes.Buffer
|
||||||
|
b.Write(fakeHmac)
|
||||||
|
|
||||||
|
b.Write(reason)
|
||||||
|
|
||||||
|
return b.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// mockDeobfuscator mock implementation of the failure deobfuscator which
|
// mockDeobfuscator mock implementation of the failure deobfuscator which
|
||||||
@@ -447,7 +456,13 @@ func newMockDeobfuscator() ErrorDecrypter {
|
|||||||
return &mockDeobfuscator{}
|
return &mockDeobfuscator{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *mockDeobfuscator) DecryptError(reason lnwire.OpaqueReason) (*ForwardingError, error) {
|
func (o *mockDeobfuscator) DecryptError(reason lnwire.OpaqueReason) (
|
||||||
|
*ForwardingError, error) {
|
||||||
|
|
||||||
|
if !bytes.Equal(reason[:32], fakeHmac) {
|
||||||
|
return nil, errors.New("fake decryption error")
|
||||||
|
}
|
||||||
|
reason = reason[32:]
|
||||||
|
|
||||||
r := bytes.NewReader(reason)
|
r := bytes.NewReader(reason)
|
||||||
failure, err := lnwire.DecodeFailure(r, 0)
|
failure, err := lnwire.DecodeFailure(r, 0)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package htlcswitch
|
package htlcswitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -23,7 +22,6 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/ticker"
|
"github.com/lightningnetwork/lnd/ticker"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -4088,10 +4086,10 @@ func TestSwitchHoldForward(t *testing.T) {
|
|||||||
expectedFailure := &lnwire.FailInvalidOnionKey{
|
expectedFailure := &lnwire.FailInvalidOnionKey{
|
||||||
OnionSHA256: shaOnionBlob,
|
OnionSHA256: shaOnionBlob,
|
||||||
}
|
}
|
||||||
var b bytes.Buffer
|
|
||||||
require.NoError(t, lnwire.EncodeFailure(&b, expectedFailure, 0))
|
|
||||||
|
|
||||||
assert.Equal(t, lnwire.OpaqueReason(b.Bytes()), failPacket.Reason)
|
fwdErr, err := newMockDeobfuscator().DecryptError(failPacket.Reason)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedFailure, fwdErr.WireMessage())
|
||||||
|
|
||||||
assertNumCircuits(t, c.s, 0, 0)
|
assertNumCircuits(t, c.s, 0, 0)
|
||||||
|
|
||||||
@@ -5515,10 +5513,13 @@ func testSwitchAliasInterceptFail(t *testing.T, zeroConf bool) {
|
|||||||
failHtlc, ok := failPacket.htlc.(*lnwire.UpdateFailHTLC)
|
failHtlc, ok := failPacket.htlc.(*lnwire.UpdateFailHTLC)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
r := bytes.NewReader(failHtlc.Reason)
|
fwdErr, err := newMockDeobfuscator().DecryptError(
|
||||||
failure, err := lnwire.DecodeFailure(r, 0)
|
failHtlc.Reason,
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
failure := fwdErr.WireMessage()
|
||||||
|
|
||||||
failureMsg, ok := failure.(*lnwire.FailTemporaryChannelFailure)
|
failureMsg, ok := failure.(*lnwire.FailTemporaryChannelFailure)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"sessionKey": "4141414141414141414141414141414141414141414141414141414141414141",
|
||||||
|
"path": [
|
||||||
|
"02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619",
|
||||||
|
"0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c",
|
||||||
|
"027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007",
|
||||||
|
"032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991",
|
||||||
|
"02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"
|
||||||
|
],
|
||||||
|
"reason": "2dd2f49c1f5af0fcad371d96e8cddbdcd5096dc309c1d4e110f955926506b3c03b44c192896f45610741c85ed4074212537e0c118d472ff3a559ae244acd9d783c65977765c5d4e00b723d00f12475aafaafff7b31c1be5a589e6e25f8da2959107206dd42bbcb43438129ce6cce2b6b4ae63edc76b876136ca5ea6cd1c6a04ca86eca143d15e53ccdc9e23953e49dc2f87bb11e5238cd6536e57387225b8fff3bf5f3e686fd08458ffe0211b87d64770db9353500af9b122828a006da754cf979738b4374e146ea79dd93656170b89c98c5f2299d6e9c0410c826c721950c780486cd6d5b7130380d7eaff994a8503a8fef3270ce94889fe996da66ed121741987010f785494415ca991b2e8b39ef2df6bde98efd2aec7d251b2772485194c8368451ad49c2354f9d30d95367bde316fec6cbdddc7dc0d25e99d3075e13d3de0822669861dafcd29de74eac48b64411987285491f98d78584d0c2a163b7221ea796f9e8671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4"
|
||||||
|
}
|
||||||
+47
-10
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
@@ -344,6 +345,9 @@ type FailIncorrectDetails struct {
|
|||||||
|
|
||||||
// height is the block height when the htlc was received.
|
// height is the block height when the htlc was received.
|
||||||
height uint32
|
height uint32
|
||||||
|
|
||||||
|
// extraOpaqueData contains additional failure message tlv data.
|
||||||
|
extraOpaqueData ExtraOpaqueData
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails
|
// NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails
|
||||||
@@ -352,8 +356,9 @@ func NewFailIncorrectDetails(amt MilliSatoshi,
|
|||||||
height uint32) *FailIncorrectDetails {
|
height uint32) *FailIncorrectDetails {
|
||||||
|
|
||||||
return &FailIncorrectDetails{
|
return &FailIncorrectDetails{
|
||||||
amount: amt,
|
amount: amt,
|
||||||
height: height,
|
height: height,
|
||||||
|
extraOpaqueData: []byte{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,6 +372,11 @@ func (f *FailIncorrectDetails) Height() uint32 {
|
|||||||
return f.height
|
return f.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraOpaqueData returns additional failure message tlv data.
|
||||||
|
func (f *FailIncorrectDetails) ExtraOpaqueData() ExtraOpaqueData {
|
||||||
|
return f.extraOpaqueData
|
||||||
|
}
|
||||||
|
|
||||||
// Code returns the failure unique code.
|
// Code returns the failure unique code.
|
||||||
//
|
//
|
||||||
// NOTE: Part of the FailureMessage interface.
|
// NOTE: Part of the FailureMessage interface.
|
||||||
@@ -412,7 +422,7 @@ func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return f.extraOpaqueData.Decode(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode writes the failure in bytes stream.
|
// Encode writes the failure in bytes stream.
|
||||||
@@ -423,7 +433,11 @@ func (f *FailIncorrectDetails) Encode(w *bytes.Buffer, pver uint32) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return WriteUint32(w, f.height)
|
if err := WriteUint32(w, f.height); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.extraOpaqueData.Encode(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final
|
// FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final
|
||||||
@@ -1222,18 +1236,41 @@ func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
|||||||
// is a 2 byte length followed by the payload itself.
|
// is a 2 byte length followed by the payload itself.
|
||||||
var failureLength uint16
|
var failureLength uint16
|
||||||
if err := ReadElement(r, &failureLength); err != nil {
|
if err := ReadElement(r, &failureLength); err != nil {
|
||||||
return nil, fmt.Errorf("unable to read error len: %v", err)
|
return nil, fmt.Errorf("unable to read failure len: %w", err)
|
||||||
}
|
|
||||||
if failureLength > FailureMessageLength {
|
|
||||||
return nil, fmt.Errorf("failure message is too "+
|
|
||||||
"long: %v", failureLength)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
failureData := make([]byte, failureLength)
|
failureData := make([]byte, failureLength)
|
||||||
if _, err := io.ReadFull(r, failureData); err != nil {
|
if _, err := io.ReadFull(r, failureData); err != nil {
|
||||||
return nil, fmt.Errorf("unable to full read payload of "+
|
return nil, fmt.Errorf("unable to full read payload of "+
|
||||||
"%v: %v", failureLength, err)
|
"%v: %w", failureLength, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the padding.
|
||||||
|
var padLength uint16
|
||||||
|
if err := ReadElement(r, &padLength); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read pad len: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.CopyN(ioutil.Discard, r, int64(padLength)); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read padding %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we are at the end of the stream now.
|
||||||
|
scratch := make([]byte, 1)
|
||||||
|
_, err := r.Read(scratch)
|
||||||
|
if err != io.EOF {
|
||||||
|
return nil, fmt.Errorf("unexpected failure bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the total length. Convert to 32 bits to prevent overflow.
|
||||||
|
totalLength := uint32(padLength) + uint32(failureLength)
|
||||||
|
if totalLength < FailureMessageLength {
|
||||||
|
return nil, fmt.Errorf("failure message too short: "+
|
||||||
|
"msg=%v, pad=%v, total=%v",
|
||||||
|
failureLength, padLength, totalLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the failure message.
|
||||||
dataReader := bytes.NewReader(failureData)
|
dataReader := bytes.NewReader(failureData)
|
||||||
|
|
||||||
return DecodeFailureMessage(dataReader, pver)
|
return DecodeFailureMessage(dataReader, pver)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -245,7 +246,7 @@ func deserializeResult(k, v []byte) (*paymentResult, error) {
|
|||||||
|
|
||||||
// Read failure.
|
// Read failure.
|
||||||
failureBytes, err := wire.ReadVarBytes(
|
failureBytes, err := wire.ReadVarBytes(
|
||||||
r, 0, lnwire.FailureMessageLength, "failure",
|
r, 0, math.MaxUint16, "failure",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in new issue
Block a user