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  }