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

fix: precondition for collaborative transactions #485

Merged
merged 22 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7eb7f4d
fix: min confs of preconditions for all utxos instead of single
theborakompanioni Aug 25, 2022
85bdb34
fix: i18n phrases of collaborative transaction preconditions
theborakompanioni Aug 26, 2022
4b478ff
fix: link to markdown docs instead of archived wiki page
theborakompanioni Aug 26, 2022
4fb1152
Merge branch 'master' into preconditions
theborakompanioni Sep 9, 2022
ea9b372
Merge branch 'master' into preconditions
theborakompanioni Sep 12, 2022
175bb4c
wip: add CoinjoinRequirements
theborakompanioni Sep 5, 2022
5745813
wip: remove min utxo size check
theborakompanioni Sep 12, 2022
83f369c
wip: use new scheduler preconditions in Jam component
theborakompanioni Sep 12, 2022
08bea1d
wip: externalize CoinjoinPreconditionViolationAlert component
theborakompanioni Sep 12, 2022
cc7b5d8
fix: only report utxos without retries of no retry is left within jar
theborakompanioni Sep 12, 2022
3f83aa0
dev: readd optional min value precondition check
theborakompanioni Sep 12, 2022
999bf39
dev: use CoinjoinPreconditionViolationAlert on Send page
theborakompanioni Sep 12, 2022
b82a912
dev: remove min utxo size check (again)
theborakompanioni Sep 12, 2022
fef437e
feat(regtest): set taker_utxo_retries and maker_timeout_sec config va…
theborakompanioni Sep 12, 2022
8a24605
test(preconditions): add test for CoinjoinRequirements
theborakompanioni Sep 12, 2022
e832cc7
chore: remove outdated component CoinjoinPreconditions
theborakompanioni Sep 12, 2022
d4ccab6
test: add test for multiple jar check in CoinjoinRequirements
theborakompanioni Sep 12, 2022
0833389
ui: display retries info as warning intead of error
theborakompanioni Sep 13, 2022
c9fb002
fix: enable starting scheduler even if preconditions are not fulfilled
theborakompanioni Sep 13, 2022
63de541
review: fix wording
theborakompanioni Sep 15, 2022
08989ac
Merge branch 'master' into preconditions
theborakompanioni Sep 15, 2022
0c5f4ad
Merge branch 'master' into preconditions
theborakompanioni Sep 15, 2022
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
2 changes: 2 additions & 0 deletions docker/regtest/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ services:
jm_rpc_password: joinmarket2
jm_directory_nodes: ${JM_DIRECTORY_NODES:?You must set the onion address generated in prepare step to your .env file}
jm_socks5: "false" # will _not_ connect to local irc over tor
jm_taker_utxo_retries: 1 # easier testing of commitment failures on regtest; default is 3
maker_timeout_sec: 10 # easier testing of maker timeouts on regtest (and "Stall Monitor" retries); default is 60
expose:
- 80 # nginx
- 62601 # obwatch
Expand Down
100 changes: 100 additions & 0 deletions src/components/CoinjoinPreconditionViolationAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { forwardRef } from 'react'
import * as rb from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useSettings } from '../context/SettingsContext'
import { CoinjoinRequirementSummary } from '../hooks/CoinjoinRequirements'
import { jarInitial } from './jars/Jar'
import { shortenStringMiddle } from '../utils'
import Sprite from './Sprite'
import Balance from './Balance'

interface CoinjoinPreconditionViolationAlertProps {
summary: CoinjoinRequirementSummary
i18nPrefix?: string
}

