BigInts, save/load, will make a calculation for block rewards soon
This commit is contained in:
@@ -124,5 +124,7 @@ target_compile_options(node PRIVATE
|
|||||||
-Wpedantic
|
-Wpedantic
|
||||||
-g
|
-g
|
||||||
)
|
)
|
||||||
target_compile_definitions(node PRIVATE)
|
target_compile_definitions(node PRIVATE
|
||||||
|
CHAIN_DATA_DIR="${CMAKE_BINARY_DIR}/chain_data"
|
||||||
|
)
|
||||||
set_target_properties(node PROPERTIES OUTPUT_NAME "minicoin_node")
|
set_target_properties(node PROPERTIES OUTPUT_NAME "minicoin_node")
|
||||||
|
|||||||
@@ -10,13 +10,14 @@
|
|||||||
#include <randomx/librx_wrapper.h>
|
#include <randomx/librx_wrapper.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t version;
|
uint64_t blockNumber;
|
||||||
uint32_t blockNumber;
|
uint64_t timestamp;
|
||||||
|
uint64_t nonce;
|
||||||
uint8_t prevHash[32];
|
uint8_t prevHash[32];
|
||||||
uint8_t merkleRoot[32];
|
uint8_t merkleRoot[32];
|
||||||
uint64_t timestamp;
|
|
||||||
uint32_t difficultyTarget; // Encoding: [1 byte exponent][3 byte coefficient]; Target = coefficient * 256^(exponent-3)
|
uint32_t difficultyTarget; // Encoding: [1 byte exponent][3 byte coefficient]; Target = coefficient * 256^(exponent-3)
|
||||||
uint64_t nonce; // Higher nonce for RandomX
|
uint8_t version;
|
||||||
|
uint8_t reserved[3]; // 3 bytes (Explicit padding for 8-byte alignment)
|
||||||
} block_header_t;
|
} block_header_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#include <block/block.h>
|
#include <block/block.h>
|
||||||
#include <dynarr.h>
|
#include <dynarr.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
DynArr* blocks;
|
DynArr* blocks;
|
||||||
@@ -16,5 +19,10 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block);
|
|||||||
block_t* Chain_GetBlock(blockchain_t* chain, size_t index);
|
block_t* Chain_GetBlock(blockchain_t* chain, size_t index);
|
||||||
size_t Chain_Size(blockchain_t* chain);
|
size_t Chain_Size(blockchain_t* chain);
|
||||||
bool Chain_IsValid(blockchain_t* chain);
|
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);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
37
include/constants.h
Normal file
37
include/constants.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#ifndef CONSTANTS_H
|
||||||
|
#define CONSTANTS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <uint256.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#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
|
||||||
|
// No max supply. Instead of halving, it'll follow a more gradual, Monero-like emission curve.
|
||||||
|
|
||||||
|
static inline uint64_t CalculateBlockReward(uint256_t currentSupply, uint64_t height) {
|
||||||
|
// Inclusive of block 0
|
||||||
|
|
||||||
|
if (current_supply.limbs[1] > 0 ||
|
||||||
|
current_supply.limbs[2] > 0 ||
|
||||||
|
current_supply.limbs[3] > 0 ||
|
||||||
|
current_supply.limbs[0] >= M_CAP) {
|
||||||
|
return TAIL_EMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t supply_64 = current_supply.limbs[0];
|
||||||
|
|
||||||
|
// Formula: (M - Supply) >> 2^k - lifted from Monero's codebase (thanks guys!)
|
||||||
|
uint64_t reward = (M_CAP - supply_64) >> EMISSION_SPEED_FACTOR;
|
||||||
|
|
||||||
|
// Check if the calculated reward has fallen below the floor
|
||||||
|
if (reward < TAIL_EMISSION) {
|
||||||
|
return TAIL_EMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reward;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool RandomX_Init(const char* key);
|
bool RandomX_Init(const char* key, bool preferFullMemory);
|
||||||
void RandomX_Destroy();
|
void RandomX_Destroy();
|
||||||
void RandomX_CalculateHash(const uint8_t* input, size_t inputLen, uint8_t* output);
|
void RandomX_CalculateHash(const uint8_t* input, size_t inputLen, uint8_t* output);
|
||||||
|
|
||||||
|
|||||||
58
include/uint256.h
Normal file
58
include/uint256.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#ifndef UINT256_H
|
||||||
|
#define UINT256_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t limbs[4]; // 4 * 64 = 256 bits
|
||||||
|
} uint256_t;
|
||||||
|
|
||||||
|
// Initialize a uint256 with a standard 64-bit value
|
||||||
|
static inline uint256_t uint256_from_u64(uint64_t val) {
|
||||||
|
uint256_t res = {{val, 0, 0, 0}};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a uint64_t (transaction amount) to a uint256_t (balance).
|
||||||
|
* Returns true if an overflow occurred (total supply exceeded 256 bits).
|
||||||
|
**/
|
||||||
|
static inline bool uint256_add_u64(uint256_t* balance, uint64_t amount) {
|
||||||
|
uint64_t old = balance->limbs[0];
|
||||||
|
balance->limbs[0] += amount;
|
||||||
|
|
||||||
|
// Check for carry: if the new value is less than the old, it wrapped around
|
||||||
|
if (balance->limbs[0] < old) {
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
balance->limbs[i]++;
|
||||||
|
// If the limb didn't wrap to 0, the carry is fully absorbed
|
||||||
|
if (balance->limbs[i] != 0) return false;
|
||||||
|
}
|
||||||
|
return true; // Overflowed all 256 bits
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds two uint256_t values together.
|
||||||
|
* Standard full addition logic.
|
||||||
|
**/
|
||||||
|
static inline bool uint256_add(uint256_t* a, const uint256_t* b) {
|
||||||
|
uint64_t carry = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
uint64_t old_a = a->limbs[i];
|
||||||
|
a->limbs[i] += b->limbs[i] + carry;
|
||||||
|
|
||||||
|
// Detect carry: current is less than what we added, or we were at max and had a carry
|
||||||
|
if (carry) {
|
||||||
|
carry = (a->limbs[i] <= old_a);
|
||||||
|
} else {
|
||||||
|
carry = (a->limbs[i] < old_a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return carry > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -140,16 +140,17 @@ bool Block_AllTransactionsValid(const block_t* block) {
|
|||||||
|
|
||||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||||
if (!Transaction_Verify(tx)) {
|
if (tx && Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
|
||||||
if (hasCoinbase) {
|
if (hasCoinbase) {
|
||||||
return false; // More than one coinbase transaction
|
return false; // More than one coinbase transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCoinbase = true;
|
hasCoinbase = true;
|
||||||
|
continue; // Coinbase transactions are valid since the miner has the right to create coins. Only rule is one per block.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Transaction_Verify(tx)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,36 @@
|
|||||||
#include <block/chain.h>
|
#include <block/chain.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
static bool EnsureDirectoryExists(const char* dirpath) {
|
||||||
|
if (!dirpath || dirpath[0] == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(dirpath, &st) == 0) {
|
||||||
|
return S_ISDIR(st.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mkdir(dirpath, 0755) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errno == EEXIST && stat(dirpath, &st) == 0) {
|
||||||
|
return S_ISDIR(st.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool BuildPath(char* out, size_t outSize, const char* dirpath, const char* filename) {
|
||||||
|
if (!out || outSize == 0 || !dirpath || !filename) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int written = snprintf(out, outSize, "%s/%s", dirpath, filename);
|
||||||
|
return written > 0 && (size_t)written < outSize;
|
||||||
|
}
|
||||||
|
|
||||||
blockchain_t* Chain_Create() {
|
blockchain_t* Chain_Create() {
|
||||||
blockchain_t* ptr = (blockchain_t*)malloc(sizeof(blockchain_t));
|
blockchain_t* ptr = (blockchain_t*)malloc(sizeof(blockchain_t));
|
||||||
@@ -24,6 +56,7 @@ void Chain_Destroy(blockchain_t* chain) {
|
|||||||
bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
||||||
if (chain && block && chain->blocks) {
|
if (chain && block && chain->blocks) {
|
||||||
DynArr_push_back(chain->blocks, block);
|
DynArr_push_back(chain->blocks, block);
|
||||||
|
chain->size++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +81,217 @@ bool Chain_IsValid(blockchain_t* chain) {
|
|||||||
if (!chain || !chain->blocks) {
|
if (!chain || !chain->blocks) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Add validation logic here
|
|
||||||
|
const size_t chainSize = DynArr_size(chain->blocks);
|
||||||
|
if (chainSize == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 1; i < chainSize; i++) {
|
||||||
|
block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
|
||||||
|
block_t* prevBlk = (block_t*)DynArr_at(chain->blocks, i - 1);
|
||||||
|
if (!blk || !prevBlk || blk->header.blockNumber != i) { return false; } // NULL blocks or blockNumber != order in chain
|
||||||
|
|
||||||
|
// Verify prevHash is valid
|
||||||
|
uint8_t prevHash[32];
|
||||||
|
Block_CalculateHash(prevBlk, prevHash);
|
||||||
|
|
||||||
|
if (memcmp(blk->header.prevHash, prevHash, 32) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A potential issue is verifying PoW, since the chain read might only have header data without transactions.
|
||||||
|
// A potnetial fix is verifying PoW as we go, when getting new blocks from peers, and only accepting blocks
|
||||||
|
//with valid PoW, so that we can assume all blocks in the chain are valid in that regard.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Genesis needs special handling because the prevHash is always invalid (no previous block)
|
||||||
|
block_t* genesis = (block_t*)DynArr_at(chain->blocks, 0);
|
||||||
|
if (!genesis || genesis->header.blockNumber != 0) { return false; }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chain_Wipe(blockchain_t* chain) {
|
||||||
|
if (chain && chain->blocks) {
|
||||||
|
DynArr_erase(chain->blocks);
|
||||||
|
chain->size = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath) {
|
||||||
|
// To avoid stalling the chain from peers, write after every block addition (THAT IS VERIFIED)
|
||||||
|
|
||||||
|
if (!chain || !chain->blocks || !EnsureDirectoryExists(dirpath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char metaPath[512];
|
||||||
|
if (!BuildPath(metaPath, sizeof(metaPath), dirpath, "chain.meta")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find metadata file (create if not exists) to get the saved chain size (+ other things)
|
||||||
|
FILE* metaFile = fopen(metaPath, "rb+");
|
||||||
|
if (!metaFile) {
|
||||||
|
metaFile = fopen(metaPath, "wb+");
|
||||||
|
if (!metaFile) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize metadata with size 0
|
||||||
|
size_t initialSize = 0;
|
||||||
|
fwrite(&initialSize, sizeof(size_t), 1, metaFile);
|
||||||
|
// Write last block hash (32 bytes of zeros for now)
|
||||||
|
uint8_t zeroHash[32] = {0};
|
||||||
|
fwrite(zeroHash, sizeof(uint8_t), 32, metaFile);
|
||||||
|
|
||||||
|
// TODO: Potentially some other things here, we'll see
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read
|
||||||
|
size_t savedSize = 0;
|
||||||
|
fread(&savedSize, sizeof(size_t), 1, metaFile);
|
||||||
|
uint8_t lastSavedHash[32];
|
||||||
|
fread(lastSavedHash, sizeof(uint8_t), 32, metaFile);
|
||||||
|
|
||||||
|
// Assume chain saved is valid, and that the chain in memory is valid (as LoadFromFile will verify the saved one)
|
||||||
|
if (savedSize > DynArr_size(chain->blocks)) {
|
||||||
|
// Saved chain is longer than current chain, this should not happen if we are always saving the current chain, but just in case, fail to save to avoid overwriting a potentially valid longer chain with a shorter one.
|
||||||
|
fclose(metaFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filename formart: dirpath/block_{index}.dat
|
||||||
|
// File format: [block_header][num_transactions][transactions...] - since block_header is fixed size, LoadFromFile will only read those by default
|
||||||
|
|
||||||
|
// Save blocks that are not yet saved
|
||||||
|
for (size_t i = savedSize; i < DynArr_size(chain->blocks); i++) {
|
||||||
|
block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
|
||||||
|
if (!blk) {
|
||||||
|
fclose(metaFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct file path
|
||||||
|
char filePath[256];
|
||||||
|
snprintf(filePath, sizeof(filePath), "%s/block_%zu.dat", dirpath, i);
|
||||||
|
|
||||||
|
FILE* blockFile = fopen(filePath, "wb");
|
||||||
|
if (!blockFile) {
|
||||||
|
fclose(metaFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write block header
|
||||||
|
fwrite(&blk->header, sizeof(block_header_t), 1, blockFile);
|
||||||
|
size_t txSize = DynArr_size(blk->transactions);
|
||||||
|
fwrite(&txSize, sizeof(size_t), 1, blockFile); // Write number of transactions
|
||||||
|
// Write transactions
|
||||||
|
for (size_t j = 0; j < txSize; j++) {
|
||||||
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, j);
|
||||||
|
if (fwrite(tx, sizeof(signed_transaction_t), 1, blockFile) != 1) {
|
||||||
|
fclose(blockFile);
|
||||||
|
fclose(metaFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(blockFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update metadata with new size and last block hash
|
||||||
|
size_t newSize = DynArr_size(chain->blocks);
|
||||||
|
fseek(metaFile, 0, SEEK_SET);
|
||||||
|
fwrite(&newSize, sizeof(size_t), 1, metaFile);
|
||||||
|
if (newSize > 0) {
|
||||||
|
block_t* lastBlock = (block_t*)DynArr_at(chain->blocks, newSize - 1);
|
||||||
|
uint8_t lastHash[32];
|
||||||
|
Block_CalculateHash(lastBlock, lastHash);
|
||||||
|
fwrite(lastHash, sizeof(uint8_t), 32, metaFile);
|
||||||
|
}
|
||||||
|
fclose(metaFile);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath) {
|
||||||
|
if (!chain || !chain->blocks || !dirpath) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read metadata file to get saved chain size (+ other things)
|
||||||
|
FILE* metaFile = fopen(metaPath, "rb");
|
||||||
|
if (!metaFile) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t savedSize = 0;
|
||||||
|
fread(&savedSize, sizeof(size_t), 1, metaFile);
|
||||||
|
uint8_t lastSavedHash[32];
|
||||||
|
fread(lastSavedHash, sizeof(uint8_t), 32, metaFile);
|
||||||
|
fclose(metaFile);
|
||||||
|
|
||||||
|
// TODO: Might add a flag to allow reading from a point onward, but just rewrite for now
|
||||||
|
DynArr_erase(chain->blocks); // Clear current chain blocks, but keep allocated memory for efficiency, since we will likely be loading a similar number of blocks as currently in memory.
|
||||||
|
|
||||||
|
// Load blocks
|
||||||
|
for (size_t i = 0; i < savedSize; i++) {
|
||||||
|
// Construct file path
|
||||||
|
char filePath[256];
|
||||||
|
snprintf(filePath, sizeof(filePath), "%s/block_%zu.dat", dirpath, i);
|
||||||
|
|
||||||
|
block_t* blk = Block_Create();
|
||||||
|
if (!blk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* blockFile = fopen(filePath, "rb");
|
||||||
|
if (!blockFile) {
|
||||||
|
Block_Destroy(blk);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read block header and transactions
|
||||||
|
if (fread(&blk->header, sizeof(block_header_t), 1, blockFile) != 1) {
|
||||||
|
fclose(blockFile);
|
||||||
|
Block_Destroy(blk);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t txSize = 0;
|
||||||
|
if (fread(&txSize, sizeof(size_t), 1, blockFile) != 1) {
|
||||||
|
fclose(blockFile);
|
||||||
|
Block_Destroy(blk);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = 0; j < txSize; j++) {
|
||||||
|
signed_transaction_t tx;
|
||||||
|
if (fread(&tx, sizeof(signed_transaction_t), 1, blockFile) != 1) {
|
||||||
|
fclose(blockFile);
|
||||||
|
Block_Destroy(blk);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Block_AddTransaction(blk, &tx);
|
||||||
|
}
|
||||||
|
fclose(blockFile);
|
||||||
|
|
||||||
|
Chain_AddBlock(chain, blk);
|
||||||
|
}
|
||||||
|
|
||||||
|
chain->size = savedSize;
|
||||||
|
|
||||||
|
// After read, you SHOULD verify chain validity. We're not doing it here since returning false is a bit unclear if the read failed or if the chain is invalid.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
94
src/main.c
94
src/main.c
@@ -11,6 +11,10 @@
|
|||||||
#include <randomx/librx_wrapper.h>
|
#include <randomx/librx_wrapper.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
|
#ifndef CHAIN_DATA_DIR
|
||||||
|
#define CHAIN_DATA_DIR "chain_data"
|
||||||
|
#endif
|
||||||
|
|
||||||
void handle_sigint(int sig) {
|
void handle_sigint(int sig) {
|
||||||
printf("Caught signal %d, exiting...\n", sig);
|
printf("Caught signal %d, exiting...\n", sig);
|
||||||
RandomX_Destroy();
|
RandomX_Destroy();
|
||||||
@@ -121,8 +125,10 @@ static bool MineBlock(block_t* block) {
|
|||||||
int main(void) {
|
int main(void) {
|
||||||
signal(SIGINT, handle_sigint);
|
signal(SIGINT, handle_sigint);
|
||||||
|
|
||||||
|
const char* chainDataDir = CHAIN_DATA_DIR;
|
||||||
|
|
||||||
// Init RandomX
|
// Init RandomX
|
||||||
if (!RandomX_Init("minicoin")) { // TODO: Use a key that is not hardcoded; E.g. hash of the last block, every thousand blocks, etc.
|
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.
|
||||||
fprintf(stderr, "failed to initialize RandomX\n");
|
fprintf(stderr, "failed to initialize RandomX\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -133,6 +139,33 @@ int main(void) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt read
|
||||||
|
if (!Chain_LoadFromFile(chain, chainDataDir)) {
|
||||||
|
printf("No existing chain loaded from %s\n", chainDataDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Chain_Size(chain) > 0) {
|
||||||
|
if (Chain_IsValid(chain)) {
|
||||||
|
printf("Loaded chain with %zu blocks from disk\n", Chain_Size(chain));
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "loaded chain is invalid, scrapping, resyncing.\n"); // TODO: Actually implement resyncing from peers instead of just scrapping the chain
|
||||||
|
const size_t badSize = Chain_Size(chain);
|
||||||
|
|
||||||
|
// Delete files (wipe dir)
|
||||||
|
for (size_t i = 0; i < badSize; i++) {
|
||||||
|
char filePath[256];
|
||||||
|
snprintf(filePath, sizeof(filePath), "%s/block_%zu.dat", chainDataDir, i);
|
||||||
|
remove(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
char metaPath[256];
|
||||||
|
snprintf(metaPath, sizeof(metaPath), "%s/chain.meta", chainDataDir);
|
||||||
|
remove(metaPath);
|
||||||
|
|
||||||
|
Chain_Wipe(chain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
secp256k1_context* secpCtx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
secp256k1_context* secpCtx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
||||||
if (!secpCtx) {
|
if (!secpCtx) {
|
||||||
fprintf(stderr, "failed to create secp256k1 context\n");
|
fprintf(stderr, "failed to create secp256k1 context\n");
|
||||||
@@ -153,6 +186,17 @@ int main(void) {
|
|||||||
return 1;
|
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;
|
signed_transaction_t tx;
|
||||||
memset(&tx, 0, sizeof(tx));
|
memset(&tx, 0, sizeof(tx));
|
||||||
tx.transaction.version = 1;
|
tx.transaction.version = 1;
|
||||||
@@ -182,22 +226,36 @@ int main(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
block->header.version = 1;
|
block->header.version = 1;
|
||||||
block->header.blockNumber = (uint32_t)Chain_Size(chain);
|
block->header.blockNumber = (uint64_t)Chain_Size(chain);
|
||||||
memset(block->header.prevHash, 0, sizeof(block->header.prevHash));
|
// 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));
|
memset(block->header.merkleRoot, 0, sizeof(block->header.merkleRoot));
|
||||||
block->header.timestamp = (uint64_t)time(NULL);
|
block->header.timestamp = (uint64_t)time(NULL);
|
||||||
|
|
||||||
const double hps = MeasureRandomXHashrate();
|
const double hps = MeasureRandomXHashrate();
|
||||||
const double targetSeconds = 60.0;
|
const double targetSeconds = 10.0;
|
||||||
const double expectedHashes = (hps > 0.0) ? (hps * targetSeconds) : 65536.0;
|
const double expectedHashes = (hps > 0.0) ? (hps * targetSeconds) : 65536.0;
|
||||||
block->header.difficultyTarget = CompactTargetForExpectedHashes(expectedHashes);
|
block->header.difficultyTarget = CompactTargetForExpectedHashes(expectedHashes);
|
||||||
block->header.nonce = 0;
|
block->header.nonce = 0;
|
||||||
|
|
||||||
printf("RandomX benchmark: %.2f H/s, target %.0fs, nBits=0x%08x\n",
|
printf("RandomX benchmark: %.2f H/s, target %.0fs, nBits=0x%08x\n",
|
||||||
hps,
|
hps,
|
||||||
targetSeconds,
|
targetSeconds,
|
||||||
block->header.difficultyTarget);
|
block->header.difficultyTarget);
|
||||||
|
|
||||||
|
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);
|
Block_AddTransaction(block, &tx);
|
||||||
printf("Added transaction to block: sender %02x... -> recipient %02x..., amount %lu, fee %lu\n",
|
printf("Added transaction to block: sender %02x... -> recipient %02x..., amount %lu, fee %lu\n",
|
||||||
tx.transaction.senderAddress[0], tx.transaction.senderAddress[31],
|
tx.transaction.senderAddress[0], tx.transaction.senderAddress[31],
|
||||||
@@ -222,10 +280,10 @@ int main(void) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Mined block %u with nonce %llu and chain size %zu\n",
|
printf("Mined block %llu with nonce %llu and chain size %zu\n",
|
||||||
block->header.blockNumber,
|
(unsigned long long)block->header.blockNumber,
|
||||||
(unsigned long long)block->header.nonce,
|
(unsigned long long)block->header.nonce,
|
||||||
Chain_Size(chain));
|
Chain_Size(chain));
|
||||||
|
|
||||||
printf("Block hash (SHA256): ");
|
printf("Block hash (SHA256): ");
|
||||||
uint8_t blockHash[32];
|
uint8_t blockHash[32];
|
||||||
@@ -241,6 +299,12 @@ int main(void) {
|
|||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
|
if (!Chain_SaveToFile(chain, chainDataDir)) {
|
||||||
|
fprintf(stderr, "failed to save chain to %s\n", chainDataDir);
|
||||||
|
} else {
|
||||||
|
printf("Saved chain with %zu blocks to %s\n", Chain_Size(chain), chainDataDir);
|
||||||
|
}
|
||||||
|
|
||||||
// Chain currently stores a copy of block_t that references the same tx array pointer,
|
// 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.
|
// so we do not destroy `block` here to avoid invalidating chain data.
|
||||||
secp256k1_context_destroy(secpCtx);
|
secp256k1_context_destroy(secpCtx);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ static randomx_cache* rxCache = NULL;
|
|||||||
static randomx_dataset* rxDataset = NULL;
|
static randomx_dataset* rxDataset = NULL;
|
||||||
static randomx_vm* rxVm = NULL;
|
static randomx_vm* rxVm = NULL;
|
||||||
|
|
||||||
bool RandomX_Init(const char* key) {
|
bool RandomX_Init(const char* key, bool preferFullMemory) {
|
||||||
if (!key || rxCache || rxVm) {
|
if (!key || rxCache || rxVm) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -24,18 +24,20 @@ bool RandomX_Init(const char* key) {
|
|||||||
randomx_init_cache(rxCache, key, strlen(key));
|
randomx_init_cache(rxCache, key, strlen(key));
|
||||||
|
|
||||||
// Prefer full-memory mode. If dataset allocation fails, fall back to light mode.
|
// Prefer full-memory mode. If dataset allocation fails, fall back to light mode.
|
||||||
rxDataset = randomx_alloc_dataset(vmFlags);
|
if (preferFullMemory) {
|
||||||
if (rxDataset) {
|
rxDataset = randomx_alloc_dataset(vmFlags);
|
||||||
const unsigned long datasetItems = randomx_dataset_item_count();
|
if (rxDataset) {
|
||||||
randomx_init_dataset(rxDataset, rxCache, 0, datasetItems);
|
const unsigned long datasetItems = randomx_dataset_item_count();
|
||||||
rxVm = randomx_create_vm(vmFlags, NULL, rxDataset);
|
randomx_init_dataset(rxDataset, rxCache, 0, datasetItems);
|
||||||
if (rxVm) {
|
rxVm = randomx_create_vm(vmFlags, NULL, rxDataset);
|
||||||
printf("RandomX initialized in full-memory mode\n");
|
if (rxVm) {
|
||||||
return true;
|
printf("RandomX initialized in full-memory mode\n");
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
randomx_release_dataset(rxDataset);
|
randomx_release_dataset(rxDataset);
|
||||||
rxDataset = NULL;
|
rxDataset = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vmFlags = baseFlags;
|
vmFlags = baseFlags;
|
||||||
|
|||||||
Reference in New Issue
Block a user