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
Changes from 1 commit
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
Prev Previous commit
Next Next commit
wip: add CoinjoinRequirements
  • Loading branch information
theborakompanioni committed Sep 12, 2022

Verified

This commit was signed with the committer’s verified signature.
theborakompanioni Thebora Kompanioni
commit 175bb4cf91e07b1ee472bd4dcaa81092a5c828a2
Original file line number Diff line number Diff line change
@@ -264,7 +264,7 @@ bond_value_exponent = 1.3
# Number of retries allowed for a specific utxo, to prevent DOS/snooping.
# Lower settings make snooping more expensive, but also prevent honest users
# from retrying if an error occurs.
taker_utxo_retries = 3
taker_utxo_retries = 1

# Number of confirmations required for the commitment utxo mentioned above.
# this effectively rate-limits a snooper.
6 changes: 5 additions & 1 deletion src/components/Jam.jsx
Original file line number Diff line number Diff line change
@@ -7,7 +7,11 @@ 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 {
COINJOIN_PRECONDITIONS,
buildCoinjoinPreconditionSummary,
useCoinjoinPreconditionSummary,
} from '../hooks/CoinjoinPrecondition'
import PageTitle from './PageTitle'
import ToggleSwitch from './ToggleSwitch'
import Sprite from './Sprite'
57 changes: 28 additions & 29 deletions src/hooks/CoinjoinPrecondition.ts
Original file line number Diff line number Diff line change
@@ -16,34 +16,33 @@ export interface CoinjoinPreconditionSummary {
amountOfMissingOverallRetries: number
}

export const buildCoinjoinPreconditionSummary = (utxos: Utxos): CoinjoinPreconditionSummary => {
const eligibleUtxos = utxos.filter((it) => !it.frozen).filter((it) => !fb.utxo.isLocked(it))
const numberOfMissingUtxos = Math.max(0, COINJOIN_PRECONDITIONS.MIN_NUMBER_OF_UTXOS - eligibleUtxos.length)

const overallRetriesRemaining = eligibleUtxos.reduce((acc, utxo) => acc + utxo.tries_remaining, 0)
const amountOfMissingOverallRetries = Math.max(
0,
COINJOIN_PRECONDITIONS.MIN_OVERALL_REMAINING_RETRIES - overallRetriesRemaining
)

const minConfirmations =
eligibleUtxos.length === 0
? 0
: eligibleUtxos.reduce((acc, utxo) => Math.min(acc, utxo.confirmations), eligibleUtxos[0].confirmations)
const amountOfMissingConfirmations = Math.max(0, COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS - minConfirmations)

const isFulfilled =
numberOfMissingUtxos === 0 && amountOfMissingOverallRetries === 0 && amountOfMissingConfirmations === 0

return {
isFulfilled,
numberOfMissingUtxos,
amountOfMissingConfirmations,
amountOfMissingOverallRetries,
}
}

export const useCoinjoinPreconditionSummary = (utxos: Utxos): CoinjoinPreconditionSummary => {
const eligibleUtxos = useMemo(() => {
return utxos.filter((it) => !it.frozen).filter((it) => !fb.utxo.isLocked(it))
}, [utxos])

return useMemo(() => {
const numberOfMissingUtxos = Math.max(0, COINJOIN_PRECONDITIONS.MIN_NUMBER_OF_UTXOS - eligibleUtxos.length)

const overallRetriesRemaining = eligibleUtxos.reduce((acc, utxo) => acc + utxo.tries_remaining, 0)
const amountOfMissingOverallRetries = Math.max(
0,
COINJOIN_PRECONDITIONS.MIN_OVERALL_REMAINING_RETRIES - overallRetriesRemaining
)

const minConfirmations =
eligibleUtxos.length === 0
? 0
: eligibleUtxos.reduce((acc, utxo) => Math.min(acc, utxo.confirmations), eligibleUtxos[0].confirmations)
const amountOfMissingConfirmations = Math.max(0, COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS - minConfirmations)

const isFulfilled =
numberOfMissingUtxos === 0 && amountOfMissingOverallRetries === 0 && amountOfMissingConfirmations === 0

return {
isFulfilled,
numberOfMissingUtxos,
amountOfMissingConfirmations,
amountOfMissingOverallRetries,
}
}, [eligibleUtxos])
return useMemo(() => buildCoinjoinPreconditionSummary(utxos), [utxos])
}
124 changes: 124 additions & 0 deletions src/hooks/CoinjoinRequirements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import * as fb from '../components/fb/utils'
import { Utxos } from '../context/WalletContext'

