diff --git a/.gitignore b/.gitignore index 3aae1e4343..42ad982b41 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ src/test/fuzz/fuzz src/test/test_factorn src/bench/bench_factorn src/qt/test/test_bitcoin-qt +src/contrib/testgen/gen_asert_test_vectors +contrib/bench/bench_factor_rho +contrib/bench/bench_factor_popen # autoreconf Makefile.in diff --git a/configure.ac b/configure.ac index 717aabdde3..6f7496ef13 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ AC_PREREQ([2.69]) -define(_CLIENT_VERSION_MAJOR, 5) -define(_CLIENT_VERSION_MINOR, 3) -define(_CLIENT_VERSION_BUILD, 420) +define(_CLIENT_VERSION_MAJOR, 6) +define(_CLIENT_VERSION_MINOR, 0) +define(_CLIENT_VERSION_BUILD, 42) define(_CLIENT_VERSION_RC, 0) define(_CLIENT_VERSION_IS_RELEASE, true ) define(_COPYRIGHT_YEAR, 2026) diff --git a/contrib/bench/bench_factor_popen.c b/contrib/bench/bench_factor_popen.c new file mode 100644 index 0000000000..36ab5c9ae4 --- /dev/null +++ b/contrib/bench/bench_factor_popen.c @@ -0,0 +1,167 @@ +/* + * bench_factor_popen.c + * + * Benchmarks block solving using coreutils `factor` via popen (batch). + * Simulates solving NBLOCKS blocks at nBits=32 (regtest difficulty). + * + * Build: gcc -O2 -o bench_factor_popen bench_factor_popen.c + * Usage: poop ./bench_factor_rho ./bench_factor_popen + */ + +#include +#include +#include +#include + +#define NBITS 32 +#define NBLOCKS 200 +#define TILDEN (16 * NBITS) /* candidate search radius */ + +/* Maximum candidates per W: 2 per jj step (+ and - direction) */ +#define MAX_CANDIDATES (TILDEN * 2) + +/* Small-prime sieve: check divisibility by small primes individually. + * We can't use a single product here since we don't have GMP. */ +static const uint64_t SMALL_P[] = {3,5,7,11,13,17,19,23,29,31,37,41,43,47}; +#define NSMALL (sizeof(SMALL_P) / sizeof(SMALL_P[0])) + +static int coprime_to_small(uint64_t n) +{ + for (int i = 0; i < (int)NSMALL; i++) { + if (n % SMALL_P[i] == 0) + return 0; + } + return 1; +} + +/* + * Factor candidates for a single W using one batched `factor` call. + * Returns 1 if a valid semiprime was found. + */ +static int solve_one(uint64_t W, uint64_t *out_factor, int64_t *out_offset) +{ + uint64_t one = (W & 1) ? 0 : 1; + + /* Collect sieve-surviving candidates with their offsets */ + uint64_t cands[MAX_CANDIDATES]; + int64_t offsets[MAX_CANDIDATES]; + int ncands = 0; + + for (int jj = 0; jj < TILDEN; jj += 2) { + uint64_t N1 = W + jj + one; + uint64_t N2 = W - jj - one; + + if (coprime_to_small(N1)) { + cands[ncands] = N1; + offsets[ncands] = jj + (int64_t)one; + ncands++; + } + if (coprime_to_small(N2)) { + cands[ncands] = N2; + offsets[ncands] = -jj - (int64_t)one; + ncands++; + } + } + + if (ncands == 0) + return 0; + + /* Build the command: "factor N1 N2 N3 ..." */ + /* Each number is at most 10 digits + space, plus "factor " prefix */ + size_t cmd_cap = 8 + (size_t)ncands * 12; + char *cmd = malloc(cmd_cap); + if (!cmd) return 0; + + int pos = snprintf(cmd, cmd_cap, "factor"); + for (int i = 0; i < ncands; i++) { + pos += snprintf(cmd + pos, cmd_cap - pos, " %lu", (unsigned long)cands[i]); + } + + FILE *fp = popen(cmd, "r"); + free(cmd); + if (!fp) return 0; + + /* Parse output line by line: "N: f1 f2 f3 ..." */ + char line[1024]; + int found = 0; + + while (fgets(line, sizeof(line), fp) && !found) { + /* Parse "N: f1 f2 ..." */ + char *colon = strchr(line, ':'); + if (!colon) continue; + + uint64_t N = strtoull(line, NULL, 10); + char *rest = colon + 1; + + /* Collect distinct prime factors and their total count */ + uint64_t factors[64]; + int nfactors = 0; + int total_factors = 0; + char *tok = strtok(rest, " \t\n"); + while (tok && nfactors < 64) { + uint64_t f = strtoull(tok, NULL, 10); + total_factors++; + /* Only add distinct factors */ + if (nfactors == 0 || factors[nfactors - 1] != f) { + factors[nfactors++] = f; + } + tok = strtok(NULL, " \t\n"); + } + + /* A semiprime is p*q (exactly 2 prime factors total, both distinct) */ + if (total_factors != 2 || nfactors != 2) continue; + + /* factors[] is already sorted (factor outputs in ascending order) */ + + /* Smaller factor must have exactly expected bits (matches consensus) */ + int b0 = 64 - __builtin_clzll(factors[0]); + int expected = NBITS / 2 + (NBITS & 1); + + if (b0 != expected) continue; + + /* Edge case: factor must not be a power of 2 */ + if (factors[0] == (1ULL << (NBITS/2 - 1))) continue; + + /* Find matching offset */ + for (int i = 0; i < ncands; i++) { + if (cands[i] == N) { + *out_factor = factors[0]; + *out_offset = offsets[i]; + found = 1; + break; + } + } + } + + pclose(fp); + return found; +} + +int main(void) +{ + /* Same PRNG and seed as rho benchmark for identical W values */ + uint64_t state = 0xDEADBEEFCAFEBABEULL; + int solved = 0; + + for (int i = 0; i < NBLOCKS; i++) { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + + uint64_t W = state; + W |= (1ULL << (NBITS - 1)); + W &= (1ULL << NBITS) - 1; + W |= 1; + + if (i < 5) + printf("W[%d] = %lu\n", i, (unsigned long)W); + + uint64_t factor; + int64_t offset; + if (solve_one(W, &factor, &offset)) + solved++; + } + + printf("popen: solved %d/%d blocks\n", solved, NBLOCKS); + return 0; +} diff --git a/contrib/bench/bench_factor_rho.c b/contrib/bench/bench_factor_rho.c new file mode 100644 index 0000000000..220a093de3 --- /dev/null +++ b/contrib/bench/bench_factor_rho.c @@ -0,0 +1,150 @@ +/* + * bench_factor_rho.c + * + * Benchmarks block solving using in-process Pollard's rho (GMP). + * Simulates solving NBLOCKS blocks at nBits=32 (regtest difficulty). + * + * Build: gcc -O2 -o bench_factor_rho bench_factor_rho.c -lgmp + * Usage: poop ./bench_factor_rho ./bench_factor_popen + */ + +#include +#include +#include +#include + +#define NBITS 32 +#define NBLOCKS 200 +#define TILDEN (16 * NBITS) /* candidate search radius */ + +/* Small-prime sieve product: 3*5*7*11*13*17*19*23*29*31*37*41*43*47 */ +static const uint64_t SMALL_PRIMES = 3ULL*5*7*11*13*17*19*23*29*31*37*41*43*47; + +/* f(z) = z^2 + 1 mod n (Pollard rho step) */ +static void f(mpz_t z, const mpz_t n, const mpz_t two) +{ + mpz_powm(z, z, two, n); + mpz_add_ui(z, z, 1ULL); + mpz_mod(z, z, n); +} + +/* + * Pollard rho factoring. + * Returns 1 if n = g * (n/g) with both factors prime. + * Returns 0 if n is prime or factoring failed. + */ +static int rho(mpz_t g, mpz_t n) +{ + if (mpz_probab_prime_p(n, 25) != 0) + return 0; + + mpz_t x, y, two, temp; + mpz_init_set_ui(x, 2); + mpz_init_set_ui(y, 2); + mpz_init_set_ui(two, 2); + mpz_set_ui(g, 1); + mpz_init(temp); + + while (mpz_cmp_ui(g, 1) == 0) { + f(x, n, two); + f(y, n, two); + f(y, n, two); + + mpz_sub(temp, x, y); + mpz_gcd(g, temp, n); + } + + mpz_divexact(temp, n, g); + + int u_p = mpz_probab_prime_p(temp, 30); + int g_p = mpz_probab_prime_p(g, 30); + + mpz_clear(x); + mpz_clear(y); + mpz_clear(temp); + mpz_clear(two); + + if ((u_p != 0) && (g_p != 0) && (mpz_cmp(g, n) != 0)) + return 1; + + return 0; +} + +/* + * Try to solve one "block" given W (a random nBits-bit number). + * Returns 1 if a valid semiprime factorization was found, 0 otherwise. + */ +static int solve_one(uint64_t W, uint64_t *out_factor, int64_t *out_offset) +{ + mpz_t n, g, gcd_val; + mpz_inits(n, g, gcd_val, NULL); + + uint64_t one = (W & 1) ? 0 : 1; + int found = 0; + + for (int jj = 0; jj < TILDEN && !found; jj += 2) { + uint64_t candidates[2] = { W + jj + one, W - jj - one }; + int64_t offsets[2] = { jj + (int64_t)one, -jj - (int64_t)one }; + + for (int k = 0; k < 2 && !found; k++) { + uint64_t N = candidates[k]; + mpz_set_ui(n, N); + mpz_gcd_ui(gcd_val, n, SMALL_PRIMES); + + if (mpz_cmp_ui(gcd_val, 1) != 0) + continue; + + int valid = rho(g, n); + int bitsize = (int)mpz_sizeinbase(g, 2); + + if (valid == 1 && bitsize == (NBITS >> 1)) { + uint64_t f1 = mpz_get_ui(g); + uint64_t f2 = N / f1; + uint64_t factor = (f1 < f2) ? f1 : f2; + + /* Edge case: cofactor must have same number of bits */ + int edge_check = f2 & (1ULL << (bitsize - 1)); + if (edge_check != 0 && factor != (1ULL << (NBITS/2 - 1))) { + *out_factor = factor; + *out_offset = offsets[k]; + found = 1; + } + } + } + } + + mpz_clears(n, g, gcd_val, NULL); + return found; +} + +int main(void) +{ + /* Seed a simple PRNG (xorshift64) for reproducible W values */ + uint64_t state = 0xDEADBEEFCAFEBABEULL; + int solved = 0; + + for (int i = 0; i < NBLOCKS; i++) { + /* Generate a random nBits-bit W */ + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + + uint64_t W = state; + /* Force exactly NBITS bits: set bit 31, clear bits above */ + W |= (1ULL << (NBITS - 1)); + W &= (1ULL << NBITS) - 1; + /* Make sure it's odd (most semiprimes are) */ + W |= 1; + + if (i < 5) + printf("W[%d] = %lu\n", i, (unsigned long)W); + + uint64_t factor; + int64_t offset; + if (solve_one(W, &factor, &offset)) + solved++; + } + + printf("rho: solved %d/%d blocks\n", solved, NBLOCKS); + return 0; +} diff --git a/contrib/testgen/gen_asert_test_vectors.cpp b/contrib/testgen/gen_asert_test_vectors.cpp new file mode 100644 index 0000000000..58c4fc633b --- /dev/null +++ b/contrib/testgen/gen_asert_test_vectors.cpp @@ -0,0 +1,228 @@ +// Copyright (c) 2020 The Bitcoin developers +// Copyright (c) 2026 The FACTOR developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// +// This program generates FACTOR ASERT test vectors, grouped as "runs" +// which start from a given anchor block datum (height, nBits, timestamp). +// +// The generated test vectors give block heights, times and ASERT nBits +// calculated at that height (i.e. for the next block). +// +// The vectors are intended to validate implementations of the same +// FACTOR ASERT algorithm in other languages against the C++ implementation. +// +// This program needs to be compiled and linked against FACTOR's libraries. +// Build with: make -C src contrib/testgen/gen_asert_test_vectors + +#include + +#include +#include +#include +#include +#include +#include + +// Required by the linker (referenced via util/translation.h) +const std::function G_TRANSLATION_FUN = nullptr; + +// FACTOR ASERT parameters +static const FACTORASERTParams MAINNET_PARAMS = { + 1800, // targetSpacing: 30 minutes + 172800, // halfLife: 2 days + 32, // nBitsMin + 1022 // nBitsMax +}; + +static const FACTORASERTParams TESTNET_PARAMS = { + 300, // targetSpacing: 5 minutes + 3600, // halfLife: 1 hour + 32, // nBitsMin + 1022 // nBitsMax +}; + +// The vectors are produced in "runs" which iterate a number of blocks, +// based on a reference anchor, a start height + increment, +// and a function which produces the time offset between iterated blocks. +struct run_params { + std::string run_name; + const FACTORASERTParams *asert_params; + int32_t anchor_nbits; // anchor nBits (even, in [nBitsMin, nBitsMax]) + int64_t anchor_height; // height of anchor block + int64_t anchor_time; // timestamp of anchor block + int64_t start_height; // height at which to start calculating + int64_t start_time; // time at start height + uint64_t iterations; // number of blocks to iterate + uint64_t (*height_incr_function)(uint64_t prev_height); // height increment function + int64_t (*timediff_function)(uint64_t iteration); // time diff function +}; + +// Height & time diff producing functions + +uint64_t height_incr_by_one(uint64_t) { + return 1; +} + +int64_t time_incr_testnet_spacing(uint64_t) { + return TESTNET_PARAMS.targetSpacing; // 300s +} + +int64_t time_incr_mainnet_spacing(uint64_t) { + return MAINNET_PARAMS.targetSpacing; // 1800s +} + +// One halfLife behind schedule per block (testnet): difficulty should halve each step +int64_t time_incr_testnet_extra_halflife(uint64_t) { + return TESTNET_PARAMS.targetSpacing + TESTNET_PARAMS.halfLife; +} + +// Blocks arrive at half the target spacing (testnet): difficulty increases +int64_t time_incr_testnet_half_spacing(uint64_t) { + return TESTNET_PARAMS.targetSpacing / 2; // 150s +} + +// Stable hashrate, solvetime is exponentially distributed (testnet) +int64_t time_incr_random_testnet(uint64_t) { + static std::mt19937 trng{424242}; + static std::exponential_distribution dist{1.0 / TESTNET_PARAMS.targetSpacing}; + return int64_t(dist(trng)); +} + +// Hashrate doubles every 10 blocks (testnet) +int64_t time_incr_random_ramp_up(uint64_t iteration) { + static std::mt19937 trng{424242}; + static std::exponential_distribution dist{1.0 / TESTNET_PARAMS.targetSpacing}; + return int64_t(dist(trng)) / (1 + int64_t(iteration / 10)); +} + +// Hashrate halves every 50 blocks (testnet) +int64_t time_incr_random_ramp_down(uint64_t iteration) { + static std::mt19937 trng{424242}; + static std::exponential_distribution dist{1.0 / TESTNET_PARAMS.targetSpacing}; + return int64_t(dist(trng)) * (1 + int64_t(iteration / 50)); +} + +// Uniform random in [-300, 900] (mean = target spacing = 300, includes negatives) +int64_t time_incr_uniform_random(uint64_t) { + static std::mt19937 trng{424242}; + static std::uniform_int_distribution dist{ + -TESTNET_PARAMS.targetSpacing, + 3 * TESTNET_PARAMS.targetSpacing}; + return dist(trng); +} + +void print_run_iteration(const uint64_t iteration, + const int64_t height, + const int64_t time, + const int32_t nbits, + const bool clamped_high) { + std::printf("%" PRIu64 " %" PRId64 " %" PRId64 " %d %d\n", + iteration, height, time, nbits, clamped_high ? 1 : 0); +} + +// Perform one parameterized run to produce FACTOR ASERT test vectors. +// Takes by value: start_time is mutated for cumulative time tracking. +void perform_run(run_params r) { + assert(r.start_height >= r.anchor_height); + assert(r.anchor_nbits % 2 == 0); + assert(r.anchor_nbits >= r.asert_params->nBitsMin); + assert(r.anchor_nbits <= r.asert_params->nBitsMax); + + // Print run header + std::printf("## description: %s\n", r.run_name.data()); + std::printf("## anchor height: %" PRId64 "\n", r.anchor_height); + std::printf("## anchor time: %" PRId64 "\n", r.anchor_time); + std::printf("## anchor nBits: %d\n", r.anchor_nbits); + std::printf("## target spacing: %" PRId64 "\n", r.asert_params->targetSpacing); + std::printf("## half life: %" PRId64 "\n", r.asert_params->halfLife); + std::printf("## nBitsMin: %d nBitsMax: %d\n", r.asert_params->nBitsMin, r.asert_params->nBitsMax); + std::printf("## start height: %" PRId64 "\n", r.start_height); + std::printf("## start time: %" PRId64 "\n", r.start_time); + std::printf("## iterations: %" PRIu64 "\n", r.iterations); + std::printf("# iteration,height,time,nBits,clampedHigh\n"); + + int64_t h = r.start_height; + for (uint64_t i = 1; i <= r.iterations; ++i) { + const int64_t timeDiff = r.start_time - r.anchor_time; + const int64_t heightDiff = h - r.anchor_height; + + ASERTResult result = CalculateFACTORASERT(*r.asert_params, r.anchor_nbits, timeDiff, heightDiff); + + print_run_iteration(i, h, r.start_time, result.nBits, result.clampedHigh); + + h += static_cast((*r.height_incr_function)(h)); + r.start_time += (*r.timediff_function)(i); + } +} + +void produce_factor_asert_test_vectors() { + const auto *tn = &TESTNET_PARAMS; + const auto *mn = &MAINNET_PARAMS; + + const std::vector run_table = { + // --- Steady state --- + {"run1 - testnet steady state at nBitsMin (32)", + tn, 32, 1, 0, 2, 300, 10, height_incr_by_one, time_incr_testnet_spacing}, + + {"run2 - testnet steady state at mid nBits (500)", + tn, 500, 1, 0, 2, 300, 10, height_incr_by_one, time_incr_testnet_spacing}, + + {"run3 - testnet steady state at nBitsMax (1022)", + tn, 1022, 1, 0, 2, 300, 10, height_incr_by_one, time_incr_testnet_spacing}, + + {"run4 - mainnet steady state at genesis (230)", + mn, 230, 1, 0, 2, 1800, 10, height_incr_by_one, time_incr_mainnet_spacing}, + + // --- Half-life behavior --- + {"run5 - testnet: each block one halfLife behind schedule (difficulty halving each step)", + tn, 500, 1, 0, 2, 300 + 3600, 20, height_incr_by_one, time_incr_testnet_extra_halflife}, + + {"run6 - testnet: blocks at half spacing (difficulty increasing)", + tn, 32, 1, 0, 2, 150, 50, height_incr_by_one, time_incr_testnet_half_spacing}, + + // --- Random solvetimes --- + {"run7 - testnet: random exponential solvetimes at mid difficulty", + tn, 200, 1, 0, 2, 300, 1000, height_incr_by_one, time_incr_random_testnet}, + + {"run8 - testnet: random solvetimes with ramping-up hashrate", + tn, 200, 1, 0, 2, 300, 500, height_incr_by_one, time_incr_random_ramp_up}, + + {"run9 - testnet: random solvetimes with ramping-down hashrate", + tn, 200, 1, 0, 2, 300, 500, height_incr_by_one, time_incr_random_ramp_down}, + + {"run10 - testnet: uniform random solvetimes (including negative)", + tn, 200, 1, 0, 2, 300, 1000, height_incr_by_one, time_incr_uniform_random}, + + // --- Boundary / clamp scenarios --- + {"run11 - testnet: near nBitsMax with fast blocks (clampedHigh expected)", + tn, 1000, 1, 0, 2, 1, 200, height_incr_by_one, + [](uint64_t) { return int64_t(1); }}, + + {"run12 - testnet: near nBitsMin with slow blocks (clampedLow expected)", + tn, 34, 1, 0, 2, 600, 50, height_incr_by_one, + [](uint64_t) { return int64_t(3600); }}, + + // --- Negative time increments --- + {"run13 - testnet: each block 1 second before the previous", + tn, 200, 1, 10000, 2, 10300, 100, height_incr_by_one, + [](uint64_t) { return int64_t(-1); }}, + }; + + for (const auto& r : run_table) { + perform_run(r); + puts(""); + } +} + + +int main() { + // Sanity-check the mt19937 RNG: 10000th value must be 4123659995. + std::mt19937 rng; + std::mt19937::result_type rval{}; + for (int i = 0; i < 10'000; ++i) + rval = rng(); + assert(rval == 4123659995UL); + + produce_factor_asert_test_vectors(); +} diff --git a/contrib/testgen/validate_nbits_aserti3_2d.py b/contrib/testgen/validate_nbits_aserti3_2d.py new file mode 100755 index 0000000000..f654265155 --- /dev/null +++ b/contrib/testgen/validate_nbits_aserti3_2d.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin developers +# Copyright (c) 2026 The FACTOR developers +# Distributed under the MIT/X11 software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# This program validates that a Python implementation of the FACTOR ASERT +# algorithm produces identical outputs to test vectors generated from the +# C++ implementation in pow.cpp. +# +# Usage: +# python3 validate_nbits_aserti3_2d.py +# python3 validate_nbits_aserti3_2d.py run* +# +# If no arguments are given, processes all run* files in the current directory. + +import os +import re +import sys + + +def load_table(header_path: str) -> list[int]: + """Parse LOG2_COMPUTE_TABLE from src/asert_table.h""" + table: list[int] = [] + with open(header_path, "r") as f: + in_array = False + for line in f: + if "LOG2_COMPUTE_TABLE" in line and "{" in line: + in_array = True + if not in_array: + continue + # Match entries like " 9087915284LL," + for m in re.finditer(r"(-?\d+)LL", line): + table.append(int(m.group(1))) + if "};" in line: + break + assert len(table) == 511, "Expected 511 table entries, got %d" % len(table) + return table + + +def calculate_factor_asert( + table: list[int], + target_spacing: int, + half_life: int, + nbits_min: int, + nbits_max: int, + anchor_nbits: int, + time_diff: int, + height_diff: int, +) -> tuple[int, bool]: + """Pure-Python implementation of CalculateFACTORASERT. + + Mirrors the C++ implementation exactly: + - Q32.32 fixed-point exponent via overflow-safe split division + - Binary search for closest table entry + - Tiebreak: lower nBits (easier difficulty) + """ + assert anchor_nbits % 2 == 0 + assert nbits_min <= anchor_nbits <= nbits_max + assert height_diff >= 0 + + # Step 1: Look up anchor's log2(compute) + anchor_idx = anchor_nbits // 2 - 1 + anchor_log2_compute = table[anchor_idx] + + # Step 2: Compute exponent in Q32.32 + expected_elapsed = height_diff * target_spacing + time_error = expected_elapsed - time_diff + + # Overflow-safe Q32.32 split division (matches C++) + # Python handles arbitrary precision, but we replicate the C++ truncation + # toward zero for division of negative numbers. + if time_error >= 0: + integer_part = time_error // half_life + remainder = time_error % half_life + else: + # C++ truncates toward zero: -7 / 3 == -2, -7 % 3 == -1 + integer_part = -((-time_error) // half_life) + remainder = -((-time_error) % half_life) + + # The fractional division must also use C++ truncation-toward-zero semantics. + # Python's // floors toward -inf, which differs by 1 when remainder < 0. + frac_num = remainder << 32 + if frac_num >= 0: + frac_part = frac_num // half_life + else: + frac_part = -((-frac_num) // half_life) + + exponent_q32 = (integer_part << 32) + frac_part + + # Step 3: target log2_compute + target_log2_compute = anchor_log2_compute + exponent_q32 + + # Step 4: Binary search + idx_min = nbits_min // 2 - 1 + idx_max = nbits_max // 2 - 1 + + clamped_high = target_log2_compute > table[idx_max] + + if target_log2_compute <= table[idx_min]: + result_idx = idx_min + elif target_log2_compute >= table[idx_max]: + result_idx = idx_max + else: + lo, hi = idx_min, idx_max + while lo + 1 < hi: + mid = lo + (hi - lo) // 2 + if table[mid] <= target_log2_compute: + lo = mid + else: + hi = mid + dist_lo = target_log2_compute - table[lo] + dist_hi = table[hi] - target_log2_compute + result_idx = lo if dist_lo <= dist_hi else hi + + new_nbits = (result_idx + 1) * 2 + return new_nbits, clamped_high + + +def check_run_file(run_file_path: str, table: list[int]) -> None: + """Reads and validates run(s) in a file against Python ASERT. + + A file may contain multiple runs separated by blank lines. + Each run has its own header (## lines) and data lines. + """ + + anchor_height = None + anchor_time = None + anchor_nbits = None + target_spacing = None + half_life = None + nbits_min = None + nbits_max = None + iterations = None + iteration_counter = 1 + runs_checked = 0 + + def finalize_run(): + nonlocal runs_checked + if iterations is not None and iterations > 0: + assert iteration_counter == iterations + 1, ( + "Expected %d iterations but found %d in run %d of %s" + % (iterations, iteration_counter - 1, runs_checked + 1, run_file_path) + ) + runs_checked += 1 + print(" OK (%d iterations)" % iterations) + + with open(run_file_path, "r") as f: + for line in f: + line = line.strip() + if line.startswith("## description:"): + # New run starting — finalize the previous one if any + if iterations is not None: + finalize_run() + # Reset state for new run + anchor_height = None + anchor_time = None + anchor_nbits = None + target_spacing = None + half_life = None + nbits_min = None + nbits_max = None + iterations = None + iteration_counter = 1 + print(line) + elif line == "": + pass + elif line.startswith("## anchor height: "): + anchor_height = int(line.split(": ", 1)[1]) + elif line.startswith("## anchor time: "): + anchor_time = int(line.split(": ", 1)[1]) + elif line.startswith("## anchor nBits: "): + anchor_nbits = int(line.split(": ", 1)[1]) + elif line.startswith("## target spacing: "): + target_spacing = int(line.split(": ", 1)[1]) + elif line.startswith("## half life: "): + half_life = int(line.split(": ", 1)[1]) + elif line.startswith("## nBitsMin: "): + parts = line.split() + nbits_min = int(parts[2]) + nbits_max = int(parts[4]) + elif line.startswith("## iterations: "): + iterations = int(line.split(": ", 1)[1]) + elif line.startswith("##") or line.startswith("#"): + pass + else: + assert anchor_height is not None, ( + "Missing anchor height in %s" % run_file_path + ) + assert iterations is not None, ( + "Missing iterations in %s" % run_file_path + ) + + parts = line.split() + it = int(parts[0]) + height = int(parts[1]) + time_secs = int(parts[2]) + nbits_from_file = int(parts[3]) + clamped_from_file = int(parts[4]) != 0 + + assert it == iteration_counter, ( + "Unexpected iteration %d (expected %d) in run %d of %s" + % (it, iteration_counter, runs_checked + 1, run_file_path) + ) + assert anchor_time is not None, ( + "Missing anchor time in %s" % run_file_path + ) + assert target_spacing is not None, ( + "Missing target spacing in %s" % run_file_path + ) + assert half_life is not None, ( + "Missing half life in %s" % run_file_path + ) + assert nbits_min is not None, ( + "Missing nbits min in %s" % run_file_path + ) + assert nbits_max is not None, ( + "Missing nbits max in %s" % run_file_path + ) + assert anchor_nbits is not None, ( + "Missing anchor nbits in %s" % run_file_path + ) + + time_diff = time_secs - anchor_time + height_diff = height - anchor_height + + calc_nbits, calc_clamped = calculate_factor_asert( + table, + target_spacing, + half_life, + nbits_min, + nbits_max, + anchor_nbits, + time_diff, + height_diff, + ) + + assert calc_nbits == nbits_from_file, ( + "nBits mismatch: Python=%d, C++=%d at iteration %d in %s" + % (calc_nbits, nbits_from_file, it, run_file_path) + ) + assert calc_clamped == clamped_from_file, ( + "clampedHigh mismatch: Python=%s, C++=%s at iteration %d in %s" + % (calc_clamped, clamped_from_file, it, run_file_path) + ) + + iteration_counter += 1 + + # Finalize the last run + finalize_run() + print("Checked %d runs" % runs_checked) + + +def main(): + # Locate the lookup table header relative to this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + table_path = os.path.join(script_dir, "..", "..", "src", "asert_table.h") + table_path = os.path.normpath(table_path) + + if not os.path.exists(table_path): + print("Cannot find %s" % table_path) + sys.exit(1) + + table = load_table(table_path) + print("Loaded %d-entry lookup table from %s" % (len(table), table_path)) + + if len(sys.argv) > 1: + run_files = sorted(sys.argv[1:]) + else: + run_files = sorted(f for f in os.listdir(".") if f.startswith("run")) + + if not run_files: + print("No run files (test vectors) found!") + print( + "Generate them with: src/contrib/testgen/gen_asert_test_vectors > vectors.txt" + ) + sys.exit(1) + + for rf in run_files: + print("\nChecking %s" % rf) + check_run_file(rf, table) + + print("\nAll OK.") + print( + "This confirms the Python FACTOR ASERT implementation matches " + + "the C++ implementation for all test vectors." + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/gen_compute_table.py b/scripts/gen_compute_table.py new file mode 100755 index 0000000000..208df4966c --- /dev/null +++ b/scripts/gen_compute_table.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Generate the FACTOR ASERT log2(compute) lookup table. + +Formula: log2_compute(n) = 0.0301 * log2(R) * n + 2 * log2(n) +where R = 3.8 and n is an even integer in [2, 1022]. + +Output: C++ header with 511-entry Q32.32 fixed-point int64_t array. +Usage: python3 scripts/gen_compute_table.py > src/asert_table.h +""" + +import decimal +import hashlib +import sys + +# Parameters +R = decimal.Decimal("3.8") +COEFF = decimal.Decimal("0.0301") +NBITS_MIN = 2 +NBITS_MAX = 1022 +Q32 = 2**32 + + +def log2_decimal(x: decimal.Decimal) -> decimal.Decimal: + """Compute log2(x) in high-precision Decimal arithmetic.""" + return x.ln() / decimal.Decimal(2).ln() + + +def main(): + decimal.getcontext().prec = 60 + + log2_R = log2_decimal(R) + + entries: list[tuple[int, int]] = [] + prev = None + for i in range(511): + n = 2 * (i + 1) # even integers 2, 4, 6, ..., 1022 + log2_n = log2_decimal(decimal.Decimal(n)) + value = COEFF * log2_R * n + 2 * log2_n + q32_value = int( + (value * Q32).to_integral_value(rounding=decimal.ROUND_HALF_EVEN) + ) + + if prev is not None: + assert q32_value > prev, ( + f"Table not monotonically increasing at nBits={n}: " + f"{q32_value} <= {prev}" + ) + prev = q32_value + entries.append((n, q32_value)) + + lines: list[str] = [] + lines.append("// Copyright (c) 2026 The FACTOR developers") + lines.append("// Distributed under the MIT software license, see the accompanying") + lines.append( + "// file COPYING or http://www.opensource.org/licenses/mit-license.php." + ) + lines.append("") + lines.append("#ifndef BITCOIN_ASERT_TABLE_H") + lines.append("#define BITCOIN_ASERT_TABLE_H") + lines.append("") + lines.append("#include ") + lines.append("") + lines.append( + "// log2(compute(nBits)) in Q32.32 fixed-point for every valid even nBits." + ) + lines.append("// Index i corresponds to nBits = 2*(i+1).") + lines.append("// Formula: log2_compute(n) = 0.0301 * log2(3.8) * n + 2 * log2(n)") + lines.append("// Generated by scripts/gen_compute_table.py") + lines.append("inline constexpr int64_t LOG2_COMPUTE_TABLE[511] = {") + for n, val in entries: + lines.append(f" {val:>20}LL, /* nBits={n:4d} */") + lines.append("};") + lines.append("") + lines.append("#endif // BITCOIN_ASERT_TABLE_H") + lines.append("") + + output = "\n".join(lines) + _ = sys.stdout.write(output) + + sha = hashlib.sha256(output.encode("utf-8")).hexdigest() + print(f"SHA-256: {sha}", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/src/Makefile.am b/src/Makefile.am index 1e3d965023..5730e727b4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -117,6 +117,7 @@ endif BITCOIN_CORE_H = \ addrdb.h \ addrman.h \ + asert_table.h \ attributes.h \ banman.h \ base58.h \ @@ -893,6 +894,8 @@ if ENABLE_BENCH include Makefile.bench.include endif +include Makefile.testgen.include + if ENABLE_QT include Makefile.qt.include endif diff --git a/src/Makefile.test.include b/src/Makefile.test.include index c1ed83f550..645f2474a1 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -64,6 +64,7 @@ endif # test_factorn binary # BITCOIN_TESTS =\ test/arith_uint256_tests.cpp \ + test/asert_tests.cpp \ test/scriptnum10.h \ test/addrman_tests.cpp \ test/amount_tests.cpp \ diff --git a/src/Makefile.testgen.include b/src/Makefile.testgen.include new file mode 100644 index 0000000000..f5ce27f023 --- /dev/null +++ b/src/Makefile.testgen.include @@ -0,0 +1,30 @@ +# Copyright (c) 2020 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +noinst_PROGRAMS += contrib/testgen/gen_asert_test_vectors + +contrib_testgen_gen_asert_test_vectors_SOURCES = \ + $(top_srcdir)/contrib/testgen/gen_asert_test_vectors.cpp + +contrib_testgen_gen_asert_test_vectors_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +contrib_testgen_gen_asert_test_vectors_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) + +contrib_testgen_gen_asert_test_vectors_LDADD = \ + $(LIBBITCOIN_SERVER) \ + $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_UTIL) \ + $(LIBBITCOIN_CONSENSUS) \ + $(LIBBITCOIN_CRYPTO) \ + $(LIBUNIVALUE) \ + $(LIBLEVELDB) \ + $(LIBLEVELDB_SSE42) \ + $(LIBMEMENV) \ + $(LIBSECP256K1) \ + $(BOOST_LIBS) \ + $(EVENT_PTHREADS_LIBS) \ + $(EVENT_LIBS) \ + $(MINIUPNPC_LIBS) \ + $(NATPMP_LIBS) + +contrib_testgen_gen_asert_test_vectors_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) diff --git a/src/asert_table.h b/src/asert_table.h new file mode 100644 index 0000000000..f571a4cc71 --- /dev/null +++ b/src/asert_table.h @@ -0,0 +1,528 @@ +// Copyright (c) 2026 The FACTOR developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_ASERT_TABLE_H +#define BITCOIN_ASERT_TABLE_H + +#include + +// log2(compute(nBits)) in Q32.32 fixed-point for every valid even nBits. +// Index i corresponds to nBits = 2*(i+1). +// Formula: log2_compute(n) = 0.0301 * log2(3.8) * n + 2 * log2(n) +// Generated by scripts/gen_compute_table.py +inline constexpr int64_t LOG2_COMPUTE_TABLE[511] = { + 9087915284LL, /* nBits= 2 */ + 18175830568LL, /* nBits= 4 */ + 23698600879LL, /* nBits= 6 */ + 27761726543LL, /* nBits= 8 */ + 31025048513LL, /* nBits= 10 */ + 33782477547LL, /* nBits= 12 */ + 36190794592LL, /* nBits= 14 */ + 38343583902LL, /* nBits= 16 */ + 40301209242LL, /* nBits= 18 */ + 42104886564LL, /* nBits= 20 */ + 43784013531LL, /* nBits= 22 */ + 45360296289LL, /* nBits= 24 */ + 46850218726LL, /* nBits= 26 */ + 48266594026LL, /* nBits= 28 */ + 49619579643LL, /* nBits= 30 */ + 50917364029LL, /* nBits= 32 */ + 52166644806LL, /* nBits= 34 */ + 53372970060LL, /* nBits= 36 */ + 54540987228LL, /* nBits= 38 */ + 55674628074LL, /* nBits= 40 */ + 56777248489LL, /* nBits= 42 */ + 57851735733LL, /* nBits= 44 */ + 58900591829LL, /* nBits= 46 */ + 59925999183LL, /* nBits= 48 */ + 60929872812LL, /* nBits= 50 */ + 61913902311LL, /* nBits= 52 */ + 62879585906LL, /* nBits= 54 */ + 63828258303LL, /* nBits= 56 */ + 64761113651LL, /* nBits= 58 */ + 65679224612LL, /* nBits= 60 */ + 66583558300LL, /* nBits= 62 */ + 67474989689LL, /* nBits= 64 */ + 68354312963LL, /* nBits= 66 */ + 69222251158LL, /* nBits= 68 */ + 70079464424LL, /* nBits= 70 */ + 70926557104LL, /* nBits= 72 */ + 71764083859LL, /* nBits= 74 */ + 72592554964LL, /* nBits= 76 */ + 73412440924LL, /* nBits= 78 */ + 74224176502LL, /* nBits= 80 */ + 75028164248LL, /* nBits= 82 */ + 75824777608LL, /* nBits= 84 */ + 76614363661LL, /* nBits= 86 */ + 77397245544LL, /* nBits= 88 */ + 78173724609LL, /* nBits= 90 */ + 78944082332LL, /* nBits= 92 */ + 79708582028LL, /* nBits= 94 */ + 80467470378LL, /* nBits= 96 */ + 81220978804LL, /* nBits= 98 */ + 81969324698LL, /* nBits= 100 */ + 82712712539LL, /* nBits= 102 */ + 83451334890LL, /* nBits= 104 */ + 84185373310LL, /* nBits= 106 */ + 84914999177LL, /* nBits= 108 */ + 85640374433LL, /* nBits= 110 */ + 86361652265LL, /* nBits= 112 */ + 87078977728LL, /* nBits= 114 */ + 87792488305LL, /* nBits= 116 */ + 88502314430LL, /* nBits= 118 */ + 89208579958LL, /* nBits= 120 */ + 89911402603LL, /* nBits= 122 */ + 90610894337LL, /* nBits= 124 */ + 91307161756LL, /* nBits= 126 */ + 92000306419LL, /* nBits= 128 */ + 92690425161LL, /* nBits= 130 */ + 93377610384LL, /* nBits= 132 */ + 94061950317LL, /* nBits= 134 */ + 94743529271LL, /* nBits= 136 */ + 95422427863LL, /* nBits= 138 */ + 96098723229LL, /* nBits= 140 */ + 96772489223LL, /* nBits= 142 */ + 97443796601LL, /* nBits= 144 */ + 98112713190LL, /* nBits= 146 */ + 98779304047LL, /* nBits= 148 */ + 99443631613LL, /* nBits= 150 */ + 100105755844LL, /* nBits= 152 */ + 100765734347LL, /* nBits= 154 */ + 101423622496LL, /* nBits= 156 */ + 102079473553LL, /* nBits= 158 */ + 102733338766LL, /* nBits= 160 */ + 103385267475LL, /* nBits= 162 */ + 104035307204LL, /* nBits= 164 */ + 104683503747LL, /* nBits= 166 */ + 105329901255LL, /* nBits= 168 */ + 105974542310LL, /* nBits= 170 */ + 106617468000LL, /* nBits= 172 */ + 107258717987LL, /* nBits= 174 */ + 107898330575LL, /* nBits= 176 */ + 108536342769LL, /* nBits= 178 */ + 109172790332LL, /* nBits= 180 */ + 109807707843LL, /* nBits= 182 */ + 110441128746LL, /* nBits= 184 */ + 111073085403LL, /* nBits= 186 */ + 111703609134LL, /* nBits= 188 */ + 112332730267LL, /* nBits= 190 */ + 112960478176LL, /* nBits= 192 */ + 113586881323LL, /* nBits= 194 */ + 114211967294LL, /* nBits= 196 */ + 114835762833LL, /* nBits= 198 */ + 115458293880LL, /* nBits= 200 */ + 116079585600LL, /* nBits= 202 */ + 116699662412LL, /* nBits= 204 */ + 117318548024LL, /* nBits= 206 */ + 117936265455LL, /* nBits= 208 */ + 118552837062LL, /* nBits= 210 */ + 119168284567LL, /* nBits= 212 */ + 119782629081LL, /* nBits= 214 */ + 120395891125LL, /* nBits= 216 */ + 121008090654LL, /* nBits= 218 */ + 121619247073LL, /* nBits= 220 */ + 122229379264LL, /* nBits= 222 */ + 122838505598LL, /* nBits= 224 */ + 123446643957LL, /* nBits= 226 */ + 124053811752LL, /* nBits= 228 */ + 124660025936LL, /* nBits= 230 */ + 125265303021LL, /* nBits= 232 */ + 125869659096LL, /* nBits= 234 */ + 126473109838LL, /* nBits= 236 */ + 127075670526LL, /* nBits= 238 */ + 127677356057LL, /* nBits= 240 */ + 128278180958LL, /* nBits= 242 */ + 128878159395LL, /* nBits= 244 */ + 129477305187LL, /* nBits= 246 */ + 130075631820LL, /* nBits= 248 */ + 130673152453LL, /* nBits= 250 */ + 131269879931LL, /* nBits= 252 */ + 131865826793LL, /* nBits= 254 */ + 132461005285LL, /* nBits= 256 */ + 133055427367LL, /* nBits= 258 */ + 133649104720LL, /* nBits= 260 */ + 134242048758LL, /* nBits= 262 */ + 134834270634LL, /* nBits= 264 */ + 135425781249LL, /* nBits= 266 */ + 136016591259LL, /* nBits= 268 */ + 136606711082LL, /* nBits= 270 */ + 137196150905LL, /* nBits= 272 */ + 137784920692LL, /* nBits= 274 */ + 138373030189LL, /* nBits= 276 */ + 138960488930LL, /* nBits= 278 */ + 139547306246LL, /* nBits= 280 */ + 140133491268LL, /* nBits= 282 */ + 140719052932LL, /* nBits= 284 */ + 141303999988LL, /* nBits= 286 */ + 141888341002LL, /* nBits= 288 */ + 142472084362LL, /* nBits= 290 */ + 143055238282LL, /* nBits= 292 */ + 143637810811LL, /* nBits= 294 */ + 144219809832LL, /* nBits= 296 */ + 144801243068LL, /* nBits= 298 */ + 145382118090LL, /* nBits= 300 */ + 145962442314LL, /* nBits= 302 */ + 146542223012LL, /* nBits= 304 */ + 147121467314LL, /* nBits= 306 */ + 147700182207LL, /* nBits= 308 */ + 148278374544LL, /* nBits= 310 */ + 148856051048LL, /* nBits= 312 */ + 149433218310LL, /* nBits= 314 */ + 150009882796LL, /* nBits= 316 */ + 150586050852LL, /* nBits= 318 */ + 151161728701LL, /* nBits= 320 */ + 151736922453LL, /* nBits= 322 */ + 152311638102LL, /* nBits= 324 */ + 152885881533LL, /* nBits= 326 */ + 153459658523LL, /* nBits= 328 */ + 154032974742LL, /* nBits= 330 */ + 154605835758LL, /* nBits= 332 */ + 155178247040LL, /* nBits= 334 */ + 155750213958LL, /* nBits= 336 */ + 156321741786LL, /* nBits= 338 */ + 156892835705LL, /* nBits= 340 */ + 157463500804LL, /* nBits= 342 */ + 158033742086LL, /* nBits= 344 */ + 158603564463LL, /* nBits= 346 */ + 159172972765LL, /* nBits= 348 */ + 159741971737LL, /* nBits= 350 */ + 160310566045LL, /* nBits= 352 */ + 160878760273LL, /* nBits= 354 */ + 161446558930LL, /* nBits= 356 */ + 162013966448LL, /* nBits= 358 */ + 162580987185LL, /* nBits= 360 */ + 163147625426LL, /* nBits= 362 */ + 163713885388LL, /* nBits= 364 */ + 164279771214LL, /* nBits= 366 */ + 164845286983LL, /* nBits= 368 */ + 165410436707LL, /* nBits= 370 */ + 165975224331LL, /* nBits= 372 */ + 166539653740LL, /* nBits= 374 */ + 167103728754LL, /* nBits= 376 */ + 167667453134LL, /* nBits= 378 */ + 168230830579LL, /* nBits= 380 */ + 168793864733LL, /* nBits= 382 */ + 169356559180LL, /* nBits= 384 */ + 169918917451LL, /* nBits= 386 */ + 170480943019LL, /* nBits= 388 */ + 171042639306LL, /* nBits= 390 */ + 171604009681LL, /* nBits= 392 */ + 172165057461LL, /* nBits= 394 */ + 172725785912LL, /* nBits= 396 */ + 173286198253LL, /* nBits= 398 */ + 173846297651LL, /* nBits= 400 */ + 174406087229LL, /* nBits= 402 */ + 174965570062LL, /* nBits= 404 */ + 175524749180LL, /* nBits= 406 */ + 176083627567LL, /* nBits= 408 */ + 176642208165LL, /* nBits= 410 */ + 177200493871LL, /* nBits= 412 */ + 177758487542LL, /* nBits= 414 */ + 178316191993LL, /* nBits= 416 */ + 178873609998LL, /* nBits= 418 */ + 179430744292LL, /* nBits= 420 */ + 179987597569LL, /* nBits= 422 */ + 180544172489LL, /* nBits= 424 */ + 181100471670LL, /* nBits= 426 */ + 181656497695LL, /* nBits= 428 */ + 182212253111LL, /* nBits= 430 */ + 182767740431LL, /* nBits= 432 */ + 183322962130LL, /* nBits= 434 */ + 183877920651LL, /* nBits= 436 */ + 184432618403LL, /* nBits= 438 */ + 184987057762LL, /* nBits= 440 */ + 185541241072LL, /* nBits= 442 */ + 186095170645LL, /* nBits= 444 */ + 186648848761LL, /* nBits= 446 */ + 187202277670LL, /* nBits= 448 */ + 187755459594LL, /* nBits= 450 */ + 188308396722LL, /* nBits= 452 */ + 188861091216LL, /* nBits= 454 */ + 189413545209LL, /* nBits= 456 */ + 189965760805LL, /* nBits= 458 */ + 190517740084LL, /* nBits= 460 */ + 191069485094LL, /* nBits= 462 */ + 191620997861LL, /* nBits= 464 */ + 192172280381LL, /* nBits= 466 */ + 192723334628LL, /* nBits= 468 */ + 193274162547LL, /* nBits= 470 */ + 193824766061LL, /* nBits= 472 */ + 194375147068LL, /* nBits= 474 */ + 194925307441LL, /* nBits= 476 */ + 195475249031LL, /* nBits= 478 */ + 196024973664LL, /* nBits= 480 */ + 196574483146LL, /* nBits= 482 */ + 197123779257LL, /* nBits= 484 */ + 197672863757LL, /* nBits= 486 */ + 198221738385LL, /* nBits= 488 */ + 198770404857LL, /* nBits= 490 */ + 199318864870LL, /* nBits= 492 */ + 199867120097LL, /* nBits= 494 */ + 200415172195LL, /* nBits= 496 */ + 200963022797LL, /* nBits= 498 */ + 201510673519LL, /* nBits= 500 */ + 202058125957LL, /* nBits= 502 */ + 202605381688LL, /* nBits= 504 */ + 203152442270LL, /* nBits= 506 */ + 203699309243LL, /* nBits= 508 */ + 204245984127LL, /* nBits= 510 */ + 204792468427LL, /* nBits= 512 */ + 205338763628LL, /* nBits= 514 */ + 205884871200LL, /* nBits= 516 */ + 206430792594LL, /* nBits= 518 */ + 206976529245LL, /* nBits= 520 */ + 207522082571LL, /* nBits= 522 */ + 208067453975LL, /* nBits= 524 */ + 208612644842LL, /* nBits= 526 */ + 209157656543LL, /* nBits= 528 */ + 209702490432LL, /* nBits= 530 */ + 210247147850LL, /* nBits= 532 */ + 210791630120LL, /* nBits= 534 */ + 211335938551LL, /* nBits= 536 */ + 211880074440LL, /* nBits= 538 */ + 212424039066LL, /* nBits= 540 */ + 212967833696LL, /* nBits= 542 */ + 213511459581LL, /* nBits= 544 */ + 214054917961LL, /* nBits= 546 */ + 214598210060LL, /* nBits= 548 */ + 215141337089LL, /* nBits= 550 */ + 215684300248LL, /* nBits= 552 */ + 216227100721LL, /* nBits= 554 */ + 216769739681LL, /* nBits= 556 */ + 217312218288LL, /* nBits= 558 */ + 217854537689LL, /* nBits= 560 */ + 218396699020LL, /* nBits= 562 */ + 218938703403LL, /* nBits= 564 */ + 219480551949LL, /* nBits= 566 */ + 220022245759LL, /* nBits= 568 */ + 220563785919LL, /* nBits= 570 */ + 221105173507LL, /* nBits= 572 */ + 221646409586LL, /* nBits= 574 */ + 222187495212LL, /* nBits= 576 */ + 222728431427LL, /* nBits= 578 */ + 223269219263LL, /* nBits= 580 */ + 223809859743LL, /* nBits= 582 */ + 224350353876LL, /* nBits= 584 */ + 224890702664LL, /* nBits= 586 */ + 225430907097LL, /* nBits= 588 */ + 225970968155LL, /* nBits= 590 */ + 226510886809LL, /* nBits= 592 */ + 227050664020LL, /* nBits= 594 */ + 227590300737LL, /* nBits= 596 */ + 228129797904LL, /* nBits= 598 */ + 228669156450LL, /* nBits= 600 */ + 229208377300LL, /* nBits= 602 */ + 229747461366LL, /* nBits= 604 */ + 230286409553LL, /* nBits= 606 */ + 230825222757LL, /* nBits= 608 */ + 231363901863LL, /* nBits= 610 */ + 231902447750LL, /* nBits= 612 */ + 232440861287LL, /* nBits= 614 */ + 232979143334LL, /* nBits= 616 */ + 233517294746LL, /* nBits= 618 */ + 234055316364LL, /* nBits= 620 */ + 234593209026LL, /* nBits= 622 */ + 235130973559LL, /* nBits= 624 */ + 235668610784LL, /* nBits= 626 */ + 236206121513LL, /* nBits= 628 */ + 236743506550LL, /* nBits= 630 */ + 237280766691LL, /* nBits= 632 */ + 237817902727LL, /* nBits= 634 */ + 238354915439LL, /* nBits= 636 */ + 238891805601LL, /* nBits= 638 */ + 239428573980LL, /* nBits= 640 */ + 239965221336LL, /* nBits= 642 */ + 240501748423LL, /* nBits= 644 */ + 241038155986LL, /* nBits= 646 */ + 241574444764LL, /* nBits= 648 */ + 242110615490LL, /* nBits= 650 */ + 242646668887LL, /* nBits= 652 */ + 243182605676LL, /* nBits= 654 */ + 243718426569LL, /* nBits= 656 */ + 244254132270LL, /* nBits= 658 */ + 244789723479LL, /* nBits= 660 */ + 245325200890LL, /* nBits= 662 */ + 245860565188LL, /* nBits= 664 */ + 246395817053LL, /* nBits= 666 */ + 246930957161LL, /* nBits= 668 */ + 247465986180LL, /* nBits= 670 */ + 248000904771LL, /* nBits= 672 */ + 248535713591LL, /* nBits= 674 */ + 249070413291LL, /* nBits= 676 */ + 249605004514LL, /* nBits= 678 */ + 250139487901LL, /* nBits= 680 */ + 250673864085LL, /* nBits= 682 */ + 251208133693LL, /* nBits= 684 */ + 251742297348LL, /* nBits= 686 */ + 252276355666LL, /* nBits= 688 */ + 252810309260LL, /* nBits= 690 */ + 253344158735LL, /* nBits= 692 */ + 253877904693LL, /* nBits= 694 */ + 254411547729LL, /* nBits= 696 */ + 254945088434LL, /* nBits= 698 */ + 255478527393LL, /* nBits= 700 */ + 256011865187LL, /* nBits= 702 */ + 256545102392LL, /* nBits= 704 */ + 257078239579LL, /* nBits= 706 */ + 257611277312LL, /* nBits= 708 */ + 258144216154LL, /* nBits= 710 */ + 258677056661LL, /* nBits= 712 */ + 259209799384LL, /* nBits= 714 */ + 259742444871LL, /* nBits= 716 */ + 260274993663LL, /* nBits= 718 */ + 260807446299LL, /* nBits= 720 */ + 261339803313LL, /* nBits= 722 */ + 261872065233LL, /* nBits= 724 */ + 262404232584LL, /* nBits= 726 */ + 262936305886LL, /* nBits= 728 */ + 263468285655LL, /* nBits= 730 */ + 264000172404LL, /* nBits= 732 */ + 264531966639LL, /* nBits= 734 */ + 265063668865LL, /* nBits= 736 */ + 265595279580LL, /* nBits= 738 */ + 266126799280LL, /* nBits= 740 */ + 266658228457LL, /* nBits= 742 */ + 267189567597LL, /* nBits= 744 */ + 267720817184LL, /* nBits= 746 */ + 268251977697LL, /* nBits= 748 */ + 268783049613LL, /* nBits= 750 */ + 269314033403LL, /* nBits= 752 */ + 269844929535LL, /* nBits= 754 */ + 270375738474LL, /* nBits= 756 */ + 270906460681LL, /* nBits= 758 */ + 271437096611LL, /* nBits= 760 */ + 271967646720LL, /* nBits= 762 */ + 272498111457LL, /* nBits= 764 */ + 273028491268LL, /* nBits= 766 */ + 273558786596LL, /* nBits= 768 */ + 274088997881LL, /* nBits= 770 */ + 274619125558LL, /* nBits= 772 */ + 275149170061LL, /* nBits= 774 */ + 275679131819LL, /* nBits= 776 */ + 276209011256LL, /* nBits= 778 */ + 276738808798LL, /* nBits= 780 */ + 277268524862LL, /* nBits= 782 */ + 277798159864LL, /* nBits= 784 */ + 278327714219LL, /* nBits= 786 */ + 278857188336LL, /* nBits= 788 */ + 279386582621LL, /* nBits= 790 */ + 279915897479LL, /* nBits= 792 */ + 280445133310LL, /* nBits= 794 */ + 280974290511LL, /* nBits= 796 */ + 281503369478LL, /* nBits= 798 */ + 282032370602LL, /* nBits= 800 */ + 282561294271LL, /* nBits= 802 */ + 283090140872LL, /* nBits= 804 */ + 283618910787LL, /* nBits= 806 */ + 284147604396LL, /* nBits= 808 */ + 284676222078LL, /* nBits= 810 */ + 285204764206LL, /* nBits= 812 */ + 285733231152LL, /* nBits= 814 */ + 286261623285LL, /* nBits= 816 */ + 286789940971LL, /* nBits= 818 */ + 287318184574LL, /* nBits= 820 */ + 287846354455LL, /* nBits= 822 */ + 288374450972LL, /* nBits= 824 */ + 288902474481LL, /* nBits= 826 */ + 289430425335LL, /* nBits= 828 */ + 289958303885LL, /* nBits= 830 */ + 290486110478LL, /* nBits= 832 */ + 291013845460LL, /* nBits= 834 */ + 291541509175LL, /* nBits= 836 */ + 292069101962LL, /* nBits= 838 */ + 292596624160LL, /* nBits= 840 */ + 293124076105LL, /* nBits= 842 */ + 293651458129LL, /* nBits= 844 */ + 294178770565LL, /* nBits= 846 */ + 294706013741LL, /* nBits= 848 */ + 295233187982LL, /* nBits= 850 */ + 295760293613LL, /* nBits= 852 */ + 296287330956LL, /* nBits= 854 */ + 296814300330LL, /* nBits= 856 */ + 297341202053LL, /* nBits= 858 */ + 297868036439LL, /* nBits= 860 */ + 298394803801LL, /* nBits= 862 */ + 298921504450LL, /* nBits= 864 */ + 299448138694LL, /* nBits= 866 */ + 299974706841LL, /* nBits= 868 */ + 300501209193LL, /* nBits= 870 */ + 301027646054LL, /* nBits= 872 */ + 301554017722LL, /* nBits= 874 */ + 302080324498LL, /* nBits= 876 */ + 302606566675LL, /* nBits= 878 */ + 303132744548LL, /* nBits= 880 */ + 303658858410LL, /* nBits= 882 */ + 304184908550LL, /* nBits= 884 */ + 304710895256LL, /* nBits= 886 */ + 305236818814LL, /* nBits= 888 */ + 305762679509LL, /* nBits= 890 */ + 306288477622LL, /* nBits= 892 */ + 306814213434LL, /* nBits= 894 */ + 307339887224LL, /* nBits= 896 */ + 307865499267LL, /* nBits= 898 */ + 308391049839LL, /* nBits= 900 */ + 308916539213LL, /* nBits= 902 */ + 309441967659LL, /* nBits= 904 */ + 309967335447LL, /* nBits= 906 */ + 310492642844LL, /* nBits= 908 */ + 311017890117LL, /* nBits= 910 */ + 311543077529LL, /* nBits= 912 */ + 312068205342LL, /* nBits= 914 */ + 312593273818LL, /* nBits= 916 */ + 313118283214LL, /* nBits= 918 */ + 313643233788LL, /* nBits= 920 */ + 314168125796LL, /* nBits= 922 */ + 314692959490LL, /* nBits= 924 */ + 315217735125LL, /* nBits= 926 */ + 315742452949LL, /* nBits= 928 */ + 316267113212LL, /* nBits= 930 */ + 316791716161LL, /* nBits= 932 */ + 317316262042LL, /* nBits= 934 */ + 317840751099LL, /* nBits= 936 */ + 318365183575LL, /* nBits= 938 */ + 318889559710LL, /* nBits= 940 */ + 319413879744LL, /* nBits= 942 */ + 319938143916LL, /* nBits= 944 */ + 320462352461LL, /* nBits= 946 */ + 320986505614LL, /* nBits= 948 */ + 321510603610LL, /* nBits= 950 */ + 322034646679LL, /* nBits= 952 */ + 322558635054LL, /* nBits= 954 */ + 323082568961LL, /* nBits= 956 */ + 323606448630LL, /* nBits= 958 */ + 324130274286LL, /* nBits= 960 */ + 324654046155LL, /* nBits= 962 */ + 325177764460LL, /* nBits= 964 */ + 325701429422LL, /* nBits= 966 */ + 326225041262LL, /* nBits= 968 */ + 326748600201LL, /* nBits= 970 */ + 327272106455LL, /* nBits= 972 */ + 327795560241LL, /* nBits= 974 */ + 328318961774LL, /* nBits= 976 */ + 328842311269LL, /* nBits= 978 */ + 329365608938LL, /* nBits= 980 */ + 329888854993LL, /* nBits= 982 */ + 330412049642LL, /* nBits= 984 */ + 330935193096LL, /* nBits= 986 */ + 331458285562LL, /* nBits= 988 */ + 331981327245LL, /* nBits= 990 */ + 332504318351LL, /* nBits= 992 */ + 333027259083LL, /* nBits= 994 */ + 333550149645LL, /* nBits= 996 */ + 334072990237LL, /* nBits= 998 */ + 334595781059LL, /* nBits=1000 */ + 335118522311LL, /* nBits=1002 */ + 335641214189LL, /* nBits=1004 */ + 336163856891LL, /* nBits=1006 */ + 336686450612LL, /* nBits=1008 */ + 337208995546LL, /* nBits=1010 */ + 337731491886LL, /* nBits=1012 */ + 338253939823LL, /* nBits=1014 */ + 338776339550LL, /* nBits=1016 */ + 339298691254LL, /* nBits=1018 */ + 339820995126LL, /* nBits=1020 */ + 340343251351LL, /* nBits=1022 */ +}; + +#endif // BITCOIN_ASERT_TABLE_H diff --git a/src/chain.h b/src/chain.h index f29c2559be..55540846a5 100644 --- a/src/chain.h +++ b/src/chain.h @@ -18,16 +18,22 @@ /** * Maximum amount of time that a block timestamp is allowed to exceed the * current network-adjusted time before the block will be accepted. + * + * A miner stamps the block at template creation, then spends an arbitrary amount + * of time factoring (eg. 180s, which is extremely fast for mainnet diff). Normally by the time the solution is broadcast, + * the timestamp, determined before factoring starts, is far in the past. + * Assuming monotonic timestamp, the only failure mode is a miner whose local clock is so far ahead (eg. 185s ahead) + * that template creation timestamp is still in the future by the time peer receives the mined block, + * or if a normal node falls 185s behind. */ -static constexpr int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60; +static constexpr int64_t MAX_FUTURE_BLOCK_TIME = 4; /** * Timestamp window used as a grace period by code that compares external * timestamps (such as timestamps passed to RPCs, or wallet key creation times) - * to block timestamps. This should be set at least as high as - * MAX_FUTURE_BLOCK_TIME. + * to block timestamps. */ -static constexpr int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME; +static constexpr int64_t TIMESTAMP_WINDOW = 2 * 60 * 60; /** * Maximum gap between node time and block time used diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 769966d80d..513a4f8521 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -102,6 +102,17 @@ class CMainParams : public CChainParams { consensus.nPowTargetSpacing = 30 * 60; // 30 Minutes * 60 Seconds |-> Seconds in 30 minutes consensus.fPowAllowMinDifficultyBlocks = false; consensus.fPowNoRetargeting = false; + + // The half life for the ASERT DAA. For every (nASERTHalfLife) seconds behind schedule the blockchain gets, + // difficulty is cut in half. Doubled if blocks are ahead of schedule. + // Two days + consensus.nASERTHalfLife = 2 * 24 * 60 * 60; + + // ASERT activation time: 2026-04-20 10:09:00 UTC, at FACT's birthday + consensus.asertActivationTime = 1776679740; + consensus.nBitsMin = 32; + consensus.nBitsMax = 1022; + consensus.nRuleChangeActivationThreshold = 639; // 95% of 672 (rounded up from 638.4) consensus.nMinerConfirmationWindow = 672; // nPowTargetTimespan / nPowTargetSpacing @@ -225,6 +236,17 @@ class CTestNetParams : public CChainParams { consensus.nPowTargetSpacing = 5 * 60; consensus.fPowAllowMinDifficultyBlocks = true; consensus.fPowNoRetargeting = false; + + // The half life for the ASERT DAA. For every (nASERTHalfLife) seconds behind schedule the blockchain gets, + // difficulty is cut in half. Doubled if blocks are ahead of schedule. + // One hour + consensus.nASERTHalfLife = 60 * 60; + + // ASERT always active on testnet + consensus.asertActivationTime = 0; + consensus.nBitsMin = 32; + consensus.nBitsMax = 1022; + consensus.nRuleChangeActivationThreshold = 90; // 75% for testchains consensus.nMinerConfirmationWindow = 288; // nPowTargetTimespan / nPowTargetSpacing @@ -376,6 +398,17 @@ class SigNetParams : public CChainParams { consensus.nPowTargetSpacing = 30 * 60; consensus.fPowAllowMinDifficultyBlocks = false; consensus.fPowNoRetargeting = false; + + // The half life for the ASERT DAA. For every (nASERTHalfLife) seconds behind schedule the blockchain gets, + // difficulty is cut in half. Doubled if blocks are ahead of schedule. + // Two days + consensus.nASERTHalfLife = 2 * 24 * 60 * 60; + + // ASERT always active on signet + consensus.asertActivationTime = 0; + consensus.nBitsMin = 32; + consensus.nBitsMax = 1022; + consensus.nRuleChangeActivationThreshold = 1815; // 90% of 2016 consensus.nMinerConfirmationWindow = 672; // nPowTargetTimespan / nPowTargetSpacing consensus.MinBIP9WarningHeight = 0; @@ -458,6 +491,17 @@ class CRegTestParams : public CChainParams { consensus.nPowTargetSpacing = 30 * 60; consensus.fPowAllowMinDifficultyBlocks = false; consensus.fPowNoRetargeting = false; + + // The half life for the ASERT DAA. For every (nASERTHalfLife) seconds behind schedule the blockchain gets, + // difficulty is cut in half. Doubled if blocks are ahead of schedule. + // One hour (match testnet for fast testing) + consensus.nASERTHalfLife = 60 * 60; + + // ASERT always active on regtest + consensus.asertActivationTime = 0; + consensus.nBitsMin = 32; + consensus.nBitsMax = 1022; + consensus.nRuleChangeActivationThreshold = 24; // 75% for testchains consensus.nMinerConfirmationWindow = 32; // Faster than normal for regtest (32 instead of 2016) diff --git a/src/consensus/params.h b/src/consensus/params.h index 732602820a..414b9ff0d5 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -119,6 +119,10 @@ struct Params { bool fPowAllowMinDifficultyBlocks; bool fPowNoRetargeting; int64_t nPowTargetSpacing; + int64_t nASERTHalfLife; + int64_t asertActivationTime; + int32_t nBitsMin; // FACTOR ASERT: minimum allowed nBits (easiest difficulty) + int32_t nBitsMax; // FACTOR ASERT: maximum allowed nBits (hardest difficulty) int64_t nPowTargetTimespan; double maxLilt; // The extrema of timespan deviation int32_t maxRetargetDelta; // The extrema of the retargeting delta diff --git a/src/init.cpp b/src/init.cpp index 09b1bfa8d6..6366c20fb4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -442,7 +442,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-maxconnections=", strprintf("Maintain at most connections to peers (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxreceivebuffer=", strprintf("Maximum per-connection receive buffer, *1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxsendbuffer=", strprintf("Maximum per-connection send buffer, *1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-maxtimeadjustment", "Deprecated. Peer time adjustment is disabled; the node uses local system clock only. This option is ignored. (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxuploadtarget=", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h). Limit does not apply to peers with 'download' permission. 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onion=", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2psam=", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1500,7 +1500,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) const CBlockIndex* tip = chainstate->m_chain.Tip(); RPCNotifyBlockChange(tip); - if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) { + if (tip && tip->nTime > GetAdjustedTime() + MAX_FUTURE_BLOCK_TIME) { strLoadError = _("The block database contains a block which appears to be from the future. " "This may be due to your computer's date and time being set incorrectly. " "Only rebuild the block database if you are sure that your computer's date and time are correct"); diff --git a/src/miner.cpp b/src/miner.cpp index 3dd6697dff..4dabb01a3c 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -30,7 +30,7 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) { int64_t nOldTime = pblock->nTime; - int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + int64_t nNewTime = std::max(pindexPrev->GetBlockTime()+1, GetAdjustedTime()); if (nOldTime < nNewTime) pblock->nTime = nNewTime; diff --git a/src/pow.cpp b/src/pow.cpp index 426b6364be..561b4ff928 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -3,17 +3,20 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include +#include +#include #include +#include +#include +#include #include //min and max. -#include +#include #include -#include #include #include #include -#include -#include //Blake2b, Scrypt and SHA3-512 #include @@ -31,10 +34,252 @@ // DeploymentActiveAfter #include +static std::atomic cachedAnchor{nullptr}; + +void ResetASERTAnchorBlockCache() noexcept { + cachedAnchor = nullptr; +} + +bool IsASERTEnabled(const Consensus::Params ¶ms, + const CBlockIndex *pindexPrev) { + if (pindexPrev == nullptr) { + return false; + } + + return pindexPrev->GetBlockTime() >= params.asertActivationTime; +} + +/** + * Returns a pointer to the anchor block used for ASERT. + * + * The anchor is the first block whose timestamp >= asertActivationTime. + * Its difficulty was still set by the old DAA, so it serves as the + * reference point from which ASERT computes all subsequent difficulties. + * + * This function is meant to be removed some time after the upgrade, once + * the anchor block is deeply buried, and behind a hard-coded checkpoint. + * + * Preconditions: - pindex must not be nullptr + * - pindex must satisfy: IsASERTEnabled(params, pindex) == true + * Postcondition: Returns a pointer to the first (lowest) block for which + * IsASERTEnabled is true, and for which IsASERTEnabled(pprev) + * is false (or for which pprev is nullptr). The return value may + * be pindex itself. + */ +static const CBlockIndex *GetASERTAnchorBlock(const CBlockIndex *const pindex, + const Consensus::Params ¶ms) { + assert(pindex); + + // - We check if we have a cached result, and if we do and it is really the + // ancestor of pindex, then we return it. + // + // - If we do not or if the cached result is not the ancestor of pindex, + // then we proceed with the more expensive walk back to find the ASERT + // anchor block. + // + // CBlockIndex::GetAncestor() is reasonably efficient; it uses CBlockIndex::pskip + // Note that if pindex == cachedAnchor, GetAncestor() here will return cachedAnchor, + // which is what we want. + const CBlockIndex *lastCached = cachedAnchor.load(); + if (lastCached && pindex->GetAncestor(lastCached->nHeight) == lastCached) + return lastCached; + + // Slow path: walk back until we find the first ancestor for which IsASERTEnabled() == true. + const CBlockIndex *anchor = pindex; + + while (anchor->pprev) { + // first, skip backwards testing IsASERTEnabled + // The below code leverages CBlockIndex::pskip to walk back efficiently. + if (IsASERTEnabled(params, anchor->pskip)) { + // skip backward + anchor = anchor->pskip; + continue; // continue skipping + } + // cannot skip here, walk back by 1 + if (!IsASERTEnabled(params, anchor->pprev)) { + // found it -- highest block where ASERT is not enabled is anchor->pprev, and + // anchor points to the first block for which IsASERTEnabled() == true + break; + } + anchor = anchor->pprev; + } + + // Overwrite the cache with the anchor we found. More likely than not, the next + // time we are asked to validate a header it will be part of same / similar chain, not + // some other unrelated chain with a totally different anchor. + cachedAnchor = anchor; + + return anchor; +} + + +// ============================================================================ +// FACTOR ASERT — operates in log2(compute) space via precomputed table. +// +// Sign convention (OPPOSITE of BCH's target-based formulation): +// Blocks too FAST → time_error > 0 → positive exponent → nBits UP (harder) +// Blocks too SLOW → time_error < 0 → negative exponent → nBits DOWN (easier) +// ============================================================================ + +ASERTResult CalculateFACTORASERT(const FACTORASERTParams& params, + int32_t anchorNBits, + int64_t timeDiff, + int64_t heightDiff) { + // --- Input validation --- + assert(anchorNBits % 2 == 0); + assert(anchorNBits >= params.nBitsMin && anchorNBits <= params.nBitsMax); + assert(heightDiff >= 0); + assert(params.halfLife > 0); + assert(params.targetSpacing > 0); + assert(params.nBitsMin >= 2 && params.nBitsMin % 2 == 0); + assert(params.nBitsMax <= 1022 && params.nBitsMax % 2 == 0); + assert(params.nBitsMin <= params.nBitsMax); + + // --- Step 1: Look up anchor's log2(compute) --- + const int anchorIdx = anchorNBits / 2 - 1; + const int64_t anchor_log2_compute = LOG2_COMPUTE_TABLE[anchorIdx]; + + // --- Step 2: Compute exponent in Q32.32 --- + // time_error > 0 when blocks arrive faster than expected (need harder) + const int64_t expected_elapsed = heightDiff * params.targetSpacing; + const int64_t time_error = expected_elapsed - timeDiff; + + // Overflow-safe Q32.32 conversion via split division. + // Direct (time_error << 32) overflows int64_t at |time_error| > ~2.15e9. + // This variant overflows only at |time_error| > 2^31 * halfLife (~11.7M years + // on mainnet), effectively eliminating the concern. + // + // Shifts go through uint64_t: in C++17, left-shifting a negative signed + // value is undefined behavior ([expr.shift]/2), which would fire for every + // slow-block header. The unsigned round-trip produces the same bit pattern + // on two's-complement platforms and is well-defined. + const int64_t integer_part = time_error / params.halfLife; + const int64_t remainder = time_error % params.halfLife; + const int64_t integer_shifted = + static_cast(static_cast(integer_part) << 32); + const int64_t remainder_shifted = + static_cast(static_cast(remainder) << 32); + const int64_t exponent_q32 = integer_shifted + + (remainder_shifted / params.halfLife); + + // --- Step 3: Compute target log2_compute --- + const int64_t target_log2_compute = anchor_log2_compute + exponent_q32; + + // --- Step 4: Binary search for closest table entry --- + const int idxMin = params.nBitsMin / 2 - 1; + const int idxMax = params.nBitsMax / 2 - 1; + + // Check clampedHigh BEFORE clamping the search result. + // This detects when the ASERT exponent pushes past what nBitsMax represents. + const bool clampedHigh = (target_log2_compute > LOG2_COMPUTE_TABLE[idxMax]); + + int resultIdx; + if (target_log2_compute <= LOG2_COMPUTE_TABLE[idxMin]) { + resultIdx = idxMin; + } else if (target_log2_compute >= LOG2_COMPUTE_TABLE[idxMax]) { + resultIdx = idxMax; + } else { + // Binary search: find lo such that TABLE[lo] <= target < TABLE[lo+1] + int lo = idxMin, hi = idxMax; + while (lo + 1 < hi) { + int mid = lo + (hi - lo) / 2; + if (LOG2_COMPUTE_TABLE[mid] <= target_log2_compute) { + lo = mid; + } else { + hi = mid; + } + } + // Pick the closer of the two adjacent entries. + // Tiebreak: lower nBits (lower index = easier difficulty). + const int64_t dist_lo = target_log2_compute - LOG2_COMPUTE_TABLE[lo]; + const int64_t dist_hi = LOG2_COMPUTE_TABLE[hi] - target_log2_compute; + resultIdx = (dist_lo <= dist_hi) ? lo : hi; + } + + const int32_t newNBits = (resultIdx + 1) * 2; + return ASERTResult{newNBits, clampedHigh}; +} + +// ============================================================================ +// FACTOR ASERT caller wrapper — bridges chain state to CalculateFACTORASERT(). +// +// fPowAllowMinDifficultyBlocks is intentionally NOT checked here. +// ASERT computes difficulty absolutely from the anchor, not relative to the +// previous block. A min-difficulty block under ASERT doesn't reset the +// schedule: it adds +1 to height_diff without meaningfully advancing +// time_diff, making the exponent *more* positive (harder). The escape +// hatch is counterproductive under ASERT and must not be invoked. +// ============================================================================ +static uint16_t GetNextFACTORASERTWorkRequired( + const CBlockIndex *pindexPrev, + const Consensus::Params ¶ms) +{ + assert(pindexPrev != nullptr); + + // Height guard: block 1 is the anchor itself (not ASERT-computed). + // Return genesis nBits so block 1 inherits the genesis difficulty. + if (pindexPrev->nHeight == 0) { + return pindexPrev->nBits; + } + + // Anchor selection + const CBlockIndex *pindexAnchor; + if (params.asertActivationTime == 0) { + // Testnet / regtest / signet: anchor is always block 1 + pindexAnchor = pindexPrev->GetAncestor(1); + } else { + // Mainnet: first block where block time >= activation time + pindexAnchor = GetASERTAnchorBlock(pindexPrev, params); + } + assert(pindexAnchor != nullptr); + + // Normalize anchor nBits to even (floor) and clamp into [nBitsMin, nBitsMax]. + // Handles odd-nBits anchors (e.g. signet genesis nBits=33) and guards + // against an anchor whose old-DAA difficulty fell outside the ASERT range. + int32_t anchorNBits = pindexAnchor->nBits & ~1; + if (anchorNBits < params.nBitsMin) { + LogPrintf("ASERT: anchor nBits %d below minimum %d, clamping\n", + anchorNBits, params.nBitsMin); + anchorNBits = params.nBitsMin; + } else if (anchorNBits > params.nBitsMax) { + LogPrintf("ASERT: anchor nBits %d above maximum %d, clamping\n", + anchorNBits, params.nBitsMax); + anchorNBits = params.nBitsMax; + } + + // Time and height measured from anchor itself (no +1, no parent). + const int64_t timeDiff = pindexPrev->GetBlockTime() - pindexAnchor->GetBlockTime(); + const int64_t heightDiff = pindexPrev->nHeight - pindexAnchor->nHeight; + + const FACTORASERTParams asertParams{ + params.nPowTargetSpacing, + params.nASERTHalfLife, + params.nBitsMin, + params.nBitsMax + }; + + const ASERTResult result = CalculateFACTORASERT(asertParams, anchorNBits, timeDiff, heightDiff); + + // Circuit breaker: if ASERT wants difficulty above nBitsMax, the + // factoring problem has become trivial at max difficulty — an + // existential threat. Return nBits=1024 which CheckProofOfWork + // rejects (>1023), halting the chain until a hardfork intervenes. + if (result.clampedHigh) { + LogPrintf("ASERT: clampedHigh — returning nBits=1024 (chain halt)\n"); + return 1024; + } + + return static_cast(result.nBits); +} + uint16_t GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader* pblock, const Consensus::Params& params) { assert(pindexLast != nullptr); + if (IsASERTEnabled(params, pindexLast)) { + return GetNextFACTORASERTWorkRequired(pindexLast, params); + } + // Check if interim DAA is active const bool isInterimDAAActive = TimeLimitedDeploymentActive(pindexLast, params, Consensus::DEPLOYMENT_INTERIM_DAA); diff --git a/src/pow.h b/src/pow.h index 69978d0ef4..cb0f0ac396 100644 --- a/src/pow.h +++ b/src/pow.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_POW_H #define BITCOIN_POW_H +#include #include #include #include @@ -15,6 +16,35 @@ class CBlockHeader; class CBlockIndex; class uint256; +/** Result of the FACTOR ASERT difficulty calculation. */ +struct ASERTResult { + int32_t nBits; ///< New difficulty (always even, in [nBitsMin, nBitsMax]) + bool clampedHigh; ///< True if unclamped result would have exceeded nBitsMax +}; + +/** Parameters for the FACTOR ASERT DAA. */ +struct FACTORASERTParams { + int64_t targetSpacing; ///< Target time between blocks, in seconds + int64_t halfLife; ///< ASERT half-life, in seconds + int32_t nBitsMin; ///< Minimum allowed nBits (even, >= 2) + int32_t nBitsMax; ///< Maximum allowed nBits (even, <= 1022) +}; + +/** + * Compute the next FACTOR nBits using the ASERT algorithm. + * + * Pure function — no chain-state dependencies. Works in log2(compute(nBits)) + * space via a precomputed lookup table with Q32.32 fixed-point arithmetic. + * + * Sign convention: a POSITIVE exponent means difficulty INCREASES (higher + * nBits). This is the OPPOSITE of BCH's target-based ASERT where a positive + * exponent increases the target (decreasing difficulty). + */ +ASERTResult CalculateFACTORASERT(const FACTORASERTParams& params, + int32_t anchorNBits, + int64_t timeDiff, + int64_t heightDiff); + uint16_t GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params&); int32_t CalculateDifficultyDelta(const int32_t nBits, const double nPeriodTimeProportionConsumed, const bool isHardDiffRemoved); int32_t CalculateInterimDifficultyDelta(const int32_t nBits, const double nPeriodTimeProportionConsumed); @@ -25,6 +55,17 @@ uint16_t CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirst bool CheckProofOfWork( const CBlockHeader& block, const Consensus::Params&); uint1024 gHash( const CBlockHeader& block, const Consensus::Params&); +/** + * ASERT caches a special block index for efficiency. If block indices are + * freed then this needs to be called to ensure no dangling pointer when a new + * block tree is created. + * (this is temporary and will be removed after the ASERT anchor is deeply buried) + */ +void ResetASERTAnchorBlockCache() noexcept; + +bool IsASERTEnabled(const Consensus::Params ¶ms, + const CBlockIndex *pindexPrev); + //Factoring pollar rho algorithm int rho( uint64_t &g, uint64_t n); int rho( mpz_t g, mpz_t n); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index e1f943be3d..567d139f1c 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include