blockdetail command, fullverify checks difficulty (needs optimizing), move general functions to utils.h

This commit is contained in:
2026-04-30 00:09:40 +02:00
parent 6cbb16d909
commit d4ec88426a
7 changed files with 660 additions and 200 deletions

View File

@@ -31,6 +31,15 @@ bool Autolykos2_Hash(
);
bool Autolykos2_LightHash(const uint8_t* seed, blockchain_t* chain, uint64_t nonce, uint8_t* out);
bool Autolykos2_LightHashAtHeight(
const uint8_t seed32[32],
const uint8_t* message,
size_t messageLen,
uint64_t nonce,
uint64_t height,
size_t dagBytes,
uint8_t out[32]
);
bool Autolykos2_CheckTarget(
Autolykos2Context* ctx,

View File

@@ -27,6 +27,7 @@ void Chain_Wipe(blockchain_t* chain);
// I/O
bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t currentSupply, uint64_t currentReward);
bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* outCurrentSupply, uint32_t* outDifficultyTarget, uint64_t* outCurrentReward, uint8_t* outLastSavedHash, bool loadTransactions);
bool Chain_LoadBlockFromFile(const char* dirpath, uint64_t blockNumber, bool loadTransactions, block_t** outBlock, size_t* outTxCount);
// Difficulty
uint32_t Chain_ComputeNextTarget(blockchain_t* chain, uint32_t currentTarget);

View File