export const CoinjoinPreconditionViolationAlert = forwardRef(
({ summary, i18nPrefix = '' }: CoinjoinPreconditionViolationAlertProps, ref: React.Ref<HTMLDivElement>) => {
const { t } = useTranslation()
const settings = useSettings()

if (summary.isFulfilled) return <></>

if (summary.numberOfMissingUtxos > 0) {
return (
<rb.Alert variant="warning" ref={ref}>
{t(`${i18nPrefix}hint_missing_utxos`, {
minConfirmations: summary.options.minConfirmations,
})}
</rb.Alert>
)
}

if (summary.numberOfMissingConfirmations > 0) {
return (
<rb.Alert variant="warning" ref={ref}>
{t(`${i18nPrefix}hint_missing_confirmations`, {
minConfirmations: summary.options.minConfirmations,
amountOfMissingConfirmations: summary.numberOfMissingConfirmations,
})}
</rb.Alert>
)
}

const utxosViolatingRetriesLeft = summary.violations
.map((it) => it.utxosViolatingRetriesLeft)
.reduce((acc, utxos) => acc.concat(utxos), [])

if (utxosViolatingRetriesLeft.length > 0) {
return (
<rb.Alert variant="warning" ref={ref}>
<>
<Trans i18nKey={`${i18nPrefix}hint_missing_retries`}>
You tried too many times. See
<a
href="https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/v0.9.7/docs/SOURCING-COMMITMENTS.md"
target="_blank"
rel="noopener noreferrer"
>
the docs
</a>{' '}
for more info.
</Trans>
<br />
<br />
<Trans i18nKey={`${i18nPrefix}hint_missing_retries_detail`} count={utxosViolatingRetriesLeft.length}>
Following utxos have been used unsuccessfully too many times:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Following utxos have been used unsuccessfully too many times:
The following UTXOs have been used unsuccessfully too many times:

<ul className="mt-2 mb-0 ps-2">
{utxosViolatingRetriesLeft.map((utxo, index) => (
<li key={index} className="mb-2 slashed-zeroes small" style={{ display: 'inline-flex' }}>
<span className="pe-1" style={{ display: 'inline-flex' }}>
<Sprite symbol="jar-closed-fill-50" width="20" height="20" />
<span className="slashed-zeroes">
<strong>{jarInitial(utxo.mixdepth)}</strong>
</span>
:
</span>
<div>
<span>{utxo.address}</span>
&nbsp;(
<Balance
valueString={`${utxo.value}`}
convertToUnit={settings.unit}
showBalance={settings.showBalance}
/>
)
<br />
<small>{shortenStringMiddle(utxo.utxo, 32)}</small>
</div>
</li>
))}
</ul>
</Trans>
</>
</rb.Alert>
)
}

return <></>
}
)
79 changes: 14 additions & 65 deletions src/components/Jam.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react'
import * as rb from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useTranslation } from 'react-i18next'
import { Formik, useFormikContext } from 'formik'
import * as Api from '../libs/JmWalletApi'
import { useSettings } from '../context/SettingsContext'
import { useServiceInfo, useReloadServiceInfo } from '../context/ServiceInfoContext'
import { useCurrentWallet, useCurrentWalletInfo, useReloadCurrentWalletInfo } from '../context/WalletContext'
import { isDebugFeatureEnabled } from '../constants/debugFeatures'
import { COINJOIN_PRECONDITIONS, useCoinjoinPreconditionSummary } from '../hooks/CoinjoinPrecondition'
import { buildCoinjoinRequirementSummary } from '../hooks/CoinjoinRequirements'
import { CoinjoinPreconditionViolationAlert } from './CoinjoinPreconditionViolationAlert'
import PageTitle from './PageTitle'
import ToggleSwitch from './ToggleSwitch'
import Sprite from './Sprite'
Expand All @@ -21,8 +22,6 @@ import styles from './Jam.module.css'
// Length of this array must be 3 for now.
const INTERNAL_DEST_ACCOUNTS = [0, 1, 2]

const SCHEDULER_START_ACCOUNT = 0

