Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LN] Allow multiple in-flight payments #2491

Merged
merged 6 commits into from
Jun 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 9 additions & 36 deletions app/actions/LNActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,11 @@ export const startDcrlnd = (
const {
grpc: { port }
} = getState();
const {
daemon: { credentials, appData, walletName }
} = getState();
const { daemon: { walletName } } = getState();
const isTestnet = sel.isTestNet(getState());
const walletPath = getWalletPath(isTestnet, walletName);
const walletPort = port;
const rpcCreds = {};

const cfg = getWalletCfg(isTestnet, walletName);
const lnCfg = dispatch(getLNWalletConfig());
const cliOptions = ipcRenderer.sendSync("get-cli-options");

// Use the stored account if it exists, the specified account if it's a number
// or create an account specifically for LN usage.
Expand All @@ -65,30 +59,7 @@ export const startDcrlnd = (
lnAccount = walletAccount;
}

if (cliOptions.rpcPresent) {
rpcCreds.rpc_user = cliOptions.rpcUser;
rpcCreds.rpc_pass = cliOptions.rpcPass;
rpcCreds.rpc_cert = cliOptions.rpcCert;
rpcCreds.rpc_host = cliOptions.rpcHost;
rpcCreds.rpc_port = cliOptions.rpcPort;
} else if (credentials) {
rpcCreds.rpc_user = credentials.rpc_user;
rpcCreds.rpc_cert = credentials.rpc_cert;
rpcCreds.rpc_pass = credentials.rpc_pass;
rpcCreds.rpc_host = credentials.rpc_host;
rpcCreds.rpc_port = credentials.rpc_port;
} else if (appData) {
rpcCreds.rpc_user = cfg.get("rpc_user");
rpcCreds.rpc_pass = cfg.get("rpc_pass");
rpcCreds.rpc_cert = `${appData}/rpc.cert`;
rpcCreds.rpc_host = cfg.get("rpc_host");
rpcCreds.rpc_port = cfg.get("rpc_port");
} else {
rpcCreds.rpc_user = cfg.get("rpc_user");
rpcCreds.rpc_pass = cfg.get("rpc_pass");
rpcCreds.rpc_host = cfg.get("rpc_host");
rpcCreds.rpc_port = cfg.get("rpc_port");
}
const rpcCreds = ipcRenderer.sendSync("get-dcrd-rpc-credentials");

try {
const res = ipcRenderer.sendSync(
Expand Down Expand Up @@ -253,8 +224,8 @@ export const waitForDcrlndSynced = (lnClient) => async () => {

for (let i = 0; i < sleepCount; i++) {
const info = await ln.getInfo(lnClient);
if (info.syncedToChain) {
sleep(); // Final sleep to let subsystems catch up.
if (info.serverActive) {
await sleep(); // Final sleep to let subsystems catch up.
return;
}
await sleep();
Expand Down Expand Up @@ -551,8 +522,9 @@ const createPaymentStream = () => (dispatch, getState) => {
// was completed or errored.
const outPayments = getState().ln.outstandingPayments;
const rhashHex = Buffer.from(pay.paymentHash, "base64").toString("hex");
if (outPayments[rhashHex]) {
const { resolve, reject } = outPayments[rhashHex];
const prevOutPayment = outPayments[rhashHex];
if (prevOutPayment) {
const { resolve, reject } = prevOutPayment;
if (pay.paymentError) {
reject(new Error(pay.paymentError));
} else {
Expand All @@ -564,6 +536,7 @@ const createPaymentStream = () => (dispatch, getState) => {
dispatch({
error: pay.paymentError,
rhashHex,
payData: { paymentError: pay.paymentError, ...prevOutPayment },
type: LNWALLET_SENDPAYMENT_FAILED
});
} else {
Expand Down Expand Up @@ -599,7 +572,7 @@ export const sendPayment = (payReq, value) => (dispatch, getState) => {
ln.sendPayment(payStream, payReq, value);
})
.catch((error) => {
dispatch({ error, type: LNWALLET_SENDPAYMENT_FAILED });
dispatch({ error, rhashHex: null, type: LNWALLET_SENDPAYMENT_FAILED });
reject(error);
});
});
Expand Down
94 changes: 83 additions & 11 deletions app/components/views/LNPage/PaymentsTab/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FormattedMessage as T } from "react-intl";
import { Balance, FormattedRelative } from "shared";
import { KeyBlueButton } from "buttons";
import { TextInput, DcrInput } from "inputs";
import { SimpleLoading } from "indicators";

const Payment = ({ payment, tsDate }) => (
<div className="ln-payment">
Expand All @@ -26,6 +27,51 @@ const Payment = ({ payment, tsDate }) => (
</div>
);

const OutstandingPayment = ({ payment, tsDate }) => (
<div className="ln-payment outstanding">
<div>
<div className="value">
<Balance amount={payment.numAtoms} />
</div>
</div>
<div>
<div>
<T
id="ln.paymentsTab.outstanding.creationDate"
m="{creationDate, date, medium} {creationDate, time, short}"
values={{ creationDate: tsDate(payment.timestamp) }}
/>
</div>
<div className="rhash">{payment.paymentHash}</div>
</div>
<SimpleLoading />
</div>
);

const FailedPayment = ({ payment, paymentError, tsDate }) => (
<div className="ln-payment failed">
<div>
<div className="value">
<Balance amount={payment.numAtoms} />
</div>
</div>
<div>
<div>
<T
id="ln.paymentsTab.failed.creationDate"
m="{creationDate, date, medium} {creationDate, time, short}"
values={{ creationDate: tsDate(payment.timestamp) }}
/>
</div>
<div className="rhash">{payment.paymentHash}</div>
</div>
<div></div>
<div class="payment-error">{paymentError}</div>
</div>
);



const EmptyDescription = () => (
<div className="empty-description">
<T id="ln.paymentsTab.emptyDescr" m="(empty description)" />
Expand Down Expand Up @@ -94,12 +140,13 @@ const DecodedPayRequest = ({

export default ({
payments,
outstandingPayments,
failedPayments,
tsDate,
payRequest,
decodedPayRequest,
decodingError,
expired,
sending,
sendValue,
onPayRequestChanged,
onSendPayment,
Expand All @@ -113,11 +160,7 @@ export default ({
<div className="ln-send-payment">
<div className="payreq">
<T id="ln.paymentsTab.payReq" m="Payment Request" />
<TextInput
disabled={sending}
value={payRequest}
onChange={onPayRequestChanged}
/>
<TextInput value={payRequest} onChange={onPayRequestChanged} />
</div>
{decodingError ? (
<div className="decoding-error">{"" + decodingError}</div>
Expand All @@ -131,17 +174,46 @@ export default ({
sendValue={sendValue}
onSendValueChanged={onSendValueChanged}
/>
<KeyBlueButton
loading={sending}
disabled={sending || expired}
className="sendpayment"
onClick={onSendPayment}>
<KeyBlueButton className="sendpayment" onClick={onSendPayment}>
<T id="ln.paymentsTab.sendBtn" m="Send" />
</KeyBlueButton>
</>
) : null}
</div>

{Object.keys(outstandingPayments).length > 0 ? (
<h2 className="ln-payments-subheader">
<T id="ln.paymentsTab.outstanding" m="Ongoing Payments" />
</h2>
) : null}

<div className="ln-payments-list">
{Object.keys(outstandingPayments).map((ph) => (
<OutstandingPayment
payment={outstandingPayments[ph].decoded}
key={"outstanding-" + ph}
tsDate={tsDate}
/>
))}
</div>

{failedPayments.length > 0 ? (
<h2 className="ln-payments-subheader">
<T id="ln.paymentsTag.failed" m="Failed Payments" />
</h2>
) : null}

<div className="ln-payments-list">
{failedPayments.map(p => (
<FailedPayment
payment={p.decoded}
paymentError={p.paymentError}
key={"failed-"+p.decoded.paymentHash}
tsDate={tsDate}
/>
))}
</div>

<h2 className="ln-payments-subheader">
<T id="ln.paymentsTab.latestPayments" m="Latest Payments" />
</h2>
Expand Down
24 changes: 9 additions & 15 deletions app/components/views/LNPage/PaymentsTab/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,16 @@ class PaymentsTab extends React.Component {
}

const { payRequest, sendValueAtom } = this.state;
this.setState({ sending: true });
this.props
.sendPayment(payRequest, sendValueAtom)
.then(() => {
this.setState({
sending: false,
payRequest: "",
decodedPayRequest: null,
sendValue: 0
});
})
.catch(() => {
this.setState({ sending: false });
});
this.setState({
payRequest: "",
decodedPayRequest: null,
sendValue: 0
});
this.props.sendPayment(payRequest, sendValueAtom);
}

render() {
const { payments, tsDate } = this.props;
const { payments, outstandingPayments, failedPayments, tsDate } = this.props;
const {
payRequest,
decodedPayRequest,
Expand All @@ -116,6 +108,8 @@ class PaymentsTab extends React.Component {
return (
<Page
payments={payments}
outstandingPayments={outstandingPayments}
failedPayments={failedPayments}
tsDate={tsDate}
payRequest={payRequest}
decodedPayRequest={decodedPayRequest}
Expand Down
2 changes: 2 additions & 0 deletions app/connectors/lnPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const mapStateToProps = selectorMap({
closedChannels: sel.lnClosedChannels,
invoices: sel.lnInvoices,
payments: sel.lnPayments,
outstandingPayments: sel.lnOutstandingPayments,
failedPayments: sel.lnFailedPayments,
tsDate: sel.tsDate,
addInvoiceAttempt: sel.lnAddInvoiceAttempt,
info: sel.lnInfo,
Expand Down
2 changes: 2 additions & 0 deletions app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,8 @@ const initialState = {
closedChannels: Array(),
invoices: Array(),
payments: Array(),
outstandingPayments: {}, // map paymentHash => payment data
failedPayments: Array(),
addInvoiceAttempt: false,
sendPaymentAttempt: false
},
Expand Down
39 changes: 18 additions & 21 deletions app/middleware/ln/google/api/annotations_pb.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,36 @@
*/
// GENERATED CODE -- DO NOT EDIT!

var jspb = require("google-protobuf");
var jspb = require('google-protobuf');
var goog = jspb;
var global = Function("return this")();
var global = Function('return this')();

var google_api_http_pb = require("../../google/api/http_pb.js");
var google_protobuf_descriptor_pb = require("google-protobuf/google/protobuf/descriptor_pb.js");
goog.exportSymbol("proto.google.api.http", null, global);
var google_api_http_pb = require('../../google/api/http_pb.js');
var google_protobuf_descriptor_pb = require('google-protobuf/google/protobuf/descriptor_pb.js');
goog.exportSymbol('proto.google.api.http', null, global);

/**
* A tuple of {field number, class constructor} for the extension
* field named `http`.
* @type {!jspb.ExtensionFieldInfo.<!proto.google.api.HttpRule>}
*/
proto.google.api.http = new jspb.ExtensionFieldInfo(
72295728,
{ http: 0 },
google_api_http_pb.HttpRule,
/** @type {?function((boolean|undefined),!jspb.Message=): !Object} */ (google_api_http_pb
.HttpRule.toObject),
0
);
72295728,
{http: 0},
google_api_http_pb.HttpRule,
/** @type {?function((boolean|undefined),!jspb.Message=): !Object} */ (
google_api_http_pb.HttpRule.toObject),
0);

google_protobuf_descriptor_pb.MethodOptions.extensionsBinary[72295728] = new jspb.ExtensionFieldBinaryInfo(
proto.google.api.http,
jspb.BinaryReader.prototype.readMessage,
jspb.BinaryWriter.prototype.writeMessage,
google_api_http_pb.HttpRule.serializeBinaryToWriter,
google_api_http_pb.HttpRule.deserializeBinaryFromReader,
false
);
proto.google.api.http,
jspb.BinaryReader.prototype.readMessage,
jspb.BinaryWriter.prototype.writeMessage,
google_api_http_pb.HttpRule.serializeBinaryToWriter,
google_api_http_pb.HttpRule.deserializeBinaryFromReader,
false);
// This registers the extension field with the extended class, so that
// toObject() will function correctly.
google_protobuf_descriptor_pb.MethodOptions.extensions[72295728] =
proto.google.api.http;
google_protobuf_descriptor_pb.MethodOptions.extensions[72295728] = proto.google.api.http;

goog.object.extend(exports, proto.google.api);
Loading