github.com/decred/dcrlnd@v0.7.6/channeldb/migration_01_to_11/route.go (about) 1 package migration_01_to_11 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "fmt" 8 "io" 9 "strconv" 10 "strings" 11 12 "github.com/decred/dcrd/dcrec/secp256k1/v4" 13 lnwire "github.com/decred/dcrlnd/channeldb/migration/lnwire21" 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 // ErrNoRouteHopsProvided is returned when a caller attempts to construct a new 23 // sphinx packet, but provides an empty set of hops for each route. 24 var ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided") 25 26 // Vertex is a simple alias for the serialization of a compressed Bitcoin 27 // public key. 28 type Vertex [VertexSize]byte 29 30 // NewVertex returns a new Vertex given a public key. 31 func NewVertex(pub *secp256k1.PublicKey) Vertex { 32 var v Vertex 33 copy(v[:], pub.SerializeCompressed()) 34 return v 35 } 36 37 // NewVertexFromBytes returns a new Vertex based on a serialized pubkey in a 38 // byte slice. 39 func NewVertexFromBytes(b []byte) (Vertex, error) { 40 vertexLen := len(b) 41 if vertexLen != VertexSize { 42 return Vertex{}, fmt.Errorf("invalid vertex length of %v, "+ 43 "want %v", vertexLen, VertexSize) 44 } 45 46 var v Vertex 47 copy(v[:], b) 48 return v, nil 49 } 50 51 // NewVertexFromStr returns a new Vertex given its hex-encoded string format. 52 func NewVertexFromStr(v string) (Vertex, error) { 53 // Return error if hex string is of incorrect length. 54 if len(v) != VertexSize*2 { 55 return Vertex{}, fmt.Errorf("invalid vertex string length of "+ 56 "%v, want %v", len(v), VertexSize*2) 57 } 58 59 vertex, err := hex.DecodeString(v) 60 if err != nil { 61 return Vertex{}, err 62 } 63 64 return NewVertexFromBytes(vertex) 65 } 66 67 // String returns a human readable version of the Vertex which is the 68 // hex-encoding of the serialized compressed public key. 69 func (v Vertex) String() string { 70 return fmt.Sprintf("%x", v[:]) 71 } 72 73 // Hop represents an intermediate or final node of the route. This naming 74 // is in line with the definition given in BOLT #4: Onion Routing Protocol. 75 // The struct houses the channel along which this hop can be reached and 76 // the values necessary to create the HTLC that needs to be sent to the 77 // next hop. It is also used to encode the per-hop payload included within 78 // the Sphinx packet. 79 type Hop struct { 80 // PubKeyBytes is the raw bytes of the public key of the target node. 81 PubKeyBytes Vertex 82 83 // ChannelID is the unique channel ID for the channel. The first 3 84 // bytes are the block height, the next 3 the index within the block, 85 // and the last 2 bytes are the output index for the channel. 86 ChannelID uint64 87 88 // OutgoingTimeLock is the timelock value that should be used when 89 // crafting the _outgoing_ HTLC from this hop. 90 OutgoingTimeLock uint32 91 92 // AmtToForward is the amount that this hop will forward to the next 93 // hop. This value is less than the value that the incoming HTLC 94 // carries as a fee will be subtracted by the hop. 95 AmtToForward lnwire.MilliAtom 96 97 // TLVRecords if non-nil are a set of additional TLV records that 98 // should be included in the forwarding instructions for this node. 99 TLVRecords []tlv.Record 100 101 // LegacyPayload if true, then this signals that this node doesn't 102 // understand the new TLV payload, so we must instead use the legacy 103 // payload. 104 LegacyPayload bool 105 } 106 107 // PackHopPayload writes to the passed io.Writer, the series of byes that can 108 // be placed directly into the per-hop payload (EOB) for this hop. This will 109 // include the required routing fields, as well as serializing any of the 110 // passed optional TLVRecords. nextChanID is the unique channel ID that 111 // references the _outgoing_ channel ID that follows this hop. This field 112 // follows the same semantics as the NextAddress field in the onion: it should 113 // be set to zero to indicate the terminal hop. 114 func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error { 115 // If this is a legacy payload, then we'll exit here as this method 116 // shouldn't be called. 117 if h.LegacyPayload { 118 return fmt.Errorf("cannot pack hop payloads for legacy " + 119 "payloads") 120 } 121 122 // Otherwise, we'll need to make a new stream that includes our 123 // required routing fields, as well as these optional values. 124 var records []tlv.Record 125 126 // Every hop must have an amount to forward and CLTV expiry. 127 amt := uint64(h.AmtToForward) 128 records = append(records, 129 record.NewAmtToFwdRecord(&amt), 130 record.NewLockTimeRecord(&h.OutgoingTimeLock), 131 ) 132 133 // BOLT 04 says the next_hop_id should be omitted for the final hop, 134 // but present for all others. 135 // 136 // TODO(conner): test using hop.Exit once available 137 if nextChanID != 0 { 138 records = append(records, 139 record.NewNextHopIDRecord(&nextChanID), 140 ) 141 } 142 143 // Append any custom types destined for this hop. 144 records = append(records, h.TLVRecords...) 145 146 // To ensure we produce a canonical stream, we'll sort the records 147 // before encoding them as a stream in the hop payload. 148 tlv.SortRecords(records) 149 150 tlvStream, err := tlv.NewStream(records...) 151 if err != nil { 152 return err 153 } 154 155 return tlvStream.Encode(w) 156 } 157 158 // Route represents a path through the channel graph which runs over one or 159 // more channels in succession. This struct carries all the information 160 // required to craft the Sphinx onion packet, and send the payment along the 161 // first hop in the path. A route is only selected as valid if all the channels 162 // have sufficient capacity to carry the initial payment amount after fees are 163 // accounted for. 164 type Route struct { 165 // TotalTimeLock is the cumulative (final) time lock across the entire 166 // route. This is the CLTV value that should be extended to the first 167 // hop in the route. All other hops will decrement the time-lock as 168 // advertised, leaving enough time for all hops to wait for or present 169 // the payment preimage to complete the payment. 170 TotalTimeLock uint32 171 172 // TotalAmount is the total amount of funds required to complete a 173 // payment over this route. This value includes the cumulative fees at 174 // each hop. As a result, the HTLC extended to the first-hop in the 175 // route will need to have at least this many satoshis, otherwise the 176 // route will fail at an intermediate node due to an insufficient 177 // amount of fees. 178 TotalAmount lnwire.MilliAtom 179 180 // SourcePubKey is the pubkey of the node where this route originates 181 // from. 182 SourcePubKey Vertex 183 184 // Hops contains details concerning the specific forwarding details at 185 // each hop. 186 Hops []*Hop 187 } 188 189 // HopFee returns the fee charged by the route hop indicated by hopIndex. 190 func (r *Route) HopFee(hopIndex int) lnwire.MilliAtom { 191 var incomingAmt lnwire.MilliAtom 192 if hopIndex == 0 { 193 incomingAmt = r.TotalAmount 194 } else { 195 incomingAmt = r.Hops[hopIndex-1].AmtToForward 196 } 197 198 // Fee is calculated as difference between incoming and outgoing amount. 199 return incomingAmt - r.Hops[hopIndex].AmtToForward 200 } 201 202 // TotalFees is the sum of the fees paid at each hop within the final route. In 203 // the case of a one-hop payment, this value will be zero as we don't need to 204 // pay a fee to ourself. 205 func (r *Route) TotalFees() lnwire.MilliAtom { 206 if len(r.Hops) == 0 { 207 return 0 208 } 209 210 return r.TotalAmount - r.Hops[len(r.Hops)-1].AmtToForward 211 } 212 213 // NewRouteFromHops creates a new Route structure from the minimally required 214 // information to perform the payment. It infers fee amounts and populates the 215 // node, chan and prev/next hop maps. 216 func NewRouteFromHops(amtToSend lnwire.MilliAtom, timeLock uint32, 217 sourceVertex Vertex, hops []*Hop) (*Route, error) { 218 219 if len(hops) == 0 { 220 return nil, ErrNoRouteHopsProvided 221 } 222 223 // First, we'll create a route struct and populate it with the fields 224 // for which the values are provided as arguments of this function. 225 // TotalFees is determined based on the difference between the amount 226 // that is send from the source and the final amount that is received 227 // by the destination. 228 route := &Route{ 229 SourcePubKey: sourceVertex, 230 Hops: hops, 231 TotalTimeLock: timeLock, 232 TotalAmount: amtToSend, 233 } 234 235 return route, nil 236 } 237 238 // ToSphinxPath converts a complete route into a sphinx PaymentPath that 239 // contains the per-hop paylods used to encoding the HTLC routing data for each 240 // hop in the route. This method also accepts an optional EOB payload for the 241 // final hop. 242 func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) { 243 var path sphinx.PaymentPath 244 245 // For each hop encoded within the route, we'll convert the hop struct 246 // to an OnionHop with matching per-hop payload within the path as used 247 // by the sphinx package. 248 for i, hop := range r.Hops { 249 pub, err := secp256k1.ParsePubKey( 250 hop.PubKeyBytes[:], 251 ) 252 if err != nil { 253 return nil, err 254 } 255 256 // As a base case, the next hop is set to all zeroes in order 257 // to indicate that the "last hop" as no further hops after it. 258 nextHop := uint64(0) 259 260 // If we aren't on the last hop, then we set the "next address" 261 // field to be the channel that directly follows it. 262 if i != len(r.Hops)-1 { 263 nextHop = r.Hops[i+1].ChannelID 264 } 265 266 var payload sphinx.HopPayload 267 268 // If this is the legacy payload, then we can just include the 269 // hop data as normal. 270 if hop.LegacyPayload { 271 // Before we encode this value, we'll pack the next hop 272 // into the NextAddress field of the hop info to ensure 273 // we point to the right now. 274 hopData := sphinx.HopData{ 275 ForwardAmount: uint64(hop.AmtToForward), 276 OutgoingCltv: hop.OutgoingTimeLock, 277 } 278 binary.BigEndian.PutUint64( 279 hopData.NextAddress[:], nextHop, 280 ) 281 282 payload, err = sphinx.NewHopPayload(&hopData, nil) 283 if err != nil { 284 return nil, err 285 } 286 } else { 287 // For non-legacy payloads, we'll need to pack the 288 // routing information, along with any extra TLV 289 // information into the new per-hop payload format. 290 // We'll also pass in the chan ID of the hop this 291 // channel should be forwarded to so we can construct a 292 // valid payload. 293 var b bytes.Buffer 294 err := hop.PackHopPayload(&b, nextHop) 295 if err != nil { 296 return nil, err 297 } 298 299 // TODO(roasbeef): make better API for NewHopPayload? 300 payload, err = sphinx.NewHopPayload(nil, b.Bytes()) 301 if err != nil { 302 return nil, err 303 } 304 } 305 306 path[i] = sphinx.OnionHop{ 307 NodePub: *pub, 308 HopPayload: payload, 309 } 310 } 311 312 return &path, nil 313 } 314 315 // String returns a human readable representation of the route. 316 func (r *Route) String() string { 317 var b strings.Builder 318 319 for i, hop := range r.Hops { 320 if i > 0 { 321 b.WriteString(",") 322 } 323 b.WriteString(strconv.FormatUint(hop.ChannelID, 10)) 324 } 325 326 return fmt.Sprintf("amt=%v, fees=%v, tl=%v, chans=%v", 327 r.TotalAmount-r.TotalFees(), r.TotalFees(), r.TotalTimeLock, 328 b.String(), 329 ) 330 }