difficulty calculation, move from randomx to autolykos2
This commit is contained in:
225
src/autolykos2/autolykos2.c
Normal file
225
src/autolykos2/autolykos2.c
Normal file
@@ -0,0 +1,225 @@
|
||||
#include <autolykos2/autolykos2.h>
|
||||
|
||||
#include "../../include/blake2/blake2.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t* buf;
|
||||
size_t cap;
|
||||
size_t len;
|
||||
} dag_buffer_t;
|
||||
|
||||
struct Autolykos2Context {
|
||||
dag_buffer_t dag;
|
||||
void* backend;
|
||||
};
|
||||
|
||||
#ifdef MINICOIN_AUTOLYKOS2_REF_AVAILABLE
|
||||
extern void* minicoin_autolykos2_ref_create(void);
|
||||
extern void minicoin_autolykos2_ref_destroy(void* handle);
|
||||
extern bool minicoin_autolykos2_ref_check_target(
|
||||
void* handle,
|
||||
const uint8_t message32[32],
|
||||
uint64_t nonce,
|
||||
uint32_t height,
|
||||
const uint8_t target32[32]
|
||||
);
|
||||
#endif
|
||||
|
||||
static bool Autolykos2_FallbackHash(
|
||||
const Autolykos2Context* ctx,
|
||||
const uint8_t* message,
|
||||
size_t messageLen,
|
||||
uint64_t nonce,
|
||||
uint32_t height,
|
||||
uint8_t outHash[32]
|
||||
) {
|
||||
uint8_t nonceBytes[8];
|
||||
uint8_t heightBytes[4];
|
||||
memcpy(nonceBytes, &nonce, sizeof(nonceBytes));
|
||||
memcpy(heightBytes, &height, sizeof(heightBytes));
|
||||
|
||||
size_t dagChunkLen = 0;
|
||||
const uint8_t* dagChunk = NULL;
|
||||
if (ctx && ctx->dag.buf && ctx->dag.len > 0) {
|
||||
dagChunk = ctx->dag.buf;
|
||||
dagChunkLen = ctx->dag.len > 64 ? 64 : ctx->dag.len;
|
||||
}
|
||||
|
||||
const size_t totalLen = messageLen + sizeof(nonceBytes) + sizeof(heightBytes) + dagChunkLen;
|
||||
uint8_t* material = (uint8_t*)malloc(totalLen == 0 ? 1 : totalLen);
|
||||
if (!material) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t off = 0;
|
||||
if (messageLen > 0) {
|
||||
memcpy(material + off, message, messageLen);
|
||||
off += messageLen;
|
||||
}
|
||||
memcpy(material + off, nonceBytes, sizeof(nonceBytes));
|
||||
off += sizeof(nonceBytes);
|
||||
memcpy(material + off, heightBytes, sizeof(heightBytes));
|
||||
off += sizeof(heightBytes);
|
||||
if (dagChunkLen > 0) {
|
||||
memcpy(material + off, dagChunk, dagChunkLen);
|
||||
}
|
||||
|
||||
const bool ok = Blake2b_Hash(material, totalLen, outHash, 32);
|
||||
free(material);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int Cmp256BE(const uint8_t a[32], const uint8_t b[32]) {
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
if (a[i] < b[i]) return -1;
|
||||
if (a[i] > b[i]) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Autolykos2Context* Autolykos2_Create(void) {
|
||||
Autolykos2Context* ctx = (Autolykos2Context*)calloc(1, sizeof(Autolykos2Context));
|
||||
if (!ctx) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef MINICOIN_AUTOLYKOS2_REF_AVAILABLE
|
||||
ctx->backend = minicoin_autolykos2_ref_create();
|
||||
#endif
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void Autolykos2_Destroy(Autolykos2Context* ctx) {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef MINICOIN_AUTOLYKOS2_REF_AVAILABLE
|
||||
if (ctx->backend) {
|
||||
minicoin_autolykos2_ref_destroy(ctx->backend);
|
||||
ctx->backend = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
free(ctx->dag.buf);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
bool Autolykos2_DagAllocate(Autolykos2Context* ctx, size_t bytes) {
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* newBuf = (uint8_t*)realloc(ctx->dag.buf, bytes == 0 ? 1 : bytes);
|
||||
if (!newBuf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->dag.buf = newBuf;
|
||||
ctx->dag.cap = bytes;
|
||||
if (ctx->dag.len > bytes) {
|
||||
ctx->dag.len = bytes;
|
||||
}
|
||||
if (bytes > 0) {
|
||||
memset(ctx->dag.buf, 0, bytes);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Autolykos2_DagAppend(Autolykos2Context* ctx, const uint8_t* data, size_t len) {
|
||||
if (!ctx || !data || len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx->dag.len + len > ctx->dag.cap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(ctx->dag.buf + ctx->dag.len, data, len);
|
||||
ctx->dag.len += len;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Autolykos2_DagClear(Autolykos2Context* ctx) {
|
||||
if (!ctx || !ctx->dag.buf) {
|
||||
return;
|
||||
}
|
||||
memset(ctx->dag.buf, 0, ctx->dag.cap);
|
||||
ctx->dag.len = 0;
|
||||
}
|
||||
|
||||
size_t Autolykos2_DagSize(const Autolykos2Context* ctx) {
|
||||
return ctx ? ctx->dag.len : 0;
|
||||
}
|
||||
|
||||
bool Autolykos2_Hash(
|
||||
Autolykos2Context* ctx,
|
||||
const uint8_t* message,
|
||||
size_t messageLen,
|
||||
uint64_t nonce,
|
||||
uint32_t height,
|
||||
uint8_t outHash[32]
|
||||
) {
|
||||
if (!ctx || !message || !outHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Autolykos2_FallbackHash(ctx, message, messageLen, nonce, height, outHash);
|
||||
}
|
||||
|
||||
bool Autolykos2_CheckTarget(
|
||||
Autolykos2Context* ctx,
|
||||
const uint8_t message32[32],
|
||||
uint64_t nonce,
|
||||
uint32_t height,
|
||||
const uint8_t target32[32],
|
||||
uint8_t outHash[32]
|
||||
) {
|
||||
if (!ctx || !message32 || !target32 || !outHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef MINICOIN_AUTOLYKOS2_REF_AVAILABLE
|
||||
if (ctx->backend) {
|
||||
const bool ok = minicoin_autolykos2_ref_check_target(ctx->backend, message32, nonce, height, target32);
|
||||
if (Autolykos2_FallbackHash(ctx, message32, 32, nonce, height, outHash)) {
|
||||
return ok;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!Autolykos2_FallbackHash(ctx, message32, 32, nonce, height, outHash)) {
|
||||
return false;
|
||||
}
|
||||
return Cmp256BE(outHash, target32) <= 0;
|
||||
}
|
||||
|
||||
bool Autolykos2_FindNonceSingleCore(
|
||||
Autolykos2Context* ctx,
|
||||
const uint8_t message32[32],
|
||||
uint32_t height,
|
||||
const uint8_t target32[32],
|
||||
uint64_t startNonce,
|
||||
uint64_t maxIterations,
|
||||
uint64_t* outNonce,
|
||||
uint8_t outHash[32]
|
||||
) {
|
||||
if (!ctx || !message32 || !target32 || !outNonce || !outHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t nonce = startNonce;
|
||||
for (uint64_t i = 0; i < maxIterations; ++i, ++nonce) {
|
||||
if (Autolykos2_CheckTarget(ctx, message32, nonce, height, target32, outHash)) {
|
||||
*outNonce = nonce;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
86
src/autolykos2/autolykos2_ref_wrapper.cpp
Normal file
86
src/autolykos2/autolykos2_ref_wrapper.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include <cpuAutolykos.h>
|
||||
#include <definitions.h>
|
||||
|
||||
// Reference implementation dependency from request.cc. We provide it locally
|
||||
// to avoid pulling the network/request stack.
|
||||
uint32_t calcN(uint32_t Hblock) {
|
||||
uint32_t headerHeight = 0;
|
||||
((uint8_t*)&headerHeight)[0] = ((uint8_t*)&Hblock)[3];
|
||||
((uint8_t*)&headerHeight)[1] = ((uint8_t*)&Hblock)[2];
|
||||
((uint8_t*)&headerHeight)[2] = ((uint8_t*)&Hblock)[1];
|
||||
((uint8_t*)&headerHeight)[3] = ((uint8_t*)&Hblock)[0];
|
||||
|
||||
uint32_t newN = INIT_N_LEN;
|
||||
if (headerHeight < IncreaseStart) {
|
||||
newN = INIT_N_LEN;
|
||||
} else if (headerHeight >= IncreaseEnd) {
|
||||
newN = MAX_N_LEN;
|
||||
} else {
|
||||
uint32_t itersNumber = (headerHeight - IncreaseStart) / IncreasePeriodForN + 1;
|
||||
for (uint32_t i = 0; i < itersNumber; i++) {
|
||||
newN = newN / 100 * 105;
|
||||
}
|
||||
}
|
||||
|
||||
return newN;
|
||||
}
|
||||
|
||||
struct minicoin_autolykos2_ref_handle {
|
||||
AutolykosAlg* alg;
|
||||
};
|
||||
|
||||
extern "C" void* minicoin_autolykos2_ref_create(void) {
|
||||
minicoin_autolykos2_ref_handle* h = new minicoin_autolykos2_ref_handle();
|
||||
h->alg = new AutolykosAlg();
|
||||
return h;
|
||||
}
|
||||
|
||||
extern "C" void minicoin_autolykos2_ref_destroy(void* handle) {
|
||||
if (!handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* h = static_cast<minicoin_autolykos2_ref_handle*>(handle);
|
||||
delete h->alg;
|
||||
delete h;
|
||||
}
|
||||
|
||||
extern "C" bool minicoin_autolykos2_ref_check_target(
|
||||
void* handle,
|
||||
const uint8_t message32[32],
|
||||
uint64_t nonce,
|
||||
uint32_t height,
|
||||
const uint8_t target32[32]
|
||||
) {
|
||||
if (!handle || !message32 || !target32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* h = static_cast<minicoin_autolykos2_ref_handle*>(handle);
|
||||
if (!h->alg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t nonceBytes[8];
|
||||
uint8_t heightBytes[4];
|
||||
std::memcpy(nonceBytes, &nonce, sizeof(nonceBytes));
|
||||
|
||||
// RunAlg expects height bytes; keep deterministic network order.
|
||||
heightBytes[0] = static_cast<uint8_t>((height >> 24) & 0xffu);
|
||||
heightBytes[1] = static_cast<uint8_t>((height >> 16) & 0xffu);
|
||||
heightBytes[2] = static_cast<uint8_t>((height >> 8) & 0xffu);
|
||||
heightBytes[3] = static_cast<uint8_t>(height & 0xffu);
|
||||
|
||||
uint8_t poolBound[32];
|
||||
std::memcpy(poolBound, target32, sizeof(poolBound));
|
||||
|
||||
return h->alg->RunAlg(
|
||||
const_cast<uint8_t*>(message32),
|
||||
nonceBytes,
|
||||
poolBound,
|
||||
heightBytes
|
||||
);
|
||||
}
|
||||
3
src/autolykos2/easylogging_init.cpp
Normal file
3
src/autolykos2/easylogging_init.cpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include <easylogging++.h>
|
||||
|
||||
INITIALIZE_EASYLOGGINGPP
|
||||
50
src/blake2/blake2.c
Normal file
50
src/blake2/blake2.c
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "../../include/blake2/blake2.h"
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool Blake2_HashInternal(
|
||||
const EVP_MD* md,
|
||||
size_t maxDigestLen,
|
||||
const uint8_t* input,
|
||||
size_t inputLen,
|
||||
uint8_t* out,
|
||||
size_t outLen
|
||||
) {
|
||||
if (!md || !out || outLen == 0 || outLen > maxDigestLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t digest[EVP_MAX_MD_SIZE];
|
||||
unsigned int digestLen = 0;
|
||||
|
||||
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = EVP_DigestInit_ex(ctx, md, NULL) == 1;
|
||||
if (ok && inputLen > 0) {
|
||||
ok = EVP_DigestUpdate(ctx, input, inputLen) == 1;
|
||||
}
|
||||
if (ok) {
|
||||
ok = EVP_DigestFinal_ex(ctx, digest, &digestLen) == 1;
|
||||
}
|
||||
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
if (!ok || digestLen < outLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(out, digest, outLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Blake2b_Hash(const uint8_t* input, size_t inputLen, uint8_t* out, size_t outLen) {
|
||||
return Blake2_HashInternal(EVP_blake2b512(), MINICOIN_BLAKE2B_OUTBYTES, input, inputLen, out, outLen);
|
||||
}
|
||||
|
||||
bool Blake2s_Hash(const uint8_t* input, size_t inputLen, uint8_t* out, size_t outLen) {
|
||||
return Blake2_HashInternal(EVP_blake2s256(), MINICOIN_BLAKE2S_OUTBYTES, input, inputLen, out, outLen);
|
||||
}
|
||||
@@ -1,6 +1,23 @@
|
||||
#include <block/block.h>
|
||||
#include <autolykos2/autolykos2.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static Autolykos2Context* g_autolykos2Ctx = NULL;
|
||||
|
||||
static Autolykos2Context* GetAutolykos2Ctx(void) {
|
||||
if (!g_autolykos2Ctx) {
|
||||
g_autolykos2Ctx = Autolykos2_Create();
|
||||
}
|
||||
return g_autolykos2Ctx;
|
||||
}
|
||||
|
||||
void Block_ShutdownPowContext(void) {
|
||||
if (g_autolykos2Ctx) {
|
||||
Autolykos2_Destroy(g_autolykos2Ctx);
|
||||
g_autolykos2Ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
block_t* Block_Create() {
|
||||
block_t* block = (block_t*)malloc(sizeof(block_t));
|
||||
if (!block) {
|
||||
@@ -116,13 +133,28 @@ void Block_CalculateMerkleRoot(const block_t* block, uint8_t* outHash) {
|
||||
DynArr_destroy(hashes2);
|
||||
}
|
||||
|
||||
void Block_CalculateRandomXHash(const block_t* block, uint8_t* outHash) {
|
||||
void Block_CalculateAutolykos2Hash(const block_t* block, uint8_t* outHash) {
|
||||
if (!block || !outHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
// PoW hash is also computed from the header only.
|
||||
RandomX_CalculateHash((const uint8_t*)&block->header, sizeof(block_header_t), outHash);
|
||||
// PoW hash is computed from the block header, while canonical block hash remains SHA256.
|
||||
Autolykos2Context* ctx = GetAutolykos2Ctx();
|
||||
if (!ctx) {
|
||||
memset(outHash, 0, 32);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Autolykos2_Hash(
|
||||
ctx,
|
||||
(const uint8_t*)&block->header,
|
||||
sizeof(block_header_t),
|
||||
block->header.nonce,
|
||||
(uint32_t)block->header.blockNumber,
|
||||
outHash
|
||||
)) {
|
||||
memset(outHash, 0, 32);
|
||||
}
|
||||
}
|
||||
|
||||
void Block_AddTransaction(block_t* block, signed_transaction_t* tx) {
|
||||
@@ -199,7 +231,7 @@ bool Block_HasValidProofOfWork(const block_t* block) {
|
||||
}
|
||||
|
||||
uint8_t hash[32];
|
||||
Block_CalculateRandomXHash(block, hash);
|
||||
Block_CalculateAutolykos2Hash(block, hash);
|
||||
|
||||
return Uint256_CompareBE(hash, target) <= 0;
|
||||
}
|
||||
|
||||
@@ -162,6 +162,8 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
fwrite(zeroHash, sizeof(uint8_t), 32, metaFile);
|
||||
uint256_t zeroSupply = {0};
|
||||
fwrite(&zeroSupply, sizeof(uint256_t), 1, metaFile);
|
||||
uint32_t initialTarget = INITIAL_DIFFICULTY;
|
||||
fwrite(&initialTarget, sizeof(uint32_t), 1, metaFile);
|
||||
|
||||
// TODO: Potentially some other things here, we'll see
|
||||
}
|
||||
@@ -215,6 +217,9 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
}
|
||||
|
||||
fclose(blockFile);
|
||||
|
||||
DynArr_destroy(blk->transactions);
|
||||
blk->transactions = NULL; // Clear transactions to save memory since they're now saved on disk
|
||||
}
|
||||
|
||||
// Update metadata with new size and last block hash
|
||||
@@ -228,12 +233,14 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
fwrite(lastHash, sizeof(uint8_t), 32, metaFile);
|
||||
}
|
||||
fwrite(¤tSupply, sizeof(uint256_t), 1, metaFile);
|
||||
uint32_t difficultyTarget = ((block_t*)DynArr_at(chain->blocks, newSize - 1))->header.difficultyTarget;
|
||||
fwrite(&difficultyTarget, sizeof(uint32_t), 1, metaFile);
|
||||
fclose(metaFile);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* outCurrentSupply) {
|
||||
bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* outCurrentSupply, uint32_t* outDifficultyTarget) {
|
||||
if (!chain || !chain->blocks || !dirpath || !outCurrentSupply) {
|
||||
return false;
|
||||
}
|
||||
@@ -259,6 +266,7 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out
|
||||
uint8_t lastSavedHash[32];
|
||||
fread(lastSavedHash, sizeof(uint8_t), 32, metaFile);
|
||||
fread(outCurrentSupply, sizeof(uint256_t), 1, metaFile);
|
||||
fread(outDifficultyTarget, sizeof(uint32_t), 1, metaFile);
|
||||
fclose(metaFile);
|
||||
|
||||
// TODO: Might add a flag to allow reading from a point onward, but just rewrite for now
|
||||
@@ -308,11 +316,9 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out
|
||||
fclose(blockFile);
|
||||
Chain_AddBlock(chain, blk);
|
||||
|
||||
if (blk->transactions) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
blk->transactions = NULL;
|
||||
}
|
||||
free(blk); // chain stores block headers/fields by value
|
||||
// Chain_AddBlock stores blocks by value, so the copied block now owns
|
||||
// blk->transactions. Only free the temporary wrapper struct here.
|
||||
free(blk);
|
||||
}
|
||||
|
||||
chain->size = savedSize;
|
||||
@@ -320,3 +326,78 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out
|
||||
// After read, you SHOULD verify chain validity. We're not doing it here since returning false is a bit unclear if the read failed or if the chain is invalid.
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t Chain_ComputeNextTarget(blockchain_t* chain, uint32_t currentTarget) {
|
||||
if (!chain || !chain->blocks) {
|
||||
return 0x00; // Impossible difficulty, only valid hash is all zeros (practically impossible)
|
||||
}
|
||||
|
||||
size_t chainSize = DynArr_size(chain->blocks);
|
||||
if (chainSize < DIFFICULTY_ADJUSTMENT_INTERVAL) {
|
||||
// Baby-chain, return initial difficulty
|
||||
return INITIAL_DIFFICULTY;
|
||||
}
|
||||
|
||||
// Assuming block validation validates timestamps, we can assume they're valid and can just read them
|
||||
block_t* lastBlock = (block_t*)DynArr_at(chain->blocks, chainSize - 1);
|
||||
block_t* adjustmentBlock = (block_t*)DynArr_at(chain->blocks, chainSize - DIFFICULTY_ADJUSTMENT_INTERVAL);
|
||||
if (!lastBlock || !adjustmentBlock) {
|
||||
return 0x00; // Impossible difficulty, only valid hash is all zeros (practically impossible)
|
||||
}
|
||||
|
||||
// Retarget uses whole-window span. Per-block average is implicit:
|
||||
// (actualTime / interval) / targetBlockTime == actualTime / targetTime.
|
||||
uint64_t actualTime = 0;
|
||||
if (lastBlock->header.timestamp > adjustmentBlock->header.timestamp) {
|
||||
actualTime = lastBlock->header.timestamp - adjustmentBlock->header.timestamp;
|
||||
}
|
||||
if (actualTime == 0) {
|
||||
return currentTarget; // Invalid/non-increasing time window; keep current target
|
||||
}
|
||||
|
||||
const uint64_t targetTime = (uint64_t)TARGET_BLOCK_TIME * (uint64_t)DIFFICULTY_ADJUSTMENT_INTERVAL;
|
||||
double timeRatio = (double)actualTime / (double)targetTime;
|
||||
|
||||
// Clamp per-epoch target movement: at most x2 easier or x2 harder. TODO: Check if the clamp should be more aggressive or looser
|
||||
if (timeRatio > 2.0) {
|
||||
timeRatio = 2.0;
|
||||
} else if (timeRatio < 0.5) {
|
||||
timeRatio = 0.5;
|
||||
}
|
||||
|
||||
uint32_t exponent = currentTarget >> 24;
|
||||
uint32_t mantissa = currentTarget & 0x007fffff;
|
||||
if (mantissa == 0 || exponent == 0) {
|
||||
return INITIAL_DIFFICULTY;
|
||||
}
|
||||
|
||||
double newMantissa = (double)mantissa * timeRatio;
|
||||
|
||||
// Normalize to compact format range.
|
||||
while (newMantissa > 8388607.0) { // 0x007fffff
|
||||
newMantissa /= 256.0;
|
||||
exponent++;
|
||||
}
|
||||
while (newMantissa > 0.0 && newMantissa < 32768.0 && exponent > 3) { // Keep coefficient in normal range
|
||||
newMantissa *= 256.0;
|
||||
exponent--;
|
||||
}
|
||||
|
||||
if (exponent > 32) {
|
||||
// Easiest representable target in our decoder range.
|
||||
return (32u << 24) | 0x007fffff;
|
||||
}
|
||||
if (exponent < 1) {
|
||||
exponent = 1;
|
||||
}
|
||||
|
||||
uint32_t newCoeff = (uint32_t)newMantissa;
|
||||
if (newCoeff == 0) {
|
||||
newCoeff = 1;
|
||||
}
|
||||
if (newCoeff > 0x007fffff) {
|
||||
newCoeff = 0x007fffff;
|
||||
}
|
||||
|
||||
return (exponent << 24) | (newCoeff & 0x007fffff);
|
||||
}
|
||||
|
||||
97
src/main.c
97
src/main.c
@@ -8,7 +8,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <randomx/librx_wrapper.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <constants.h>
|
||||
@@ -19,56 +18,11 @@
|
||||
|
||||
void handle_sigint(int sig) {
|
||||
printf("Caught signal %d, exiting...\n", sig);
|
||||
RandomX_Destroy();
|
||||
Block_ShutdownPowContext();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static double MonotonicSeconds(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (double)ts.tv_sec + ((double)ts.tv_nsec / 1000000000.0);
|
||||
}
|
||||
|
||||
static double MeasureRandomXHashrate(void) {
|
||||
uint8_t input[80] = {0};
|
||||
uint8_t outHash[32];
|
||||
uint64_t counter = 0;
|
||||
|
||||
const double start = MonotonicSeconds();
|
||||
double now = start;
|
||||
do {
|
||||
memcpy(input, &counter, sizeof(counter));
|
||||
RandomX_CalculateHash(input, sizeof(input), outHash);
|
||||
counter++;
|
||||
now = MonotonicSeconds();
|
||||
} while ((now - start) < 0.75); // short local benchmark window
|
||||
|
||||
const double elapsed = now - start;
|
||||
if (elapsed <= 0.0 || counter == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return (double)counter / elapsed;
|
||||
}
|
||||
|
||||
static uint32_t CompactTargetForExpectedHashes(double expectedHashes) {
|
||||
if (expectedHashes < 1.0) {
|
||||
expectedHashes = 1.0;
|
||||
}
|
||||
|
||||
// For exponent 0x1f: target = mantissa * 2^(8*(0x1f-3)) = mantissa * 2^224
|
||||
// So expected hashes ~= 2^256 / target = 2^32 / mantissa.
|
||||
double mantissaF = 4294967296.0 / expectedHashes;
|
||||
if (mantissaF < 1.0) {
|
||||
mantissaF = 1.0;
|
||||
}
|
||||
if (mantissaF > 8388607.0) {
|
||||
mantissaF = 8388607.0; // 0x007fffff
|
||||
}
|
||||
|
||||
const uint32_t mantissa = (uint32_t)mantissaF;
|
||||
return (0x1fU << 24) | (mantissa & 0x007fffffU);
|
||||
}
|
||||
uint32_t difficultyTarget = INITIAL_DIFFICULTY;
|
||||
|
||||
static bool MineBlock(block_t* block) {
|
||||
if (!block) {
|
||||
@@ -91,24 +45,18 @@ int main(int argc, char* argv[]) {
|
||||
signal(SIGINT, handle_sigint);
|
||||
|
||||
const char* chainDataDir = CHAIN_DATA_DIR;
|
||||
const uint64_t blocksToMine = 10;
|
||||
const uint64_t blocksToMine = 1000;
|
||||
const double targetSeconds = TARGET_BLOCK_TIME;
|
||||
|
||||
uint256_t currentSupply = uint256_from_u64(0);
|
||||
|
||||
// Init RandomX
|
||||
if (!RandomX_Init("minicoin", false)) { // TODO: Use a key that is not hardcoded; E.g. hash of the last block, every thousand blocks, difficulty recalibration, etc.
|
||||
fprintf(stderr, "failed to initialize RandomX\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
blockchain_t* chain = Chain_Create();
|
||||
if (!chain) {
|
||||
fprintf(stderr, "failed to create chain\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply)) {
|
||||
if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply, &difficultyTarget)) {
|
||||
printf("No existing chain loaded from %s\n", chainDataDir);
|
||||
}
|
||||
|
||||
@@ -138,17 +86,6 @@ int main(int argc, char* argv[]) {
|
||||
if (argc > 1 && strcmp(argv[1], "-mine") == 0) {
|
||||
printf("Mining %llu blocks with target time %.0fs...\n", (unsigned long long)blocksToMine, targetSeconds);
|
||||
|
||||
const double hps = MeasureRandomXHashrate();
|
||||
const double expectedHashes = (hps > 0.0) ? (hps * targetSeconds) : 65536.0;
|
||||
const uint32_t calibratedBits = CompactTargetForExpectedHashes(expectedHashes);
|
||||
//const uint32_t calibratedBits = 0xffffffff; // Absurdly low diff for testing
|
||||
|
||||
printf("RandomX benchmark: %.2f H/s, target %.0fs, nBits=0x%08x, diff=%.2f\n",
|
||||
hps,
|
||||
targetSeconds,
|
||||
calibratedBits,
|
||||
(double)(1.f / calibratedBits) * 4294967296.0); // Basic representation as a big number for now
|
||||
|
||||
uint8_t minerAddress[32];
|
||||
SHA256((const unsigned char*)"minicoin-miner-1", strlen("minicoin-miner-1"), minerAddress);
|
||||
|
||||
@@ -157,7 +94,7 @@ int main(int argc, char* argv[]) {
|
||||
if (!block) {
|
||||
fprintf(stderr, "failed to create block\n");
|
||||
Chain_Destroy(chain);
|
||||
RandomX_Destroy();
|
||||
Block_ShutdownPowContext();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -174,7 +111,7 @@ int main(int argc, char* argv[]) {
|
||||
memset(block->header.prevHash, 0, sizeof(block->header.prevHash));
|
||||
}
|
||||
block->header.timestamp = (uint64_t)time(NULL);
|
||||
block->header.difficultyTarget = calibratedBits;
|
||||
block->header.difficultyTarget = difficultyTarget;
|
||||
block->header.nonce = 0;
|
||||
|
||||
signed_transaction_t coinbaseTx;
|
||||
@@ -195,7 +132,7 @@ int main(int argc, char* argv[]) {
|
||||
fprintf(stderr, "failed to mine block within nonce range\n");
|
||||
Block_Destroy(block);
|
||||
Chain_Destroy(chain);
|
||||
RandomX_Destroy();
|
||||
Block_ShutdownPowContext();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -203,28 +140,36 @@ int main(int argc, char* argv[]) {
|
||||
fprintf(stderr, "failed to append block to chain\n");
|
||||
Block_Destroy(block);
|
||||
Chain_Destroy(chain);
|
||||
RandomX_Destroy();
|
||||
Block_ShutdownPowContext();
|
||||
return 1;
|
||||
}
|
||||
|
||||
(void)uint256_add_u64(¤tSupply, coinbaseTx.transaction.amount);
|
||||
|
||||
uint8_t blockHash[32];
|
||||
Block_CalculateHash(block, blockHash);
|
||||
printf("Mined block %llu/%llu (height=%llu) nonce=%llu reward=%llu supply=%llu merkle=%02x%02x%02x%02x... hash=%02x%02x%02x%02x...\n",
|
||||
uint8_t canonicalHash[32];
|
||||
uint8_t powHash[32];
|
||||
Block_CalculateHash(block, canonicalHash);
|
||||
Block_CalculateAutolykos2Hash(block, powHash);
|
||||
printf("Mined block %llu/%llu (height=%llu) nonce=%llu reward=%llu supply=%llu diff=%#x merkle=%02x%02x%02x%02x... pow=%02x%02x%02x%02x... canonical=%02x%02x%02x%02x...\n",
|
||||
(unsigned long long)(mined + 1),
|
||||
(unsigned long long)blocksToMine,
|
||||
(unsigned long long)block->header.blockNumber,
|
||||
(unsigned long long)block->header.nonce,
|
||||
(unsigned long long)coinbaseTx.transaction.amount,
|
||||
(unsigned long long)currentSupply.limbs[0],
|
||||
(unsigned int)block->header.difficultyTarget,
|
||||
block->header.merkleRoot[0], block->header.merkleRoot[1], block->header.merkleRoot[2], block->header.merkleRoot[3],
|
||||
blockHash[0], blockHash[1], blockHash[2], blockHash[3]);
|
||||
powHash[0], powHash[1], powHash[2], powHash[3],
|
||||
canonicalHash[0], canonicalHash[1], canonicalHash[2], canonicalHash[3]);
|
||||
|
||||
free(block); // chain stores blocks by value; transactions are owned by chain copy
|
||||
|
||||
// Save chain after each mined block
|
||||
Chain_SaveToFile(chain, chainDataDir, currentSupply);
|
||||
|
||||
if (Chain_Size(chain) % DIFFICULTY_ADJUSTMENT_INTERVAL == 0) {
|
||||
difficultyTarget = Chain_ComputeNextTarget(chain, difficultyTarget);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Chain_SaveToFile(chain, chainDataDir, currentSupply)) {
|
||||
@@ -248,6 +193,6 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
Chain_Destroy(chain);
|
||||
RandomX_Destroy();
|
||||
Block_ShutdownPowContext();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
#include <randomx/librx_wrapper.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static randomx_cache* rxCache = NULL;
|
||||
static randomx_dataset* rxDataset = NULL;
|
||||
static randomx_vm* rxVm = NULL;
|
||||
|
||||
bool RandomX_Init(const char* key, bool preferFullMemory) {
|
||||
if (!key || rxCache || rxVm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const randomx_flags baseFlags = randomx_get_flags() | RANDOMX_FLAG_JIT;
|
||||
randomx_flags vmFlags = baseFlags | RANDOMX_FLAG_FULL_MEM;
|
||||
|
||||
rxCache = randomx_alloc_cache(baseFlags);
|
||||
if (!rxCache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomx_init_cache(rxCache, key, strlen(key));
|
||||
|
||||
// Prefer full-memory mode. If dataset allocation fails, fall back to light mode.
|
||||
if (preferFullMemory) {
|
||||
rxDataset = randomx_alloc_dataset(vmFlags);
|
||||
if (rxDataset) {
|
||||
const unsigned long datasetItems = randomx_dataset_item_count();
|
||||
randomx_init_dataset(rxDataset, rxCache, 0, datasetItems);
|
||||
rxVm = randomx_create_vm(vmFlags, NULL, rxDataset);
|
||||
if (rxVm) {
|
||||
printf("RandomX initialized in full-memory mode\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
randomx_release_dataset(rxDataset);
|
||||
rxDataset = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
vmFlags = baseFlags;
|
||||
rxVm = randomx_create_vm(vmFlags, rxCache, NULL);
|
||||
if (!rxVm) {
|
||||
randomx_release_cache(rxCache);
|
||||
rxCache = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("RandomX initialized in light mode\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void RandomX_Destroy() {
|
||||
if (rxVm) {
|
||||
randomx_destroy_vm(rxVm);
|
||||
rxVm = NULL;
|
||||
}
|
||||
if (rxDataset) {
|
||||
randomx_release_dataset(rxDataset);
|
||||
rxDataset = NULL;
|
||||
}
|
||||
if (rxCache) {
|
||||
randomx_release_cache(rxCache);
|
||||
rxCache = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void RandomX_CalculateHash(const uint8_t* input, size_t inputLen, uint8_t* output) {
|
||||
if (!rxVm || !input || !output) {
|
||||
return;
|
||||
}
|
||||
randomx_calculate_hash(rxVm, input, inputLen, output);
|
||||
}
|
||||
Reference in New Issue
Block a user