github.com/decred/dcrlnd@v0.7.6/lnrpc/invoicesrpc/utils.go (about) 1 package invoicesrpc 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 7 "github.com/decred/dcrd/chaincfg/v3" 8 "github.com/decred/dcrd/dcrec/secp256k1/v4" 9 "github.com/decred/dcrlnd/channeldb" 10 "github.com/decred/dcrlnd/lnrpc" 11 "github.com/decred/dcrlnd/lnwire" 12 "github.com/decred/dcrlnd/zpay32" 13 ) 14 15 // decodePayReq decodes the invoice payment request if present. This is needed, 16 // because not all information is stored in dedicated invoice fields. If there 17 // is no payment request present, a dummy request will be returned. This can 18 // happen with just-in-time inserted keysend invoices. 19 func decodePayReq(invoice *channeldb.Invoice, 20 activeNetParams *chaincfg.Params) (*zpay32.Invoice, error) { 21 22 paymentRequest := string(invoice.PaymentRequest) 23 if paymentRequest == "" { 24 preimage := invoice.Terms.PaymentPreimage 25 if preimage == nil { 26 return &zpay32.Invoice{}, nil 27 } 28 hash := [32]byte(preimage.Hash()) 29 return &zpay32.Invoice{ 30 PaymentHash: &hash, 31 }, nil 32 } 33 34 var err error 35 decoded, err := zpay32.Decode(paymentRequest, activeNetParams) 36 if err != nil { 37 return nil, fmt.Errorf("unable to decode payment "+ 38 "request: %v", err) 39 } 40 return decoded, nil 41 42 } 43 44 // CreateRPCInvoice creates an *lnrpc.Invoice from the *channeldb.Invoice. 45 func CreateRPCInvoice(invoice *channeldb.Invoice, 46 activeNetParams *chaincfg.Params) (*lnrpc.Invoice, error) { 47 48 decoded, err := decodePayReq(invoice, activeNetParams) 49 if err != nil { 50 return nil, err 51 } 52 53 var rHash []byte 54 if decoded.PaymentHash != nil { 55 rHash = decoded.PaymentHash[:] 56 } 57 58 var descHash []byte 59 if decoded.DescriptionHash != nil { 60 descHash = decoded.DescriptionHash[:] 61 } 62 63 fallbackAddr := "" 64 if decoded.FallbackAddr != nil { 65 fallbackAddr = decoded.FallbackAddr.String() 66 } 67 68 settleDate := int64(0) 69 if !invoice.SettleDate.IsZero() { 70 settleDate = invoice.SettleDate.Unix() 71 } 72 73 // Convert between the `lnrpc` and `routing` types. 74 routeHints := CreateRPCRouteHints(decoded.RouteHints) 75 76 preimage := invoice.Terms.PaymentPreimage 77 atomsAmt := invoice.Terms.Value.ToAtoms() 78 atomsAmtPaid := invoice.AmtPaid.ToAtoms() 79 80 isSettled := invoice.State == channeldb.ContractSettled 81 82 var state lnrpc.Invoice_InvoiceState 83 switch invoice.State { 84 case channeldb.ContractOpen: 85 state = lnrpc.Invoice_OPEN 86 case channeldb.ContractSettled: 87 state = lnrpc.Invoice_SETTLED 88 case channeldb.ContractCanceled: 89 state = lnrpc.Invoice_CANCELED 90 case channeldb.ContractAccepted: 91 state = lnrpc.Invoice_ACCEPTED 92 default: 93 return nil, fmt.Errorf("unknown invoice state %v", 94 invoice.State) 95 } 96 97 rpcHtlcs := make([]*lnrpc.InvoiceHTLC, 0, len(invoice.Htlcs)) 98 for key, htlc := range invoice.Htlcs { 99 var state lnrpc.InvoiceHTLCState 100 switch htlc.State { 101 case channeldb.HtlcStateAccepted: 102 state = lnrpc.InvoiceHTLCState_ACCEPTED 103 case channeldb.HtlcStateSettled: 104 state = lnrpc.InvoiceHTLCState_SETTLED 105 case channeldb.HtlcStateCanceled: 106 state = lnrpc.InvoiceHTLCState_CANCELED 107 default: 108 return nil, fmt.Errorf("unknown state %v", htlc.State) 109 } 110 111 rpcHtlc := lnrpc.InvoiceHTLC{ 112 ChanId: key.ChanID.ToUint64(), 113 HtlcIndex: key.HtlcID, 114 AcceptHeight: int32(htlc.AcceptHeight), 115 AcceptTime: htlc.AcceptTime.Unix(), 116 ExpiryHeight: int32(htlc.Expiry), 117 AmtMAtoms: uint64(htlc.Amt), 118 State: state, 119 CustomRecords: htlc.CustomRecords, 120 MppTotalAmtMAtoms: uint64(htlc.MppTotalAmt), 121 } 122 123 // Populate any fields relevant to AMP payments. 124 if htlc.AMP != nil { 125 rootShare := htlc.AMP.Record.RootShare() 126 setID := htlc.AMP.Record.SetID() 127 128 var preimage []byte 129 if htlc.AMP.Preimage != nil { 130 preimage = htlc.AMP.Preimage[:] 131 } 132 133 rpcHtlc.Amp = &lnrpc.AMP{ 134 RootShare: rootShare[:], 135 SetId: setID[:], 136 ChildIndex: htlc.AMP.Record.ChildIndex(), 137 Hash: htlc.AMP.Hash[:], 138 Preimage: preimage, 139 } 140 } 141 142 // Only report resolved times if htlc is resolved. 143 if htlc.State != channeldb.HtlcStateAccepted { 144 rpcHtlc.ResolveTime = htlc.ResolveTime.Unix() 145 } 146 147 rpcHtlcs = append(rpcHtlcs, &rpcHtlc) 148 } 149 150 isAmp := invoice.Terms.Features.HasFeature(lnwire.AMPOptional) 151 152 rpcInvoice := &lnrpc.Invoice{ 153 Memo: string(invoice.Memo[:]), 154 RHash: rHash, 155 Value: int64(atomsAmt), 156 ValueMAtoms: int64(invoice.Terms.Value), 157 CreationDate: invoice.CreationDate.Unix(), 158 SettleDate: settleDate, 159 Settled: isSettled, 160 PaymentRequest: string(invoice.PaymentRequest), 161 DescriptionHash: descHash, 162 Expiry: int64(invoice.Terms.Expiry.Seconds()), 163 CltvExpiry: uint64(invoice.Terms.FinalCltvDelta), 164 FallbackAddr: fallbackAddr, 165 RouteHints: routeHints, 166 AddIndex: invoice.AddIndex, 167 Private: len(routeHints) > 0, 168 SettleIndex: invoice.SettleIndex, 169 AmtPaidAtoms: int64(atomsAmtPaid), 170 AmtPaidMAtoms: int64(invoice.AmtPaid), 171 AmtPaid: int64(invoice.AmtPaid), 172 State: state, 173 Htlcs: rpcHtlcs, 174 Features: CreateRPCFeatures(invoice.Terms.Features), 175 IsKeysend: len(invoice.PaymentRequest) == 0 && !isAmp, 176 PaymentAddr: invoice.Terms.PaymentAddr[:], 177 IsAmp: isAmp, 178 } 179 180 rpcInvoice.AmpInvoiceState = make(map[string]*lnrpc.AMPInvoiceState) 181 for setID, ampState := range invoice.AMPState { 182 183 setIDStr := hex.EncodeToString(setID[:]) 184 185 var state lnrpc.InvoiceHTLCState 186 switch ampState.State { 187 case channeldb.HtlcStateAccepted: 188 state = lnrpc.InvoiceHTLCState_ACCEPTED 189 case channeldb.HtlcStateSettled: 190 state = lnrpc.InvoiceHTLCState_SETTLED 191 case channeldb.HtlcStateCanceled: 192 state = lnrpc.InvoiceHTLCState_CANCELED 193 default: 194 return nil, fmt.Errorf("unknown state %v", ampState.State) 195 } 196 197 rpcInvoice.AmpInvoiceState[setIDStr] = &lnrpc.AMPInvoiceState{ 198 State: state, 199 SettleIndex: ampState.SettleIndex, 200 SettleTime: ampState.SettleDate.Unix(), 201 AmtPaidMAtoms: int64(ampState.AmtPaid), 202 } 203 204 // If at least one of the present HTLC sets show up as being 205 // settled, then we'll mark the invoice itself as being 206 // settled. 207 if ampState.State == channeldb.HtlcStateSettled { 208 rpcInvoice.Settled = true // nolint:staticcheck 209 rpcInvoice.State = lnrpc.Invoice_SETTLED 210 } 211 } 212 213 if preimage != nil { 214 rpcInvoice.RPreimage = preimage[:] 215 } 216 217 return rpcInvoice, nil 218 } 219 220 // CreateRPCFeatures maps a feature vector into a list of lnrpc.Features. 221 func CreateRPCFeatures(fv *lnwire.FeatureVector) map[uint32]*lnrpc.Feature { 222 if fv == nil { 223 return nil 224 } 225 226 features := fv.Features() 227 rpcFeatures := make(map[uint32]*lnrpc.Feature, len(features)) 228 for bit := range features { 229 rpcFeatures[uint32(bit)] = &lnrpc.Feature{ 230 Name: fv.Name(bit), 231 IsRequired: bit.IsRequired(), 232 IsKnown: fv.IsKnown(bit), 233 } 234 } 235 236 return rpcFeatures 237 } 238 239 // CreateRPCRouteHints takes in the decoded form of an invoice's route hints 240 // and converts them into the lnrpc type. 241 func CreateRPCRouteHints(routeHints [][]zpay32.HopHint) []*lnrpc.RouteHint { 242 var res []*lnrpc.RouteHint 243 244 for _, route := range routeHints { 245 hopHints := make([]*lnrpc.HopHint, 0, len(route)) 246 for _, hop := range route { 247 pubKey := hex.EncodeToString( 248 hop.NodeID.SerializeCompressed(), 249 ) 250 251 hint := &lnrpc.HopHint{ 252 NodeId: pubKey, 253 ChanId: hop.ChannelID, 254 FeeBaseMAtoms: hop.FeeBaseMAtoms, 255 FeeProportionalMillionths: hop.FeeProportionalMillionths, 256 CltvExpiryDelta: uint32(hop.CLTVExpiryDelta), 257 } 258 259 hopHints = append(hopHints, hint) 260 } 261 262 routeHint := &lnrpc.RouteHint{HopHints: hopHints} 263 res = append(res, routeHint) 264 } 265 266 return res 267 } 268 269 // CreateZpay32HopHints takes in the lnrpc form of route hints and converts them 270 // into an invoice decoded form. 271 func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, error) { 272 var res [][]zpay32.HopHint 273 for _, route := range routeHints { 274 hopHints := make([]zpay32.HopHint, 0, len(route.HopHints)) 275 for _, hop := range route.HopHints { 276 pubKeyBytes, err := hex.DecodeString(hop.NodeId) 277 if err != nil { 278 return nil, err 279 } 280 p, err := secp256k1.ParsePubKey(pubKeyBytes) 281 if err != nil { 282 return nil, err 283 } 284 hopHints = append(hopHints, zpay32.HopHint{ 285 NodeID: p, 286 ChannelID: hop.ChanId, 287 FeeBaseMAtoms: hop.FeeBaseMAtoms, 288 FeeProportionalMillionths: hop.FeeProportionalMillionths, 289 CLTVExpiryDelta: uint16(hop.CltvExpiryDelta), 290 }) 291 } 292 res = append(res, hopHints) 293 } 294 return res, nil 295 }