From 3f76a1f2a9dbdb908d0a911619af26b4207828a1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:48:24 +0000 Subject: [PATCH] docs: Add technical details for hit calculation The `docs/epow/technical-explanation.md` file was missing a detailed explanation of how the `hit` value is calculated in the ePoW consensus mechanism. This commit adds a new section that breaks down the `hit` calculation step-by-step. It also corrects the description of the main mining loop to clarify that randomness is introduced via the Argon2id salt, allowing miners to perform many unique hash attempts per second. This ensures the documentation is now technically accurate and provides a clearer understanding of the mining process. --- docs/epow/technical-explanation.md | 89 +++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/docs/epow/technical-explanation.md b/docs/epow/technical-explanation.md index e168a7fb..1840303a 100644 --- a/docs/epow/technical-explanation.md +++ b/docs/epow/technical-explanation.md @@ -80,41 +80,92 @@ This formula means that as `elapsed_time` increases, the `target` decreases, mak ### 4. Hashing and Finding a Valid Block -Unlike traditional Proof of Work where miners search for a random `nonce`, in ePoW, the mining process is an iteration over the **`elapsed` time**. The `nonce` is calculated deterministically, not found. +In ePoW, the mining process is an iteration over **hash attempts**, where miners continuously generate new hashes until they find a `hit` that exceeds the `target`. While the `target` value only changes once per second (with the `elapsed_time`), miners can perform many thousands or millions of hash attempts within that second. + +This is possible because the hashing process introduces randomness, allowing each attempt to be unique. Here is the mining loop: -1. For each second that passes, the miner increments the `elapsed_time` value. -2. A new **Argon2id hash** is calculated using the `previous_block_date` and the new `elapsed_time`. Argon2id is a memory-hard hashing algorithm, making it resistant to ASIC miners. -3. A deterministic **`nonce`** is then calculated using a SHA-256 hash of the miner's address, the `elapsed_time`, and the new Argon2 hash. There is no randomness here; for the same inputs, the output `nonce` is always the same. -4. The miner calculates the `hit` value. -5. The miner recalculates the `target`, which changes with each increment of `elapsed_time`. -6. If `hit > target`, a valid block has been found. The miner can then submit the block with the successful `elapsed_time`, the calculated `argon` hash, and the deterministic `nonce`. +1. **Outer Loop (Time-based):** For each second that passes, the miner updates the `elapsed_time` and recalculates the `target`. +2. **Inner Loop (Hashing):** Within each second, the miner performs as many hashing attempts as their hardware allows. +3. **Generate a Random Argon Hash:** For each attempt, a new **Argon2id hash** is calculated. The Argon2id algorithm automatically generates a **random salt** for each hash. This randomness is the key to getting a different result on each attempt. +4. **Calculate a Deterministic Nonce:** The new, random `argon_hash` is used to calculate a deterministic `nonce`. While the `nonce` calculation itself is deterministic, the randomness of its input (`argon_hash`) ensures the `nonce` is unique for each attempt. +5. **Calculate the Hit:** The miner calculates the `hit` value based on this unique `nonce`. +6. **Check Against Target:** If `hit > target`, a valid block has been found. The miner submits the block with the successful `elapsed_time`, the `argon` hash, and the `nonce` that produced the winning `hit`. **Pseudo-code:** ``` previous_block_date = node.get_latest_block().date while (true): + // Outer loop: updated each second current_timestamp = get_current_time() elapsed_time = current_timestamp - previous_block_date - - // Recalculate target for the current elapsed_time target = (difficulty * BLOCK_TIME) / elapsed_time - // Calculate hashes deterministically - argon_hash = argon2("previous_block_date-elapsed_time") - nonce = sha256("miner_address-previous_block_date-elapsed_time-argon_hash") + // Inner loop: runs as fast as possible + while (get_current_time() - current_timestamp < 1): + // Each call to argon2 produces a different hash due to random salt + argon_hash = argon2("previous_block_date-elapsed_time") + + nonce = sha256("miner_address-previous_block_date-elapsed_time-argon_hash") + + hit = calculate_hit(miner_address, nonce, block_height, difficulty) + + if (hit > target): + // Block found! + submit_block(nonce, argon_hash, elapsed_time) + exit_loops() // Exit both inner and outer loops +``` + +#### Calculating the Hit + +The `hit` value is a crucial part of ePoW, representing the quality of a single hash attempt. It's a number that must exceed the current `target` to solve the block. The `hit` is unique to each attempt and to each miner. + +Here's why each `hit` is different: +* **For the same miner:** A miner can generate thousands of `hit` values per second because each call to the Argon2id hashing function produces a new, random `argon_hash`. This changes the `nonce`, which in turn changes the `hit`. +* **For different miners:** Even with the same `elapsed_time` and `difficulty`, two different miners will always calculate different `hit` values. This is because their unique `miner_address` is a key component of the `nonce` calculation, ensuring they are not working on the same puzzle. + +The calculation process is as follows: + +1. **Create a Base String:** A unique string is created for each attempt, combining the miner's address, the current `nonce`, the block `height`, and the current `difficulty`. + - `base = miner_address + "-" + nonce + "-" + height + "-" + difficulty` + +2. **Double SHA-256 Hash:** The `base` string is hashed twice using the SHA-256 algorithm. + - `hash = sha256(sha256(base))` + +3. **Extract a Hash Part:** The first 8 characters (4 bytes) of the resulting hexadecimal hash are extracted. + - `hash_part = substring(hash, 0, 8)` + +4. **Convert to a Number:** This hexadecimal `hash_part` is converted into a numerical value. + - `value = hex_to_decimal(hash_part)` + +5. **Calculate the Final Hit:** The final `hit` is calculated using a formula that scales the `value`. `BLOCK_TARGET_MUL` is a constant with a value of `1000`. + - `hit = (0xffffffff * BLOCK_TARGET_MUL) / value` + + +**Pseudo-code for `calculate_hit`:** +``` +function calculate_hit(miner_address, nonce, block_height, difficulty): + // 1. Create the base string + base_string = miner_address + "-" + nonce + "-" + block_height + "-" + difficulty + + // 2. Double SHA-256 hash + hash1 = sha256(base_string) + hash2 = sha256(hash1) + + // 3. Extract the first 8 characters + hash_part = substring(hash2, 0, 8) - hit = calculate_hit(miner_address, nonce, block_height, difficulty) + // 4. Convert hex to decimal + value = hex_to_decimal(hash_part) - if (hit > target): - // Block found! - submit_block(nonce, argon_hash, elapsed_time) - break + // 5. Calculate the final hit + // Note: 0xffffffff is the maximum 32-bit unsigned integer + BLOCK_TARGET_MUL = 1000 + hit = (0xffffffff * BLOCK_TARGET_MUL) / value - // Wait for the next second to increment elapsed_time - wait(1_second) + return hit ``` ### 5. Submitting a Block