diff --git a/src/RFC/MinoTari/TIP-RFC-MT-0004_MinoTari_PoW_Difficulty_changes.md b/src/RFC/MinoTari/TIP-RFC-MT-0004_MinoTari_PoW_Difficulty_changes.md new file mode 100644 index 0000000..545e944 --- /dev/null +++ b/src/RFC/MinoTari/TIP-RFC-MT-0004_MinoTari_PoW_Difficulty_changes.md @@ -0,0 +1,548 @@ +# A-TIP-RFC-MT-0004: MinoTari PoW difficulty changes + +| TIP | [A-TIP-RFC-MT-0004](#/RFC/MinoTari/TIP-RFC-MT-0004_MinoTari_PoW_Difficulty_changes.md) | +|-----------------|---------------------------------------------------------------------------| +| Title | MinoTari PoW difficulty changes | +| Last Modified | 2026-08-19 | +| Authors | SW van Heerden | +| Status | Accepted | +| Type | Architecture | +| Created | 2026-06-11 | +| References | | + +## Overview + +Tari Core currently uses a geometric-mean-style comparison to determine +the best chain tip across multiple Proof-of-Work (PoW) algorithms. + +The geometric mean is a measure of central tendency calculated by +multiplying a set of values together and then taking the n-th root +(where n is the number of values). Unlike the arithmetic mean, the +geometric mean is well suited to comparing values that span multiple +orders of magnitude. + +Tari does not compute the full geometric mean: the final n-th root step +is omitted to avoid floating-point operations. Because the protocol only +needs to order two values rather than compute an exact mean, the root is +unnecessary --- multiplying the accumulated difficulties preserves the +same ordering. + +Tari currently supports four PoW algorithms: + +- RxM +- RxT +- Sha3x +- C29 + +To compare two competing chain tips, Tari calculates: + +```text +RxM * RxT * Sha3x * C29 +``` + +The chain with the larger product is considered to have more accumulated +PoW. + +### Example + +Assume the total accumulated difficulties for the four algorithms are: + +```text +RxM, RxT, Sha3x, C29 +``` + +Now consider two competing new blocks: + +- One mined on RxM with difficulty `x` +- One mined on Sha3x with difficulty `y` + +We compare: + +```text +(RxM + x) * RxT * Sha3x * C29 +RxM * RxT * (Sha3x + y) * C29 +``` + +After cancelling common terms, this comparison reduces to evaluating: + +```text +x / RxM vs y / Sha3x +``` + +Whichever ratio is larger represents the larger relative increase in +accumulated PoW. + +### Problem + +This approach works while hash rate is balanced across the algorithms. +If one algorithm (for example Sha3x) sees a large hash rate increase --- +say because ASIC hardware becomes dominant relative to GPU mining --- its +relative contribution can dominate the comparison. + +As accumulated difficulty grows, terms such as `x / RxM` converge toward +zero unless `x` scales proportionally with `RxM`. If one algorithm's +difficulty grows much faster than the others, its blocks consistently +produce larger relative increases. + +One algorithm can therefore reorg blocks mined by the other algorithms +disproportionately often, which undermines the original design goal of +using multiple PoW algorithms for decentralization and hardware +diversity. + +## Proposed Change 1 + +Tari currently calculates target difficulty using an LWMA (Linearly +Weighted Moving Average) over the last 90 blocks. The LWMA uses: + +- Target block time +- Header timestamps +- Historical target difficulties + +to compute the next target difficulty. + +### Threat model + +The mechanism proposed here targets a miner whose hash power is +concentrated in a **single** PoW algorithm and who therefore has to mine +consecutive blocks with that one algorithm in order to build a competing +chain. The canonical case is Sha3x ASIC capacity growing until it can +routinely out-pace the GPU and CPU algorithms. + +It does not defend against a miner who controls capacity in two or more +algorithms and can alternate between them. Such a miner never produces a +same-algorithm run and never incurs a penalty. This limitation is +inherent to a rule keyed on the previous block's algorithm. + +Because RxM and RxT are both RandomX variants mined by the same +hardware, that exemption covers RandomX capacity by construction: a +miner with CPU hash rate can alternate the two variants at no cost. This +is a deliberate choice, discussed under *Penalty scope* below, and it +means the mechanism binds Sha3x and C29 in practice. + +### Penalty scope + +The backoff is keyed on the **PoW algorithm** of the preceding blocks. +All four algorithms --- RxM, RxT, Sha3x and C29 --- are treated +independently. A run is a sequence of consecutive blocks of the *same* +algorithm, and a block of any other algorithm resets the modifier. + +An earlier draft of this RFC grouped RxM and RxT into a single RandomX +penalty class, on the grounds that both are mined by the same hardware +and a RandomX farm could otherwise alternate them to bypass the +mechanism. That grouping is not adopted, for two reasons. + +First, it is not neutral between algorithms. All four algorithms are +configured with the same base target time, and each algorithm's LWMA +pins that algorithm to its own target time independently of hash rate, +so in steady state each wins roughly one quarter of blocks. A combined +RandomX class would therefore hold roughly half of all blocks and pay a +far larger expected penalty than Sha3x --- about 1.47x against about +1.15x in simulation. The mechanism would tax the CPU-mined algorithms +hardest while the Sha3x concentration it was written to deter paid +least. + +Second, the grouping is the dominant contributor to the block time +inflation described below. It raises the mean block interval by roughly +8%, against roughly 2% with the algorithms kept separate. + +The cost of keeping them separate is that a miner with RandomX capacity +can alternate RxM and RxT and pay no penalty at all, including when +building a private chain. This is accepted: it is the same exemption +already granted to any miner holding capacity in two algorithms, and the +threat model above names single-algorithm Sha3x concentration as the +target. + +Penalty scope affects only the backoff rule. Accumulated difficulty is +still tracked per algorithm, and the chain comparison in the Overview is +unchanged. + +### The backoff rule + +This RFC introduces an exponential backoff for consecutive blocks mined +by the same algorithm: + +- If a block is mined with algorithm A and the previous block was also + mined with algorithm A, the target time for A is doubled. +- The doubling compounds for each further consecutive block of the + same algorithm. +- If a block of any other algorithm is mined, the target time for A + resets to its base consensus value. + +### Cap + +The doubling is capped at **32x**. Writing `r` for the run length --- the +number of consecutive blocks of the same algorithm ending at and +including the block being mined --- the modifier is: + +```text +m = min( 2^(r - 1), 32 ) +``` + +| Run length `r` | Modifier `m` | +|----------------|--------------| +| 1 | 1 | +| 2 | 2 | +| 3 | 4 | +| 4 | 8 | +| 5 | 16 | +| 6 | 32 | +| 7 or more | 32 | + +The cap is a liveness requirement, not a tuning choice. The modifier +only resets when a block of a *different* algorithm is mined. If an +algorithm is ever the sole active miner --- for example if the other +algorithms lose their miners after a fork --- there is nothing to reset +it, and an uncapped modifier would double indefinitely until the chain +stopped producing blocks, with no recovery path short of another fork. +The cap bounds that failure to a 32x slowdown: at an 8 minute base +target, a chain mined by a single algorithm settles at roughly 4.3 hours +per block and resumes normal operation as soon as any other algorithm +mines. A chain left with only RandomX miners does not reach this state, +because RxM and RxT reset each other. + +The cap is reached after 5 doublings, i.e. on the **sixth** consecutive +block of an algorithm. + +### Expected cost, and why the factor is two + +The doubling factor is not arbitrary. It places the point at which the +mechanism becomes prohibitive exactly at the majority threshold. + +Model each block's algorithm as independent, with `p` the share of +blocks won by algorithm A. For a block mined by A, let `j` be the number +of immediately preceding consecutive A blocks, so that `m = 2^j`. The +`j` preceding blocks are A and the one before them is not, giving: + +```text +P(j = k) = p^k * (1 - p) +``` + +The expected modifier paid by a block of algorithm A is therefore: + +```text +E[m] = sum over k of (1 - p) * p^k * 2^k + = (1 - p) / (1 - 2p) for p < 1/2 +``` + +The series converges only while `2p < 1`. Below 50% block share the +expected penalty is finite and mild; as share approaches 50% it grows +without bound. More generally, a backoff factor of `b` places the +divergence at `p = 1/b`: a factor of 1.5 would only become prohibitive +above a two-thirds share, and a factor of 3 would penalize an algorithm +holding only a third of blocks. **A factor of two is the choice that +puts the pole at the majority threshold**, which is the threshold the +protocol already treats as the security boundary. + +With the 32x cap the expectation is finite everywhere, since a run of +five or more preceding blocks pays a flat 32: + +```text +E[m] = (1 - p) * (1 - (2p)^5) / (1 - 2p) + 32 * p^5 +``` + +| Block share `p` | `E[m]` uncapped | `E[m]` capped at 32 | +|----------------------|-----------------|---------------------| +| 0.25 (four algos) | 1.50 | 1.48 | +| 1/3 (three algos) | 2.00 | 1.87 | +| 0.40 | 3.00 | 2.34 | +| 0.45 | 5.50 | 2.84 | +| 0.50 | diverges | 3.50 | +| 0.60 | diverges | 5.47 | +| 0.75 | diverges | 10.89 | + +The cap is a deliberate trade. It buys the liveness guarantee described +above, and it pays for that by blunting the sharp majority threshold: an +algorithm at 50% share pays 3.5x rather than an unbounded amount. The +mechanism therefore taxes concentration progressively rather than +imposing a hard ceiling on it. + +#### Why these figures are an upper bound + +The independent-arrival model above is what fixes the backoff factor at +two, but it overstates the penalty actually paid, and by a wide margin. +It ignores the feedback the mechanism itself creates: while an algorithm +holds the tip it is throttled by `m` and the other three are not, so it +is markedly less likely to extend its own run than `p` suggests. + +Simulating the race between algorithms through to the LWMA fixed point, +at four live algorithms with equal base target times: + +| Run length `r` | P(extend) modelled | P(extend) simulated | +|----------------|--------------------|---------------------| +| 1 | 0.25 | 0.13 | +| 2 | 0.25 | 0.07 | +| 3 | 0.25 | 0.04 | +| 4 | 0.25 | 0.03 | + +Long runs are therefore far rarer than `p^k`, and long runs are exactly +what the `2^k` weight multiplies. The realised expected modifier is +correspondingly lower: + +| Live algorithms | `E[m]` modelled | `E[m]` simulated | +|-----------------|-----------------|------------------| +| 4 | 1.48 | 1.16 | +| 3 | 1.87 | 1.23 | + +Note also that `p` is a share of *blocks won*, not of hash rate, and the +two are only loosely coupled here. Because each algorithm's LWMA pins it +to its own target time, steady-state block share is set by the +configured target times and is insensitive to hash rate: a Sha3x hash +rate boom raises Sha3x difficulty rather than its block share. Shares +materially above 1/4 arise from algorithms losing their miners +altogether, not from one algorithm out-hashing the others. The rows at +`p >= 0.4` above are therefore about attack and abandonment scenarios, +not about ordinary operation. + +### Effect on mean block time + +Because solve times are normalized by `m` before entering the LWMA, the +LWMA has no feedback path that fully corrects for the penalty. It +converges to the point where the *normalized* mean solve time equals the +base target time `T`, while wall-clock intervals remain longer than +that. Block production is therefore slower than the configured target. + +A first-order estimate treats the modifier and the solve time as +independent, giving `E[solve_time] = E[m] * T`. That overstates the +effect substantially, for three reasons: + +- Runs are suppressed by the penalty itself, so `E[m]` is much smaller + than the independent model predicts. See *Why these figures are an + upper bound* above. +- The chain does not stall while an algorithm is penalized. Only the + algorithm holding the tip is throttled; the other three continue at + full rate, so total block production dips rather than scaling down + by `m`. +- The normalization does not fully blind the LWMA. Tari keeps one LWMA + per algorithm, so a solve time is the gap between consecutive blocks + of *that* algorithm and spans blocks of other algorithms. Penalties + in force during the gap stretch it, but only the modifier of the + block that closes the gap is divided out. The residual stretch is + visible to the LWMA, which lowers difficulty in response and + partially compensates. + +Simulating the race through to the LWMA fixed point at the mainnet +configuration --- four algorithms with equal base target times --- gives +the following inflation of the mean chain block interval: + +| Configuration | Inflation | +|---------------------------------------|---------------| +| Four algorithms live | 1.7% | +| Three algorithms live (one abandoned) | 3.5% | +| Single algorithm live | 32x (the cap) | + +At under 2% the effect does not warrant reducing the base target times +to compensate, and doing so would misconfigure the chain if the number +of live algorithms later changed. For comparison, grouping RxM and RxT +into one penalty class raises the four-algorithm figure to 8.4% and the +three-algorithm figure to 19.6%, which is the second reason that +grouping is not adopted. + +The inflation that does remain is a consequence of normalizing solve +times rather than letting the LWMA absorb the penalty. The alternative +--- omitting the normalization --- holds block time exactly constant but +lets the LWMA cancel the penalty in steady state, leaving it effective +only transiently. + +### Difficulty calculation + +The current difficulty calculation is: + +```text +next_target_difficulty = ave_difficulty * k / weighted_times + +where k = block_window * (block_window + 1) * target_time / 2 +``` + +Because the penalty enters the formula only through `target_time`, it +can be expressed as a single multiplier: + +```text +adjusted_next_target_difficulty = next_target_difficulty * m + +where m = the penalty modifier +``` + +The `adjusted_next_target_difficulty` is used only as the target +difficulty that the mined block must meet. It is not counted when +calculating the total accumulated PoW of the chain --- the unadjusted +difficulty is used there. + +The modifier applied to each block in the window must also be fed back +into the next difficulty calculation. The weighted times are currently +the sum over the window of: + +```text +weighted_times = sum( solve_time[i] * (i + 1) ) + +where i = index of the block within the window +``` + +Each solve time is normalized by the modifier that was in force for that +block, so that a block mined against an inflated target is not read as a +drop in hash rate: + +```text +weighted_times = sum( solve_time[i] / m[i] * (i + 1) ) + +where +i = index of the block within the window +m[i] = modifier applied to block i +``` + +#### Order of normalization and clamping + +The existing LWMA clamps each solve time before use, to bound the +influence of manipulated timestamps. The normalization above interacts +with that clamp, and the order is consensus-critical. + +Normalization is applied **first**, and the clamp is then applied to the +normalized value against the **unchanged** base bounds: + +```text +solve_time[i] = clamp( raw_solve_time[i] / m[i], min_bound, max_bound ) +``` + +Clamping the raw solve time against the base bounds and dividing +afterwards is incorrect. A block legitimately mined against a 32x target +takes roughly 32x longer, so the base upper bound would truncate a real +solve time and the subsequent division would yield a spuriously small +value, driving a false difficulty spike. Because `m[i]` is always +positive, clamping the normalized value against the base bounds is +exactly equivalent to clamping the raw value against bounds scaled by +`m[i]`, which is the intended behaviour. + +#### Integer arithmetic + +`solve_time[i] / m[i]` must not be evaluated as an integer division per +term: with `m = 32` a 15 second solve time truncates to zero, and the +error accumulates across every block in the window. Because every +modifier is a power of two dividing the 32x cap, the sum can be kept in +exact integer arithmetic by scaling every term by the cap: + +```text +weighted_times = sum( raw_solve_time[i] * (i + 1) * (M_MAX / m[i]) ) +k = block_window * (block_window + 1) * target_time * M_MAX / 2 + +where M_MAX = 32 +``` + +`M_MAX / m[i]` is an exact integer for every permitted modifier, and +scaling both `k` and `weighted_times` by the same factor leaves +`next_target_difficulty` unchanged. + +This mechanism: + +- Does not alter the underlying hash rate. +- Does not directly manipulate accumulated difficulty. +- Makes selfish mining exponentially more expensive for any single + algorithm attempting to dominate. +- Encourages natural interleaving of algorithms. + +### Example + +Assume the base target time for Sha3x is **8 minutes**. + +- If the previous block was Sha3x, the next Sha3x target time becomes + **16 minutes**. +- If another Sha3x block is mined consecutively, the target time + becomes **32 minutes**. +- After six consecutive Sha3x blocks the target time reaches **4 hours + 16 minutes** (32x) and stops growing. +- If a block of any other algorithm is mined, the Sha3x target time + resets to **8 minutes**. + +An RxM block followed by an RxT block is a run of one in each algorithm, +so neither block is penalized. Switching between the two RandomX +variants resets the modifier, and a miner with RandomX capacity can +alternate them indefinitely without ever paying a penalty. + +## Consequences 1 + +### Positive + +- Significantly increases the cost of selfish mining. A private chain + of depth 6 built by a single algorithm costs 63 units of work + against 6 for an honest chain of the same depth. +- Reduces the ability of a single algorithm to reorg multiple blocks. +- Preserves the multi-algorithm decentralization objective. +- Treating every algorithm independently keeps the mechanism neutral + between them: all four carry the same steady-state block share and + the same expected modifier, so no algorithm is taxed more than + another for mining honestly. +- The cost to honest mining is small --- about 1.16x on average with + four algorithms live --- while remaining progressive in + concentration. + +### Negative + +- Requires a hard fork. +- Changes block-time dynamics under certain hash rate distributions. +- May introduce more short-term variance in block intervals. +- Inflates the mean block interval by roughly 2% with four algorithms + live, rising as algorithms are abandoned. See *Effect on mean block + time*. +- The 32x cap blunts the majority threshold: an algorithm at 50% block + share pays 3.5x on average rather than an unbounded amount. +- Does not prevent a miner holding capacity in two or more algorithms + from alternating between them to avoid the penalty entirely. Because + RxM and RxT run on the same hardware, this exempts RandomX capacity + in full, including when building a private chain. +- If a single algorithm is left as the only active miner, the chain + runs at up to 32x the base target time until another algorithm mines + a block. +- Penalizes a single-algorithm miner for a run that occurs by chance, + not only for one produced deliberately. + +### Neutral + +- Does not change the geometric-mean comparison logic directly. +- Modifies the LWMA only through its target-time input and the + corresponding normalization of solve times; the shape of the moving + average is unchanged. +- Requires no new header fields. The modifier for any block is derived + from the PoW algorithms of the preceding blocks, so validators can + recompute it from headers alone --- noting that this requires up to + 5 headers of lookback beyond the start of the LWMA window. + +## Proposed Change 2 + +Tari currently uses a block window of 90 blocks for the LWMA. This gives +a stable difficulty, but it takes several blocks to respond to a change +in hash rate. When large miners switch on and off, the resulting hash +rate swings produce correspondingly large swings in solve time, so a +faster response is desirable. Given Tari's long target block time, the +window should be reduced to 45 blocks. + +## Consequences 2 + +### Positive + +- Responds faster to an increase or decrease in hash power. + +### Negative + +- Can cause more oscillation in the difficulty. + +## References + +- https://github.com/zawy12/difficulty-algorithms/issues/3#issuecomment-442129791 +- https://github.com/zcash/zcash/issues/4021 + + +## Change History + +### 2026-06-11 + +* Document Created. + +### 2026-08-19 + +* RxM and RxT are no longer grouped into a shared RandomX penalty class; + every algorithm is its own penalty scope. +* Corrected the *Effect on mean block time* analysis. The previous + figure of 87% came from the independent-arrival model, which ignores + that the penalty suppresses the runs it taxes, that unpenalized + algorithms keep mining at full rate, and that the per-algorithm LWMA + is only partially blinded by the normalization. Simulated inflation is + 1.7% with four algorithms live, and the base target times no longer + need to be reduced to compensate. diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 03d606a..c21744e 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -7,6 +7,7 @@ - [TIP-PROC-3: Community Forums](TIP-0003_community_forums.md) - [TIP-RFC-O-8003: Tari Use Cases](RFC-8003_TariUseCases.md) - [Accepted](accepted.md) + - [TIP-RFC-MT-0004: MinoTari PoW difficulty changes](RFC/MinoTari/TIP-RFC-MT-0004_MinoTari_PoW_Difficulty_changes.md) - [TIP-RFC-MT-0241: XMR Atomic swap](RFC-0241_AtomicSwapXMR.md) - [Implemented](implemented.md) - [TIP-PROC-2: The Tari Community Charter](TIP-0002_tari_community_charter.md)