github.com/decred/dcrlnd@v0.7.6/routing/route/route.go (about)

     1  package route
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"strings"
    11  
    12  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    13  	"github.com/decred/dcrlnd/lnwire"
    14  	"github.com/decred/dcrlnd/record"
    15  	"github.com/decred/dcrlnd/tlv"
    16  	sphinx "github.com/decred/lightning-onion/v4"
    17  )
    18  
    19  // VertexSize is the size of the array to store a vertex.
    20  const VertexSize = 33
    21  
    22  var (
    23  	// ErrNoRouteHopsProvided is returned when a caller attempts to
    24  	// construct a new sphinx packet, but provides an empty set of hops for
    25  	// each route.
    26  	ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided")
    27  
    28  	// ErrMaxRouteHopsExceeded is returned when a caller attempts to
    29  	// construct a new sphinx packet, but provides too many hops.
    30  	ErrMaxRouteHopsExceeded = fmt.Errorf("route has too many hops")
    31  
    32  	// ErrIntermediateMPPHop is returned when a hop tries to deliver an MPP
    33  	// record to an intermediate hop, only final hops can receive MPP
    34  	// records.
    35  	ErrIntermediateMPPHop = errors.New("cannot send MPP to intermediate")
    36  
    37  	// ErrAMPMissingMPP is returned when the caller tries to attach an AMP
    38  	// record but no MPP record is presented for the final hop.
    39  	ErrAMPMissingMPP = errors.New("cannot send AMP without MPP record")
    40  )
    41  
    42  // Vertex is a simple alias for the serialization of a compressed Bitcoin
    43  // public key.
    44  type Vertex [VertexSize]byte
    45  
    46  // NewVertex returns a new Vertex given a public key.
    47  func NewVertex(pub *secp256k1.PublicKey) Vertex {
    48  	var v Vertex
    49  	copy(v[:], pub.SerializeCompressed())
    50  	return v
    51  }
    52  
    53  // NewVertexFromBytes returns a new Vertex based on a serialized pubkey in a
    54  // byte slice.
    55  func NewVertexFromBytes(b []byte) (Vertex, error) {
    56  	vertexLen := len(b)
    57  	if vertexLen != VertexSize {
    58  		return Vertex{}, fmt.Errorf("invalid vertex length of %v, "+
    59  			"want %v", vertexLen, VertexSize)
    60  	}
    61  
    62  	var v Vertex
    63  	copy(v[:], b)
    64  	return v, nil
    65  }
    66  
    67  // NewVertexFromStr returns a new Vertex given its hex-encoded string format.
    68  func NewVertexFromStr(v string) (Vertex, error) {
    69  	// Return error if hex string is of incorrect length.
    70  	if len(v) != VertexSize*2 {
    71  		return Vertex{}, fmt.Errorf("invalid vertex string length of "+
    72  			"%v, want %v", len(v), VertexSize*2)
    73  	}
    74  
    75  	vertex, err := hex.DecodeString(v)
    76  	if err != nil {
    77  		return Vertex{}, err
    78  	}
    79  
    80  	return NewVertexFromBytes(vertex)
    81  }
    82  
    83  // String returns a human readable version of the Vertex which is the
    84  // hex-encoding of the serialized compressed public key.
    85  func (v Vertex) String() string {
    86  	return fmt.Sprintf("%x", v[:])
    87  }
    88  
    89  // Hop represents an intermediate or final node of the route. This naming
    90  // is in line with the definition given in BOLT #4: Onion Routing Protocol.
    91  // The struct houses the channel along which this hop can be reached and
    92  // the values necessary to create the HTLC that needs to be sent to the
    93  // next hop. It is also used to encode the per-hop payload included within
    94  // the Sphinx packet.
    95  type Hop struct {
    96  	// PubKeyBytes is the raw bytes of the public key of the target node.
    97  	PubKeyBytes Vertex
    98  
    99  	// ChannelID is the unique channel ID for the channel. The first 3
   100  	// bytes are the block height, the next 3 the index within the block,
   101  	// and the last 2 bytes are the output index for the channel.
   102  	ChannelID uint64
   103  
   104  	// OutgoingTimeLock is the timelock value that should be used when
   105  	// crafting the _outgoing_ HTLC from this hop.
   106  	OutgoingTimeLock uint32
   107  
   108  	// AmtToForward is the amount that this hop will forward to the next
   109  	// hop. This value is less than the value that the incoming HTLC
   110  	// carries as a fee will be subtracted by the hop.
   111  	AmtToForward lnwire.MilliAtom
   112  
   113  	// MPP encapsulates the data required for option_mpp. This field should
   114  	// only be set for the final hop.
   115  	MPP *record.MPP
   116  
   117  	// AMP encapsulates the data required for option_amp. This field should
   118  	// only be set for the final hop.
   119  	AMP *record.AMP
   120  
   121  	// CustomRecords if non-nil are a set of additional TLV records that
   122  	// should be included in the forwarding instructions for this node.
   123  	CustomRecords record.CustomSet
   124  
   125  	// LegacyPayload if true, then this signals that this node doesn't
   126  	// understand the new TLV payload, so we must instead use the legacy
   127  	// payload.
   128  	LegacyPayload bool
   129  }
   130  
   131  // Copy returns a deep copy of the Hop.
   132  func (h *Hop) Copy() *Hop {
   133  	c := *h
   134  
   135  	if h.MPP != nil {
   136  		m := *h.MPP
   137  		c.MPP = &m
   138  	}
   139  
   140  	if h.AMP != nil {
   141  		a := *h.AMP
   142  		c.AMP = &a
   143  	}
   144  
   145  	return &c
   146  }
   147  
   148  // PackHopPayload writes to the passed io.Writer, the series of byes that can
   149  // be placed directly into the per-hop payload (EOB) for this hop. This will
   150  // include the required routing fields, as well as serializing any of the
   151  // passed optional TLVRecords.  nextChanID is the unique channel ID that
   152  // references the _outgoing_ channel ID that follows this hop. This field
   153  // follows the same semantics as the NextAddress field in the onion: it should
   154  // be set to zero to indicate the terminal hop.
   155  func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error {
   156  	// If this is a legacy payload, then we'll exit here as this method
   157  	// shouldn't be called.
   158  	if h.LegacyPayload {
   159  		return fmt.Errorf("cannot pack hop payloads for legacy " +
   160  			"payloads")
   161  	}
   162  
   163  	// Otherwise, we'll need to make a new stream that includes our
   164  	// required routing fields, as well as these optional values.
   165  	var records []tlv.Record
   166  
   167  	// Every hop must have an amount to forward and CLTV expiry.
   168  	amt := uint64(h.AmtToForward)
   169  	records = append(records,
   170  		record.NewAmtToFwdRecord(&amt),
   171  		record.NewLockTimeRecord(&h.OutgoingTimeLock),
   172  	)
   173  
   174  	// BOLT 04 says the next_hop_id should be omitted for the final hop,
   175  	// but present for all others.
   176  	//
   177  	// TODO(conner): test using hop.Exit once available
   178  	if nextChanID != 0 {
   179  		records = append(records,
   180  			record.NewNextHopIDRecord(&nextChanID),
   181  		)
   182  	}
   183  
   184  	// If an MPP record is destined for this hop, ensure that we only ever
   185  	// attach it to the final hop. Otherwise the route was constructed
   186  	// incorrectly.
   187  	if h.MPP != nil {
   188  		if nextChanID == 0 {
   189  			records = append(records, h.MPP.Record())
   190  		} else {
   191  			return ErrIntermediateMPPHop
   192  		}
   193  	}
   194  
   195  	// If an AMP record is destined for this hop, ensure that we only ever
   196  	// attach it if we also have an MPP record. We can infer that this is
   197  	// already a final hop if MPP is non-nil otherwise we would have exited
   198  	// above.
   199  	if h.AMP != nil {
   200  		if h.MPP != nil {
   201  			records = append(records, h.AMP.Record())
   202  		} else {
   203  			return ErrAMPMissingMPP
   204  		}
   205  	}
   206  
   207  	// Append any custom types destined for this hop.
   208  	tlvRecords := tlv.MapToRecords(h.CustomRecords)
   209  	records = append(records, tlvRecords...)
   210  
   211  	// To ensure we produce a canonical stream, we'll sort the records
   212  	// before encoding them as a stream in the hop payload.
   213  	tlv.SortRecords(records)
   214  
   215  	tlvStream, err := tlv.NewStream(records...)
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	return tlvStream.Encode(w)
   221  }
   222  
   223  // Size returns the total size this hop's payload would take up in the onion
   224  // packet.
   225  func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
   226  	if h.LegacyPayload {
   227  		return sphinx.LegacyHopDataSize
   228  	}
   229  
   230  	var payloadSize uint64
   231  
   232  	addRecord := func(tlvType tlv.Type, length uint64) {
   233  		payloadSize += tlv.VarIntSize(uint64(tlvType)) +
   234  			tlv.VarIntSize(length) + length
   235  	}
   236  
   237  	// Add amount size.
   238  	addRecord(record.AmtOnionType, tlv.SizeTUint64(uint64(h.AmtToForward)))
   239  
   240  	// Add lock time size.
   241  	addRecord(
   242  		record.LockTimeOnionType,
   243  		tlv.SizeTUint64(uint64(h.OutgoingTimeLock)),
   244  	)
   245  
   246  	// Add next hop if present.
   247  	if nextChanID != 0 {
   248  		addRecord(record.NextHopOnionType, 8)
   249  	}
   250  
   251  	// Add mpp if present.
   252  	if h.MPP != nil {
   253  		addRecord(record.MPPOnionType, h.MPP.PayloadSize())
   254  	}
   255  
   256  	// Add amp if present.
   257  	if h.AMP != nil {
   258  		addRecord(record.AMPOnionType, h.AMP.PayloadSize())
   259  	}
   260  
   261  	// Add custom records.
   262  	for k, v := range h.CustomRecords {
   263  		addRecord(tlv.Type(k), uint64(len(v)))
   264  	}
   265  
   266  	// Add the size required to encode the payload length.
   267  	payloadSize += tlv.VarIntSize(payloadSize)
   268  
   269  	// Add HMAC.
   270  	payloadSize += sphinx.HMACSize
   271  
   272  	return payloadSize
   273  }
   274  
   275  // Route represents a path through the channel graph which runs over one or
   276  // more channels in succession. This struct carries all the information
   277  // required to craft the Sphinx onion packet, and send the payment along the
   278  // first hop in the path. A route is only selected as valid if all the channels
   279  // have sufficient capacity to carry the initial payment amount after fees are
   280  // accounted for.
   281  type Route struct {
   282  	// TotalTimeLock is the cumulative (final) time lock across the entire
   283  	// route. This is the CLTV value that should be extended to the first
   284  	// hop in the route. All other hops will decrement the time-lock as
   285  	// advertised, leaving enough time for all hops to wait for or present
   286  	// the payment preimage to complete the payment.
   287  	TotalTimeLock uint32
   288  
   289  	// TotalAmount is the total amount of funds required to complete a
   290  	// payment over this route. This value includes the cumulative fees at
   291  	// each hop. As a result, the HTLC extended to the first-hop in the
   292  	// route will need to have at least this many satoshis, otherwise the
   293  	// route will fail at an intermediate node due to an insufficient
   294  	// amount of fees.
   295  	TotalAmount lnwire.MilliAtom
   296  
   297  	// SourcePubKey is the pubkey of the node where this route originates
   298  	// from.
   299  	SourcePubKey Vertex
   300  
   301  	// Hops contains details concerning the specific forwarding details at
   302  	// each hop.
   303  	Hops []*Hop
   304  }
   305  
   306  // Copy returns a deep copy of the Route.
   307  func (r *Route) Copy() *Route {
   308  	c := *r
   309  
   310  	c.Hops = make([]*Hop, len(r.Hops))
   311  	for i := range r.Hops {
   312  		c.Hops[i] = r.Hops[i].Copy()
   313  	}
   314  
   315  	return &c
   316  }
   317  
   318  // HopFee returns the fee charged by the route hop indicated by hopIndex.
   319  func (r *Route) HopFee(hopIndex int) lnwire.MilliAtom {
   320  	var incomingAmt lnwire.MilliAtom
   321  	if hopIndex == 0 {
   322  		incomingAmt = r.TotalAmount
   323  	} else {
   324  		incomingAmt = r.Hops[hopIndex-1].AmtToForward
   325  	}
   326  
   327  	// Fee is calculated as difference between incoming and outgoing amount.
   328  	return incomingAmt - r.Hops[hopIndex].AmtToForward
   329  }
   330  
   331  // TotalFees is the sum of the fees paid at each hop within the final route. In
   332  // the case of a one-hop payment, this value will be zero as we don't need to
   333  // pay a fee to ourself.
   334  func (r *Route) TotalFees() lnwire.MilliAtom {
   335  	if len(r.Hops) == 0 {
   336  		return 0
   337  	}
   338  
   339  	return r.TotalAmount - r.ReceiverAmt()
   340  }
   341  
   342  // ReceiverAmt is the amount received by the final hop of this route.
   343  func (r *Route) ReceiverAmt() lnwire.MilliAtom {
   344  	if len(r.Hops) == 0 {
   345  		return 0
   346  	}
   347  
   348  	return r.Hops[len(r.Hops)-1].AmtToForward
   349  }
   350  
   351  // FinalHop returns the last hop of the route, or nil if the route is empty.
   352  func (r *Route) FinalHop() *Hop {
   353  	if len(r.Hops) == 0 {
   354  		return nil
   355  	}
   356  
   357  	return r.Hops[len(r.Hops)-1]
   358  }
   359  
   360  // NewRouteFromHops creates a new Route structure from the minimally required
   361  // information to perform the payment. It infers fee amounts and populates the
   362  // node, chan and prev/next hop maps.
   363  func NewRouteFromHops(amtToSend lnwire.MilliAtom, timeLock uint32,
   364  	sourceVertex Vertex, hops []*Hop) (*Route, error) {
   365  
   366  	if len(hops) == 0 {
   367  		return nil, ErrNoRouteHopsProvided
   368  	}
   369  
   370  	// First, we'll create a route struct and populate it with the fields
   371  	// for which the values are provided as arguments of this function.
   372  	// TotalFees is determined based on the difference between the amount
   373  	// that is send from the source and the final amount that is received
   374  	// by the destination.
   375  	route := &Route{
   376  		SourcePubKey:  sourceVertex,
   377  		Hops:          hops,
   378  		TotalTimeLock: timeLock,
   379  		TotalAmount:   amtToSend,
   380  	}
   381  
   382  	return route, nil
   383  }
   384  
   385  // ToSphinxPath converts a complete route into a sphinx PaymentPath that
   386  // contains the per-hop paylods used to encoding the HTLC routing data for each
   387  // hop in the route. This method also accepts an optional EOB payload for the
   388  // final hop.
   389  func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
   390  	var path sphinx.PaymentPath
   391  
   392  	// We can only construct a route if there are hops provided.
   393  	if len(r.Hops) == 0 {
   394  		return nil, ErrNoRouteHopsProvided
   395  	}
   396  
   397  	// Check maximum route length.
   398  	if len(r.Hops) > sphinx.NumMaxHops {
   399  		return nil, ErrMaxRouteHopsExceeded
   400  	}
   401  
   402  	// For each hop encoded within the route, we'll convert the hop struct
   403  	// to an OnionHop with matching per-hop payload within the path as used
   404  	// by the sphinx package.
   405  	for i, hop := range r.Hops {
   406  		pub, err := secp256k1.ParsePubKey(
   407  			hop.PubKeyBytes[:],
   408  		)
   409  		if err != nil {
   410  			return nil, err
   411  		}
   412  
   413  		// As a base case, the next hop is set to all zeroes in order
   414  		// to indicate that the "last hop" as no further hops after it.
   415  		nextHop := uint64(0)
   416  
   417  		// If we aren't on the last hop, then we set the "next address"
   418  		// field to be the channel that directly follows it.
   419  		if i != len(r.Hops)-1 {
   420  			nextHop = r.Hops[i+1].ChannelID
   421  		}
   422  
   423  		var payload sphinx.HopPayload
   424  
   425  		// If this is the legacy payload, then we can just include the
   426  		// hop data as normal.
   427  		if hop.LegacyPayload {
   428  			// Before we encode this value, we'll pack the next hop
   429  			// into the NextAddress field of the hop info to ensure
   430  			// we point to the right now.
   431  			hopData := sphinx.HopData{
   432  				ForwardAmount: uint64(hop.AmtToForward),
   433  				OutgoingCltv:  hop.OutgoingTimeLock,
   434  			}
   435  			binary.BigEndian.PutUint64(
   436  				hopData.NextAddress[:], nextHop,
   437  			)
   438  
   439  			payload, err = sphinx.NewHopPayload(&hopData, nil)
   440  			if err != nil {
   441  				return nil, err
   442  			}
   443  		} else {
   444  			// For non-legacy payloads, we'll need to pack the
   445  			// routing information, along with any extra TLV
   446  			// information into the new per-hop payload format.
   447  			// We'll also pass in the chan ID of the hop this
   448  			// channel should be forwarded to so we can construct a
   449  			// valid payload.
   450  			var b bytes.Buffer
   451  			err := hop.PackHopPayload(&b, nextHop)
   452  			if err != nil {
   453  				return nil, err
   454  			}
   455  
   456  			// TODO(roasbeef): make better API for NewHopPayload?
   457  			payload, err = sphinx.NewHopPayload(nil, b.Bytes())
   458  			if err != nil {
   459  				return nil, err
   460  			}
   461  		}
   462  
   463  		path[i] = sphinx.OnionHop{
   464  			NodePub:    *pub,
   465  			HopPayload: payload,
   466  		}
   467  	}
   468  
   469  	return &path, nil
   470  }
   471  
   472  // String returns a human readable representation of the route.
   473  func (r *Route) String() string {
   474  	var b strings.Builder
   475  
   476  	amt := r.TotalAmount
   477  	for i, hop := range r.Hops {
   478  		if i > 0 {
   479  			b.WriteString(" -> ")
   480  		}
   481  		b.WriteString(fmt.Sprintf("%s (%v)",
   482  			lnwire.NewShortChanIDFromInt(hop.ChannelID),
   483  			amt,
   484  		))
   485  		amt = hop.AmtToForward
   486  	}
   487  
   488  	return fmt.Sprintf("%v, cltv %v",
   489  		b.String(), r.TotalTimeLock,
   490  	)
   491  }