CONSENSUS - INT-devs/intcoin GitHub Wiki
Version: 1.0.0 Status: 🟢 Production Beta (RandomX + Enhanced Mempool) Tests: 17/17 test suites passing (100%) Last Updated: January 2, 2026
INTcoin uses a hybrid consensus mechanism combining RandomX Proof-of-Work with Digishield V5 dynamic difficulty adjustment, enhanced with intelligent transaction prioritization.
- Overview
- RandomX Proof-of-Work
- Digishield V5 Difficulty Adjustment
- Block Rewards
- Implementation
- Security Considerations
- Test Coverage
| Parameter | Value | Purpose |
|---|---|---|
| Algorithm | RandomX | ASIC-resistant PoW |
| Block Time | 2 minutes (120 seconds) | Target block interval |
| Difficulty Adjustment | Digishield V5 (every block) | Responsive difficulty |
| Initial Reward | 105,113,636 INT | Block subsidy |
| Halving Interval | 1,051,200 blocks (~4 years) | Reward reduction |
| Max Halvings | 64 | After which rewards = 0 |
| Total Supply | 221 Trillion INT | Maximum coin supply |
| Emission Period | ~256 years | Total emission time |
###Formula Summary
Block Reward = 105,113,636 >> (height / 1,051,200)
Difficulty Adjustment = Digishield V5 (per-block)
Target Hash < (2^256 / difficulty)
RandomX is a Proof-of-Work algorithm optimized for general-purpose CPUs. It uses random code execution and memory-hard techniques to ensure ASIC resistance.
Status: ✅ Complete (100%)
- ASIC Resistance: Optimized for CPUs, economically unfeasible for ASICs
- Fair Distribution: Allows CPU mining participation
- Security: Proven algorithm used by Monero since 2019
- Memory Hard: Requires 2GB+ memory, prevents lightweight implementations
RandomX uses epoch-based key rotation to maintain ASIC resistance:
Epoch Length: 2048 blocks (~2.8 days at 2-minute blocks)
Epoch Key: SHA3-256("INTcoin-RandomX-Epoch-{N}")
Block 0-2047: Epoch 0
Block 2048-4095: Epoch 1
Block 4096-6143: Epoch 2
...1. Get block header (excluding randomx_hash field)
2. Get epoch number: epoch = height / 2048
3. Generate epoch key: SHA3-256("INTcoin-RandomX-Epoch-{epoch}")
4. Initialize RandomX cache with epoch key
5. Serialize block header
6. Calculate RandomX hash
7. Check: hash < target
// x86_64 (Intel/AMD)
- JIT compilation enabled
- Hardware AES instructions
- AVX2 vectorization
// ARM64 (Apple Silicon, AWS Graviton)
- Hardware AES instructions
- NEON vectorization
- Software JIT
// Other architectures
- Interpreter mode
- Software AES
- Portable implementation| CPU | Hashrate | Power |
|---|---|---|
| AMD Ryzen 9 5950X (16c) | ~18 KH/s | ~105W |
| Intel i9-12900K (16c) | ~16 KH/s | ~125W |
| Apple M1 Max (10c) | ~6 KH/s | ~30W |
| Apple M2 Ultra (24c) | ~15 KH/s | ~60W |
#include "intcoin/consensus.h"
// Initialize RandomX (call once at startup)
auto init_result = RandomXValidator::Initialize();
if (init_result.IsError()) {
// Handle error
}
// Calculate hash for block header
BlockHeader header;
header.version = 1;
header.prev_block_hash = previous_block.GetHash();
header.merkle_root = CalculateMerkleRoot(transactions);
header.timestamp = GetCurrentTime();
header.bits = next_difficulty;
header.nonce = 0;
header.randomx_key = RandomXValidator::GetRandomXKey(height);
// Mine (find valid nonce)
while (true) {
auto hash_result = RandomXValidator::CalculateHash(header);
if (hash_result.IsOk()) {
uint256 hash = *hash_result.value;
if (hash < target) {
// Found valid block!
header.randomx_hash = hash;
break;
}
}
header.nonce++;
}
// Validate block hash
auto validate_result = RandomXValidator::ValidateBlockHash(header);
if (validate_result.IsOk()) {
// Block is valid
}
// Update dataset for new epoch (automatic)
if (RandomXValidator::NeedsDatasetUpdate(height)) {
RandomXValidator::UpdateDataset(height);
}
// Shutdown (call at exit)
RandomXValidator::Shutdown();Source Files:
-
src/consensus/consensus.cpp- RandomX implementation -
include/intcoin/consensus.h- API definitions -
tests/test_randomx.cpp- Test suite
Key Functions:
Result<void> RandomXValidator::Initialize();
void RandomXValidator::Shutdown();
Result<uint256> RandomXValidator::CalculateHash(const BlockHeader& header);
Result<void> RandomXValidator::ValidateBlockHash(const BlockHeader& header);
uint256 RandomXValidator::GetRandomXKey(uint64_t height);
bool RandomXValidator::NeedsDatasetUpdate(uint64_t height);
Result<void> RandomXValidator::UpdateDataset(uint64_t height);Global State (thread-safe with mutex):
randomx_cache* g_randomx_cache; // RandomX cache
randomx_vm* g_randomx_vm; // RandomX VM
uint256 g_current_key; // Current epoch key
uint64_t g_current_epoch; // Current epoch number
std::mutex g_randomx_mutex; // Thread safety
bool g_randomx_initialized; // Initialization flagDigishield V5 is INTcoin's enhanced per-block difficulty adjustment algorithm with four key improvements over V3:
- 120-block averaging window (vs 60 in V3) - more stability
- Median timestamp from last 11 blocks - manipulation resistance
- Asymmetric damping: 3x up, 4x down - time-warp attack resistance
- Exponentially weighted moving average (EWMA) - recent blocks weighted higher
Status: 🟢 Implemented
- Fast Response: Adjusts every block (vs. Bitcoin's 2016 blocks)
- Attack Resistant: Asymmetric damping prevents time-warp attacks
- Manipulation Proof: Median timestamps prevent single-miner timestamp manipulation
- Responsive: EWMA weights recent blocks 3x more than oldest blocks
- Stable: 120-block window provides smooth difficulty transitions
DIFFICULTY_AVERAGING_WINDOW = 120 blocks // V5: increased from 60
MEDIAN_TIMESTAMP_WINDOW = 11 blocks // V5: new
DAMPING_FACTOR_UP = 3 // V5: asymmetric (faster increase)
DAMPING_FACTOR_DOWN = 4 // V5: asymmetric (slower decrease)
EWMA_DECAY = 0.97 // V5: new
TARGET_BLOCK_TIME = 120 seconds
function GetNextWorkRequired(last_block, blockchain):
// Get last N blocks
blocks = blockchain.GetLastNBlocks(DIFFICULTY_AVERAGING_WINDOW)
// V5: Use median timestamps (prevents manipulation)
newest_median = GetMedianTimestamp(blocks[0:11])
oldest_median = GetMedianTimestamp(blocks[109:120])
actual_timespan = newest_median - oldest_median
// Calculate expected timespan
expected_timespan = (AVERAGING_WINDOW - MEDIAN_WINDOW) * TARGET_BLOCK_TIME
// V5: Asymmetric damping (resists time-warp attacks)
if actual_timespan < expected_timespan:
// Blocks too fast - allow faster difficulty increase
min_timespan = expected_timespan / DAMPING_FACTOR_UP
actual_timespan = max(min_timespan, actual_timespan)
else:
// Blocks too slow - allow slower difficulty decrease
max_timespan = expected_timespan * DAMPING_FACTOR_DOWN
actual_timespan = min(max_timespan, actual_timespan)
// V5: EWMA - weight recent blocks higher
weights[i] = EWMA_DECAY^i // Recent blocks weighted ~3x more
avg_difficulty = WeightedAverage(blocks.difficulty, weights)
// Calculate new difficulty
new_difficulty = avg_difficulty * expected_timespan / actual_timespan
return new_difficultyAveraging Window: Uses last 60 blocks to smooth out variance Damping: Limits adjustment to ±25% per block Min/Max Difficulty: Enforces minimum and maximum difficulty bounds
// src/consensus/consensus.cpp
uint32_t DifficultyCalculator::GetNextWorkRequired(
const BlockHeader& last_block,
const Blockchain& chain
) {
const int AVERAGING_WINDOW = 60;
const int DAMPING_FACTOR = 4;
// Get last N blocks
std::vector<BlockHeader> blocks;
for (int i = 0; i < AVERAGING_WINDOW; i++) {
blocks.push_back(chain.GetBlockHeader(last_block.height - i));
}
// Calculate actual timespan
uint64_t actual_timespan = blocks[0].timestamp - blocks[AVERAGING_WINDOW-1].timestamp;
// Calculate average difficulty
uint256 total_difficulty{};
for (const auto& block : blocks) {
total_difficulty += CompactToTarget(block.bits);
}
uint256 avg_difficulty = total_difficulty / AVERAGING_WINDOW;
// Calculate expected timespan
uint64_t expected_timespan = (AVERAGING_WINDOW - 1) * consensus::TARGET_BLOCK_TIME;
// Apply damping
uint64_t min_timespan = expected_timespan / DAMPING_FACTOR;
uint64_t max_timespan = expected_timespan * DAMPING_FACTOR;
actual_timespan = std::clamp(actual_timespan, min_timespan, max_timespan);
// Calculate new difficulty
uint256 new_target = avg_difficulty * actual_timespan / expected_timespan;
// Enforce limits
if (new_target > consensus::MAX_TARGET) {
new_target = consensus::MAX_TARGET;
}
if (new_target < consensus::MIN_TARGET) {
new_target = consensus::MIN_TARGET;
}
return TargetToCompact(new_target);
}
uint256 DifficultyCalculator::CompactToTarget(uint32_t compact) {
// Convert compact difficulty representation to full target
uint32_t exponent = compact >> 24;
uint32_t mantissa = compact & 0x00FFFFFF;
uint256 target{};
if (exponent <= 3) {
mantissa >>= (8 * (3 - exponent));
target = uint256{mantissa};
} else {
target = uint256{mantissa};
// Shift left by (exponent - 3) bytes
// Implementation needed
}
return target;
}
uint32_t DifficultyCalculator::TargetToCompact(const uint256& target) {
// Convert full target to compact difficulty representation
// Find first non-zero byte
int leading_zeros = 0;
for (int i = 31; i >= 0; i--) {
if (target[i] != 0) {
break;
}
leading_zeros++;
}
uint32_t exponent = 32 - leading_zeros;
uint32_t mantissa = /* extract 3 bytes */;
return (exponent << 24) | mantissa;
}uint64_t GetBlockReward(uint64_t height) {
uint64_t halvings = height / consensus::HALVING_INTERVAL; // 1,051,200 blocks
if (halvings >= consensus::MAX_HALVINGS) { // 64 halvings
return 0;
}
uint64_t reward = consensus::INITIAL_BLOCK_REWARD; // 105,113,636 INT
reward >>= halvings; // Divide by 2^halvings
return reward;
}| Halving | Block Range | Blocks | Reward (INT) | Coins Minted | % of Total |
|---|---|---|---|---|---|
| 0 | 0 - 1,051,199 | 1,051,200 | 105,113,636 | 110.5T | 50.0% |
| 1 | 1,051,200 - 2,102,399 | 1,051,200 | 52,556,818 | 55.25T | 25.0% |
| 2 | 2,102,400 - 3,153,599 | 1,051,200 | 26,278,409 | 27.62T | 12.5% |
| 3 | 3,153,600 - 4,204,799 | 1,051,200 | 13,139,204 | 13.81T | 6.25% |
| ... | ... | ... | ... | ... | ... |
| 63 | 66,225,600 - 67,276,799 | 1,051,200 | ~0.01 | ~12K | ~0.000001% |
Total Supply: ~221 Trillion INT (after 64 halvings)
uint64_t GetSupplyAtHeight(uint64_t height) {
uint64_t supply = 0;
uint64_t current_height = 0;
while (current_height < height) {
uint64_t next_halving = ((current_height / consensus::HALVING_INTERVAL) + 1)
* consensus::HALVING_INTERVAL;
uint64_t blocks_this_period = std::min(next_halving, height) - current_height;
supply += blocks_this_period * GetBlockReward(current_height);
current_height += blocks_this_period;
}
return supply;
}Coinbase transactions are special transactions that create new coins and collect transaction fees. They must be validated to prevent inflation attacks and ensure fair reward distribution.
Status: ✅ Complete (100%)
- Inflation Prevention: Ensures miners don't create more coins than allowed
- Economic Security: Maintains the emission schedule and total supply cap
- Fee Collection: Validates miners can only claim fees from included transactions
- Attack Prevention: Prevents double-spending of block rewards
// Coinbase validation checks:
1. Transaction must be a coinbase (1 input with prevout index = 0xFFFFFFFF)
2. Calculate block subsidy with halving based on height
3. Total output ≤ (subsidy + transaction fees)
4. Outputs must exist and have valid scripts
5. Miners may claim less than full reward (burns allowed)Source: src/consensus/consensus.cpp:537-587
Result<void> ConsensusValidator::ValidateCoinbase(const Transaction& coinbase,
uint64_t height,
uint64_t total_fees) {
// 1. Check if this is actually a coinbase transaction
if (!coinbase.IsCoinbase()) {
return Result<void>::Error("Transaction is not a coinbase transaction");
}
// 2. Calculate block subsidy with halving
uint64_t subsidy = 0;
uint64_t halving_count = height / consensus::HALVING_INTERVAL;
if (halving_count >= consensus::MAX_HALVINGS) {
// After max halvings, no more subsidy (only fees)
subsidy = 0;
} else {
// Halve the reward for each halving period using bit shift
subsidy = consensus::INITIAL_BLOCK_REWARD >> halving_count;
}
// 3. Calculate expected reward (subsidy + fees)
uint64_t expected_reward = subsidy + total_fees;
// 4. Validate outputs exist
if (coinbase.outputs.empty()) {
return Result<void>::Error("Coinbase transaction has no outputs");
}
// 5. Validate output scripts are not empty
for (const auto& output : coinbase.outputs) {
if (output.script_pubkey.bytes.empty()) {
return Result<void>::Error("Coinbase output has empty script");
}
}
// 6. Check total output value doesn't exceed expected reward
uint64_t total_output = coinbase.GetTotalOutputValue();
if (total_output > expected_reward) {
return Result<void>::Error(
"Coinbase output value exceeds expected reward (got " +
std::to_string(total_output) + ", expected max " +
std::to_string(expected_reward) + ")"
);
}
// Note: It's acceptable for miners to claim less than the full reward
// (this is sometimes done intentionally or due to bugs)
return Result<void>::Ok();
}The block subsidy uses efficient bit-shift operations for halving:
// Every 1,051,200 blocks (~4 years), reward halves
uint64_t halving_count = height / 1051200;
// Bit shift right = divide by 2^n
// subsidy >>= 1 means subsidy = subsidy / 2
// subsidy >> n means subsidy = subsidy / 2^n
uint64_t subsidy = 105113636 >> halving_count;
// Examples:
// Height 0: 105113636 >> 0 = 105,113,636 INT
// Height 1,051,200: 105113636 >> 1 = 52,556,818 INT
// Height 2,102,400: 105113636 >> 2 = 26,278,409 INT
// Height 3,153,600: 105113636 >> 3 = 13,139,204 INTThe validation returns specific error messages for different failure scenarios:
| Error | Cause | Impact |
|---|---|---|
Transaction is not a coinbase transaction |
IsCoinbase() returns false |
Block rejected |
Coinbase transaction has no outputs |
Empty outputs vector | Block rejected |
Coinbase output has empty script |
Invalid script_pubkey | Block rejected |
Coinbase output value exceeds expected reward |
Output > (subsidy + fees) | Block rejected (inflation attempt) |
- Overflow Protection: Uses 64-bit integers for all calculations
- Halving Limit: After 64 halvings, subsidy = 0 (only fees remain)
- Underflow Allowed: Miners can claim less than full reward (intentional burns)
-
Fee Calculation: Caller must compute
total_feesfrom all transactions
#include "intcoin/consensus.h"
// Validate a block's coinbase transaction
Block block;
uint64_t total_fees = 0;
// Calculate fees from all non-coinbase transactions
for (size_t i = 1; i < block.transactions.size(); i++) {
const auto& tx = block.transactions[i];
// Get input value
uint64_t input_value = 0;
for (const auto& input : tx.inputs) {
auto utxo = blockchain.GetUTXO(input.prevout);
if (utxo.IsOk()) {
input_value += utxo.value->amount;
}
}
// Get output value
uint64_t output_value = tx.GetTotalOutputValue();
// Fee = input - output
if (input_value > output_value) {
total_fees += (input_value - output_value);
}
}
// Validate coinbase
auto result = ConsensusValidator::ValidateCoinbase(
block.transactions[0], // Coinbase must be first transaction
block.header.height,
total_fees
);
if (result.IsError()) {
std::cerr << "Coinbase validation failed: " << result.error << "\n";
// Reject block
}Coinbase validation is tested as part of the consensus validation test suite:
./tests/test_validation
# Tests include:
# - Valid coinbase with exact reward
# - Valid coinbase with less than full reward
# - Invalid coinbase with excessive output
# - Invalid coinbase with empty outputs
# - Coinbase validation across halving boundariessrc/consensus/consensus.cpp # Implementation
include/intcoin/consensus.h # API definitions
tests/test_randomx.cpp # RandomX tests
tests/test_difficulty.cpp # Difficulty tests (TODO)
namespace consensus {
constexpr uint64_t TARGET_BLOCK_TIME = 120; // 2 minutes
constexpr uint64_t HALVING_INTERVAL = 1051200; // ~4 years
constexpr uint64_t INITIAL_BLOCK_REWARD = 105113636; // INT
constexpr uint64_t MAX_HALVINGS = 64;
constexpr uint64_t MAX_SUPPLY = 221000000000000; // 221 Trillion INT
constexpr uint64_t MAX_BLOCK_SIZE = 8 * 1024 * 1024; // 8 MB
constexpr uint64_t COINBASE_MATURITY = 100; // blocks
constexpr uint32_t MIN_DIFFICULTY_BITS = 0x1f00ffff;
}- Epoch Keys: Different keys every 2048 blocks prevents precomputation
- Memory Hard: 2GB+ memory requirement prevents lightweight ASICs
- CPU Optimized: Fair distribution among CPU miners
- Thread Safe: All operations protected by mutex
- Timestamp Manipulation: Limited by median time past rule
- Hashrate Attacks: Damping factor prevents rapid difficulty swings
- Timewarp Attacks: Per-block adjustment prevents exploitation
- 51% Attacks: Does not prevent, but makes consistent attacks expensive
Timestamp Manipulation:
- Attacker mines blocks with false timestamps
- Mitigation: Median Time Past (MTP) rule
- Blocks must have timestamp > MTP of last 11 blocks
Difficulty Manipulation:
- Attacker mines blocks quickly to lower difficulty
- Mitigation: Damping factor limits adjustment speed
- Maximum adjustment: ±25% per block
Selfish Mining:
- Attacker withholds blocks to gain advantage
- Mitigation: Not fully prevented by consensus
- Requires >25% hashrate for profitability
Status: ✅ 6/6 tests passing
- RandomX Initialization - Tests VM and cache initialization
- Key Generation - Tests epoch-based key generation
- Hash Calculation - Tests deterministic hashing
- Dataset Updates - Tests epoch transitions
- Block Validation - Tests PoW validation
- Shutdown/Cleanup - Tests resource cleanup
- Basic Adjustment - Test difficulty increases/decreases correctly
- Damping - Test adjustment limits are enforced
- Edge Cases - Test minimum/maximum difficulty
- Timewarp Protection - Test timestamp manipulation protection
- Averaging - Test 60-block averaging window
cd build
./tests/test_randomx
# Expected output:
# ========================================
# RandomX PoW Tests
# ========================================
# ✓ Test 1: RandomX Initialization
# ✓ Test 2: Key Generation
# ✓ Test 3: Hash Calculation
# ✓ Test 4: Dataset Updates
# ✓ Test 5: Block Validation
# ✓ Test 6: Shutdown/Cleanup
# ========================================
# ✓ All RandomX tests passed!
# ========================================