const ValuesListener = ({ handler }) => {
const { values } = useFormikContext()

Expand All @@ -48,16 +47,9 @@ export default function Jam() {
const [isLoading, setIsLoading] = useState(true)
const [collaborativeOperationRunning, setCollaborativeOperationRunning] = useState(false)

const startJarUtxos = useMemo(() => {
if (!walletInfo) return null

return walletInfo.data.utxos.utxos.filter((it) => it.mixdepth === SCHEDULER_START_ACCOUNT)
}, [walletInfo])

const schedulerPreconditionSummary = useCoinjoinPreconditionSummary(startJarUtxos || [])
const isSchedulerPreconditionsFulfilled = useMemo(
() => schedulerPreconditionSummary.isFulfilled,
[schedulerPreconditionSummary]
const schedulerPreconditionSummary = useMemo(
() => buildCoinjoinRequirementSummary(walletInfo?.data.utxos.utxos || []),
[walletInfo]
)

// Returns one fresh address for each requested mixdepth.
Expand Down Expand Up @@ -135,7 +127,7 @@ export default function Jam() {
}, [serviceInfo])

const startSchedule = async (values) => {
if (isLoading || collaborativeOperationRunning || !isSchedulerPreconditionsFulfilled) {
if (isLoading || collaborativeOperationRunning) {
return
}

Expand Down Expand Up @@ -219,54 +211,15 @@ export default function Jam() {
</rb.Alert>
)}
<rb.Fade
in={!collaborativeOperationRunning && !isSchedulerPreconditionsFulfilled}
in={!collaborativeOperationRunning && !schedulerPreconditionSummary.isFulfilled}
mountOnEnter={true}
unmountOnExit={true}
className="mb-4"
>
<rb.Alert variant="warning" className="mb-4">
<>
{schedulerPreconditionSummary.numberOfMissingUtxos > 0 ? (
<Trans i18nKey="scheduler.precondition.hint_missing_utxos">
To run the scheduler you need at least one UTXO with{' '}
<strong>{{ minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO }}</strong>{' '}
confirmations. Fund your wallet and wait for{' '}
<strong>{{ minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO }}</strong>{' '}
blocks.
</Trans>
) : schedulerPreconditionSummary.amountOfMissingConfirmations > 0 ? (
<Trans i18nKey="scheduler.precondition.hint_missing_confirmations">
The scheduler requires one of your UTXOs to have{' '}
<strong>
{{
/* this comment is a hack for "prettier" and prevents the removal of "{' '}"
(which is essential for parameterized translations to work). */
minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO,
}}
</strong>{' '}
or more confirmations. Wait for{' '}
<strong>
{{ amountOfMissingConfirmations: schedulerPreconditionSummary.amountOfMissingConfirmations }}
</strong>{' '}
more block(s).
</Trans>
) : (
schedulerPreconditionSummary.amountOfMissingOverallRetries > 0 && (
<Trans i18nKey="scheduler.precondition.hint_missing_overall_retries">
You've tried running the scheduler unsuccessfully too many times in a row. For security reasons,
you need a fresh UTXO to try again. See{' '}
<a
href="https://github.com/JoinMarket-Org/joinmarket/wiki/Sourcing-commitments-for-joins#sourcing-external-commitments"
target="_blank"
rel="noopener noreferrer"
>
the docs
</a>{' '}
for more information.
</Trans>
)
)}
</>
</rb.Alert>
<CoinjoinPreconditionViolationAlert
summary={schedulerPreconditionSummary}
i18nPrefix="scheduler.precondition."
/>
</rb.Fade>
{!collaborativeOperationRunning && walletInfo && (
<>
Expand Down Expand Up @@ -421,11 +374,7 @@ export default function Jam() {
className={styles.submit}
variant="dark"
type="submit"
disabled={
isSubmitting ||
isLoading ||
(!collaborativeOperationRunning && (!isValid || !isSchedulerPreconditionsFulfilled))
}
disabled={isSubmitting || isLoading || (!collaborativeOperationRunning && !isValid)}
>
<div className="d-flex justify-content-center align-items-center">
{collaborativeOperationRunning ? t('scheduler.button_stop') : t('scheduler.button_start')}
Expand Down
63 changes: 12 additions & 51 deletions src/components/Send.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ import { useReloadCurrentWalletInfo, useCurrentWallet, useCurrentWalletInfo } fr
import { useServiceInfo, useReloadServiceInfo } from '../context/ServiceInfoContext'
import { useLoadConfigValue } from '../context/ServiceConfigContext'
import { useSettings } from '../context/SettingsContext'
import { COINJOIN_PRECONDITIONS, useCoinjoinPreconditionSummary } from '../hooks/CoinjoinPrecondition'
import { buildCoinjoinRequirementSummary } from '../hooks/CoinjoinRequirements'

import * as Api from '../libs/JmWalletApi'
import { SATS, formatBtc, formatSats } from '../utils'
import { routes } from '../constants/routes'
import styles from './Send.module.css'
import { ConfirmModal } from './Modal'
import { CoinjoinPreconditionViolationAlert } from './CoinjoinPreconditionViolationAlert'
import { jarInitial, jarName } from './jars/Jar'

const IS_COINJOIN_DEFAULT_VAL = true
Expand Down Expand Up @@ -213,54 +214,6 @@ function SweepAccordionToggle({ eventKey }) {
)
}

function CoinjoinPreconditionFailedAlert({ coinjoinPreconditionSummary }) {
return (
<rb.Alert variant="warning" className="mb-4">
<>
{coinjoinPreconditionSummary.numberOfMissingUtxos > 0 ? (
<Trans i18nKey="send.coinjoin_precondition.hint_missing_utxos">
To execute a collaborative transaction you need at least one UTXO with{' '}
<strong>{{ minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO }}</strong>{' '}
confirmations in the source jar. Select another jar to send from or fund this jar and wait for{' '}
<strong>{{ minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO }}</strong> blocks.
</Trans>
) : coinjoinPreconditionSummary.amountOfMissingConfirmations > 0 ? (
<Trans i18nKey="send.coinjoin_precondition.hint_missing_confirmations">
A collaborative transaction requires one of your UTXOs to have{' '}
<strong>
{{
/* this comment is a hack for "prettier" and prevents the removal of "{' '}"
(which is essential for parameterized translations to work). */
minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO,
}}
</strong>{' '}
or more confirmations. Select another jar to send from or wait for{' '}
<strong>
{{ amountOfMissingConfirmations: coinjoinPreconditionSummary.amountOfMissingConfirmations }}
</strong>{' '}
more block(s).
</Trans>
) : (
coinjoinPreconditionSummary.amountOfMissingOverallRetries > 0 && (
<Trans i18nKey="send.coinjoin_precondition.hint_missing_overall_retries">
You've tried executing a collaborative transaction from this jar unsuccessfully too many times in a row.
For security reasons, you need a fresh UTXO to try again. See{' '}
<a
href="https://github.com/JoinMarket-Org/joinmarket/wiki/Sourcing-commitments-for-joins#sourcing-external-commitments"
target="_blank"
rel="noopener noreferrer"
>
the docs
</a>{' '}
for more information.
</Trans>
)
)}
</>
</rb.Alert>
)
}

export default function Send() {
const { t } = useTranslation()
const wallet = useCurrentWallet()
Expand Down Expand Up @@ -324,7 +277,10 @@ export default function Send() {
return walletInfo.data.utxos.utxos.filter((it) => it.mixdepth === account)
}, [walletInfo, account])

const coinjoinPreconditionSummary = useCoinjoinPreconditionSummary(sourceJarUtxos || [])
const coinjoinPreconditionSummary = useMemo(
() => buildCoinjoinRequirementSummary(sourceJarUtxos || []),
[sourceJarUtxos]
)

useEffect(() => {
if (
Expand Down Expand Up @@ -717,7 +673,12 @@ export default function Send() {
)}

{!isLoading && !isOperationDisabled && isCoinjoin && !coinjoinPreconditionSummary.isFulfilled && (
<CoinjoinPreconditionFailedAlert coinjoinPreconditionSummary={coinjoinPreconditionSummary} />
<div className="mb-4">
<CoinjoinPreconditionViolationAlert
summary={coinjoinPreconditionSummary}
i18nPrefix="send.coinjoin_precondition."
/>
</div>
)}

{!isLoading && walletInfo && (
Expand Down
Loading