github.com/decred/dcrlnd@v0.7.6/htlcswitch/hop/payload.go (about) 1 package hop 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "io" 7 8 "github.com/decred/dcrlnd/lnwire" 9 "github.com/decred/dcrlnd/record" 10 "github.com/decred/dcrlnd/tlv" 11 sphinx "github.com/decred/lightning-onion/v4" 12 ) 13 14 // PayloadViolation is an enum encapsulating the possible invalid payload 15 // violations that can occur when processing or validating a payload. 16 type PayloadViolation byte 17 18 const ( 19 // OmittedViolation indicates that a type was expected to be found the 20 // payload but was absent. 21 OmittedViolation PayloadViolation = iota 22 23 // IncludedViolation indicates that a type was expected to be omitted 24 // from the payload but was present. 25 IncludedViolation 26 27 // RequiredViolation indicates that an unknown even type was found in 28 // the payload that we could not process. 29 RequiredViolation 30 ) 31 32 // String returns a human-readable description of the violation as a verb. 33 func (v PayloadViolation) String() string { 34 switch v { 35 case OmittedViolation: 36 return "omitted" 37 38 case IncludedViolation: 39 return "included" 40 41 case RequiredViolation: 42 return "required" 43 44 default: 45 return "unknown violation" 46 } 47 } 48 49 // ErrInvalidPayload is an error returned when a parsed onion payload either 50 // included or omitted incorrect records for a particular hop type. 51 type ErrInvalidPayload struct { 52 // Type the record's type that cause the violation. 53 Type tlv.Type 54 55 // Violation is an enum indicating the type of violation detected in 56 // processing Type. 57 Violation PayloadViolation 58 59 // FinalHop if true, indicates that the violation is for the final hop 60 // in the route (identified by next hop id), otherwise the violation is 61 // for an intermediate hop. 62 FinalHop bool 63 } 64 65 // Error returns a human-readable description of the invalid payload error. 66 func (e ErrInvalidPayload) Error() string { 67 hopType := "intermediate" 68 if e.FinalHop { 69 hopType = "final" 70 } 71 72 return fmt.Sprintf("onion payload for %s hop %v record with type %d", 73 hopType, e.Violation, e.Type) 74 } 75 76 // Payload encapsulates all information delivered to a hop in an onion payload. 77 // A Hop can represent either a TLV or legacy payload. The primary forwarding 78 // instruction can be accessed via ForwardingInfo, and additional records can be 79 // accessed by other member functions. 80 type Payload struct { 81 // FwdInfo holds the basic parameters required for HTLC forwarding, e.g. 82 // amount, cltv, and next hop. 83 FwdInfo ForwardingInfo 84 85 // MPP holds the info provided in an option_mpp record when parsed from 86 // a TLV onion payload. 87 MPP *record.MPP 88 89 // AMP holds the info provided in an option_amp record when parsed from 90 // a TLV onion payload. 91 AMP *record.AMP 92 93 // customRecords are user-defined records in the custom type range that 94 // were included in the payload. 95 customRecords record.CustomSet 96 } 97 98 // NewLegacyPayload builds a Payload from the amount, cltv, and next hop 99 // parameters provided by leegacy onion payloads. 100 func NewLegacyPayload(f *sphinx.HopData) *Payload { 101 nextHop := binary.BigEndian.Uint64(f.NextAddress[:]) 102 103 return &Payload{ 104 FwdInfo: ForwardingInfo{ 105 Network: DecredNetwork, 106 NextHop: lnwire.NewShortChanIDFromInt(nextHop), 107 AmountToForward: lnwire.MilliAtom(f.ForwardAmount), 108 OutgoingCTLV: f.OutgoingCltv, 109 }, 110 customRecords: make(record.CustomSet), 111 } 112 } 113 114 // NewPayloadFromReader builds a new Hop from the passed io.Reader. The reader 115 // should correspond to the bytes encapsulated in a TLV onion payload. 116 func NewPayloadFromReader(r io.Reader) (*Payload, error) { 117 var ( 118 cid uint64 119 amt uint64 120 cltv uint32 121 mpp = &record.MPP{} 122 amp = &record.AMP{} 123 ) 124 125 tlvStream, err := tlv.NewStream( 126 record.NewAmtToFwdRecord(&amt), 127 record.NewLockTimeRecord(&cltv), 128 record.NewNextHopIDRecord(&cid), 129 mpp.Record(), 130 amp.Record(), 131 ) 132 if err != nil { 133 return nil, err 134 } 135 136 parsedTypes, err := tlvStream.DecodeWithParsedTypes(r) 137 if err != nil { 138 return nil, err 139 } 140 141 // Validate whether the sender properly included or omitted tlv records 142 // in accordance with BOLT 04. 143 nextHop := lnwire.NewShortChanIDFromInt(cid) 144 err = ValidateParsedPayloadTypes(parsedTypes, nextHop) 145 if err != nil { 146 return nil, err 147 } 148 149 // Check for violation of the rules for mandatory fields. 150 violatingType := getMinRequiredViolation(parsedTypes) 151 if violatingType != nil { 152 return nil, ErrInvalidPayload{ 153 Type: *violatingType, 154 Violation: RequiredViolation, 155 FinalHop: nextHop == Exit, 156 } 157 } 158 159 // If no MPP field was parsed, set the MPP field on the resulting 160 // payload to nil. 161 if _, ok := parsedTypes[record.MPPOnionType]; !ok { 162 mpp = nil 163 } 164 165 // If no AMP field was parsed, set the MPP field on the resulting 166 // payload to nil. 167 if _, ok := parsedTypes[record.AMPOnionType]; !ok { 168 amp = nil 169 } 170 171 // Filter out the custom records. 172 customRecords := NewCustomRecords(parsedTypes) 173 174 return &Payload{ 175 FwdInfo: ForwardingInfo{ 176 Network: DecredNetwork, 177 NextHop: nextHop, 178 AmountToForward: lnwire.MilliAtom(amt), 179 OutgoingCTLV: cltv, 180 }, 181 MPP: mpp, 182 AMP: amp, 183 customRecords: customRecords, 184 }, nil 185 } 186 187 // ForwardingInfo returns the basic parameters required for HTLC forwarding, 188 // e.g. amount, cltv, and next hop. 189 func (h *Payload) ForwardingInfo() ForwardingInfo { 190 return h.FwdInfo 191 } 192 193 // NewCustomRecords filters the types parsed from the tlv stream for custom 194 // records. 195 func NewCustomRecords(parsedTypes tlv.TypeMap) record.CustomSet { 196 customRecords := make(record.CustomSet) 197 for t, parseResult := range parsedTypes { 198 if parseResult == nil || t < record.CustomTypeStart { 199 continue 200 } 201 customRecords[uint64(t)] = parseResult 202 } 203 return customRecords 204 } 205 206 // ValidateParsedPayloadTypes checks the types parsed from a hop payload to 207 // ensure that the proper fields are either included or omitted. The finalHop 208 // boolean should be true if the payload was parsed for an exit hop. The 209 // requirements for this method are described in BOLT 04. 210 func ValidateParsedPayloadTypes(parsedTypes tlv.TypeMap, 211 nextHop lnwire.ShortChannelID) error { 212 213 isFinalHop := nextHop == Exit 214 215 _, hasAmt := parsedTypes[record.AmtOnionType] 216 _, hasLockTime := parsedTypes[record.LockTimeOnionType] 217 _, hasNextHop := parsedTypes[record.NextHopOnionType] 218 _, hasMPP := parsedTypes[record.MPPOnionType] 219 _, hasAMP := parsedTypes[record.AMPOnionType] 220 221 switch { 222 223 // All hops must include an amount to forward. 224 case !hasAmt: 225 return ErrInvalidPayload{ 226 Type: record.AmtOnionType, 227 Violation: OmittedViolation, 228 FinalHop: isFinalHop, 229 } 230 231 // All hops must include a cltv expiry. 232 case !hasLockTime: 233 return ErrInvalidPayload{ 234 Type: record.LockTimeOnionType, 235 Violation: OmittedViolation, 236 FinalHop: isFinalHop, 237 } 238 239 // The exit hop should omit the next hop id. If nextHop != Exit, the 240 // sender must have included a record, so we don't need to test for its 241 // inclusion at intermediate hops directly. 242 case isFinalHop && hasNextHop: 243 return ErrInvalidPayload{ 244 Type: record.NextHopOnionType, 245 Violation: IncludedViolation, 246 FinalHop: true, 247 } 248 249 // Intermediate nodes should never receive MPP fields. 250 case !isFinalHop && hasMPP: 251 return ErrInvalidPayload{ 252 Type: record.MPPOnionType, 253 Violation: IncludedViolation, 254 FinalHop: isFinalHop, 255 } 256 257 // Intermediate nodes should never receive AMP fields. 258 case !isFinalHop && hasAMP: 259 return ErrInvalidPayload{ 260 Type: record.AMPOnionType, 261 Violation: IncludedViolation, 262 FinalHop: isFinalHop, 263 } 264 } 265 266 return nil 267 } 268 269 // MultiPath returns the record corresponding the option_mpp parsed from the 270 // onion payload. 271 func (h *Payload) MultiPath() *record.MPP { 272 return h.MPP 273 } 274 275 // AMPRecord returns the record corresponding with option_amp parsed from the 276 // onion payload. 277 func (h *Payload) AMPRecord() *record.AMP { 278 return h.AMP 279 } 280 281 // CustomRecords returns the custom tlv type records that were parsed from the 282 // payload. 283 func (h *Payload) CustomRecords() record.CustomSet { 284 return h.customRecords 285 } 286 287 // getMinRequiredViolation checks for unrecognized required (even) fields in the 288 // standard range and returns the lowest required type. Always returning the 289 // lowest required type allows a failure message to be deterministic. 290 func getMinRequiredViolation(set tlv.TypeMap) *tlv.Type { 291 var ( 292 requiredViolation bool 293 minRequiredViolationType tlv.Type 294 ) 295 for t, parseResult := range set { 296 // If a type is even but not known to us, we cannot process the 297 // payload. We are required to understand a field that we don't 298 // support. 299 // 300 // We always accept custom fields, because a higher level 301 // application may understand them. 302 if parseResult == nil || t%2 != 0 || 303 t >= record.CustomTypeStart { 304 305 continue 306 } 307 308 if !requiredViolation || t < minRequiredViolationType { 309 minRequiredViolationType = t 310 } 311 requiredViolation = true 312 } 313 314 if requiredViolation { 315 return &minRequiredViolationType 316 } 317 318 return nil 319 }