diff --git a/include/autolykos2/autolykos2.h b/include/autolykos2/autolykos2.h index c67700d..fe60b6e 100644 --- a/include/autolykos2/autolykos2.h +++ b/include/autolykos2/autolykos2.h @@ -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, diff --git a/include/block/chain.h b/include/block/chain.h index 8680882..a066a4f 100644 --- a/include/block/chain.h +++ b/include/block/chain.h @@ -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); diff --git a/include/utils.h b/include/utils.h index 901b12f..dbd4eb2 100644 --- a/include/utils.h +++ b/include/utils.h @@ -3,8 +3,10 @@ #include #include +#include #include #include +#include #include 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; diff --git a/src/autolykos2/autolykos2.c b/src/autolykos2/autolykos2.c index 27ea020..a99f0b8 100644 --- a/src/autolykos2/autolykos2.c +++ b/src/autolykos2/autolykos2.c @@ -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], diff --git a/src/block/block.c b/src/block/block.c index f1561cf..dc547c5 100644 --- a/src/block/block.c +++ b/src/block/block.c @@ -1,5 +1,6 @@ #include #include +#include #include 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; diff --git a/src/block/chain.c b/src/block/chain.c index 1937028..036317d 100644 --- a/src/block/chain.c +++ b/src/block/chain.c @@ -1,6 +1,7 @@ #include #include #include +#include #include 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) diff --git a/src/main.c b/src/main.c index 6312632..f72f9fd 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include @@ -16,7 +16,6 @@ #include #include -#include #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(¤tSupply, supplyStr, sizeof(supplyStr)); printf("Current chain has %zu blocks, total supply %s\n", Chain_Size(chain), supplyStr); - printf("Commands: mine , send
, balance [address], connect , flushchain, fullverify, wipechain, genaddr, exit\n"); + printf("Commands: mine , send
, balance [address], connect , flushchain, fullverify, blockdetail , 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, ¤tSupply, ¤tReward, &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 \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);