package lnrpc import ( "encoding/hex" "errors" "fmt" "sort" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/sweep" "google.golang.org/protobuf/encoding/protojson" ) const ( // RegisterRPCMiddlewareURI is the full RPC method URI for the // middleware registration call. This is declared here rather than where // it's mainly used to avoid circular package dependencies. RegisterRPCMiddlewareURI = "/lnrpc.Lightning/RegisterRPCMiddleware" ) var ( // ProtoJSONMarshalOpts is a struct that holds the default marshal // options for marshaling protobuf messages into JSON in a // human-readable way. This should only be used in the CLI and in // integration tests. ProtoJSONMarshalOpts = &protojson.MarshalOptions{ EmitUnpopulated: true, UseProtoNames: true, Indent: " ", UseHexForBytes: true, } // ProtoJSONUnmarshalOpts is a struct that holds the default unmarshal // options for un-marshaling lncli JSON into protobuf messages. This // should only be used in the CLI and in integration tests. ProtoJSONUnmarshalOpts = &protojson.UnmarshalOptions{ AllowPartial: false, UseHexForBytes: true, } // RESTJsonMarshalOpts is a struct that holds the default marshal // options for marshaling protobuf messages into REST JSON in a // human-readable way. This should be used when interacting with the // REST proxy only. RESTJsonMarshalOpts = &protojson.MarshalOptions{ EmitUnpopulated: true, UseProtoNames: true, } // RESTJsonUnmarshalOpts is a struct that holds the default unmarshal // options for un-marshaling REST JSON into protobuf messages. This // should be used when interacting with the REST proxy only. RESTJsonUnmarshalOpts = &protojson.UnmarshalOptions{ AllowPartial: false, } ) // RPCTransaction returns a rpc transaction. func RPCTransaction(tx *lnwallet.TransactionDetail) *Transaction { var destAddresses []string // Re-package destination output information. var outputDetails []*OutputDetail for _, o := range tx.OutputDetails { // Note: DestAddresses is deprecated but we keep // populating it with addresses for backwards // compatibility. for _, a := range o.Addresses { destAddresses = append(destAddresses, a.EncodeAddress()) } var address string if len(o.Addresses) == 1 { address = o.Addresses[0].EncodeAddress() } outputDetails = append(outputDetails, &OutputDetail{ OutputType: MarshallOutputType(o.OutputType), Address: address, PkScript: hex.EncodeToString(o.PkScript), OutputIndex: int64(o.OutputIndex), Amount: int64(o.Value), IsOurAddress: o.IsOurAddress, }) } previousOutpoints := make([]*PreviousOutPoint, len(tx.PreviousOutpoints)) for idx, previousOutPoint := range tx.PreviousOutpoints { previousOutpoints[idx] = &PreviousOutPoint{ Outpoint: previousOutPoint.OutPoint, IsOurOutput: previousOutPoint.IsOurOutput, } } // We also get unconfirmed transactions, so BlockHash can be nil. blockHash := "" if tx.BlockHash != nil { blockHash = tx.BlockHash.String() } return &Transaction{ TxHash: tx.Hash.String(), Amount: int64(tx.Value), NumConfirmations: tx.NumConfirmations, BlockHash: blockHash, BlockHeight: tx.BlockHeight, TimeStamp: tx.Timestamp, TotalFees: tx.TotalFees, DestAddresses: destAddresses, OutputDetails: outputDetails, RawTxHex: hex.EncodeToString(tx.RawTx), Label: tx.Label, PreviousOutpoints: previousOutpoints, } } // RPCTransactionDetails returns a set of rpc transaction details. func RPCTransactionDetails(txns []*lnwallet.TransactionDetail) *TransactionDetails { txDetails := &TransactionDetails{ Transactions: make([]*Transaction, len(txns)), } for i, tx := range txns { txDetails.Transactions[i] = RPCTransaction(tx) } // Sort transactions by number of confirmations rather than height so // that unconfirmed transactions (height =0; confirmations =-1) will // follow the most recently set of confirmed transactions. If we sort // by height, unconfirmed transactions will follow our oldest // transactions, because they have lower block heights. sort.Slice(txDetails.Transactions, func(i, j int) bool { return txDetails.Transactions[i].NumConfirmations < txDetails.Transactions[j].NumConfirmations }) return txDetails } // ExtractMinConfs extracts the minimum number of confirmations that each // output used to fund a transaction should satisfy. func ExtractMinConfs(minConfs int32, spendUnconfirmed bool) (int32, error) { switch { // Ensure that the MinConfs parameter is non-negative. case minConfs < 0: return 0, errors.New("minimum number of confirmations must " + "be a non-negative number") // The transaction should not be funded with unconfirmed outputs // unless explicitly specified by SpendUnconfirmed. We do this to // provide sane defaults to the OpenChannel RPC, as otherwise, if the // MinConfs field isn't explicitly set by the caller, we'll use // unconfirmed outputs without the caller being aware. case minConfs == 0 && !spendUnconfirmed: return 1, nil // In the event that the caller set MinConfs > 0 and SpendUnconfirmed to // true, we'll return an error to indicate the conflict. case minConfs > 0 && spendUnconfirmed: return 0, errors.New("SpendUnconfirmed set to true with " + "MinConfs > 0") // The funding transaction of the new channel to be created can be // funded with unconfirmed outputs. case spendUnconfirmed: return 0, nil // If none of the above cases matched, we'll return the value set // explicitly by the caller. default: return minConfs, nil } } // GetChanPointFundingTxid returns the given channel point's funding txid in // raw bytes. func GetChanPointFundingTxid(chanPoint *ChannelPoint) (*chainhash.Hash, error) { var txid []byte // A channel point's funding txid can be get/set as a byte slice or a // string. In the case it is a string, decode it. switch chanPoint.GetFundingTxid().(type) { case *ChannelPoint_FundingTxidBytes: txid = chanPoint.GetFundingTxidBytes() case *ChannelPoint_FundingTxidStr: s := chanPoint.GetFundingTxidStr() h, err := chainhash.NewHashFromStr(s) if err != nil { return nil, err } txid = h[:] } return chainhash.NewHash(txid) } // GetChannelOutPoint returns the outpoint of the related channel point. func GetChannelOutPoint(chanPoint *ChannelPoint) (*OutPoint, error) { var txid []byte // A channel point's funding txid can be get/set as a byte slice or a // string. In the case it is a string, decode it. switch chanPoint.GetFundingTxid().(type) { case *ChannelPoint_FundingTxidBytes: txid = chanPoint.GetFundingTxidBytes() case *ChannelPoint_FundingTxidStr: s := chanPoint.GetFundingTxidStr() h, err := chainhash.NewHashFromStr(s) if err != nil { return nil, err } txid = h[:] } return &OutPoint{ TxidBytes: txid, OutputIndex: chanPoint.OutputIndex, }, nil } // CalculateFeeRate uses either satPerByte or satPerVByte, but not both, from a // request to calculate the fee rate. It provides compatibility for the // deprecated field, satPerByte. Once the field is safe to be removed, the // check can then be deleted. func CalculateFeeRate(satPerByte, satPerVByte uint64, targetConf uint32, estimator chainfee.Estimator) (chainfee.SatPerKWeight, error) { var feeRate chainfee.SatPerKWeight // We only allow using either the deprecated field or the new field. if satPerByte != 0 && satPerVByte != 0 { return feeRate, fmt.Errorf("either SatPerByte or " + "SatPerVByte should be set, but not both") } // Default to satPerVByte, and overwrite it if satPerByte is set. satPerKw := chainfee.SatPerKVByte(satPerVByte * 1000).FeePerKWeight() if satPerByte != 0 { satPerKw = chainfee.SatPerKVByte( satPerByte * 1000, ).FeePerKWeight() } // Based on the passed fee related parameters, we'll determine an // appropriate fee rate for this transaction. feePref := sweep.FeeEstimateInfo{ ConfTarget: targetConf, FeeRate: satPerKw, } // TODO(yy): need to pass the configured max fee here. feeRate, err := feePref.Estimate(estimator, 0) if err != nil { return feeRate, err } return feeRate, nil }