From 075793c24c1a1ed71060a31d1c0bf191650a907a Mon Sep 17 00:00:00 2001 From: DcruBro Date: Mon, 30 Mar 2026 23:46:22 +0200 Subject: [PATCH] huge chain test, added 1.5% yearly inflation at 3.5 million blocks --- CMakeLists.txt | 13 +++++++++ TODO.txt | 5 +++- include/block/chain.h | 5 ++-- include/block/transaction.h | 15 +++++++++-- include/constants.h | 54 ++++++++++++++++++++++++++++++++++--- src/block/chain.c | 10 +++++-- src/main.c | 40 +++++++++++++++++---------- 7 files changed, 116 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bad2ac2..58ea3b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,19 @@ if(MINICOIN_ENABLE_AUTOLYKOS2_REF) add_library(autolykos2_ref STATIC ${AUTOLYKOS2_REF_SOURCES}) target_include_directories(autolykos2_ref PRIVATE ${AUTOLYKOS2_REF_BASE}/include) + # Upstream source uses `malloc/free/exit/EXIT_FAILURE` without including + # stdlib headers in some C++ translation units. AppleClang can compile this, + # while Linux Clang fails. Force-include stdlib.h for C++ in this vendored lib. + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(autolykos2_ref PRIVATE + $<$:-include> + $<$:stdlib.h> + ) + elseif(MSVC) + target_compile_options(autolykos2_ref PRIVATE + $<$:/FIstdlib.h> + ) + endif() target_link_libraries(autolykos2_ref PRIVATE ${CMAKE_THREAD_LIBS_INIT} OpenSSL::SSL diff --git a/TODO.txt b/TODO.txt index 3da5cb8..287158d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -5,4 +5,7 @@ Implement Horizen's "Reorg Penalty" system to make it harder for the young chain Make transactions private. A bit more work, but it's a challenge worth taking on. I want to make an "optional privacy" system, where the TX can be public or private. Of course private TXs need more bytes, so the fees (although low) will be higher for them. -I need to figure out a way to make the privacy work without a UTXO system, and instead, with a "Balance Sheet" approach. \ No newline at end of file +I need to figure out a way to make the privacy work without a UTXO system, and instead, with a "Balance Sheet" approach. + +I want to move away from the Monero emission. I want to do something a bit radical for cryptocurrency, but I feel like it's necessary to make it more like money: +a constant inflation rate of 1.5% per year. It's lower than fiat (USD is ~2.8% per year), and it additionally doesn't fluctuate during crisis. It's constant. \ No newline at end of file diff --git a/include/block/chain.h b/include/block/chain.h index d48be0d..9b4cc8b 100644 --- a/include/block/chain.h +++ b/include/block/chain.h @@ -7,7 +7,6 @@ #include #include #include -#include #include typedef struct { @@ -24,8 +23,8 @@ bool Chain_IsValid(blockchain_t* chain); void Chain_Wipe(blockchain_t* chain); // I/O -bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t currentSupply); -bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* outCurrentSupply, uint32_t* outDifficultyTarget); +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); // Difficulty uint32_t Chain_ComputeNextTarget(blockchain_t* chain, uint32_t currentTarget); diff --git a/include/block/transaction.h b/include/block/transaction.h index 69746c2..646cc02 100644 --- a/include/block/transaction.h +++ b/include/block/transaction.h @@ -23,9 +23,20 @@ static inline bool Address_IsCoinbase(const uint8_t address[32]) { // 178 bytes total for v1 typedef struct { uint8_t version; + // Only one "input" sender address uint8_t senderAddress[32]; - uint8_t recipientAddress[32]; - uint64_t amount; + + // The "main" recepient address and amount. This is the only required output, and is used for calculating the transaction hash and signature. + uint8_t recipientAddress1[32]; + uint64_t amount1; + // The "extra" recepient address and amount. This can safely be NULL/0 if not used and has multiple uses: + // - Sending zero: parital spend, sender keeps coins on the same address + // - Sending to a different address: normal spend, sender's coins move to a new address, e.g. change address + // - Private Transactions: Can nullify the whole original stealth address input (sender) and send change to a new stealth address (recipient 2) to obfuscate the transaction graph. + // Note that coinbase will have this as NULL/0 (for now, but we could have multiple payouts in the future) + uint8_t recipientAddress2[32]; + uint64_t amount2; + uint64_t fee; // Rewarded to the miner; can be zero, but the miner may choose to ignore transactions with very low fees uint8_t compressedPublicKey[33]; // Timestamp is dictated by the block diff --git a/include/constants.h b/include/constants.h index 68334a5..b0d525f 100644 --- a/include/constants.h +++ b/include/constants.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #define DECIMALS 1000000000000ULL #define DIFFICULTY_ADJUSTMENT_INTERVAL 960 // Every 960 blocks (roughly every 24 hours with a 90 second block time) @@ -12,6 +14,8 @@ #define INITIAL_DIFFICULTY 0x1f0c1422 // Default compact target used by Autolykos2 PoW (This is ridiculously low) //#define INITIAL_DIFFICULTY 0x1d1b7c51 // This takes 90s on my machine with a single thread, good for testing +#define INFLATION_PERCENTAGE_PER_EPOCH 15 // 1.5% + // Future Autolykos2 constants: #define EPOCH_LENGTH 350000 // ~1 year at 90s #define BASE_DAG_SIZE (2ULL << 30) // 2 GB @@ -31,15 +35,53 @@ * - Phase 2: Stable DAG growth (target is the max cap) to provide a stable environment for GPU miners, 320k blocks (roughly 11 months) **/ +static uint64_t currentReward = 0; // Global variable to track current block reward; updated with each block mined 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; +// Call every epoch +static inline uint64_t GetInflationRateReward(uint256_t currentSupply, blockchain_t* chain) { + if (!chain || !chain->blocks) { return 0x00; } // Invalid + size_t height = Chain_Size(chain); + + block_t* blk = (block_t*)Chain_GetBlock(chain, height - 1); // Last block + if (!blk) { return 0x00; } // Invalid + + if (height % EPOCH_LENGTH == 0) { + // Calculate the new block reward (using all integer math to avoid floating point issues) + + // 1. Multiply supply by 3 + uint256_t multiplied = currentSupply; + uint256_t temp = currentSupply; + uint256_add(&multiplied, &temp); // currentSupply * 2 + uint256_add(&multiplied, &temp); // currentSupply * 3 + + // 2. Divide by 70,000,000 using scalar short division + uint64_t divisor = 70000000ULL; + uint256_t quotient = {{0, 0, 0, 0}}; + unsigned __int128 remainder = 0; + + // Work from the most significant limb to the least + for (int i = 3; i >= 0; i--) { + unsigned __int128 current = (remainder << 64) | multiplied.limbs[i]; + quotient.limbs[i] = (uint64_t)(current / divisor); + remainder = current % divisor; + } + + currentReward = quotient.limbs[0]; // Update the global reward variable with the new calculated reward for this epoch + return quotient.limbs[0]; // Return the least significant limb as the reward (the rest should be 0 for reasonable supply levels) + } + + return currentReward; +} + +static inline uint64_t CalculateBlockReward(uint256_t currentSupply, blockchain_t* chain) { + if (!chain || !chain->blocks) { return 0x00; } // Invalid + + uint64_t height = Chain_Size(chain); if (currentSupply.limbs[1] > 0 || currentSupply.limbs[2] > 0 || @@ -58,7 +100,11 @@ static inline uint64_t CalculateBlockReward(uint256_t currentSupply, uint64_t he // Check if the calculated reward has fallen below the floor if (reward < TAIL_EMISSION) { - return TAIL_EMISSION; + if (height < EPOCH_LENGTH * 10) { // Transitionary period to inflation of 1.5% per epoch + return TAIL_EMISSION; + } else { + return GetInflationRateReward(currentSupply, chain); // After the transitionary period, switch to the inflation-based reward + } } return reward; diff --git a/src/block/chain.c b/src/block/chain.c index 291dc82..fc62453 100644 --- a/src/block/chain.c +++ b/src/block/chain.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -134,8 +135,9 @@ void Chain_Wipe(blockchain_t* chain) { Chain_ClearBlocks(chain); } -bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t currentSupply) { +bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t currentSupply, uint64_t currentReward) { // To avoid stalling the chain from peers, write after every block addition (THAT IS VERIFIED) + // TODO: Write to one "db" file instead of one file per block - filesystems (and rm *) don't like millions of files :( if (!chain || !chain->blocks || !EnsureDirectoryExists(dirpath)) { return false; @@ -164,6 +166,8 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren fwrite(&zeroSupply, sizeof(uint256_t), 1, metaFile); uint32_t initialTarget = INITIAL_DIFFICULTY; fwrite(&initialTarget, sizeof(uint32_t), 1, metaFile); + uint64_t initialReward = 0; + fwrite(&initialReward, sizeof(uint64_t), 1, metaFile); // TODO: Potentially some other things here, we'll see } @@ -235,12 +239,13 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren 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); + fwrite(¤tReward, sizeof(uint64_t), 1, metaFile); fclose(metaFile); return true; } -bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* outCurrentSupply, uint32_t* outDifficultyTarget) { +bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* outCurrentSupply, uint32_t* outDifficultyTarget, uint64_t* outCurrentReward) { if (!chain || !chain->blocks || !dirpath || !outCurrentSupply) { return false; } @@ -267,6 +272,7 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out fread(lastSavedHash, sizeof(uint8_t), 32, metaFile); fread(outCurrentSupply, sizeof(uint256_t), 1, metaFile); fread(outDifficultyTarget, sizeof(uint32_t), 1, metaFile); + fread(outCurrentReward, sizeof(uint64_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 c222cd5..7a806e9 100644 --- a/src/main.c +++ b/src/main.c @@ -24,6 +24,9 @@ void handle_sigint(int sig) { 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 bool MineBlock(block_t* block) { if (!block) { return false; @@ -45,7 +48,7 @@ int main(int argc, char* argv[]) { signal(SIGINT, handle_sigint); const char* chainDataDir = CHAIN_DATA_DIR; - const uint64_t blocksToMine = 1000; + const uint64_t blocksToMine = 4000000; const double targetSeconds = TARGET_BLOCK_TIME; uint256_t currentSupply = uint256_from_u64(0); @@ -56,10 +59,14 @@ int main(int argc, char* argv[]) { return 1; } - if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply, &difficultyTarget)) { + if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply, &difficultyTarget, ¤tReward)) { printf("No existing chain loaded from %s\n", chainDataDir); } + if (currentReward == 0) { + currentReward = CalculateBlockReward(currentSupply, chain); + } + if (Chain_Size(chain) > 0) { if (Chain_IsValid(chain)) { printf("Loaded chain with %zu blocks from disk\n", Chain_Size(chain)); @@ -117,9 +124,11 @@ int main(int argc, char* argv[]) { signed_transaction_t coinbaseTx; memset(&coinbaseTx, 0, sizeof(coinbaseTx)); coinbaseTx.transaction.version = 1; - coinbaseTx.transaction.amount = CalculateBlockReward(currentSupply, block->header.blockNumber); + coinbaseTx.transaction.amount1 = currentReward; coinbaseTx.transaction.fee = 0; - memcpy(coinbaseTx.transaction.recipientAddress, minerAddress, sizeof(minerAddress)); + memcpy(coinbaseTx.transaction.recipientAddress1, minerAddress, sizeof(minerAddress)); + coinbaseTx.transaction.recipientAddress2[0] = 0; // Mark recipient 2 as unused + coinbaseTx.transaction.amount2 = 0; memset(coinbaseTx.transaction.compressedPublicKey, 0, sizeof(coinbaseTx.transaction.compressedPublicKey)); memset(coinbaseTx.transaction.senderAddress, 0xFF, sizeof(coinbaseTx.transaction.senderAddress)); Block_AddTransaction(block, &coinbaseTx); @@ -144,7 +153,7 @@ int main(int argc, char* argv[]) { return 1; } - (void)uint256_add_u64(¤tSupply, coinbaseTx.transaction.amount); + (void)uint256_add_u64(¤tSupply, coinbaseTx.transaction.amount1); uint8_t canonicalHash[32]; uint8_t powHash[32]; @@ -155,7 +164,7 @@ int main(int argc, char* argv[]) { (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)coinbaseTx.transaction.amount1, (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], @@ -164,15 +173,18 @@ int main(int argc, char* argv[]) { free(block); // chain stores blocks by value; transactions are owned by chain copy - // Save chain after each mined block - Chain_SaveToFile(chain, chainDataDir, currentSupply); + // Save chain after each mined block; NOTE: In reality, blocks will appear every ~90s, so this won't be a realistic bottleneck on the mainnet + Chain_SaveToFile(chain, chainDataDir, currentSupply, currentReward); - if (Chain_Size(chain) % DIFFICULTY_ADJUSTMENT_INTERVAL == 0) { - difficultyTarget = Chain_ComputeNextTarget(chain, difficultyTarget); - } + currentReward = CalculateBlockReward(currentSupply, chain); // Update the global currentReward for the next block + + // TEST, disabled diff + //if (Chain_Size(chain) % DIFFICULTY_ADJUSTMENT_INTERVAL == 0) { + // difficultyTarget = Chain_ComputeNextTarget(chain, difficultyTarget); + //} } - if (!Chain_SaveToFile(chain, chainDataDir, currentSupply)) { + if (!Chain_SaveToFile(chain, chainDataDir, currentSupply, currentReward)) { fprintf(stderr, "failed to save chain to %s\n", chainDataDir); } else { printf("Saved chain with %zu blocks to %s (supply=%llu)\n", @@ -185,12 +197,12 @@ int main(int argc, char* argv[]) { } // Print chain - for (size_t i = 0; i < Chain_Size(chain); i++) { + /*for (size_t i = 0; i < Chain_Size(chain); i++) { block_t* blk = Chain_GetBlock(chain, i); if (blk) { Block_Print(blk); } - } + }*/ Chain_Destroy(chain); Block_ShutdownPowContext();