Monero-style emission

This commit is contained in:
2026-03-29 23:30:31 +02:00
parent 0d7adc39e0
commit 50e357d8a2
6 changed files with 106 additions and 184 deletions

View File

@@ -1,16 +1,18 @@
#include <block/chain.h>
#include <block/transaction.h>
#include <openssl/sha.h>
#include <secp256k1.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <randomx/librx_wrapper.h>
#include <signal.h>
#include <constants.h>
#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, &currentSupply)) {
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(&currentSupply, 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;
}