# How Difficulty Is Adjusted in Amadeus Protocol's UPoW: A Technical Deep Dive

> Explore how Amadeus Protocol's UPoW adjusts difficulty using a logarithmic algorithm based on solved proofs and epoch data. Learn about tolerance bands and hard caps for stable network performance.

- Repository: [Amadeus Protocol/node](https://github.com/amadeusprotocol/node)
- Tags: deep-dive
- Published: 2026-08-20

---

**Amadeus Protocol adjusts difficulty in its Uni‑Proof‑of‑Work (UPoW) consensus by retargeting each epoch based on the number of solved proofs‑of‑work (`sols`) produced, using a logarithmic step algorithm with ±10% tolerance bands and hard caps on per‑epoch changes.**

The **UPoW difficulty adjustment** mechanism ensures stable block production rates despite fluctuating network hash power. This article examines the complete implementation in the `amadeusprotocol/node` codebase, covering the mathematical retarget algorithm in [`sol_difficulty.rs`](https://github.com/amadeusprotocol/node/blob/main/sol_difficulty.rs) and the epoch orchestration logic in [`epoch.rs`](https://github.com/amadeusprotocol/node/blob/main/epoch.rs).

## The Retarget Algorithm in [`sol_difficulty.rs`](https://github.com/amadeusprotocol/node/blob/main/sol_difficulty.rs)

The core difficulty calculation lives in [`ex/native/rdb/src/consensus/bic/sol_difficulty.rs`](https://github.com/amadeusprotocol/node/blob/main/ex/native/rdb/src/consensus/bic/sol_difficulty.rs). The `next()` function implements a **bounded, tolerance‑driven retarget scheme** that prevents volatile difficulty swings.

### Function Signature and Constants

```rust
// Lines 6-15 in sol_difficulty.rs
const TOL_NUM: u64 = 1;      // Tolerance numerator
const TOL_DEN: u64 = 10;     // 10% tolerance band
const MAX_STEP_UP: u32 = 2;  // Maximum difficulty increase per epoch
const MAX_STEP_DOWN: u32 = 3; // Maximum difficulty decrease per epoch
const UP_SLOWDOWN: u64 = 2;  // Slowdown factor for upward adjustments
const DIFF_MIN_BITS: u32 = 20;
const DIFF_MAX_BITS: u32 = 64;

```

### The Four Adjustment Cases

The `next(prev_bits, sols, target) → u32` function handles four distinct scenarios (lines 43-58):

1. **Within tolerance band** – If `sols` falls between `target * 9/10` and `target * 11/10`, difficulty remains unchanged.

2. **Zero sols** – When `sols == 0`, difficulty drops aggressively but is capped by `MAX_STEP_DOWN` (3 bits).

3. **Over‑target** – For `sols > target * 1.1`, difficulty increases proportionally to `log₂(sols/target)`, divided by `UP_SLOWDOWN` (2), and clamped to `MAX_STEP_UP` (2 bits).

4. **Under‑target** – For `sols < target * 0.9`, difficulty decreases by a similar logarithmic step, limited to `MAX_STEP_DOWN` (3 bits).

### Logarithmic Ratio Helpers

The algorithm uses efficient integer arithmetic for ratio calculations:

```rust
// Helper functions in sol_difficulty.rs
fn ceil_div(a: u64, b: u64) -> u64;
fn ilog2_floor(n: u64) -> u32;
fn ceil_log2_ratio(num: u64, den: u64) -> u32;
fn clamp_bits(bits: u32) -> u32;  // Enforces DIFF_MIN_BITS/DIFF_MAX_BITS bounds

```

## Epoch Boundary Invocation in [`epoch.rs`](https://github.com/amadeusprotocol/node/blob/main/epoch.rs)

The retarget algorithm is triggered at each epoch's conclusion via `update_difficulty_and_log_sols` in [`ex/native/rdb/src/consensus/bic/epoch.rs`](https://github.com/amadeusprotocol/node/blob/main/ex/native/rdb/src/consensus/bic/epoch.rs).

### Step‑by‑Step Execution Flow

| Step | Action | Source Lines |
|------|--------|--------------|
| 1 | Read current difficulty from `bic:epoch:diff_bits` | 43-44 |
| 2 | Select target based on fork height | 48-52 |
| 3 | Call `sol_difficulty::next()` with computed parameters | 53 |
| 4 | Persist new difficulty to current and historical keys | 54-56 |

### Fork‑Dependent Target Selection

The protocol supports two distinct sol targets depending on epoch height:

```rust
// Lines 48-52 in epoch.rs
let target = if epoch_height >= protocol::forkheight2() {
    sol_difficulty::TARGET_SOLS_EPOCH2  // 180_000
} else {
    sol_difficulty::TARGET_SOLS_EPOCH   // 380_000
};

```

- **Pre‑FORKHEIGHT2** (epoch < 767): Target of **380,000 sols/epoch**
- **Post‑FORKHEIGHT2** (epoch ≥ 767): Reduced target of **180,000 sols/epoch**

## Practical Simulation Example

Below is a runnable Rust example demonstrating the **UPoW difficulty adjustment** logic:

```rust
use crate::consensus::bic::sol_difficulty;

/// Simulate difficulty retarget for any epoch
fn simulate_retgt(prev_bits: u32, sols_this_epoch: u64, epoch_number: u64) {
    // Mirror the fork-aware target selection from epoch.rs
    let target = if epoch_number >= 767 { // FORKHEIGHT2
        sol_difficulty::TARGET_SOLS_EPOCH2  // 180_000
    } else {
        sol_difficulty::TARGET_SOLS_EPOCH   // 380_000
    };

    let next_bits = sol_difficulty::next(prev_bits, sols_this_epoch, target);
    
    println!(
        "Epoch {} – prev: {} bits, sols: {}, target: {}, next: {} bits",
        epoch_number, prev_bits, sols_this_epoch, target, next_bits
    );
}

fn main() {
    // Legacy epoch: under-target by 16% → steps down 1 bit
    simulate_retgt(23, 318_000, 500);  // Output: next = 22
    
    // Post-fork: exactly on target (180k) → no change
    simulate_retgt(23, 180_000, 800);  // Output: next = 23
    
    // Post-fork: 77% over target → steps up 1 bit (capped by MAX_STEP_UP=2)
    simulate_retgt(23, 318_000, 800);  // Output: next = 24
}

```

## Complete Adjustment Flow

```

┌─────────────────────┐
│  End of Epoch       │
│  (total_sols known) │
└─────────┬───────────┘
          ▼
┌─────────────────────┐
│ update_difficulty_  │
│   and_log_sols()    │
│  • Read old diff    │
│  • Select target    │
│  • Call next()      │
└─────────┬───────────┘
          ▼
┌─────────────────────┐
│ sol_difficulty::next│
│  • Apply tolerance  │
│  • Compute log-step │
│  • Clamp to bounds  │
└─────────┬───────────┘
          ▼
┌─────────────────────┐
│  Store new diff to  │
│  bic:epoch:diff_bits│
│  (current + epoch)  │
└─────────────────────┘

```

## Monitoring and Observability

The current difficulty is exposed for external monitoring via [`prometheus.ex`](https://github.com/amadeusprotocol/node/blob/main/prometheus.ex):

```elixir

# In ex/lib/http/prometheus.ex

:prometheus_gauge.set(:amadeus_difficulty_bits, diff_bits)

```

This allows operators to track **UPoW difficulty adjustment** trends in real time.

## Summary

- **Tolerance band**: ±10% around target prevents unnecessary oscillations
- **Step limits**: Maximum 2 bits up, 3 bits down per epoch
- **Logarithmic scaling**: Adjustments proportional to `log₂(ratio)` not linear ratio
- **Fork-aware targets**: 380,000 pre‑fork, 180,000 post‑fork sols/epoch
- **Hard bounds**: Difficulty always clamped between 20 and 64 bits
- **Zero‑sol handling**: Aggressive but bounded decrease prevents deadlock

## Frequently Asked Questions

### How does UPoW difficulty differ from Bitcoin's difficulty adjustment?

**Bitcoin retargets every 2016 blocks (~2 weeks) using a simple ratio formula with 4× bounds.** UPoW adjusts every **epoch** (duration variable, typically hours not weeks) using **logarithmic steps** with tighter **10% tolerance bands** and **asymmetric step limits** (2 up, 3 down). The logarithmic approach smooths volatility compared to Bitcoin's direct proportionality.

### What happens if no sols are produced in an entire epoch?

**The difficulty drops by up to 3 bits (`MAX_STEP_DOWN`).** This is the most aggressive single‑epoch decrease allowed, designed to recover from catastrophic hash‑power loss without allowing instant trivial mining. After multiple zero‑sol epochs, difficulty approaches `DIFF_MIN_BITS` (20).

### Why was the sol target reduced from 380,000 to 180,000 at FORKHEIGHT2?

**The 53% target reduction (to `TARGET_SOLS_EPOCH2`) recalibrated epoch duration expectations** as the protocol matured. Lower targets produce more frequent retargeting opportunities, improving responsiveness to hash‑power changes while maintaining the same **±10% tolerance** and **logarithmic step** mechanics.

### Can difficulty change by more than 2-3 bits in consecutive epochs?

**Yes, but only through sustained divergence from target.** The per‑epoch caps (`MAX_STEP_UP`/`MAX_STEP_DOWN`) are absolute, so a sustained 50% over‑target condition would require multiple epochs to fully correct. This intentional **inertia** prevents difficulty overreaction to transient hash‑power spikes.