I want to say up front that I am not a Monero developer and not an expert on this codebase. I am writing it up in case it is useful. I may well be misreading it.
As far as I can tell, the validator in src/validation/decorators/isMoneroConfirmationTiers.ts enforces:
minConfirmations is an integer >= 0
0 appears at most once
0 is not allowed on the final catch-all tier
every non-final tier has a positive upToTotalFiat
the final tier has no upToTotalFiat
What I do not see anywhere is a check that the tiers are in ascending order by upToTotalFiat, or that minConfirmations never decreases as the bound rises.
In src/utils/monero/resolveMinConfirmations.ts the lookup returns the first tier whose bound covers the total:
One 0-conf tier, catch-all last with no bound, every non-final tier positive. But an order totalling 1000 matches the first tier and resolves to 0 confirmations. My assumption is that the rule keeping 0 off the catch-all tier exists to cap how much value can ever be credited unconfirmed, and a transposed bound in .env seems to get around that cap.
A few other configs that look to me like they also pass:
duplicate upToTotalFiat values on two tiers
minConfirmations decreasing as the bound rises, for example 10, then 5, then 1
"1e9" as a bound, since Number("1e9") is finite and positive
On scope: this needs the shop operator to write the config wrong. I do not think a buyer can trigger it, so it reads to me as a footgun rather than anything remotely exploitable. I am reporting it because it is in the money path and it fails silently. Nothing at startup or at request time tells the operator their tiers are in the wrong order.
For a fix, sorting inside resolveMinConfirmations would work, though adding the ordering check to the validator seems better to me, since it would fail at boot rather than silently reordering what the operator wrote. A test for it would be worth adding either way. The existing spec covers the other invariants pretty thoroughly, which is partly why this one stood out.
Apologies if I have misread something here.
I want to say up front that I am not a Monero developer and not an expert on this codebase. I am writing it up in case it is useful. I may well be misreading it.
As far as I can tell, the validator in `src/validation/decorators/isMoneroConfirmationTiers.ts` enforces:
- `minConfirmations` is an integer >= 0
- 0 appears at most once
- 0 is not allowed on the final catch-all tier
- every non-final tier has a positive `upToTotalFiat`
- the final tier has no `upToTotalFiat`
What I do not see anywhere is a check that the tiers are in ascending order by `upToTotalFiat`, or that `minConfirmations` never decreases as the bound rises.
In `src/utils/monero/resolveMinConfirmations.ts` the lookup returns the first tier whose bound covers the total:
```ts
for (const tier of tiers) {
if (tier.upToTotalFiat === undefined) {
return tier.minConfirmations;
}
if (new Decimal(totalFiat).lte(tier.upToTotalFiat)) {
return tier.minConfirmations;
}
}
```
And `src/config/index.ts` parses the env var without sorting:
```ts
confirmationTiers: JSON.parse(env('MONERO_CONFIRMATION_TIERS')) as MoneroConfirmationTier[]
```
If I am reading this right, that means the following config passes validation:
```json
[{"upToTotalFiat":"1000","minConfirmations":0},
{"upToTotalFiat":"10","minConfirmations":5},
{"minConfirmations":10}]
```
One 0-conf tier, catch-all last with no bound, every non-final tier positive. But an order totalling 1000 matches the first tier and resolves to 0 confirmations. My assumption is that the rule keeping 0 off the catch-all tier exists to cap how much value can ever be credited unconfirmed, and a transposed bound in `.env` seems to get around that cap.
A few other configs that look to me like they also pass:
- duplicate `upToTotalFiat` values on two tiers
- `minConfirmations` decreasing as the bound rises, for example 10, then 5, then 1
- `"1e9"` as a bound, since `Number("1e9")` is finite and positive
On scope: this needs the shop operator to write the config wrong. I do not think a buyer can trigger it, so it reads to me as a footgun rather than anything remotely exploitable. I am reporting it because it is in the money path and it fails silently. Nothing at startup or at request time tells the operator their tiers are in the wrong order.
For a fix, sorting inside `resolveMinConfirmations` would work, though adding the ordering check to the validator seems better to me, since it would fail at boot rather than silently reordering what the operator wrote. A test for it would be worth adding either way. The existing spec covers the other invariants pretty thoroughly, which is partly why this one stood out.
Apologies if I have misread something here.
Thanks for report. I am aware of this and I treat it as misconfiguration issue. In future version this will be configured from settings in CMS and from there it will receive deeper validation + probably some warning if mempool tier will have big value.
Thanks for report. I am aware of this and I treat it as misconfiguration issue. In future version this will be configured from settings in CMS and from there it will receive deeper validation + probably some warning if mempool tier will have big value.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
I want to say up front that I am not a Monero developer and not an expert on this codebase. I am writing it up in case it is useful. I may well be misreading it.
As far as I can tell, the validator in
src/validation/decorators/isMoneroConfirmationTiers.tsenforces:minConfirmationsis an integer >= 0upToTotalFiatupToTotalFiatWhat I do not see anywhere is a check that the tiers are in ascending order by
upToTotalFiat, or thatminConfirmationsnever decreases as the bound rises.In
src/utils/monero/resolveMinConfirmations.tsthe lookup returns the first tier whose bound covers the total:And
src/config/index.tsparses the env var without sorting:If I am reading this right, that means the following config passes validation:
One 0-conf tier, catch-all last with no bound, every non-final tier positive. But an order totalling 1000 matches the first tier and resolves to 0 confirmations. My assumption is that the rule keeping 0 off the catch-all tier exists to cap how much value can ever be credited unconfirmed, and a transposed bound in
.envseems to get around that cap.A few other configs that look to me like they also pass:
upToTotalFiatvalues on two tiersminConfirmationsdecreasing as the bound rises, for example 10, then 5, then 1"1e9"as a bound, sinceNumber("1e9")is finite and positiveOn scope: this needs the shop operator to write the config wrong. I do not think a buyer can trigger it, so it reads to me as a footgun rather than anything remotely exploitable. I am reporting it because it is in the money path and it fails silently. Nothing at startup or at request time tells the operator their tiers are in the wrong order.
For a fix, sorting inside
resolveMinConfirmationswould work, though adding the ordering check to the validator seems better to me, since it would fail at boot rather than silently reordering what the operator wrote. A test for it would be worth adding either way. The existing spec covers the other invariants pretty thoroughly, which is partly why this one stood out.Apologies if I have misread something here.
Thanks for report. I am aware of this and I treat it as misconfiguration issue. In future version this will be configured from settings in CMS and from there it will receive deeper validation + probably some warning if mempool tier will have big value.