@@ -3,8 +3,10 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <crypto/crypto.h>
#include <uint256.h>
typedef struct {
@@ -31,6 +33,158 @@ static inline void AddressToHexString(const uint8_t address[32], char out[65]) {
to_hex(address, out);
}
static inline void AddressFromCompressedPubkey(const uint8_t compressedPubkey[33], uint8_t outAddress[32]) {
if (!compressedPubkey || !outAddress) {
return;
}
SHA256(compressedPubkey, 33, outAddress);
}
static inline void PrintHexBytes(const uint8_t* bytes, size_t length) {
if (!bytes) {
return;
}
for (size_t i = 0; i < length; ++i) {
printf("%02x", bytes[i]);
}
}
static inline int CompareHashToTarget(const uint8_t hash[32], const uint8_t target[32]) {
if (!hash || !target) {
return 1;
}
for (size_t i = 0; i < 32; ++i) {
if (hash[i] < target[i]) {
return -1;
}
if (hash[i] > target[i]) {
return 1;
}
}
return 0;
}
static inline bool DecodeCompactTarget(uint32_t nBits, uint8_t target[32]) {
if (!target) {
return false;
}
memset(target, 0, 32);
uint32_t exponent = nBits >> 24;
uint32_t mantissa = nBits & 0x007fffff;
bool negative = (nBits & 0x00800000) != 0;
if (negative || mantissa == 0) {
return false;
}
if (exponent <= 3) {
mantissa >>= 8 * (3 - exponent);
target[29] = (uint8_t)((mantissa >> 16) & 0xffu);
target[30] = (uint8_t)((mantissa >> 8) & 0xffu);
target[31] = (uint8_t)(mantissa & 0xffu);
} else {
uint32_t byteIndex = exponent - 3;
if (byteIndex > 29) {
return false;
}
target[32 - byteIndex - 3] = (uint8_t)((mantissa >> 16) & 0xffu);
target[32 - byteIndex - 2] = (uint8_t)((mantissa >> 8) & 0xffu);
target[32 - byteIndex - 1] = (uint8_t)(mantissa & 0xffu);
}
return true;
}
static inline bool GenerateTestMinerIdentity(uint8_t privateKey[32], uint8_t compressedPubkey[33], uint8_t address[32]) {
if (!privateKey || !compressedPubkey || !address) {
return false;
}
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
if (!ctx) {
return false;
}
uint8_t seed[64];
secp256k1_pubkey pubkey;
for (uint64_t counter = 0; counter < 1024; ++counter) {
const char* base = "skalacoin-test-miner-key";
size_t baseLen = strlen(base);
memcpy(seed, base, baseLen);
memcpy(seed + baseLen, &counter, sizeof(counter));
SHA256(seed, baseLen + sizeof(counter), privateKey);
if (!secp256k1_ec_seckey_verify(ctx, privateKey)) {
continue;
}
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, privateKey)) {
continue;
}
size_t pubLen = 33;
if (!secp256k1_ec_pubkey_serialize(ctx, compressedPubkey, &pubLen, &pubkey, SECP256K1_EC_COMPRESSED) || pubLen != 33) {
continue;
}
AddressFromCompressedPubkey(compressedPubkey, address);
secp256k1_context_destroy(ctx);
return true;
}
secp256k1_context_destroy(ctx);
return false;
}
static inline bool GenerateRandomTestAddress(uint8_t outAddress[32]) {
if (!outAddress) {
return false;
}
uint8_t privateKey[32];
uint8_t compressedPubkey[33];
secp256k1_pubkey pubkey;
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
if (!ctx) {
return false;
}
for (size_t attempt = 0; attempt < 4096; ++attempt) {
for (size_t i = 0; i < sizeof(privateKey); ++i) {
privateKey[i] = (uint8_t)(rand() & 0xFF);
}
if (!secp256k1_ec_seckey_verify(ctx, privateKey)) {
continue;
}
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, privateKey)) {
continue;
}
size_t pubLen = sizeof(compressedPubkey);
if (!secp256k1_ec_pubkey_serialize(ctx, compressedPubkey, &pubLen, &pubkey, SECP256K1_EC_COMPRESSED) || pubLen != 33) {
continue;
}
AddressFromCompressedPubkey(compressedPubkey, outAddress);
secp256k1_context_destroy(ctx);
return true;
}
secp256k1_context_destroy(ctx);
return false;
}
static inline bool ParseHexAddress32(const char* in, uint8_t outAddress[32]) {
if (!in || !outAddress) {
return false;

View File

@@ -179,7 +179,8 @@ static bool ReadDagLaneFromSeed(const uint8_t seed32[32], uint32_t laneIndex, ui
}
static bool Autolykos2_HashCore(
const uint8_t seed32[32],
const uint8_t messageSeed32[32],
const uint8_t dagSeed32[32],
const uint8_t* message,
size_t messageLen,
uint64_t nonce,
@@ -189,7 +190,7 @@ static bool Autolykos2_HashCore(
bool useContextDag,
uint8_t outHash[32]
) {
if (!seed32 || !message || !outHash || laneCount == 0) {
if (!messageSeed32 || !message || !outHash || laneCount == 0) {
return false;
}
@@ -200,7 +201,7 @@ static bool Autolykos2_HashCore(
uint32_t laneIndex = 0;
uint8_t lane[32];
if (!ComputeLaneIndex(seed32, nonce, height, round, laneCount, &laneIndex)) {
if (!ComputeLaneIndex(messageSeed32, nonce, height, round, laneCount, &laneIndex)) {
return false;
}
@@ -209,7 +210,7 @@ static bool Autolykos2_HashCore(
return false;
}
} else {
if (!ReadDagLaneFromSeed(seed32, laneIndex, lane)) {
if (!ReadDagLaneFromSeed(dagSeed32 ? dagSeed32 : messageSeed32, laneIndex, lane)) {
return false;
}
}
@@ -221,7 +222,7 @@ static bool Autolykos2_HashCore(
uint8_t baseHash[32];
uint8_t accHash[32];
if (!Autolykos2_FallbackHash(seed32, message, messageLen, nonce, height, baseHash)) {
if (!Autolykos2_FallbackHash(messageSeed32, message, messageLen, nonce, height, baseHash)) {
return false;
}
if (!Blake2b_Hash(acc, sizeof(acc), accHash, sizeof(accHash))) {
@@ -377,6 +378,7 @@ bool Autolykos2_Hash(
return Autolykos2_HashCore(
seed32,
NULL,
message,
messageLen,
nonce,
@@ -406,6 +408,7 @@ bool Autolykos2_LightHash(const uint8_t* seed, blockchain_t* chain, uint64_t non
// Light path derives the needed DAG lanes from seed on-demand, no large DAG allocation required.
return Autolykos2_HashCore(
seed,
seed,
seed,
32,
@@ -418,6 +421,43 @@ bool Autolykos2_LightHash(const uint8_t* seed, blockchain_t* chain, uint64_t non
);
}
bool Autolykos2_LightHashAtHeight(
const uint8_t seed32[32],
const uint8_t* message,
size_t messageLen,
uint64_t nonce,
uint64_t height,
size_t dagBytes,
uint8_t out[32]
) {
if (!seed32 || !message || !out || dagBytes < 32 || (dagBytes % 32) != 0) {
return false;
}
const size_t laneCount64 = dagBytes / 32u;
if (laneCount64 == 0 || laneCount64 > UINT32_MAX) {
return false;
}
uint8_t messageSeed32[32];
if (!DeriveSeedFromMessage(message, messageLen, messageSeed32)) {
return false;
}
return Autolykos2_HashCore(
messageSeed32,
seed32,
message,
messageLen,
nonce,
height,
(uint32_t)laneCount64,
NULL,
false,
out
);
}
bool Autolykos2_CheckTarget(
Autolykos2Context* ctx,
const uint8_t message32[32],

View File

@@ -1,5 +1,6 @@
#include <block/block.h>
#include <autolykos2/autolykos2.h>
#include <utils.h>
#include <stdlib.h>
static Autolykos2Context* g_autolykos2Ctx = NULL;
@@ -85,79 +86,51 @@ void Block_CalculateMerkleRoot(const block_t* block, uint8_t* outHash) {
return;
}
// TODO: Make this not shit
DynArr* hashes1 = DynArr_create(sizeof(uint8_t) * 32, 1);
DynArr* hashes2 = DynArr_create(sizeof(uint8_t) * 32, 1);
if (!hashes1 || !hashes2) {
if (hashes1) DynArr_destroy(hashes1);
if (hashes2) DynArr_destroy(hashes2);
uint8_t* current = (uint8_t*)malloc(txCount * 32u);
uint8_t* next = (uint8_t*)malloc(txCount * 32u);
if (!current || !next) {
free(current);
free(next);
memset(outHash, 0, 32);
return;
}
// Handle the transactions
for (size_t i = 0; i < txCount - 1; i++) {
for (size_t i = 0; i < txCount; ++i) {
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
signed_transaction_t* txNext = (signed_transaction_t*)DynArr_at(block->transactions, i + 1);
uint8_t buf1[32] = {0}; uint8_t buf2[32] = {0}; // Zeroed out
// Unless if by some miracle the hash just so happens to be all zeros,
// I think we can safely assume that a 1 : 2^256 chance will NEVER be hit
Transaction_CalculateHash(tx, buf1);
Transaction_CalculateHash(txNext, buf2);
// Concat the two hashes
uint8_t dataInBuffer[64] = {0};
uint8_t* nextStart = dataInBuffer;
nextStart += 32;
memcpy(dataInBuffer, buf1, 32);
if (txNext) { memcpy(nextStart, buf2, 32); }
// Double hash that tx set
uint8_t outHash[32];
SHA256((const unsigned char*)dataInBuffer, 64, outHash);
SHA256(outHash, 32, outHash);
// Copy to the hashes dynarr
DynArr_push_back(hashes1, outHash);
if (!tx) {
free(current);
free(next);
memset(outHash, 0, 32);
return;
}
Transaction_CalculateHash(tx, current + (i * 32u));
}
// Move to hashing the existing ones until only one remains
do {
for (size_t i = 0; i < DynArr_size(hashes1) - 1; i++) {
uint8_t* hash1 = (uint8_t*)DynArr_at(hashes1, i); uint8_t* hash2 = (uint8_t*)DynArr_at(hashes1, i + 1);
size_t levelCount = txCount;
while (levelCount > 1) {
size_t nextCount = 0;
for (size_t i = 0; i < levelCount; i += 2) {
const uint8_t* left = current + (i * 32u);
const uint8_t* right = (i + 1 < levelCount) ? current + ((i + 1) * 32u) : left;
// Concat the two hashes
uint8_t dataInBuffer[64] = {0};
uint8_t* nextStart = dataInBuffer;
nextStart += 32;
memcpy(dataInBuffer, hash1, 32);
memcpy(nextStart, hash2, 32);
uint8_t dataInBuffer[64];
memcpy(dataInBuffer, left, 32);
memcpy(dataInBuffer + 32, right, 32);
// Double hash that tx set
uint8_t outHash[32];
SHA256((const unsigned char*)dataInBuffer, 64, outHash);
SHA256(outHash, 32, outHash);
DynArr_push_back(hashes2, outHash);
SHA256((const unsigned char*)dataInBuffer, 64, next + (nextCount * 32u));
SHA256(next + (nextCount * 32u), 32, next + (nextCount * 32u));
++nextCount;
}
DynArr_erase(hashes1);
for (size_t i = 0; i < DynArr_size(hashes2); i++) {
DynArr_push_back(hashes1, (uint8_t*)DynArr_at(hashes2, i));
}
DynArr_erase(hashes2);
} while (DynArr_size(hashes1) > 1);
// Final Merkle
uint8_t* merkle = (uint8_t*)DynArr_at(hashes1, 0);
if (merkle) {
memcpy(outHash, merkle, 32);
} else {
memset(outHash, 0, 32);
uint8_t* swap = current;
current = next;
next = swap;
levelCount = nextCount;
}
DynArr_destroy(hashes1);
DynArr_destroy(hashes2);
memcpy(outHash, current, 32);
free(current);
free(next);
}
void Block_CalculateAutolykos2Hash(const block_t* block, uint8_t* outHash) {
@@ -216,39 +189,6 @@ static int Uint256_CompareBE(const uint8_t a[32], const uint8_t b[32]) {
return 0;
}
static bool DecodeCompactTarget(uint32_t nBits, uint8_t target[32]) {
memset(target, 0, 32);
uint32_t exponent = nBits >> 24;
uint32_t mantissa = nBits & 0x007fffff; // ignore sign bit for now
bool negative = (nBits & 0x00800000) != 0;
if (negative || mantissa == 0) {
return false;
}
// Compute: target = mantissa * 256^(exponent - 3)
if (exponent <= 3) {
mantissa >>= 8 * (3 - exponent);
target[29] = (mantissa >> 16) & 0xff;
target[30] = (mantissa >> 8) & 0xff;
target[31] = mantissa & 0xff;
} else {
uint32_t byte_index = exponent - 3; // number of zero-bytes appended on right
if (byte_index > 29) {
return false; // overflow 256 bits
}
target[32 - byte_index - 3] = (mantissa >> 16) & 0xff;
target[32 - byte_index - 2] = (mantissa >> 8) & 0xff;
target[32 - byte_index - 1] = mantissa & 0xff;
}
return true;
}
bool Block_HasValidProofOfWork(const block_t* block) {
if (!block) {
return false;

View File

@@ -1,6 +1,7 @@
#include <block/chain.h>
#include <constants.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
uint64_t currentBlockHeight = 0;
@@ -611,6 +612,178 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out
return true;
}
bool Chain_LoadBlockFromFile(const char* dirpath, uint64_t blockNumber, bool loadTransactions, block_t** outBlock, size_t* outTxCount) {
if (!dirpath || !outBlock) {
return false;
}
*outBlock = NULL;
if (outTxCount) {
*outTxCount = 0;
}
struct stat st;
if (stat(dirpath, &st) != 0 || !S_ISDIR(st.st_mode)) {
return false;
}
char metaPath[512];
if (!BuildPath(metaPath, sizeof(metaPath), dirpath, "chain.meta")) {
return false;
}
char chainPath[512];
if (!BuildPath(chainPath, sizeof(chainPath), dirpath, "chain.data")) {
return false;
}
char tablePath[512];
if (!BuildPath(tablePath, sizeof(tablePath), dirpath, "chain.table")) {
return false;
}
FILE* metaFile = fopen(metaPath, "rb");
FILE* chainFile = fopen(chainPath, "rb");
FILE* tableFile = fopen(tablePath, "rb");
if (!metaFile || !chainFile || !tableFile) {
if (metaFile) fclose(metaFile);
if (chainFile) fclose(chainFile);
if (tableFile) fclose(tableFile);
return false;
}
size_t savedSize = 0;
if (fread(&savedSize, sizeof(size_t), 1, metaFile) != 1) {
fclose(metaFile);
fclose(chainFile);
fclose(tableFile);
return false;
}
fclose(metaFile);
if (blockNumber >= (uint64_t)savedSize) {
fclose(chainFile);
fclose(tableFile);
return false;
}
uint64_t tableOffset = blockNumber * (uint64_t)sizeof(block_table_entry_t);
if (blockNumber != 0 && tableOffset / blockNumber != (uint64_t)sizeof(block_table_entry_t)) {
fclose(chainFile);
fclose(tableFile);
return false;
}
if (tableOffset > (uint64_t)LONG_MAX || fseek(tableFile, (long)tableOffset, SEEK_SET) != 0) {
fclose(chainFile);
fclose(tableFile);
return false;
}
block_table_entry_t loc;
if (fread(&loc, sizeof(block_table_entry_t), 1, tableFile) != 1) {
fclose(chainFile);
fclose(tableFile);
return false;
}
if (loc.blockNumber != blockNumber) {
fclose(chainFile);
fclose(tableFile);
return false;
}
if (loc.byteNumber > (uint64_t)LONG_MAX || fseek(chainFile, (long)loc.byteNumber, SEEK_SET) != 0) {
fclose(chainFile);
fclose(tableFile);
return false;
}
block_t* blk = (block_t*)calloc(1, sizeof(block_t));
if (!blk) {
fclose(chainFile);
fclose(tableFile);
return false;
}
if (fread(&blk->header, sizeof(block_header_t), 1, chainFile) != 1) {
free(blk);
fclose(chainFile);
fclose(tableFile);
return false;
}
size_t txSize = 0;
if (fread(&txSize, sizeof(size_t), 1, chainFile) != 1) {
free(blk);
fclose(chainFile);
fclose(tableFile);
return false;
}
if (outTxCount) {
*outTxCount = txSize;
}
if (loadTransactions) {
blk->transactions = DYNARR_CREATE(signed_transaction_t, txSize == 0 ? 1 : txSize);
if (!blk->transactions) {
free(blk);
fclose(chainFile);
fclose(tableFile);
return false;
}
for (size_t i = 0; i < txSize; ++i) {
signed_transaction_t tx;
if (fread(&tx, sizeof(signed_transaction_t), 1, chainFile) != 1) {
DynArr_destroy(blk->transactions);
free(blk);
fclose(chainFile);
fclose(tableFile);
return false;
}
if (!DynArr_push_back(blk->transactions, &tx)) {
DynArr_destroy(blk->transactions);
free(blk);
fclose(chainFile);
fclose(tableFile);
return false;
}
}
} else {
if (txSize > 0) {
uint64_t skipBytes = (uint64_t)txSize * (uint64_t)sizeof(signed_transaction_t);
if (txSize != 0 && skipBytes / txSize != (uint64_t)sizeof(signed_transaction_t)) {
free(blk);
fclose(chainFile);
fclose(tableFile);
return false;
}
if (skipBytes > (uint64_t)LONG_MAX) {
free(blk);
fclose(chainFile);
fclose(tableFile);
return false;
}
if (fseek(chainFile, (long)skipBytes, SEEK_CUR) != 0) {
free(blk);
fclose(chainFile);
fclose(tableFile);
return false;
}
}
blk->transactions = NULL;
}
fclose(chainFile);
fclose(tableFile);
*outBlock = blk;
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)

View File

@@ -1,6 +1,6 @@
#include <block/chain.h>
#include <block/transaction.h>
#include <openssl/sha.h>
#include <utils.h>
#include <stdbool.h>
#include <stdint.h>
@@ -16,7 +16,6 @@
#include <autolykos2/autolykos2.h>
#include <nets/net_node.h>
#include <crypto/crypto.h>
#ifndef CHAIN_DATA_DIR
#define CHAIN_DATA_DIR "chain_data"
@@ -34,56 +33,6 @@ uint32_t difficultyTarget = INITIAL_DIFFICULTY;
// extern the currentReward from constants.h so we can update it as we mine blocks and save it to disk
extern uint64_t currentReward;
static void AddressFromCompressedPubkey(const uint8_t compressedPubkey[33], uint8_t outAddress[32]) {
if (!compressedPubkey || !outAddress) {
return;
}
SHA256(compressedPubkey, 33, outAddress);
}
static bool GenerateTestMinerIdentity(uint8_t privateKey[32], uint8_t compressedPubkey[33], uint8_t address[32]) {
if (!privateKey || !compressedPubkey || !address) {
return false;
}
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
if (!ctx) {
return false;
}
uint8_t seed[64];
secp256k1_pubkey pubkey;
for (uint64_t counter = 0; counter < 1024; ++counter) {
const char* base = "skalacoin-test-miner-key";
size_t baseLen = strlen(base);
memcpy(seed, base, baseLen);
memcpy(seed + baseLen, &counter, sizeof(counter));
SHA256(seed, baseLen + sizeof(counter), privateKey);
if (!secp256k1_ec_seckey_verify(ctx, privateKey)) {
continue;
}
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, privateKey)) {
continue;
}
size_t pubLen = 33;
if (!secp256k1_ec_pubkey_serialize(ctx, compressedPubkey, &pubLen, &pubkey, SECP256K1_EC_COMPRESSED) || pubLen != 33) {
continue;
}
AddressFromCompressedPubkey(compressedPubkey, address);
secp256k1_context_destroy(ctx);
return true;
}
secp256k1_context_destroy(ctx);
return false;
}
static bool MineBlock(block_t* block) {
if (!block) {
return false;
@@ -157,6 +106,158 @@ static void AddCoinbaseTransaction(block_t* block, const uint8_t minerAddress[32
Block_AddTransaction(block, &coinbaseTx);
}
static void PrintBlockDetail(const block_t* block, size_t txCount, const uint8_t canonicalHash[32], const uint8_t powHash[32]) {
if (!block) {
return;
}
printf("Block #%llu\n", (unsigned long long)block->header.blockNumber);
printf(" Timestamp: %llu\n", (unsigned long long)block->header.timestamp);
printf(" Nonce: %llu\n", (unsigned long long)block->header.nonce);
printf(" Difficulty Target: 0x%08x\n", block->header.difficultyTarget);
printf(" Version: %u\n", block->header.version);
printf(" Reserved: %02x %02x %02x\n",
block->header.reserved[0],
block->header.reserved[1],
block->header.reserved[2]);
printf(" Previous Hash: ");
PrintHexBytes(block->header.prevHash, sizeof(block->header.prevHash));
printf("\n");
printf(" Merkle Root: ");
PrintHexBytes(block->header.merkleRoot, sizeof(block->header.merkleRoot));
printf("\n");
printf(" Transactions on disk: %zu\n", txCount);
printf(" Canonical Hash: ");
PrintHexBytes(canonicalHash, 32);
printf("\n");
printf(" PoW Hash: ");
PrintHexBytes(powHash, 32);
printf("\n");
}
static bool ComputeEpochSeedForHeightFromChain(const blockchain_t* chain, uint64_t blockHeight, uint8_t outSeed[32]) {
if (!chain || !outSeed) {
return false;
}
const uint64_t epochIndex = blockHeight / EPOCH_LENGTH;
if (epochIndex == 0) {
memset(outSeed, DAG_GENESIS_SEED, 32);
return true;
}
const uint64_t seedBlockNumber = (epochIndex * EPOCH_LENGTH) - 1ULL;
if (seedBlockNumber >= Chain_Size((blockchain_t*)chain)) {
return false;
}
block_t* seedBlock = Chain_GetBlock((blockchain_t*)chain, (size_t)seedBlockNumber);
if (!seedBlock) {
return false;
}
Block_CalculateHash(seedBlock, outSeed);
return true;
}
static bool ComputeEpochDagBytesForHeightFromChain(const blockchain_t* chain, uint64_t blockHeight, size_t* outDagBytes) {
if (!chain || !outDagBytes) {
return false;
}
if (blockHeight <= EPOCH_LENGTH) {
*outDagBytes = DAG_BASE_SIZE;
return true;
}
const uint64_t lastBlockNumber = blockHeight - 1ULL;
const uint64_t epochStartBlockNumber = lastBlockNumber - EPOCH_LENGTH;
if (lastBlockNumber >= Chain_Size((blockchain_t*)chain) || epochStartBlockNumber >= Chain_Size((blockchain_t*)chain)) {
return false;
}
block_t* lastBlock = Chain_GetBlock((blockchain_t*)chain, (size_t)lastBlockNumber);
block_t* epochStartBlock = Chain_GetBlock((blockchain_t*)chain, (size_t)epochStartBlockNumber);
if (!lastBlock || !epochStartBlock) {
return false;
}
int64_t difficultyDelta = (int64_t)epochStartBlock->header.difficultyTarget - (int64_t)lastBlock->header.difficultyTarget;
int64_t growth = (int64_t)((int64_t)DAG_BASE_GROWTH * difficultyDelta);
if (growth > 0) {
int64_t maxUp = (int64_t)((DAG_BASE_SIZE * 15ULL) / 100ULL);
if (growth > maxUp) {
growth = maxUp;
}
if (growth > (int64_t)DAG_MAX_UP_SWING_GB) {
growth = (int64_t)DAG_MAX_UP_SWING_GB;
}
} else {
int64_t maxDown = (int64_t)((DAG_BASE_SIZE * 10ULL) / 100ULL);
if (-growth > maxDown) {
growth = -maxDown;
}
if (-growth > (int64_t)DAG_MAX_DOWN_SWING_GB) {
growth = -(int64_t)DAG_MAX_DOWN_SWING_GB;
}
}
const int64_t targetSize = (int64_t)DAG_BASE_SIZE + growth;
if (targetSize <= 0) {
return false;
}
*outDagBytes = (size_t)targetSize;
return true;
}
static bool ComputeHistoricalAutolykosHashFromChain(const blockchain_t* chain, const block_t* block, uint64_t blockHeight, uint8_t outHash[32]) {
if (!chain || !block || !outHash) {
return false;
}
uint8_t seed[32];
size_t dagBytes = 0;
if (!ComputeEpochSeedForHeightFromChain(chain, blockHeight, seed)) {
return false;
}
if (!ComputeEpochDagBytesForHeightFromChain(chain, blockHeight, &dagBytes)) {
return false;
}
return Autolykos2_LightHashAtHeight(
seed,
(const uint8_t*)&block->header,
sizeof(block_header_t),
block->header.nonce,
blockHeight,
dagBytes,
outHash
);
}
static bool ComputeHistoricalAutolykosHashFromDisk(const char* chainDataDir, uint64_t blockHeight, const block_t* block, uint8_t outHash[32]) {
if (!chainDataDir || !block || !outHash) {
return false;
}
blockchain_t* headerChain = Chain_Create();
if (!headerChain) {
return false;
}
uint256_t supply = uint256_from_u64(0);
uint32_t difficulty = INITIAL_DIFFICULTY;
uint64_t reward = 0;
uint8_t lastHash[32] = {0};
bool loaded = Chain_LoadFromFile(headerChain, chainDataDir, &supply, &difficulty, &reward, lastHash, false);
bool ok = loaded && ComputeHistoricalAutolykosHashFromChain(headerChain, block, blockHeight, outHash);
Chain_Destroy(headerChain);
return ok;
}
static bool MineAndAppendBlock(blockchain_t* chain,
block_t* block,
uint256_t* currentSupply,
@@ -221,47 +322,6 @@ static bool MineAndAppendBlock(blockchain_t* chain,
return true;
}
static bool GenerateRandomTestAddress(uint8_t outAddress[32]) {
if (!outAddress) {
return false;
}
uint8_t privateKey[32];
uint8_t compressedPubkey[33];
secp256k1_pubkey pubkey;
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
if (!ctx) {
return false;
}
for (size_t attempt = 0; attempt < 4096; ++attempt) {
for (size_t i = 0; i < sizeof(privateKey); ++i) {
privateKey[i] = (uint8_t)(rand() & 0xFF);
}
if (!secp256k1_ec_seckey_verify(ctx, privateKey)) {
continue;
}
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, privateKey)) {
continue;
}
size_t pubLen = sizeof(compressedPubkey);
if (!secp256k1_ec_pubkey_serialize(ctx, compressedPubkey, &pubLen, &pubkey, SECP256K1_EC_COMPRESSED) || pubLen != 33) {
continue;
}
AddressFromCompressedPubkey(compressedPubkey, outAddress);
secp256k1_context_destroy(ctx);
return true;
}
secp256k1_context_destroy(ctx);
return false;
}
static void WipeChainFiles(const char* chainDataDir) {
if (!chainDataDir) {
return;
@@ -288,45 +348,81 @@ static bool VerifyChainFully(blockchain_t* chain) {
}
size_t chainSize = Chain_Size(chain);
// Build a lightweight previous-block-only chain to compute expected difficulty
blockchain_t* prevChain = Chain_Create();
if (!prevChain) { return false; }
uint32_t expectedDifficulty = INITIAL_DIFFICULTY;
for (size_t i = 0; i < chainSize; ++i) {
block_t* blk = Chain_GetBlock(chain, i);
if (!blk || !blk->transactions) {
Chain_Destroy(prevChain);
return false;
}
if (blk->header.blockNumber != (uint64_t)i) {
Chain_Destroy(prevChain);
return false;
}
if (i == 0) {
uint8_t zeroHash[32] = {0};
if (memcmp(blk->header.prevHash, zeroHash, sizeof(zeroHash)) != 0) {
Chain_Destroy(prevChain);
return false;
}
} else {
block_t* prevBlk = Chain_GetBlock(chain, i - 1);
if (!prevBlk) {
Chain_Destroy(prevChain);
return false;
}
uint8_t expectedPrevHash[32];
Block_CalculateHash(prevBlk, expectedPrevHash);
if (memcmp(blk->header.prevHash, expectedPrevHash, sizeof(expectedPrevHash)) != 0) {
Chain_Destroy(prevChain);
return false;
}
}
if (!Block_HasValidProofOfWork(blk)) {
// Determine expected difficulty for this block. TODO: Optimize to recompute at adjustment intervals only instead of every block.
if (i < DIFFICULTY_ADJUSTMENT_INTERVAL) {
expectedDifficulty = INITIAL_DIFFICULTY;
} else if ((i % DIFFICULTY_ADJUSTMENT_INTERVAL) == 0) {
// Compute target using previous blocks only (0..i-1)
expectedDifficulty = Chain_ComputeNextTarget(prevChain, expectedDifficulty);
}
// Ensure the block's header difficulty matches the expected difficulty (can't cheat easier)
if (blk->header.difficultyTarget != expectedDifficulty) {
Chain_Destroy(prevChain);
return false;
}
uint8_t powHash[32];
if (!ComputeHistoricalAutolykosHashFromChain(chain, blk, (uint64_t)i, powHash)) {
Chain_Destroy(prevChain);
return false;
}
uint8_t target[32];
if (!DecodeCompactTarget(blk->header.difficultyTarget, target)) {
return false;
}
if (CompareHashToTarget(powHash, target) > 0) {
return false;
}
if (!Block_AllTransactionsValid(blk)) {
Chain_Destroy(prevChain);
return false;
}
uint8_t expectedMerkle[32];
Block_CalculateMerkleRoot(blk, expectedMerkle);
if (memcmp(blk->header.merkleRoot, expectedMerkle, sizeof(expectedMerkle)) != 0) {
Chain_Destroy(prevChain);
return false;
}
@@ -334,8 +430,16 @@ static bool VerifyChainFully(blockchain_t* chain) {
// release its in-memory transaction list to reduce peak memory usage.
DynArr_destroy(blk->transactions);
blk->transactions = NULL;
// Push a header-only copy of this block into prevChain for future difficulty calculations.
block_t headerOnly;
memset(&headerOnly, 0, sizeof(headerOnly));
headerOnly.header = blk->header;
headerOnly.transactions = NULL;
(void)DynArr_push_back(prevChain->blocks, &headerOnly);
}
Chain_Destroy(prevChain);
return true;
}
@@ -426,7 +530,7 @@ int main(int argc, char* argv[]) {
char supplyStr[80];
Uint256ToDecimal(&currentSupply, supplyStr, sizeof(supplyStr));
printf("Current chain has %zu blocks, total supply %s\n", Chain_Size(chain), supplyStr);
printf("Commands: mine <x>, send <address> <amount>, balance [address], connect <ipv4>, flushchain, fullverify, wipechain, genaddr, exit\n");
printf("Commands: mine <x>, send <address> <amount>, balance [address], connect <ipv4>, flushchain, fullverify, blockdetail <block number>, wipechain, genaddr, exit\n");
char line[1024];
while (true) {
@@ -481,7 +585,7 @@ int main(int argc, char* argv[]) {
free(block); // Chain stores block by value and owns copied transaction array.
if (i % 1000 == 0) {
if (i % 50 == 0) {
// Mid-mine flush
(void)FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward);
}
@@ -550,17 +654,59 @@ int main(int argc, char* argv[]) {
Transaction_Sign(&spendTx, minerPrivateKey);
Block_AddTransaction(block, &spendTx);
printf("Created transaction sending %llu pebble(s) to ", (unsigned long long)amount);
char recipientHex[65];
AddressToHexString(recipientAddress, recipientHex);
printf("%s\n\nMining block...\n", recipientHex);
if (!MineAndAppendBlock(chain, block, &currentSupply, &currentReward, &difficultyTarget)) {
Block_Destroy(block);
continue;
}
FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward);
free(block);
printf("send committed in mined block\n");
continue;
}
if (strcmp(cmd, "blockdetail") == 0) {
char* blockNumberStr = strtok(NULL, " \t");
char* extra = strtok(NULL, " \t");
if (!blockNumberStr || extra) {
printf("usage: blockdetail <block number>\n");
continue;
}
char* endptr = NULL;
unsigned long long requestedBlock = strtoull(blockNumberStr, &endptr, 10);
if (*blockNumberStr == '\0' || blockNumberStr[0] == '-' || (endptr && *endptr != '\0')) {
printf("invalid block number\n");
continue;
}
block_t* detailBlock = NULL;
size_t txCount = 0;
if (!Chain_LoadBlockFromFile(chainDataDir, (uint64_t)requestedBlock, false, &detailBlock, &txCount)) {
printf("block %llu not found\n", requestedBlock);
continue;
}
uint8_t canonicalHash[32];
uint8_t powHash[32];
Block_CalculateHash(detailBlock, canonicalHash);
if (!ComputeHistoricalAutolykosHashFromDisk(chainDataDir, (uint64_t)requestedBlock, detailBlock, powHash)) {
Block_Destroy(detailBlock);
printf("failed to calculate block %llu proof hash\n", requestedBlock);
continue;
}
PrintBlockDetail(detailBlock, txCount, canonicalHash, powHash);
Block_Destroy(detailBlock);
continue;
}
if (strcmp(cmd, "balance") == 0) {
char* addressStr = strtok(NULL, " \t");
char* extra = strtok(NULL, " \t");
@@ -654,9 +800,6 @@ int main(int argc, char* argv[]) {
bool ok = false;
if (loaded) {
uint8_t dagSeed[32];
GetNextDAGSeed(verifyChain, dagSeed);
(void)Block_RebuildAutolykos2Dag(CalculateTargetDAGSize(verifyChain), dagSeed);
ok = VerifyChainFully(verifyChain);
}
@@ -699,7 +842,7 @@ int main(int argc, char* argv[]) {
break;
}
printf("Unknown command. Available: mine, send, balance, connect, flushchain, fullverify, wipechain, genaddr, exit\n");
printf("Unknown command. Available: mine, send, balance, connect, flushchain, fullverify, blockdetail, wipechain, genaddr, exit\n");
}
(void)FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward);