Skip to content

Commit

Permalink
Merge e9be16e into e64493f
Browse files Browse the repository at this point in the history
  • Loading branch information
cfromknecht committed Oct 12, 2019
2 parents e64493f + e9be16e commit dd67b93
Show file tree
Hide file tree
Showing 23 changed files with 1,149 additions and 616 deletions.
2 changes: 1 addition & 1 deletion contractcourt/interfaces.go
Expand Up @@ -21,7 +21,7 @@ type Registry interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
eob []byte) (*invoices.HodlEvent, error)
payload invoices.Payload) (*invoices.HodlEvent, error)

// HodlUnsubscribeAll unsubscribes from all hodl events.
HodlUnsubscribeAll(subscriber chan<- interface{})
Expand Down
2 changes: 1 addition & 1 deletion contractcourt/mock_registry_test.go
Expand Up @@ -24,7 +24,7 @@ type mockRegistry struct {
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
eob []byte) (*invoices.HodlEvent, error) {
payload invoices.Payload) (*invoices.HodlEvent, error) {

r.notifyChan <- notifyExitHopData{
hodlChan: hodlChan,
Expand Down
51 changes: 17 additions & 34 deletions htlcswitch/hop/iterator.go
Expand Up @@ -16,15 +16,13 @@ import (
// interpret the forwarding information encoded within the HTLC packet, and hop
// to encode the forwarding information for the _next_ hop.
type Iterator interface {
// ForwardingInstructions returns the set of fields that detail exactly
// _how_ this hop should forward the HTLC to the next hop.
// Additionally, the information encoded within the returned
// ForwardingInfo is to be used by each hop to authenticate the
// information given to it by the prior hop.
ForwardingInstructions() (ForwardingInfo, error)

// ExtraOnionBlob returns the additional EOB data (if available).
ExtraOnionBlob() []byte
// HopPayload returns the set of fields that detail exactly _how_ this
// hop should forward the HTLC to the next hop. Additionally, the
// information encoded within the returned ForwardingInfo is to be used
// by each hop to authenticate the information given to it by the prior
// hop. The payload will also contain any additional TLV fields provided
// by the sender.
HopPayload() (*Payload, error)

// EncodeNextHop encodes the onion packet destined for the next hop
// into the passed io.Writer.
Expand Down Expand Up @@ -72,50 +70,35 @@ func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
return r.processedPacket.NextPacket.Encode(w)
}

// ForwardingInstructions returns the set of fields that detail exactly _how_
// this hop should forward the HTLC to the next hop. Additionally, the
// information encoded within the returned ForwardingInfo is to be used by each
// hop to authenticate the information given to it by the prior hop.
// HopPayload returns the set of fields that detail exactly _how_ this hop
// should forward the HTLC to the next hop. Additionally, the information
// encoded within the returned ForwardingInfo is to be used by each hop to
// authenticate the information given to it by the prior hop. The payload will
// also contain any additional TLV fields provided by the sender.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ForwardingInstructions() (ForwardingInfo, error) {
func (r *sphinxHopIterator) HopPayload() (*Payload, error) {
switch r.processedPacket.Payload.Type {

// If this is the legacy payload, then we'll extract the information
// directly from the pre-populated ForwardingInstructions field.
case sphinx.PayloadLegacy:
fwdInst := r.processedPacket.ForwardingInstructions
p := NewLegacyPayload(fwdInst)

return p.ForwardingInfo(), nil
return NewLegacyPayload(fwdInst), nil

// Otherwise, if this is the TLV payload, then we'll make a new stream
// to decode only what we need to make routing decisions.
case sphinx.PayloadTLV:
p, err := NewPayloadFromReader(bytes.NewReader(
return NewPayloadFromReader(bytes.NewReader(
r.processedPacket.Payload.Payload,
))
if err != nil {
return ForwardingInfo{}, err
}

return p.ForwardingInfo(), nil

default:
return ForwardingInfo{}, fmt.Errorf("unknown "+
"sphinx payload type: %v",
return nil, fmt.Errorf("unknown sphinx payload type: %v",
r.processedPacket.Payload.Type)
}
}

// ExtraOnionBlob returns the additional EOB data (if available).
func (r *sphinxHopIterator) ExtraOnionBlob() []byte {
if r.processedPacket.Payload.Type == sphinx.PayloadLegacy {
return nil
}

return r.processedPacket.Payload.Payload
}

// ExtractErrorEncrypter decodes and returns the ErrorEncrypter for this hop,
// along with a failure code to signal if the decoding was successful. The
// ErrorEncrypter is used to encrypt errors back to the sender in the event that
Expand Down
3 changes: 2 additions & 1 deletion htlcswitch/hop/iterator_test.go
Expand Up @@ -85,12 +85,13 @@ func TestSphinxHopIteratorForwardingInstructions(t *testing.T) {
for i, testCase := range testCases {
iterator.processedPacket = testCase.sphinxPacket

fwdInfo, err := iterator.ForwardingInstructions()
pld, err := iterator.HopPayload()
if err != nil {
t.Fatalf("#%v: unable to extract forwarding "+
"instructions: %v", i, err)
}

fwdInfo := pld.ForwardingInfo()
if fwdInfo != testCase.expectedFwdInfo {
t.Fatalf("#%v: wrong fwding info: expected %v, got %v",
i, spew.Sdump(testCase.expectedFwdInfo),
Expand Down
26 changes: 26 additions & 0 deletions htlcswitch/hop/payload.go
Expand Up @@ -50,6 +50,10 @@ type Payload struct {
// FwdInfo holds the basic parameters required for HTLC forwarding, e.g.
// amount, cltv, and next hop.
FwdInfo ForwardingInfo

// MPP holds the info provided in an option_mpp record when parsed from
// a TLV onion payload.
MPP *record.MPP
}

// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
Expand All @@ -74,12 +78,14 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
cid uint64
amt uint64
cltv uint32
mpp = new(record.MPP)
)

tlvStream, err := tlv.NewStream(
record.NewAmtToFwdRecord(&amt),
record.NewLockTimeRecord(&cltv),
record.NewNextHopIDRecord(&cid),
mpp.Record(),
)
if err != nil {
return nil, err
Expand All @@ -99,13 +105,18 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
return nil, err
}

if _, ok := parsedTypes[record.MPPOnionType]; !ok {
mpp = nil
}

return &Payload{
FwdInfo: ForwardingInfo{
Network: BitcoinNetwork,
NextHop: nextHop,
AmountToForward: lnwire.MilliSatoshi(amt),
OutgoingCTLV: cltv,
},
MPP: mpp,
}, nil
}

Expand All @@ -127,6 +138,7 @@ func ValidateParsedPayloadTypes(parsedTypes tlv.TypeSet,
_, hasAmt := parsedTypes[record.AmtOnionType]
_, hasLockTime := parsedTypes[record.LockTimeOnionType]
_, hasNextHop := parsedTypes[record.NextHopOnionType]
_, hasMPP := parsedTypes[record.MPPOnionType]

switch {

Expand Down Expand Up @@ -155,7 +167,21 @@ func ValidateParsedPayloadTypes(parsedTypes tlv.TypeSet,
Omitted: false,
FinalHop: true,
}

// Intermediate nodes should never receive MPP fields.
case !isFinalHop && hasMPP:
return ErrInvalidPayload{
Type: record.MPPOnionType,
Omitted: false,
FinalHop: isFinalHop,
}
}

return nil
}

// MultiPath returns the record corresponding the option_mpp parsed from the
// onion payload.
func (h *Payload) MultiPath() *record.MPP {
return h.MPP
}
89 changes: 85 additions & 4 deletions htlcswitch/hop/payload_test.go
Expand Up @@ -6,13 +6,15 @@ import (
"testing"

"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
)

type decodePayloadTest struct {
name string
payload []byte
expErr error
name string
payload []byte
expErr error
shouldHaveMPP bool
}

var decodePayloadTests = []decodePayloadTest{
Expand Down Expand Up @@ -67,6 +69,62 @@ var decodePayloadTests = []decodePayloadTest{
FinalHop: true,
},
},
{
name: "valid intermediate hop",
payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
expErr: nil,
},
{
name: "valid final hop",
payload: []byte{0x02, 0x00, 0x04, 0x00},
expErr: nil,
},
{
name: "intermediate hop with mpp",
payload: []byte{
// amount
0x02, 0x00,
// cltv
0x04, 0x00,
// next hop id
0x06, 0x08,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// mpp
0xff, 0xce, 0xa5, 0x0d, 0x22, 0xf5, 0x13, 0xf5, 0xf5,
0x28,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
},
expErr: hop.ErrInvalidPayload{
Type: record.MPPOnionType,
Omitted: false,
FinalHop: false,
},
},
{
name: "final hop with mpp",
payload: []byte{
// amount
0x02, 0x00,
// cltv
0x04, 0x00,
// mpp
0xff, 0xce, 0xa5, 0x0d, 0x22, 0xf5, 0x13, 0xf5, 0xf5,
0x28,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
},
expErr: nil,
shouldHaveMPP: true,
},
}

// TestDecodeHopPayloadRecordValidation asserts that parsing the payloads in the
Expand All @@ -81,9 +139,32 @@ func TestDecodeHopPayloadRecordValidation(t *testing.T) {
}

func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) {
_, err := hop.NewPayloadFromReader(bytes.NewReader(test.payload))
var (
testTotalMsat = lnwire.MilliSatoshi(8)
testAddr = [32]byte{
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
}
)

p, err := hop.NewPayloadFromReader(bytes.NewReader(test.payload))
if !reflect.DeepEqual(test.expErr, err) {
t.Fatalf("expected error mismatch, want: %v, got: %v",
test.expErr, err)
}

// Assert MPP fields if we expect them.
if test.shouldHaveMPP {
if p.MPP == nil {
t.Fatalf("payload should have MPP record")
}
if p.MPP.TotalMsat() != testTotalMsat {
t.Fatalf("invalid total msat")
}
if p.MPP.PaymentAddr() != testAddr {
t.Fatalf("invalid payment addr")
}
}
}
2 changes: 1 addition & 1 deletion htlcswitch/interfaces.go
Expand Up @@ -27,7 +27,7 @@ type InvoiceDatabase interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
eob []byte) (*invoices.HodlEvent, error)
payload invoices.Payload) (*invoices.HodlEvent, error)

// CancelInvoice attempts to cancel the invoice corresponding to the
// passed payment hash.
Expand Down
11 changes: 6 additions & 5 deletions htlcswitch/link.go
Expand Up @@ -2645,7 +2645,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,

heightNow := l.cfg.Switch.BestHeight()

fwdInfo, err := chanIterator.ForwardingInstructions()
pld, err := chanIterator.HopPayload()
if err != nil {
// If we're unable to process the onion payload, or we
// we received malformed TLV stream, then we should
Expand All @@ -2663,11 +2663,12 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
continue
}

fwdInfo := pld.ForwardingInfo()

switch fwdInfo.NextHop {
case hop.Exit:
updated, err := l.processExitHop(
pd, obfuscator, fwdInfo, heightNow,
chanIterator.ExtraOnionBlob(),
pd, obfuscator, fwdInfo, heightNow, pld,
)
if err != nil {
l.fail(LinkFailureError{code: ErrInternalError},
Expand Down Expand Up @@ -2840,7 +2841,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
// returns a boolean indicating whether the commitment tx needs an update.
func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
obfuscator hop.ErrorEncrypter, fwdInfo hop.ForwardingInfo,
heightNow uint32, eob []byte) (bool, error) {
heightNow uint32, payload invoices.Payload) (bool, error) {

// If hodl.ExitSettle is requested, we will not validate the final hop's
// ADD, nor will we settle the corresponding invoice or respond with the
Expand Down Expand Up @@ -2891,7 +2892,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,

event, err := l.cfg.Registry.NotifyExitHopHtlc(
invoiceHash, pd.Amount, pd.Timeout, int32(heightNow),
circuitKey, l.hodlQueue.ChanIn(), eob,
circuitKey, l.hodlQueue.ChanIn(), payload,
)

switch err {
Expand Down

0 comments on commit dd67b93

Please sign in to comment.