From 50e357d8a23161c5cf8d9f11254c5b9b4d9c7c51 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Sun, 29 Mar 2026 23:30:31 +0200 Subject: [PATCH] Monero-style emission --- include/block/chain.h | 6 +- include/constants.h | 19 ++- include/randomx/librx_wrapper.h | 2 +- include/uint256.h | 2 +- src/block/chain.c | 10 +- src/main.c | 251 +++++++++++--------------------- 6 files changed, 106 insertions(+), 184 deletions(-) diff --git a/include/block/chain.h b/include/block/chain.h index 72de91e..307c60f 100644 --- a/include/block/chain.h +++ b/include/block/chain.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include typedef struct { DynArr* blocks; @@ -22,7 +24,7 @@ bool Chain_IsValid(blockchain_t* chain); void Chain_Wipe(blockchain_t* chain); // I/O -bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath); -bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath); +bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t currentSupply); +bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* outCurrentSupply); #endif diff --git a/include/constants.h b/include/constants.h index 5dac3a1..3406b0a 100644 --- a/include/constants.h +++ b/include/constants.h @@ -7,21 +7,24 @@ #define DECIMALS 1000000000000ULL #define EMISSION_SPEED_FACTOR 20 -const uint64_t M_CAP = 18446744073709551615ULL; // Max uint64 -const uint64_t TAIL_EMISSION = (uint64_t)(1.0 * DECIMALS); // Emission floor is 1.0 coins per block +static const uint64_t M_CAP = 18446744073709551615ULL; // Max uint64 +static const uint64_t TAIL_EMISSION = DECIMALS; // Emission floor is 1.0 coins per block // No max supply. Instead of halving, it'll follow a more gradual, Monero-like emission curve. +static uint256_t currentSupply = {{0, 0, 0, 0}}; // Global variable to track total supply; updated with each block mined + static inline uint64_t CalculateBlockReward(uint256_t currentSupply, uint64_t height) { // Inclusive of block 0 + (void)height; - if (current_supply.limbs[1] > 0 || - current_supply.limbs[2] > 0 || - current_supply.limbs[3] > 0 || - current_supply.limbs[0] >= M_CAP) { + if (currentSupply.limbs[1] > 0 || + currentSupply.limbs[2] > 0 || + currentSupply.limbs[3] > 0 || + currentSupply.limbs[0] >= M_CAP) { return TAIL_EMISSION; } - uint64_t supply_64 = current_supply.limbs[0]; + uint64_t supply_64 = currentSupply.limbs[0]; // Formula: (M - Supply) >> 2^k - lifted from Monero's codebase (thanks guys!) uint64_t reward = (M_CAP - supply_64) >> EMISSION_SPEED_FACTOR; @@ -34,4 +37,4 @@ static inline uint64_t CalculateBlockReward(uint256_t currentSupply, uint64_t he return reward; } -#endif \ No newline at end of file +#endif diff --git a/include/randomx/librx_wrapper.h b/include/randomx/librx_wrapper.h index fdf7258..7c4d6e5 100644 --- a/include/randomx/librx_wrapper.h +++ b/include/randomx/librx_wrapper.h @@ -18,4 +18,4 @@ void RandomX_CalculateHash(const uint8_t* input, size_t inputLen, uint8_t* outpu } #endif -#endif \ No newline at end of file +#endif diff --git a/include/uint256.h b/include/uint256.h index 20dba76..6aa8c92 100644 --- a/include/uint256.h +++ b/include/uint256.h @@ -55,4 +55,4 @@ static inline bool uint256_add(uint256_t* a, const uint256_t* b) { return carry > 0; } -#endif \ No newline at end of file +#endif diff --git a/src/block/chain.c b/src/block/chain.c index 3b10391..532f737 100644 --- a/src/block/chain.c +++ b/src/block/chain.c @@ -119,7 +119,7 @@ void Chain_Wipe(blockchain_t* chain) { } } -bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath) { +bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t currentSupply) { // To avoid stalling the chain from peers, write after every block addition (THAT IS VERIFIED) if (!chain || !chain->blocks || !EnsureDirectoryExists(dirpath)) { @@ -145,6 +145,8 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath) { // Write last block hash (32 bytes of zeros for now) uint8_t zeroHash[32] = {0}; fwrite(zeroHash, sizeof(uint8_t), 32, metaFile); + uint256_t zeroSupply = {0}; + fwrite(&zeroSupply, sizeof(uint256_t), 1, metaFile); // TODO: Potentially some other things here, we'll see } @@ -210,13 +212,14 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath) { Block_CalculateHash(lastBlock, lastHash); fwrite(lastHash, sizeof(uint8_t), 32, metaFile); } + fwrite(¤tSupply, sizeof(uint256_t), 1, metaFile); fclose(metaFile); return true; } -bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath) { - if (!chain || !chain->blocks || !dirpath) { +bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* outCurrentSupply) { + if (!chain || !chain->blocks || !dirpath || !outCurrentSupply) { return false; } @@ -240,6 +243,7 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath) { fread(&savedSize, sizeof(size_t), 1, metaFile); uint8_t lastSavedHash[32]; fread(lastSavedHash, sizeof(uint8_t), 32, metaFile); + fread(outCurrentSupply, sizeof(uint256_t), 1, metaFile); fclose(metaFile); // TODO: Might add a flag to allow reading from a point onward, but just rewrite for now diff --git a/src/main.c b/src/main.c index 6199977..038a0c4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,16 +1,18 @@ #include #include #include -#include #include #include #include +#include #include #include #include #include +#include + #ifndef CHAIN_DATA_DIR #define CHAIN_DATA_DIR "chain_data" #endif @@ -68,43 +70,6 @@ static uint32_t CompactTargetForExpectedHashes(double expectedHashes) { return (0x1fU << 24) | (mantissa & 0x007fffffU); } -static bool GenerateKeypair( - const secp256k1_context* ctx, - uint8_t outPrivateKey[32], - uint8_t outCompressedPublicKey[33] -) { - if (!ctx || !outPrivateKey || !outCompressedPublicKey) { - return false; - } - - secp256k1_pubkey pubkey; - for (size_t i = 0; i < 1024; ++i) { - arc4random_buf(outPrivateKey, 32); - if (!secp256k1_ec_seckey_verify(ctx, outPrivateKey)) { - continue; - } - - if (!secp256k1_ec_pubkey_create(ctx, &pubkey, outPrivateKey)) { - continue; - } - - size_t serializedLen = 33; - if (!secp256k1_ec_pubkey_serialize( - ctx, - outCompressedPublicKey, - &serializedLen, - &pubkey, - SECP256K1_EC_COMPRESSED - )) { - continue; - } - - return serializedLen == 33; - } - - return false; -} - static bool MineBlock(block_t* block) { if (!block) { return false; @@ -126,6 +91,10 @@ int main(void) { signal(SIGINT, handle_sigint); const char* chainDataDir = CHAIN_DATA_DIR; + const uint64_t blocksToMine = 10; + const double targetSeconds = 90.0; + + 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. @@ -139,8 +108,7 @@ int main(void) { return 1; } - // Attempt read - if (!Chain_LoadFromFile(chain, chainDataDir)) { + if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply)) { printf("No existing chain loaded from %s\n", chainDataDir); } @@ -166,149 +134,94 @@ int main(void) { } } - secp256k1_context* secpCtx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - if (!secpCtx) { - fprintf(stderr, "failed to create secp256k1 context\n"); - Chain_Destroy(chain); - return 1; - } - - uint8_t senderPrivateKey[32]; - uint8_t receiverPrivateKey[32]; - uint8_t senderCompressedPublicKey[33]; - uint8_t receiverCompressedPublicKey[33]; - - if (!GenerateKeypair(secpCtx, senderPrivateKey, senderCompressedPublicKey) || - !GenerateKeypair(secpCtx, receiverPrivateKey, receiverCompressedPublicKey)) { - fprintf(stderr, "failed to generate keypairs\n"); - secp256k1_context_destroy(secpCtx); - Chain_Destroy(chain); - return 1; - } - - // Coinbase TX - no signature needed, one per block - signed_transaction_t coinbaseTx; - memset(&coinbaseTx, 0, sizeof(coinbaseTx)); - coinbaseTx.transaction.version = 1; - coinbaseTx.transaction.amount = 50; // Block reward - coinbaseTx.transaction.fee = 0; - SHA256(receiverCompressedPublicKey, 33, coinbaseTx.transaction.recipientAddress); - memset(coinbaseTx.transaction.compressedPublicKey, 0x00, 33); // No public key for coinbase - memset(coinbaseTx.transaction.senderAddress, 0xFF, 32); // Coinbase marker - - // Test TX - signed_transaction_t tx; - memset(&tx, 0, sizeof(tx)); - tx.transaction.version = 1; - tx.transaction.amount = 100; - tx.transaction.fee = 1; - - SHA256(senderCompressedPublicKey, 33, tx.transaction.senderAddress); - SHA256(receiverCompressedPublicKey, 33, tx.transaction.recipientAddress); - memcpy(tx.transaction.compressedPublicKey, senderCompressedPublicKey, 33); - - Transaction_Sign(&tx, senderPrivateKey); - if (!Transaction_Verify(&tx)) { - fprintf(stderr, "signed transaction did not verify\n"); - secp256k1_context_destroy(secpCtx); - Chain_Destroy(chain); - RandomX_Destroy(); - return 1; - } - - block_t* block = Block_Create(); - if (!block) { - fprintf(stderr, "failed to create block\n"); - secp256k1_context_destroy(secpCtx); - Chain_Destroy(chain); - RandomX_Destroy(); - return 1; - } - - block->header.version = 1; - block->header.blockNumber = (uint64_t)Chain_Size(chain); - // Get prevHash from last block if exists - if (Chain_Size(chain) > 0) { - block_t* lastBlock = Chain_GetBlock(chain, Chain_Size(chain) - 1); - if (lastBlock) { - Block_CalculateHash(lastBlock, block->header.prevHash); - } else { - memset(block->header.prevHash, 0, sizeof(block->header.prevHash)); - } - } else { - memset(block->header.prevHash, 0, sizeof(block->header.prevHash)); - } - memset(block->header.merkleRoot, 0, sizeof(block->header.merkleRoot)); - block->header.timestamp = (uint64_t)time(NULL); - const double hps = MeasureRandomXHashrate(); - const double targetSeconds = 10.0; const double expectedHashes = (hps > 0.0) ? (hps * targetSeconds) : 65536.0; - block->header.difficultyTarget = CompactTargetForExpectedHashes(expectedHashes); - block->header.nonce = 0; + const uint32_t calibratedBits = CompactTargetForExpectedHashes(expectedHashes); printf("RandomX benchmark: %.2f H/s, target %.0fs, nBits=0x%08x\n", hps, targetSeconds, - block->header.difficultyTarget); + calibratedBits); - Block_AddTransaction(block, &coinbaseTx); - printf("Added coinbase transaction to block: recipient %02x... -> amount %lu\n", - coinbaseTx.transaction.recipientAddress[0], coinbaseTx.transaction.recipientAddress[31], - coinbaseTx.transaction.amount); - Block_AddTransaction(block, &tx); - printf("Added transaction to block: sender %02x... -> recipient %02x..., amount %lu, fee %lu\n", - tx.transaction.senderAddress[0], tx.transaction.senderAddress[31], - tx.transaction.recipientAddress[0], tx.transaction.recipientAddress[31], - tx.transaction.amount, tx.transaction.fee); + uint8_t minerAddress[32]; + SHA256((const unsigned char*)"minicoin-miner-1", strlen("minicoin-miner-1"), minerAddress); - if (!MineBlock(block)) { - fprintf(stderr, "failed to mine block within nonce range\n"); - Block_Destroy(block); - secp256k1_context_destroy(secpCtx); - Chain_Destroy(chain); - RandomX_Destroy(); - return 1; + for (uint64_t mined = 0; mined < blocksToMine; ++mined) { + block_t* block = Block_Create(); + if (!block) { + fprintf(stderr, "failed to create block\n"); + Chain_Destroy(chain); + RandomX_Destroy(); + return 1; + } + + block->header.version = 1; + block->header.blockNumber = (uint64_t)Chain_Size(chain); + if (Chain_Size(chain) > 0) { + block_t* lastBlock = Chain_GetBlock(chain, Chain_Size(chain) - 1); + if (lastBlock) { + Block_CalculateHash(lastBlock, block->header.prevHash); + } else { + memset(block->header.prevHash, 0, sizeof(block->header.prevHash)); + } + } else { + memset(block->header.prevHash, 0, sizeof(block->header.prevHash)); + } + memset(block->header.merkleRoot, 0, sizeof(block->header.merkleRoot)); + block->header.timestamp = (uint64_t)time(NULL); + block->header.difficultyTarget = calibratedBits; + block->header.nonce = 0; + + signed_transaction_t coinbaseTx; + memset(&coinbaseTx, 0, sizeof(coinbaseTx)); + coinbaseTx.transaction.version = 1; + coinbaseTx.transaction.amount = CalculateBlockReward(currentSupply, block->header.blockNumber); + coinbaseTx.transaction.fee = 0; + memcpy(coinbaseTx.transaction.recipientAddress, minerAddress, sizeof(minerAddress)); + memset(coinbaseTx.transaction.compressedPublicKey, 0, sizeof(coinbaseTx.transaction.compressedPublicKey)); + memset(coinbaseTx.transaction.senderAddress, 0xFF, sizeof(coinbaseTx.transaction.senderAddress)); + Block_AddTransaction(block, &coinbaseTx); + + if (!MineBlock(block)) { + fprintf(stderr, "failed to mine block within nonce range\n"); + Block_Destroy(block); + Chain_Destroy(chain); + RandomX_Destroy(); + return 1; + } + + if (!Chain_AddBlock(chain, block)) { + fprintf(stderr, "failed to append block to chain\n"); + Block_Destroy(block); + Chain_Destroy(chain); + RandomX_Destroy(); + 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 hash=%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], + blockHash[0], blockHash[1], blockHash[2], blockHash[3]); } - if (!Chain_AddBlock(chain, block)) { - fprintf(stderr, "failed to append block to chain\n"); - Block_Destroy(block); - secp256k1_context_destroy(secpCtx); - Chain_Destroy(chain); - RandomX_Destroy(); - return 1; - } - - printf("Mined block %llu with nonce %llu and chain size %zu\n", - (unsigned long long)block->header.blockNumber, - (unsigned long long)block->header.nonce, - Chain_Size(chain)); - - printf("Block hash (SHA256): "); - uint8_t blockHash[32]; - Block_CalculateHash(block, blockHash); - for (size_t i = 0; i < 32; ++i) { - printf("%02x", blockHash[i]); - } - printf("\nBlock hash (RandomX): "); - uint8_t randomXHash[32]; - Block_CalculateRandomXHash(block, randomXHash); - for (size_t i = 0; i < 32; ++i) { - printf("%02x", randomXHash[i]); - } - printf("\n"); - - if (!Chain_SaveToFile(chain, chainDataDir)) { + if (!Chain_SaveToFile(chain, chainDataDir, currentSupply)) { fprintf(stderr, "failed to save chain to %s\n", chainDataDir); } else { - printf("Saved chain with %zu blocks to %s\n", Chain_Size(chain), chainDataDir); + printf("Saved chain with %zu blocks to %s (supply=%llu)\n", + Chain_Size(chain), + chainDataDir, + (unsigned long long)currentSupply.limbs[0]); } - // Chain currently stores a copy of block_t that references the same tx array pointer, - // so we do not destroy `block` here to avoid invalidating chain data. - secp256k1_context_destroy(secpCtx); - Chain_Destroy(chain); + RandomX_Destroy(); return 0; }