Merge pull request #3466 from cfromknecht/htlcswitch-hop

htlcswitch/hop: new package for hop payload parsing/encapsulation
This commit is contained in:
Joost Jager 2019-09-05 12:31:23 +02:00 committed by GitHub
commit 6638e1ddde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 217 additions and 156 deletions

@ -9,6 +9,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
) )
@ -311,7 +312,7 @@ func (cm *circuitMap) restoreMemState() error {
// documented case of stray keystones emerges for // documented case of stray keystones emerges for
// forwarded payments, this check should be removed, but // forwarded payments, this check should be removed, but
// with extreme caution. // with extreme caution.
if strayKeystone.OutKey.ChanID != sourceHop { if strayKeystone.OutKey.ChanID != hop.Source {
continue continue
} }
@ -396,9 +397,9 @@ func (cm *circuitMap) trimAllOpenCircuits() error {
// First, skip any channels that have not been assigned their // First, skip any channels that have not been assigned their
// final channel identifier, otherwise we would try to trim // final channel identifier, otherwise we would try to trim
// htlcs belonging to the all-zero, sourceHop ID. // htlcs belonging to the all-zero, hop.Source ID.
chanID := activeChannel.ShortChanID() chanID := activeChannel.ShortChanID()
if chanID == sourceHop { if chanID == hop.Source {
continue continue
} }

@ -0,0 +1,29 @@
package hop
import (
"github.com/lightningnetwork/lnd/lnwire"
)
// ForwardingInfo contains all the information that is necessary to forward and
// incoming HTLC to the next hop encoded within a valid HopIterator instance.
// Forwarding links are to use this information to authenticate the information
// received within the incoming HTLC, to ensure that the prior hop didn't
// tamper with the end-to-end routing information at all.
type ForwardingInfo struct {
// Network is the target blockchain network that the HTLC will travel
// over next.
Network Network
// NextHop is the channel ID of the next hop. The received HTLC should
// be forwarded to this particular channel in order to continue the
// end-to-end route.
NextHop lnwire.ShortChannelID
// AmountToForward is the amount of milli-satoshis that the receiving
// node should forward to the next hop.
AmountToForward lnwire.MilliSatoshi
// OutgoingCTLV is the specified value of the CTLV timelock to be used
// in the outgoing HTLC.
OutgoingCTLV uint32
}

28
htlcswitch/hop/network.go Normal file

@ -0,0 +1,28 @@
package hop
// Network indicates the blockchain network that is intended to be the next hop
// for a forwarded HTLC. The existence of this field within the ForwardingInfo
// struct enables the ability for HTLC to cross chain-boundaries at will.
type Network uint8
const (
// BitcoinNetwork denotes that an HTLC is to be forwarded along the
// Bitcoin link with the specified short channel ID.
BitcoinNetwork Network = iota
// LitecoinNetwork denotes that an HTLC is to be forwarded along the
// Litecoin link with the specified short channel ID.
LitecoinNetwork
)
// String returns the string representation of the target Network.
func (c Network) String() string {
switch c {
case BitcoinNetwork:
return "Bitcoin"
case LitecoinNetwork:
return "Litecoin"
default:
return "Kekcoin"
}
}

75
htlcswitch/hop/payload.go Normal file

@ -0,0 +1,75 @@
package hop
import (
"encoding/binary"
"io"
"github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
// Payload encapsulates all information delivered to a hop in an onion payload.
// A Hop can represent either a TLV or legacy payload. The primary forwarding
// instruction can be accessed via ForwardingInfo, and additional records can be
// accessed by other member functions.
type Payload struct {
// FwdInfo holds the basic parameters required for HTLC forwarding, e.g.
// amount, cltv, and next hop.
FwdInfo ForwardingInfo
}
// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
// parameters provided by leegacy onion payloads.
func NewLegacyPayload(f *sphinx.HopData) *Payload {
nextHop := binary.BigEndian.Uint64(f.NextAddress[:])
return &Payload{
FwdInfo: ForwardingInfo{
Network: BitcoinNetwork,
NextHop: lnwire.NewShortChanIDFromInt(nextHop),
AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount),
OutgoingCTLV: f.OutgoingCltv,
},
}
}
// NewPayloadFromReader builds a new Hop from the passed io.Reader. The reader
// should correspond to the bytes encapsulated in a TLV onion payload.
func NewPayloadFromReader(r io.Reader) (*Payload, error) {
var (
cid uint64
amt uint64
cltv uint32
)
tlvStream, err := tlv.NewStream(
record.NewAmtToFwdRecord(&amt),
record.NewLockTimeRecord(&cltv),
record.NewNextHopIDRecord(&cid),
)
if err != nil {
return nil, err
}
err = tlvStream.Decode(r)
if err != nil {
return nil, err
}
return &Payload{
FwdInfo: ForwardingInfo{
Network: BitcoinNetwork,
NextHop: lnwire.NewShortChanIDFromInt(cid),
AmountToForward: lnwire.MilliSatoshi(amt),
OutgoingCTLV: cltv,
},
}, nil
}
// ForwardingInfo returns the basic parameters required for HTLC forwarding,
// e.g. amount, cltv, and next hop.
func (h *Payload) ForwardingInfo() ForwardingInfo {
return h.FwdInfo
}

13
htlcswitch/hop/type.go Normal file

@ -0,0 +1,13 @@
package hop
import "github.com/lightningnetwork/lnd/lnwire"
var (
// Exit is a special "hop" denoting that an incoming HTLC is meant to
// pay finally to the receiving node.
Exit lnwire.ShortChannelID
// Source is a sentinel "hop" denoting that an incoming HTLC is
// initiated by our own switch.
Source lnwire.ShortChannelID
)

@ -2,82 +2,15 @@ package htlcswitch
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
) )
// NetworkHop indicates the blockchain network that is intended to be the next
// hop for a forwarded HTLC. The existence of this field within the
// ForwardingInfo struct enables the ability for HTLC to cross chain-boundaries
// at will.
type NetworkHop uint8
const (
// BitcoinHop denotes that an HTLC is to be forwarded along the Bitcoin
// link with the specified short channel ID.
BitcoinHop NetworkHop = iota
// LitecoinHop denotes that an HTLC is to be forwarded along the
// Litecoin link with the specified short channel ID.
LitecoinHop
)
// String returns the string representation of the target NetworkHop.
func (c NetworkHop) String() string {
switch c {
case BitcoinHop:
return "Bitcoin"
case LitecoinHop:
return "Litecoin"
default:
return "Kekcoin"
}
}
var (
// exitHop is a special "hop" which denotes that an incoming HTLC is
// meant to pay finally to the receiving node.
exitHop lnwire.ShortChannelID
// sourceHop is a sentinel value denoting that an incoming HTLC is
// initiated by our own switch.
sourceHop lnwire.ShortChannelID
)
// ForwardingInfo contains all the information that is necessary to forward and
// incoming HTLC to the next hop encoded within a valid HopIterator instance.
// Forwarding links are to use this information to authenticate the information
// received within the incoming HTLC, to ensure that the prior hop didn't
// tamper with the end-to-end routing information at all.
type ForwardingInfo struct {
// Network is the target blockchain network that the HTLC will travel
// over next.
Network NetworkHop
// NextHop is the channel ID of the next hop. The received HTLC should
// be forwarded to this particular channel in order to continue the
// end-to-end route.
NextHop lnwire.ShortChannelID
// AmountToForward is the amount of milli-satoshis that the receiving
// node should forward to the next hop.
AmountToForward lnwire.MilliSatoshi
// OutgoingCTLV is the specified value of the CTLV timelock to be used
// in the outgoing HTLC.
OutgoingCTLV uint32
// TODO(roasbeef): modify sphinx logic to not just discard the
// remaining bytes, instead should include the rest as excess
}
// HopIterator is an interface that abstracts away the routing information // HopIterator is an interface that abstracts away the routing information
// included in HTLC's which includes the entirety of the payment path of an // included in HTLC's which includes the entirety of the payment path of an
// HTLC. This interface provides two basic method which carry out: how to // HTLC. This interface provides two basic method which carry out: how to
@ -89,7 +22,7 @@ type HopIterator interface {
// Additionally, the information encoded within the returned // Additionally, the information encoded within the returned
// ForwardingInfo is to be used by each hop to authenticate the // ForwardingInfo is to be used by each hop to authenticate the
// information given to it by the prior hop. // information given to it by the prior hop.
ForwardingInstructions() (ForwardingInfo, error) ForwardingInstructions() (hop.ForwardingInfo, error)
// ExtraOnionBlob returns the additional EOB data (if available). // ExtraOnionBlob returns the additional EOB data (if available).
ExtraOnionBlob() []byte ExtraOnionBlob() []byte
@ -146,64 +79,35 @@ func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
// hop to authenticate the information given to it by the prior hop. // hop to authenticate the information given to it by the prior hop.
// //
// NOTE: Part of the HopIterator interface. // NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ForwardingInstructions() (ForwardingInfo, error) { func (r *sphinxHopIterator) ForwardingInstructions() (
var ( hop.ForwardingInfo, error) {
nextHop lnwire.ShortChannelID
amt uint64
cltv uint32
)
switch r.processedPacket.Payload.Type { switch r.processedPacket.Payload.Type {
// If this is the legacy payload, then we'll extract the information // If this is the legacy payload, then we'll extract the information
// directly from the pre-populated ForwardingInstructions field. // directly from the pre-populated ForwardingInstructions field.
case sphinx.PayloadLegacy: case sphinx.PayloadLegacy:
fwdInst := r.processedPacket.ForwardingInstructions fwdInst := r.processedPacket.ForwardingInstructions
p := hop.NewLegacyPayload(fwdInst)
switch r.processedPacket.Action { return p.ForwardingInfo(), nil
case sphinx.ExitNode:
nextHop = exitHop
case sphinx.MoreHops:
s := binary.BigEndian.Uint64(fwdInst.NextAddress[:])
nextHop = lnwire.NewShortChanIDFromInt(s)
}
amt = fwdInst.ForwardAmount
cltv = fwdInst.OutgoingCltv
// Otherwise, if this is the TLV payload, then we'll make a new stream // Otherwise, if this is the TLV payload, then we'll make a new stream
// to decode only what we need to make routing decisions. // to decode only what we need to make routing decisions.
case sphinx.PayloadTLV: case sphinx.PayloadTLV:
var cid uint64 p, err := hop.NewPayloadFromReader(bytes.NewReader(
tlvStream, err := tlv.NewStream(
record.NewAmtToFwdRecord(&amt),
record.NewLockTimeRecord(&cltv),
record.NewNextHopIDRecord(&cid),
)
if err != nil {
return ForwardingInfo{}, err
}
err = tlvStream.Decode(bytes.NewReader(
r.processedPacket.Payload.Payload, r.processedPacket.Payload.Payload,
)) ))
if err != nil { if err != nil {
return ForwardingInfo{}, err return hop.ForwardingInfo{}, err
} }
nextHop = lnwire.NewShortChanIDFromInt(cid) return p.ForwardingInfo(), nil
default: default:
return ForwardingInfo{}, fmt.Errorf("unknown sphinx payload "+ return hop.ForwardingInfo{}, fmt.Errorf("unknown "+
"type: %v", r.processedPacket.Payload.Type) "sphinx payload type: %v",
r.processedPacket.Payload.Type)
} }
return ForwardingInfo{
Network: BitcoinHop,
NextHop: nextHop,
AmountToForward: lnwire.MilliSatoshi(amt),
OutgoingCTLV: cltv,
}, nil
} }
// ExtraOnionBlob returns the additional EOB data (if available). // ExtraOnionBlob returns the additional EOB data (if available).

