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

[FEATURE] conf target #23

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions frontend/additional/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,22 @@ const validateSeed = (seed) => {
return null;
};

/**
* @param {number} conf
* @param {boolean} required
* @returns {string|null}
*/
const validateConfTarget = (conf, required = false) => {
if (required && !conf) {
return statusCodes.EXCEPTION_FIELD_IS_REQUIRED;
} else if (required && !Number.isInteger(conf)) {
return statusCodes.EXCEPTION_FIELD_DIGITS_ONLY;
} else if (!required && !Number.isInteger(conf)) {
return statusCodes.EXCEPTION_FIELD_DIGITS_ONLY;
}
return null;
};

export {
validateBitcoinAddr,
validateChannelHost,
Expand All @@ -184,4 +200,5 @@ export {
validatePassSeed,
validateSeed,
validateUserExistence,
validateConfTarget,
};
45 changes: 40 additions & 5 deletions frontend/components/channels/modal/create-channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Checkbox from "components/ui/checkbox";
import ErrorFieldTooltip from "components/ui/error-field-tooltip";
import { channelsOperations as operations, channelsSelectors as selectors } from "modules/channels";
import { error, info } from "modules/notifications";
import { MAX_CHANNEL_SIZE, ELEMENT_NAME_MAX_LENGTH, MIN_CHANNEL_SIZE } from "config/consts";
import { MAX_CHANNEL_SIZE, ELEMENT_NAME_MAX_LENGTH, MIN_CHANNEL_SIZE, CHANNEL_OPEN_CONFIRMATION } from "config/consts";
import { statusCodes } from "config";
import { PEACH } from "config/node-settings";
import { ChannelsFullPath } from "routes";
Expand All @@ -21,6 +21,7 @@ class CreateChannel extends Component {
super(props);
this.state = {
amountError: null,
confTargetError: null,
custom: this.props.prepareNewChannel ? this.props.prepareNewChannel.custom : false,
lightningError: null,
nameError: null,
Expand Down Expand Up @@ -101,25 +102,30 @@ class CreateChannel extends Component {
let name = this.channel__name.value.trim();
let amount = parseFloat(this.channel__amount.value.trim());
const lightning = this.channel__lightningId.value.trim();
let confTarget = parseInt(this.channel__conf_target.value.trim(), 10);
confTarget = Number.isNaN(confTarget) || confTarget < 0 ? CHANNEL_OPEN_CONFIRMATION : confTarget;
let nameError = validators.validateName(name);
const amountError = this._validateAmount(amount);
const lightningError = this.state.custom ? validators.validateChannelHost(lightning) : null;
const confTargetError = validators.validateConfTarget(confTarget);
if (channels) {
channels.forEach((channel) => {
if (name === channel.name) {
nameError = statusCodes.EXCEPTION_CHANNEL_CREATE_CHANNEL_EXISTS;
}
});
}
if (nameError || amountError || lightningError) {
if (nameError || amountError || lightningError || confTargetError) {
this.setState({
amountError, lightningError, nameError, processing: false,
amountError, confTargetError, lightningError, nameError, processing: false,
});
return;
}
let lightningId = PEACH.pubKey;
let peer = null;
this.setState({ amountError, lightningError, nameError });
this.setState({
amountError, confTargetError, lightningError, nameError,
});
if (this.state.custom) {
const [tempLight, tempPeer] = lightning.split("@");
lightningId = tempLight;
Expand All @@ -130,7 +136,14 @@ class CreateChannel extends Component {
amount = dispatch(appOperations.convertToSatoshi(amount));
name = name || `CHANNEL ${firstEmptyChannelDefaultName}`;
dispatch(appOperations.closeModal());
let response = await dispatch(operations.prepareNewChannel(lightningId, amount, peer, name, this.state.custom));
let response = await dispatch(operations.prepareNewChannel(
lightningId,
amount,
peer,
name,
this.state.custom,
confTarget,
));
if (!response.ok) {
this.showErrorNotification(response.error);
return;
Expand Down Expand Up @@ -218,6 +231,28 @@ class CreateChannel extends Component {
<ErrorFieldTooltip text={this.state.amountError} />
</div>
</div>
<div className="row mt-14">
<div className="col-xs-12">
<div className="form-label">
<label htmlFor="channel__conf_target">Amount of block confirmations</label>
</div>
</div>
<div className="col-xs-12">
<DigitsField
id="channel__conf_target"
className={`form-text ${this.state.confTargetError ? "form-text__error" : ""}`}
name="conf_target"
placeholder={`${CHANNEL_OPEN_CONFIRMATION} blocks`}
setRef={(ref) => {
this.channel__conf_target = ref;
}}
value={prepareNewChannel ? prepareNewChannel.confTarget : null}
disabled={this.state.processing}
setOnChange={() => { this.setState({ confTargetError: null }) }}
/>
<ErrorFieldTooltip text={this.state.confTargetError} />
</div>
</div>
<div className="row mt-14">
<div className="col-xs-12">
<div className="form-label">
Expand Down
39 changes: 34 additions & 5 deletions frontend/components/onchain/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ELEMENT_NAME_MAX_LENGTH,
LIGHTNING_ID_LENGTH,
SIMNET_NETWORK,
ONCHAIN_SEND_COINS_CONFIRMATION,
} from "config/consts";
import { BITCOIN_SETTINGS } from "config/node-settings";
import ErrorFieldTooltip from "components/ui/error-field-tooltip";
Expand All @@ -33,6 +34,7 @@ class Onchain extends Component {
this.state = {
amount: null,
amountError: null,
confTargetError: null,
nameError: null,
toError: null,
};
Expand All @@ -55,19 +57,22 @@ class Onchain extends Component {
const { dispatch } = this.props;
const name = this.name.value.trim();
const to = this.to.value.trim();
let confTarget = parseInt(this.confTarget.value.trim(), 10);
confTarget = Number.isNaN(confTarget) || confTarget < 0 ? ONCHAIN_SEND_COINS_CONFIRMATION : confTarget;
let amount = parseFloat(this.amount.value.trim());
const nameError = validators.validateName(name, false, true, true, undefined, true);
const toError = this.validateTo(to);
const confTargetError = validators.validateConfTarget(confTarget);
const amountError = dispatch(accountOperations.checkAmount(amount, "bitcoin"));

if (nameError || toError || amountError) {
this.setState({ amountError, nameError, toError });
this.setState({
amountError, confTargetError, nameError, toError,
});
if (nameError || toError || amountError || confTargetError) {
return;
}

this.setState({ amountError, nameError, toError });
amount = dispatch(appOperations.convertToSatoshi(amount));
const response = await dispatch(operations.prepareSendCoins(to, amount, name));
const response = await dispatch(operations.prepareSendCoins(to, amount, name, confTarget));
if (!response.ok) {
this.setState({ amountError: response.error });
return;
Expand Down Expand Up @@ -102,6 +107,7 @@ class Onchain extends Component {
this.setState({ amount: null });
this.form.reset();
this.amountComponent.reset();
this.confTargetComponent.reset();
};

renderOnchain = () => {
Expand Down Expand Up @@ -195,6 +201,29 @@ class Onchain extends Component {
</div>
<div className="col-xs-12" />
</div>
<div className="row mt-14">
<div className="col-xs-12">
<div className="form-label">
<label htmlFor="channel__conf_target">Amount of block confirmations</label>
</div>
</div>
<div className="col-xs-12">
<DigitsField
id="channel__conf_target"
className={`form-text ${this.state.confTargetError ? "form-text__error" : ""}`}
name="conf_target"
placeholder={`${ONCHAIN_SEND_COINS_CONFIRMATION} blocks`}
ref={(ref) => {
this.confTargetComponent = ref;
}}
setRef={(ref) => {
this.confTarget = ref;
}}
setOnChange={() => { this.setState({ confTargetError: null }) }}
/>
<ErrorFieldTooltip text={this.state.confTargetError} />
</div>
</div>
<div className="row mt-30">
<div className="col-xs-12 text-right">
{usd}
Expand Down
18 changes: 14 additions & 4 deletions frontend/components/onchain/modal/details.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,17 @@ class OnChainDetails extends Component {
Transaction fee
</div>
<div className="send-form__value">
~ <BalanceWithMeasure satoshi={this.props.fee} />
~ <BalanceWithMeasure satoshi={sendCoinsDetails.fee} />
</div>
</div>
</div>
<div className="row send-form__row">
<div className="col-xs-12">
<div className="send-form__label">
Blocks confirmation
</div>
<div className="send-form__value">
{this.props.sendCoinsDetails.confTarget}
</div>
</div>
</div>
Expand All @@ -108,7 +118,7 @@ class OnChainDetails extends Component {
Amount
</div>
<div className="send-form__value send-form__summary">
<BtcToUsd amount={(sendCoinsDetails.amount + this.props.fee)} />
<BtcToUsd amount={(sendCoinsDetails.amount + sendCoinsDetails.fee)} />
</div>
</div>
</div>
Expand Down Expand Up @@ -145,16 +155,16 @@ class OnChainDetails extends Component {

OnChainDetails.propTypes = {
dispatch: PropTypes.func.isRequired,
fee: PropTypes.number.isRequired,
sendCoinsDetails: PropTypes.shape({
amount: PropTypes.number.isRequired,
confTarget: PropTypes.number.isRequired,
fee: PropTypes.number.isRequired,
name: PropTypes.string,
recepient: PropTypes.string.isRequired,
}).isRequired,
};

const mapStateToProps = state => ({
fee: state.onchain.fee,
sendCoinsDetails: state.onchain.sendCoinsDetails,
});

Expand Down
2 changes: 2 additions & 0 deletions frontend/config/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export const CHANNELS_INTERVAL_TIMEOUT = 30000;
export const LND_SYNC_STATUS_INTERVAL_TIMEOUT = 15000;
export const USD_PER_BTC_INTERVAL_TIMEOUT = 60 * 60 * 1000;
export const CHANNEL_CLOSE_CONFIRMATION = 6;
export const CHANNEL_OPEN_CONFIRMATION = 3;
export const ONCHAIN_SEND_COINS_CONFIRMATION = 0;
export const MIN_CHANNEL_SIZE = 2e4;
export const USERNAME_MAX_LENGTH = 100;
export const ELEMENT_NAME_MAX_LENGTH = 100;
Expand Down
6 changes: 4 additions & 2 deletions frontend/modules/channels/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { statusCodes } from "config";
import { appOperations, appActions, appTypes } from "modules/app";
import { accountOperations, accountTypes } from "modules/account";
import { db, successPromise, errorPromise, logger } from "additional";
import { CHANNEL_CLOSE_CONFIRMATION, CHANNEL_LEFT_AMOUNT_TO_NOTIFY } from "config/consts";
import { CHANNEL_CLOSE_CONFIRMATION, CHANNEL_LEFT_AMOUNT_TO_NOTIFY, CHANNEL_OPEN_CONFIRMATION } from "config/consts";
import { onChainOperations } from "modules/onchain";
import * as actions from "./actions";
import * as types from "./types";
Expand Down Expand Up @@ -285,13 +285,14 @@ function connectPeer(lightningID, peerAddress) {
};
}

function prepareNewChannel(lightningID, capacity, peerAddress, name, custom) {
function prepareNewChannel(lightningID, capacity, peerAddress, name, custom, confTarget = CHANNEL_OPEN_CONFIRMATION) {
return async (dispatch, getState) => {
if (getState().account.kernelConnectIndicator !== accountTypes.KERNEL_CONNECTED) {
return errorPromise(statusCodes.EXCEPTION_ACCOUNT_NO_KERNEL, prepareNewChannel);
}
const newChannel = {
capacity,
confTarget,
custom,
host: peerAddress,
lightningID,
Expand Down Expand Up @@ -400,6 +401,7 @@ function createNewChannel() {
const responseChannels = await window.ipcClient("openChannel", {
local_funding_amount: newChannelDetails.capacity,
node_pubkey_string: newChannelDetails.lightningID,
target_conf: newChannelDetails.confTarget,
});
if (!responseChannels.ok) {
dispatch(actions.errorCreateNewChannel(responseChannels.error));
Expand Down
4 changes: 3 additions & 1 deletion frontend/modules/onchain/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ const setOnChainHistory = history => ({
type: types.SET_ONCHAIN_HISTORY,
});

const sendCoinsPreparing = (recepient, amount, name) => ({
const sendCoinsPreparing = (recepient, amount, name, confTarget, fee) => ({
payload: {
amount,
confTarget,
fee,
name: name || "",
recepient,
},
Expand Down
40 changes: 35 additions & 5 deletions frontend/modules/onchain/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { statusCodes } from "config";
import { appOperations, appActions } from "modules/app";
import { accountOperations, accountTypes } from "modules/account";
import { db, successPromise, errorPromise, unsuccessPromise, logger } from "additional";
import { MAX_DB_NUM_CONFIRMATIONS, TX_NUM_CONFIRMATIONS_TO_SHOW_NOTIFY } from "config/consts";
import {
MAX_DB_NUM_CONFIRMATIONS,
TX_NUM_CONFIRMATIONS_TO_SHOW_NOTIFY,
ONCHAIN_SEND_COINS_CONFIRMATION,
} from "config/consts";
import { store } from "store/configure-store";
import orderBy from "lodash/orderBy";
import keyBy from "lodash/keyBy";
Expand All @@ -11,6 +15,23 @@ import has from "lodash/has";
import * as actions from "./actions";
import * as types from "./types";

/**
* @param {String} addr
* @param {Integer} amount
* @param {Integer} target_conf
* @returns {Object}
*/
async function calculateFee(addr, amount, target_conf = 0) {
const { ok, response } = await window.ipcClient("estimateFee", { AddrToAmount: { [addr]: amount }, target_conf });
if (!ok) {
return { fee_sat: 0, feerate_sat_per_kw: 0 };
}
return {
fee_sat: parseInt(response.fee_sat, 10),
feerate_sat_per_kw: parseInt(response.feerate_sat_per_kw, 10),
};
}

function openSendCoinsModal() {
return dispatch => dispatch(appActions.setModalState(types.MODAL_STATE_SEND_COINS));
}
Expand Down Expand Up @@ -160,12 +181,13 @@ function getOnchainHistory() {
};
}

function prepareSendCoins(recepient, amount, name) {
function prepareSendCoins(recepient, amount, name, confTarget = ONCHAIN_SEND_COINS_CONFIRMATION) {
return async (dispatch, getState) => {
if (getState().account.kernelConnectIndicator !== accountTypes.KERNEL_CONNECTED) {
return unsuccessPromise(prepareSendCoins);
}
dispatch(actions.sendCoinsPreparing(recepient, amount, name));
const fee = await calculateFee(recepient, amount, confTarget);
dispatch(actions.sendCoinsPreparing(recepient, amount, name, confTarget, fee.fee_sat));
return successPromise();
};
}
Expand All @@ -181,10 +203,17 @@ function sendCoins() {
if (!getState().onchain.sendCoinsDetails) {
return errorPromise(statusCodes.EXCEPTION_SEND_COINS_DETAILS_REQUIRED, sendCoins);
}
const { name, recepient, amount } = getState().onchain.sendCoinsDetails;
const {
name,
recepient,
amount,
confTarget,
fee,
} = getState().onchain.sendCoinsDetails;
const response = await window.ipcClient("sendCoins", {
addr: recepient,
amount,
target_conf: confTarget,
});
if (response.ok) {
dispatch(accountOperations.checkBalance());
Expand All @@ -194,7 +223,7 @@ function sendCoins() {
.insert()
.values({
address: recepient,
amount: -amount - getState().onchain.fee,
amount: -amount - fee,
blockHash: "",
blockHeight: 0,
name: name || "Regular payment",
Expand Down Expand Up @@ -238,6 +267,7 @@ window.ipcRenderer.on("transactions-update", (event) => {
});

export {
calculateFee,
getOnchainHistory,
openSendCoinsModal,
prepareSendCoins,
Expand Down
Loading