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 }