huge chain test, added 1.5% yearly inflation at 3.5 million blocks
This commit is contained in:
@@ -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
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-include>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:stdlib.h>
|
||||
)
|
||||
elseif(MSVC)
|
||||
target_compile_options(autolykos2_ref PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:CXX>:/FIstdlib.h>
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(autolykos2_ref PRIVATE
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
OpenSSL::SSL
|
||||
|
||||
3
TODO.txt
3
TODO.txt
@@ -6,3 +6,6 @@ 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.
|
||||
|
||||
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.
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <constants.h>
|
||||
#include <uint256.h>
|
||||
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <stdint.h>
|
||||
#include <uint256.h>
|
||||
#include <stdbool.h>
|
||||
#include <block/chain.h>
|
||||
#include <block/block.h>
|
||||
|
||||
#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) {
|
||||
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;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <block/chain.h>
|
||||
#include <constants.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
@@ -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
|
||||
|
||||
40
src/main.c
40
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();
|
||||
|
||||
Reference in New Issue
Block a user