type CoinjoinRequirementOptions = {
minNumberOfUtxos: number // min amount of utxos available
// https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/v0.9.7/docs/SOURCING-COMMITMENTS.md#wait-for-at-least-5-confirmations
minConfirmations: number // all utxos needs X confirmations
minValueFactor: number // value of utxos must be in certain range (e.g 0.2 := min valued utxo must not be less than 20% of most valued utxo)
}

export const DEFAULT_REQUIREMENT_OPTIONS: CoinjoinRequirementOptions = {
minNumberOfUtxos: 1,
minConfirmations: 5, // default of `taker_utxo_age` in jm config
minValueFactor: 0.2, // default of `taker_utxo_amtpercent` in jm config
}

export interface CoinjoinRequirementViolation {
hasViolations: boolean
utxosViolatingRetriesLeft: Utxos
utxosViolatingMinValue: Utxos
utxosViolatingMinConfirmations: Utxos
}

export type CoinjoinRequirementViolationWithJarIndex = { jarIndex: number } & CoinjoinRequirementViolation

export interface CoinjoinRequirementSummary {
isFulfilled: boolean
numberOfMissingUtxos: number
numberOfMissingConfirmations: number
violations: CoinjoinRequirementViolationWithJarIndex[]
}

const filterEligibleUtxos = (utxos: Utxos) => {
return utxos.filter((it) => !it.frozen).filter((it) => !fb.utxo.isLocked(it))
}

const filterUtxosViolatingMinConfirmationRequirement = (utxos: Utxos, minConfirmation: number) => {
return utxos.filter((it) => it.confirmations < minConfirmation)
}

const filterUtxosViolatingTriesLeftRequirement = (utxos: Utxos) => {
return utxos.filter((it) => it.tries_remaining === 0)
}

const filterUtxosViolatingMinValueRequirement = (utxos: Utxos, minValueFactor: number) => {
const transactionValue = utxos.reduce((acc, utxo) => acc + utxo.value, 0)
return utxos.filter((it) => it.value / minValueFactor < transactionValue)
}

const buildCoinjoinViolationSummaryForJar = (
utxos: Utxos,
options: CoinjoinRequirementOptions
): CoinjoinRequirementViolation => {
const eligibleUtxos = filterEligibleUtxos(utxos)
const utxosViolatingRetriesLeft = filterUtxosViolatingTriesLeftRequirement(eligibleUtxos)
const utxosViolatingMinValue = filterUtxosViolatingMinValueRequirement(eligibleUtxos, options.minValueFactor)
const utxosViolatingMinConfirmations = filterUtxosViolatingMinConfirmationRequirement(
eligibleUtxos,
options.minConfirmations
)

const hasViolations =
utxosViolatingRetriesLeft.length > 0 ||
utxosViolatingMinValue.length > 0 ||
utxosViolatingMinConfirmations.length > 0

return {
hasViolations,
utxosViolatingRetriesLeft,
utxosViolatingMinValue,
utxosViolatingMinConfirmations,
}
}

type UtxosByJar = { [key: number]: Utxos }

const groupByJar = (utxos: Utxos): UtxosByJar => {
return utxos.reduce((res, utxo) => {
const { mixdepth } = utxo
res[mixdepth] = res[mixdepth] || []
res[mixdepth].push(utxo)
return res
}, {} as UtxosByJar)
}

export const buildCoinjoinRequirementSummary = (
utxos: Utxos,
options = DEFAULT_REQUIREMENT_OPTIONS
): CoinjoinRequirementSummary => {
const eligibleUtxos = filterEligibleUtxos(utxos)
const utxosByJars = groupByJar(eligibleUtxos)

const violations: CoinjoinRequirementViolationWithJarIndex[] = []

for (const jarIndex in utxosByJars) {
const violationsByJar = buildCoinjoinViolationSummaryForJar(utxosByJars[jarIndex], options)
if (violationsByJar.hasViolations) {
violations.push({ jarIndex: +jarIndex, ...violationsByJar })
}
}

const lowestConfInWallet = violations
.filter((it) => it.utxosViolatingMinConfirmations.length > 0)
.map((it) =>
it.utxosViolatingMinConfirmations.reduce(
(acc, utxo) => Math.min(acc, utxo.confirmations),
Number.MAX_SAFE_INTEGER
)
)
.reduce((acc, lowestConfPerJar) => Math.min(acc, lowestConfPerJar), Number.MAX_SAFE_INTEGER)

const numberOfMissingConfirmations = Math.max(0, options.minConfirmations - lowestConfInWallet)

const numberOfMissingUtxos = Math.max(0, options.minNumberOfUtxos - eligibleUtxos.length)

const isFulfilled = numberOfMissingUtxos === 0 && numberOfMissingConfirmations === 0 && violations.length === 0

return {
isFulfilled,
numberOfMissingUtxos,
numberOfMissingConfirmations,
violations,
}
}