@ -7,6 +7,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
sphinx "github.com/lightningnetwork/lightning-onion" sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/tlv"
@ -29,7 +30,7 @@ func TestSphinxHopIteratorForwardingInstructions(t *testing.T) {
// Next, we'll make the hop forwarding information that we should // Next, we'll make the hop forwarding information that we should
// extract each type, no matter the payload type. // extract each type, no matter the payload type.
nextAddrInt := binary.BigEndian.Uint64(hopData.NextAddress[:]) nextAddrInt := binary.BigEndian.Uint64(hopData.NextAddress[:])
expectedFwdInfo := ForwardingInfo{ expectedFwdInfo := hop.ForwardingInfo{
NextHop: lnwire.NewShortChanIDFromInt(nextAddrInt), NextHop: lnwire.NewShortChanIDFromInt(nextAddrInt),
AmountToForward: lnwire.MilliSatoshi(hopData.ForwardAmount), AmountToForward: lnwire.MilliSatoshi(hopData.ForwardAmount),
OutgoingCTLV: hopData.OutgoingCltv, OutgoingCTLV: hopData.OutgoingCltv,
@ -53,7 +54,7 @@ func TestSphinxHopIteratorForwardingInstructions(t *testing.T) {
var testCases = []struct { var testCases = []struct {
sphinxPacket *sphinx.ProcessedPacket sphinxPacket *sphinx.ProcessedPacket
expectedFwdInfo ForwardingInfo expectedFwdInfo hop.ForwardingInfo
}{ }{
// A regular legacy payload that signals more hops. // A regular legacy payload that signals more hops.
{ {

@ -16,6 +16,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/htlcswitch/hodl" "github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
@ -436,8 +437,8 @@ func (l *channelLink) Start() error {
// off point, since all indexes below that are committed. This action // off point, since all indexes below that are committed. This action
// is only performed if the link's final short channel ID has been // is only performed if the link's final short channel ID has been
// assigned, otherwise we would try to trim the htlcs belonging to the // assigned, otherwise we would try to trim the htlcs belonging to the
// all-zero, sourceHop ID. // all-zero, hop.Source ID.
if l.ShortChanID() != sourceHop { if l.ShortChanID() != hop.Source {
localHtlcIndex, err := l.channel.NextLocalHtlcIndex() localHtlcIndex, err := l.channel.NextLocalHtlcIndex()
if err != nil { if err != nil {
return fmt.Errorf("unable to retrieve next local "+ return fmt.Errorf("unable to retrieve next local "+
@ -535,7 +536,7 @@ func (l *channelLink) WaitForShutdown() {
// the all-zero source ID, meaning that the channel has had its ID finalized. // the all-zero source ID, meaning that the channel has had its ID finalized.
func (l *channelLink) EligibleToForward() bool { func (l *channelLink) EligibleToForward() bool {
return l.channel.RemoteNextRevocation() != nil && return l.channel.RemoteNextRevocation() != nil &&
l.ShortChanID() != sourceHop l.ShortChanID() != hop.Source
} }
// sampleNetworkFee samples the current fee rate on the network to get into the // sampleNetworkFee samples the current fee rate on the network to get into the
@ -961,7 +962,7 @@ func (l *channelLink) htlcManager() {
// only attempt to resolve packages if our short chan id indicates that // only attempt to resolve packages if our short chan id indicates that
// the channel is not pending, otherwise we should have no htlcs to // the channel is not pending, otherwise we should have no htlcs to
// reforward. // reforward.
if l.ShortChanID() != sourceHop { if l.ShortChanID() != hop.Source {
if err := l.resolveFwdPkgs(); err != nil { if err := l.resolveFwdPkgs(); err != nil {
l.fail(LinkFailureError{code: ErrInternalError}, l.fail(LinkFailureError{code: ErrInternalError},
"unable to resolve fwd pkgs: %v", err) "unable to resolve fwd pkgs: %v", err)
@ -2075,7 +2076,7 @@ func (l *channelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
if err != nil { if err != nil {
l.errorf("unable to refresh short_chan_id for chan_id=%v: %v", l.errorf("unable to refresh short_chan_id for chan_id=%v: %v",
chanID, err) chanID, err)
return sourceHop, err return hop.Source, err
} }
sid := l.channel.ShortChanID() sid := l.channel.ShortChanID()
@ -2674,7 +2675,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
} }
switch fwdInfo.NextHop { switch fwdInfo.NextHop {
case exitHop: case hop.Exit:
updated, err := l.processExitHop( updated, err := l.processExitHop(
pd, obfuscator, fwdInfo, heightNow, pd, obfuscator, fwdInfo, heightNow,
chanIterator.ExtraOnionBlob(), chanIterator.ExtraOnionBlob(),
@ -2849,7 +2850,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
// processExitHop handles an htlc for which this link is the exit hop. It // processExitHop handles an htlc for which this link is the exit hop. It
// returns a boolean indicating whether the commitment tx needs an update. // returns a boolean indicating whether the commitment tx needs an update.
func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
obfuscator ErrorEncrypter, fwdInfo ForwardingInfo, obfuscator ErrorEncrypter, fwdInfo hop.ForwardingInfo,
heightNow uint32, eob []byte) (bool, error) { heightNow uint32, eob []byte) (bool, error) {
// If hodl.ExitSettle is requested, we will not validate the final hop's // If hodl.ExitSettle is requested, we will not validate the final hop's

@ -26,6 +26,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/htlcswitch/hodl" "github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
@ -1936,7 +1937,7 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
} }
addPkt := htlcPacket{ addPkt := htlcPacket{
htlc: htlc, htlc: htlc,
incomingChanID: sourceHop, incomingChanID: hop.Source,
incomingHTLCID: 0, incomingHTLCID: 0,
obfuscator: NewMockObfuscator(), obfuscator: NewMockObfuscator(),
} }
@ -2016,7 +2017,7 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
} }
addPkt = htlcPacket{ addPkt = htlcPacket{
htlc: htlc, htlc: htlc,
incomingChanID: sourceHop, incomingChanID: hop.Source,
incomingHTLCID: 1, incomingHTLCID: 1,
obfuscator: NewMockObfuscator(), obfuscator: NewMockObfuscator(),
} }
@ -2534,7 +2535,7 @@ func genAddsAndCircuits(numHtlcs int, htlc *lnwire.UpdateAddHTLC) (
for i := 0; i < numHtlcs; i++ { for i := 0; i < numHtlcs; i++ {
addPkt := htlcPacket{ addPkt := htlcPacket{
htlc: htlc, htlc: htlc,
incomingChanID: sourceHop, incomingChanID: hop.Source,
incomingHTLCID: uint64(i), incomingHTLCID: uint64(i),
obfuscator: NewMockObfuscator(), obfuscator: NewMockObfuscator(),
} }
@ -4307,10 +4308,10 @@ func generateHtlcAndInvoice(t *testing.T,
htlcAmt := lnwire.NewMSatFromSatoshis(10000) htlcAmt := lnwire.NewMSatFromSatoshis(10000)
htlcExpiry := testStartingHeight + testInvoiceCltvExpiry htlcExpiry := testStartingHeight + testInvoiceCltvExpiry
hops := []ForwardingInfo{ hops := []hop.ForwardingInfo{
{ {
Network: BitcoinHop, Network: hop.BitcoinNetwork,
NextHop: exitHop, NextHop: hop.Exit,
AmountToForward: htlcAmt, AmountToForward: htlcAmt,
OutgoingCTLV: uint32(htlcExpiry), OutgoingCTLV: uint32(htlcExpiry),
}, },

@ -23,6 +23,7 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
@ -268,14 +269,16 @@ func (s *mockServer) QuitSignal() <-chan struct{} {
// mockHopIterator represents the test version of hop iterator which instead // mockHopIterator represents the test version of hop iterator which instead
// of encrypting the path in onion blob just stores the path as a list of hops. // of encrypting the path in onion blob just stores the path as a list of hops.
type mockHopIterator struct { type mockHopIterator struct {
hops []ForwardingInfo hops []hop.ForwardingInfo
} }
func newMockHopIterator(hops ...ForwardingInfo) HopIterator { func newMockHopIterator(hops ...hop.ForwardingInfo) HopIterator {
return &mockHopIterator{hops: hops} return &mockHopIterator{hops: hops}
} }
func (r *mockHopIterator) ForwardingInstructions() (ForwardingInfo, error) { func (r *mockHopIterator) ForwardingInstructions() (
hop.ForwardingInfo, error) {
h := r.hops[0] h := r.hops[0]
r.hops = r.hops[1:] r.hops = r.hops[1:]
return h, nil return h, nil
@ -300,7 +303,7 @@ func (r *mockHopIterator) EncodeNextHop(w io.Writer) error {
} }
for _, hop := range r.hops { for _, hop := range r.hops {
if err := hop.encode(w); err != nil { if err := encodeFwdInfo(w, &hop); err != nil {
return err return err
} }
} }
@ -308,7 +311,7 @@ func (r *mockHopIterator) EncodeNextHop(w io.Writer) error {
return nil return nil
} }
func (f *ForwardingInfo) encode(w io.Writer) error { func encodeFwdInfo(w io.Writer, f *hop.ForwardingInfo) error {
if _, err := w.Write([]byte{byte(f.Network)}); err != nil { if _, err := w.Write([]byte{byte(f.Network)}); err != nil {
return err return err
} }
@ -429,10 +432,10 @@ func (p *mockIteratorDecoder) DecodeHopIterator(r io.Reader, rHash []byte,
} }
hopLength := binary.BigEndian.Uint32(b[:]) hopLength := binary.BigEndian.Uint32(b[:])
hops := make([]ForwardingInfo, hopLength) hops := make([]hop.ForwardingInfo, hopLength)
for i := uint32(0); i < hopLength; i++ { for i := uint32(0); i < hopLength; i++ {
f := &ForwardingInfo{} f := &hop.ForwardingInfo{}
if err := f.decode(r); err != nil { if err := decodeFwdInfo(r, f); err != nil {
return nil, lnwire.CodeTemporaryChannelFailure return nil, lnwire.CodeTemporaryChannelFailure
} }
@ -480,12 +483,12 @@ func (p *mockIteratorDecoder) DecodeHopIterators(id []byte,
return resps, nil return resps, nil
} }
func (f *ForwardingInfo) decode(r io.Reader) error { func decodeFwdInfo(r io.Reader, f *hop.ForwardingInfo) error {
var net [1]byte var net [1]byte
if _, err := r.Read(net[:]); err != nil { if _, err := r.Read(net[:]); err != nil {
return err return err
} }
f.Network = NetworkHop(net[0]) f.Network = hop.Network(net[0])
if err := binary.Read(r, binary.BigEndian, &f.NextHop); err != nil { if err := binary.Read(r, binary.BigEndian, &f.NextHop); err != nil {
return err return err

@ -16,6 +16,7 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@ -354,7 +355,7 @@ func (s *Switch) GetPaymentResult(paymentID uint64, paymentHash lntypes.Hash,
nChan <-chan *networkResult nChan <-chan *networkResult
err error err error
outKey = CircuitKey{ outKey = CircuitKey{
ChanID: sourceHop, ChanID: hop.Source,
HtlcID: paymentID, HtlcID: paymentID,
} }
) )
@ -428,7 +429,7 @@ func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, paymentID uint64,
// this stage it means that packet haven't left boundaries of our // this stage it means that packet haven't left boundaries of our
// system and something wrong happened. // system and something wrong happened.
packet := &htlcPacket{ packet := &htlcPacket{
incomingChanID: sourceHop, incomingChanID: hop.Source,
incomingHTLCID: paymentID, incomingHTLCID: paymentID,
outgoingChanID: firstHop, outgoingChanID: firstHop,
htlc: htlc, htlc: htlc,
@ -513,7 +514,7 @@ func (s *Switch) forward(packet *htlcPacket) error {
return ErrDuplicateAdd return ErrDuplicateAdd
case len(actions.Fails) == 1: case len(actions.Fails) == 1:
if packet.incomingChanID == sourceHop { if packet.incomingChanID == hop.Source {
return err return err
} }
@ -1031,14 +1032,14 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
case *lnwire.UpdateAddHTLC: case *lnwire.UpdateAddHTLC:
// Check if the node is set to reject all onward HTLCs and also make // Check if the node is set to reject all onward HTLCs and also make
// sure that HTLC is not from the source node. // sure that HTLC is not from the source node.
if s.cfg.RejectHTLC && packet.incomingChanID != sourceHop { if s.cfg.RejectHTLC && packet.incomingChanID != hop.Source {
failure := &lnwire.FailChannelDisabled{} failure := &lnwire.FailChannelDisabled{}
addErr := fmt.Errorf("unable to forward any htlcs") addErr := fmt.Errorf("unable to forward any htlcs")
return s.failAddPacket(packet, failure, addErr) return s.failAddPacket(packet, failure, addErr)
} }
if packet.incomingChanID == sourceHop { if packet.incomingChanID == hop.Source {
// A blank incomingChanID indicates that this is // A blank incomingChanID indicates that this is
// a pending user-initiated payment. // a pending user-initiated payment.
return s.handleLocalDispatch(packet) return s.handleLocalDispatch(packet)
@ -1080,7 +1081,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
// If the link doesn't yet have a source chan ID, then // If the link doesn't yet have a source chan ID, then
// we'll skip it as well. // we'll skip it as well.
case link.ShortChanID() == sourceHop: case link.ShortChanID() == hop.Source:
continue continue
} }
@ -1222,7 +1223,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
// //
// TODO(roasbeef): only do this once link actually // TODO(roasbeef): only do this once link actually
// fully settles? // fully settles?
localHTLC := packet.incomingChanID == sourceHop localHTLC := packet.incomingChanID == hop.Source
if !localHTLC { if !localHTLC {
s.fwdEventMtx.Lock() s.fwdEventMtx.Lock()
s.pendingFwdingEvents = append( s.pendingFwdingEvents = append(
@ -1241,7 +1242,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
// A blank IncomingChanID in a circuit indicates that it is a pending // A blank IncomingChanID in a circuit indicates that it is a pending
// user-initiated payment. // user-initiated payment.
if packet.incomingChanID == sourceHop { if packet.incomingChanID == hop.Source {
return s.handleLocalDispatch(packet) return s.handleLocalDispatch(packet)
} }
@ -1798,7 +1799,7 @@ func (s *Switch) reforwardResponses() error {
shortChanID := openChannel.ShortChanID() shortChanID := openChannel.ShortChanID()
// Locally-initiated payments never need reforwarding. // Locally-initiated payments never need reforwarding.
if shortChanID == sourceHop { if shortChanID == hop.Source {
continue continue
} }
@ -1994,7 +1995,7 @@ func (s *Switch) AddLink(link ChannelLink) error {
} }
shortChanID := link.ShortChanID() shortChanID := link.ShortChanID()
if shortChanID == sourceHop { if shortChanID == hop.Source {
log.Infof("Adding pending link chan_id=%v, short_chan_id=%v", log.Infof("Adding pending link chan_id=%v, short_chan_id=%v",
chanID, shortChanID) chanID, shortChanID)
@ -2157,7 +2158,7 @@ func (s *Switch) UpdateShortChanID(chanID lnwire.ChannelID) error {
} }
// Reject any blank short channel ids. // Reject any blank short channel ids.
if shortChanID == sourceHop { if shortChanID == hop.Source {
return fmt.Errorf("refusing trivial short_chan_id for chan_id=%v"+ return fmt.Errorf("refusing trivial short_chan_id for chan_id=%v"+
"live link", chanID) "live link", chanID)
} }

@ -25,6 +25,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
@ -596,7 +597,9 @@ func generatePayment(invoiceAmt, htlcAmt lnwire.MilliSatoshi, timelock uint32,
} }
// generateRoute generates the path blob by given array of peers. // generateRoute generates the path blob by given array of peers.
func generateRoute(hops ...ForwardingInfo) ([lnwire.OnionPacketSize]byte, error) { func generateRoute(hops ...hop.ForwardingInfo) (
[lnwire.OnionPacketSize]byte, error) {
var blob [lnwire.OnionPacketSize]byte var blob [lnwire.OnionPacketSize]byte
if len(hops) == 0 { if len(hops) == 0 {
return blob, errors.New("empty path") return blob, errors.New("empty path")
@ -635,16 +638,17 @@ type threeHopNetwork struct {
// also the time lock value needed to route an HTLC with the target amount over // also the time lock value needed to route an HTLC with the target amount over
// the specified path. // the specified path.
func generateHops(payAmt lnwire.MilliSatoshi, startingHeight uint32, func generateHops(payAmt lnwire.MilliSatoshi, startingHeight uint32,
path ...*channelLink) (lnwire.MilliSatoshi, uint32, []ForwardingInfo) { path ...*channelLink) (lnwire.MilliSatoshi, uint32,
[]hop.ForwardingInfo) {
totalTimelock := startingHeight totalTimelock := startingHeight
runningAmt := payAmt runningAmt := payAmt
hops := make([]ForwardingInfo, len(path)) hops := make([]hop.ForwardingInfo, len(path))
for i := len(path) - 1; i >= 0; i-- { for i := len(path) - 1; i >= 0; i-- {
// If this is the last hop, then the next hop is the special // If this is the last hop, then the next hop is the special
// "exit node". Otherwise, we look to the "prior" hop. // "exit node". Otherwise, we look to the "prior" hop.
nextHop := exitHop nextHop := hop.Exit
if i != len(path)-1 { if i != len(path)-1 {
nextHop = path[i+1].channel.ShortChanID() nextHop = path[i+1].channel.ShortChanID()
} }
@ -679,8 +683,8 @@ func generateHops(payAmt lnwire.MilliSatoshi, startingHeight uint32,
amount = runningAmt - fee amount = runningAmt - fee
} }
hops[i] = ForwardingInfo{ hops[i] = hop.ForwardingInfo{
Network: BitcoinHop, Network: hop.BitcoinNetwork,
NextHop: nextHop, NextHop: nextHop,
AmountToForward: amount, AmountToForward: amount,
OutgoingCTLV: timeLock, OutgoingCTLV: timeLock,
@ -731,7 +735,7 @@ func waitForPayFuncResult(payFunc func() error, d time.Duration) error {
// * from Alice to Carol through the Bob // * from Alice to Carol through the Bob
// * from Alice to some another peer through the Bob // * from Alice to some another peer through the Bob
func makePayment(sendingPeer, receivingPeer lnpeer.Peer, func makePayment(sendingPeer, receivingPeer lnpeer.Peer,
firstHop lnwire.ShortChannelID, hops []ForwardingInfo, firstHop lnwire.ShortChannelID, hops []hop.ForwardingInfo,
invoiceAmt, htlcAmt lnwire.MilliSatoshi, invoiceAmt, htlcAmt lnwire.MilliSatoshi,
timelock uint32) *paymentResponse { timelock uint32) *paymentResponse {
@ -765,7 +769,7 @@ func makePayment(sendingPeer, receivingPeer lnpeer.Peer,
// preparePayment creates an invoice at the receivingPeer and returns a function // preparePayment creates an invoice at the receivingPeer and returns a function
// that, when called, launches the payment from the sendingPeer. // that, when called, launches the payment from the sendingPeer.
func preparePayment(sendingPeer, receivingPeer lnpeer.Peer, func preparePayment(sendingPeer, receivingPeer lnpeer.Peer,
firstHop lnwire.ShortChannelID, hops []ForwardingInfo, firstHop lnwire.ShortChannelID, hops []hop.ForwardingInfo,
invoiceAmt, htlcAmt lnwire.MilliSatoshi, invoiceAmt, htlcAmt lnwire.MilliSatoshi,
timelock uint32) (*channeldb.Invoice, func() error, error) { timelock uint32) (*channeldb.Invoice, func() error, error) {
@ -1246,7 +1250,7 @@ func (n *twoHopNetwork) stop() {
} }
func (n *twoHopNetwork) makeHoldPayment(sendingPeer, receivingPeer lnpeer.Peer, func (n *twoHopNetwork) makeHoldPayment(sendingPeer, receivingPeer lnpeer.Peer,
firstHop lnwire.ShortChannelID, hops []ForwardingInfo, firstHop lnwire.ShortChannelID, hops []hop.ForwardingInfo,
invoiceAmt, htlcAmt lnwire.MilliSatoshi, invoiceAmt, htlcAmt lnwire.MilliSatoshi,
timelock uint32, preimage lntypes.Preimage) chan error { timelock uint32, preimage lntypes.Preimage) chan error {