TCP Node boilerplate; CLI interface

This commit is contained in:
2026-04-23 16:24:26 +02:00
parent d631eb190d
commit 9c99eec3a8
13 changed files with 1635 additions and 578 deletions

View File

@@ -13,9 +13,9 @@
#include <constants.h>
#include <autolykos2/autolykos2.h>
#include <time.h>
#include <nets/net_node.h>
#include <crypto/crypto.h>
#ifndef CHAIN_DATA_DIR
#define CHAIN_DATA_DIR "chain_data"
@@ -83,19 +83,6 @@ static bool GenerateTestMinerIdentity(uint8_t privateKey[32], uint8_t compressed
return false;
}
static int testCounts = 0;
static void MakeTestRecipientAddress(uint8_t outAddress[32]) {
if (!outAddress) {
return;
}
const char* label = "skalacoin-test-recipient-address-";
char buffer[256];
snprintf(buffer, sizeof(buffer), "%s%d", label, testCounts);
SHA256((const unsigned char*)buffer, strlen(buffer), outAddress);
testCounts++;
}
static void Uint256ToDecimal(const uint256_t* value, char* out, size_t outSize) {
if (!value || !out || outSize == 0) {
return;
@@ -160,14 +147,289 @@ static bool MineBlock(block_t* block) {
}
}
static bool ParseHexAddress32(const char* in, uint8_t outAddress[32]) {
if (!in || !outAddress) {
return false;
}
const char* p = in;
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
}
if (strlen(p) != 64) {
return false;
}
for (size_t i = 0; i < 32; ++i) {
char hi = p[i * 2];
char lo = p[i * 2 + 1];
int hiVal = (hi >= '0' && hi <= '9') ? (hi - '0') :
(hi >= 'a' && hi <= 'f') ? (10 + hi - 'a') :
(hi >= 'A' && hi <= 'F') ? (10 + hi - 'A') : -1;
int loVal = (lo >= '0' && lo <= '9') ? (lo - '0') :
(lo >= 'a' && lo <= 'f') ? (10 + lo - 'a') :
(lo >= 'A' && lo <= 'F') ? (10 + lo - 'A') : -1;
if (hiVal < 0 || loVal < 0) {
return false;
}
outAddress[i] = (uint8_t)((hiVal << 4) | loVal);
}
return true;
}
static void AddressToHexString(const uint8_t address[32], char out[65]) {
if (!address || !out) {
return;
}
to_hex(address, out);
}
static bool FlushChainAndSheet(blockchain_t* chain,
const char* chainDataDir,
uint256_t currentSupply,
uint64_t currentReward) {
bool chainSaved = Chain_SaveToFile(chain, chainDataDir, currentSupply, currentReward);
bool sheetSaved = BalanceSheet_SaveToFile(chainDataDir);
if (!chainSaved) {
fprintf(stderr, "failed to save chain to %s\n", chainDataDir);
}
if (!sheetSaved) {
fprintf(stderr, "failed to save balance sheet to %s\n", chainDataDir);
}
return chainSaved && sheetSaved;
}
static block_t* BuildNextBlock(blockchain_t* chain, uint32_t difficultyTarget) {
block_t* block = Block_Create();
if (!block) {
return NULL;
}
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));
}
block->header.timestamp = (uint64_t)time(NULL);
block->header.difficultyTarget = difficultyTarget;
block->header.nonce = 0;
return block;
}
static void AddCoinbaseTransaction(block_t* block, const uint8_t minerAddress[32], uint64_t reward) {
signed_transaction_t coinbaseTx;
Transaction_Init(&coinbaseTx);
coinbaseTx.transaction.version = 1;
coinbaseTx.transaction.amount1 = reward;
coinbaseTx.transaction.fee = 0;
memcpy(coinbaseTx.transaction.recipientAddress1, minerAddress, 32);
memset(coinbaseTx.transaction.recipientAddress2, 0, sizeof(coinbaseTx.transaction.recipientAddress2));
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);
}
static bool MineAndAppendBlock(blockchain_t* chain,
block_t* block,
uint256_t* currentSupply,
uint64_t* currentReward,
uint32_t* difficultyTarget) {
if (!chain || !block || !currentSupply || !currentReward || !difficultyTarget) {
return false;
}
uint8_t merkleRoot[32];
Block_CalculateMerkleRoot(block, merkleRoot);
memcpy(block->header.merkleRoot, merkleRoot, sizeof(block->header.merkleRoot));
if (!MineBlock(block)) {
fprintf(stderr, "failed to mine block within nonce range\n");
return false;
}
if (!Chain_AddBlock(chain, block)) {
fprintf(stderr, "failed to append block to chain\n");
return false;
}
uint64_t coinbaseAmount = 0;
if (block->transactions && DynArr_size(block->transactions) > 0) {
signed_transaction_t* firstTx = (signed_transaction_t*)DynArr_at(block->transactions, 0);
if (firstTx && Address_IsCoinbase(firstTx->transaction.senderAddress)) {
coinbaseAmount = firstTx->transaction.amount1;
}
}
(void)uint256_add_u64(currentSupply, coinbaseAmount);
uint8_t canonicalHash[32];
uint8_t powHash[32];
Block_CalculateHash(block, canonicalHash);
Block_CalculateAutolykos2Hash(block, powHash);
char supplyStr[80];
Uint256ToDecimal(currentSupply, supplyStr, sizeof(supplyStr));
printf("Mined block height=%llu nonce=%llu reward=%llu supply=%s diff=%#x pow=%02x%02x%02x%02x... canonical=%02x%02x%02x%02x...\n",
(unsigned long long)block->header.blockNumber,
(unsigned long long)block->header.nonce,
(unsigned long long)coinbaseAmount,
supplyStr,
(unsigned int)block->header.difficultyTarget,
powHash[0], powHash[1], powHash[2], powHash[3],
canonicalHash[0], canonicalHash[1], canonicalHash[2], canonicalHash[3]);
*currentReward = CalculateBlockReward(*currentSupply, chain);
if (Chain_Size(chain) % DIFFICULTY_ADJUSTMENT_INTERVAL == 0) {
*difficultyTarget = Chain_ComputeNextTarget(chain, *difficultyTarget);
}
if (Chain_Size(chain) % EPOCH_LENGTH == 0 && Chain_Size(chain) > 0) {
uint8_t dagSeed[32];
GetNextDAGSeed(chain, dagSeed);
(void)Block_RebuildAutolykos2Dag(CalculateTargetDAGSize(chain), dagSeed);
}
return true;
}
static bool GenerateRandomTestAddress(uint8_t outAddress[32]) {
if (!outAddress) {
return false;
}
uint8_t privateKey[32];
uint8_t compressedPubkey[33];
secp256k1_pubkey pubkey;
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
if (!ctx) {
return false;
}
for (size_t attempt = 0; attempt < 4096; ++attempt) {
for (size_t i = 0; i < sizeof(privateKey); ++i) {
privateKey[i] = (uint8_t)(rand() & 0xFF);
}
if (!secp256k1_ec_seckey_verify(ctx, privateKey)) {
continue;
}
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, privateKey)) {
continue;
}
size_t pubLen = sizeof(compressedPubkey);
if (!secp256k1_ec_pubkey_serialize(ctx, compressedPubkey, &pubLen, &pubkey, SECP256K1_EC_COMPRESSED) || pubLen != 33) {
continue;
}
AddressFromCompressedPubkey(compressedPubkey, outAddress);
secp256k1_context_destroy(ctx);
return true;
}
secp256k1_context_destroy(ctx);
return false;
}
static void WipeChainFiles(const char* chainDataDir) {
if (!chainDataDir) {
return;
}
char path[512];
snprintf(path, sizeof(path), "%s/chain.meta", chainDataDir);
remove(path);
snprintf(path, sizeof(path), "%s/chain.data", chainDataDir);
remove(path);
snprintf(path, sizeof(path), "%s/chain.table", chainDataDir);
remove(path);
snprintf(path, sizeof(path), "%s/balance_sheet.data", chainDataDir);
remove(path);
}
static bool VerifyChainFully(blockchain_t* chain) {
if (!chain || !chain->blocks) {
return false;
}
size_t chainSize = Chain_Size(chain);
for (size_t i = 0; i < chainSize; ++i) {
block_t* blk = Chain_GetBlock(chain, i);
if (!blk || !blk->transactions) {
return false;
}
if (blk->header.blockNumber != (uint64_t)i) {
return false;
}
if (i == 0) {
uint8_t zeroHash[32] = {0};
if (memcmp(blk->header.prevHash, zeroHash, sizeof(zeroHash)) != 0) {
return false;
}
} else {
block_t* prevBlk = Chain_GetBlock(chain, i - 1);
if (!prevBlk) {
return false;
}
uint8_t expectedPrevHash[32];
Block_CalculateHash(prevBlk, expectedPrevHash);
if (memcmp(blk->header.prevHash, expectedPrevHash, sizeof(expectedPrevHash)) != 0) {
return false;
}
}
if (!Block_HasValidProofOfWork(blk)) {
return false;
}
if (!Block_AllTransactionsValid(blk)) {
return false;
}
uint8_t expectedMerkle[32];
Block_CalculateMerkleRoot(blk, expectedMerkle);
if (memcmp(blk->header.merkleRoot, expectedMerkle, sizeof(expectedMerkle)) != 0) {
return false;
}
}
return true;
}
int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
signal(SIGINT, handle_sigint);
srand((unsigned int)time(NULL));
BalanceSheet_Init();
const char* chainDataDir = CHAIN_DATA_DIR;
const uint64_t blocksToMine = 1000;
const double targetSeconds = TARGET_BLOCK_TIME;
uint256_t currentSupply = uint256_from_u64(0);
@@ -180,13 +442,13 @@ int main(int argc, char* argv[]) {
blockchain_t* chain = Chain_Create();
if (!chain) {
fprintf(stderr, "failed to create chain\n");
Node_Destroy(node);
BalanceSheet_Destroy();
return 1;
}
uint8_t lastSavedHash[32];
bool isFirstBlockOfLoadedChain = true;
if (!Chain_LoadFromFile(chain, chainDataDir, &currentSupply, &difficultyTarget, &currentReward, lastSavedHash)) {
uint8_t lastSavedHash[32] = {0};
if (!Chain_LoadFromFile(chain, chainDataDir, &currentSupply, &difficultyTarget, &currentReward, lastSavedHash, false)) {
printf("No existing chain loaded from %s\n", chainDataDir);
}
@@ -199,8 +461,6 @@ int main(int argc, char* argv[]) {
? (PHASE1_TARGET_BLOCKS / EMISSION_ACCELERATION_FACTOR)
: 1;
// During phase 1, reward is deterministic from (supply,height), so always recompute.
// This avoids using stale on-disk cached rewards (e.g. floor reward after genesis).
if ((uint64_t)Chain_Size(chain) < effectivePhase1Blocks || currentReward == 0) {
currentReward = CalculateBlockReward(currentSupply, chain);
}
@@ -209,286 +469,292 @@ int main(int argc, char* argv[]) {
uint8_t dagSeed[32];
GetNextDAGSeed(chain, dagSeed);
(void)Block_RebuildAutolykos2Dag(CalculateTargetDAGSize(chain), dagSeed);
printf("Built initial DAG with seed %02x%02x%02x%02x... and size %zu bytes\n",
dagSeed[0], dagSeed[1], dagSeed[2], dagSeed[3],
CalculateTargetDAGSize(chain));
}
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);
fprintf(stderr, "loaded chain is invalid, wiping persisted state.\n");
WipeChainFiles(chainDataDir);
Chain_Wipe(chain);
BalanceSheet_Destroy();
BalanceSheet_Init();
currentSupply = uint256_from_u64(0);
difficultyTarget = INITIAL_DIFFICULTY;
currentReward = CalculateBlockReward(currentSupply, chain);
}
}
// Get flag from argv "-mine" to mine blocks
if (argc > 1 && strcmp(argv[1], "-mine") == 0) {
printf("Mining %llu blocks with target time %.0fs...\n", (unsigned long long)blocksToMine, targetSeconds);
uint8_t minerAddress[32];
uint8_t minerPrivateKey[32];
uint8_t minerCompressedPubkey[33];
if (!GenerateTestMinerIdentity(minerPrivateKey, minerCompressedPubkey, minerAddress)) {
fprintf(stderr, "failed to generate test miner keypair\n");
Chain_Destroy(chain);
Node_Destroy(node);
Block_ShutdownPowContext();
BalanceSheet_Destroy();
return 1;
}
uint8_t minerAddress[32];
uint8_t minerPrivateKey[32];
uint8_t minerCompressedPubkey[33];
if (!GenerateTestMinerIdentity(minerPrivateKey, minerCompressedPubkey, minerAddress)) {
fprintf(stderr, "failed to generate test miner keypair\n");
Chain_Destroy(chain);
Block_ShutdownPowContext();
BalanceSheet_Destroy();
return 1;
char minerAddressHex[65];
AddressToHexString(minerAddress, minerAddressHex);
printf("Test miner address: %s\n", minerAddressHex);
char supplyStr[80];
Uint256ToDecimal(&currentSupply, supplyStr, sizeof(supplyStr));
printf("Current chain has %zu blocks, total supply %s\n", Chain_Size(chain), supplyStr);
printf("Commands: mine <x>, send <address> <amount>, balance [address], flushchain, fullverify, wipechain, genaddr, exit\n");
char line[1024];
while (true) {
printf("> ");
fflush(stdout);
if (!fgets(line, sizeof(line), stdin)) {
break;
}
for (uint64_t mined = 0; mined < blocksToMine; ++mined) {
block_t* block = Block_Create();
line[strcspn(line, "\r\n")] = '\0';
if (line[0] == '\0') {
continue;
}
char* cmd = strtok(line, " \t");
if (!cmd) {
continue;
}
if (strcmp(cmd, "mine") == 0) {
char* blocksStr = strtok(NULL, " \t");
if (!blocksStr) {
printf("usage: mine <x>\n");
continue;
}
char* endptr = NULL;
unsigned long long requested = strtoull(blocksStr, &endptr, 10);
if (*blocksStr == '\0' || blocksStr[0] == '-' || (endptr && *endptr != '\0')) {
printf("invalid block count\n");
continue;
}
printf("Mining %llu block(s)...\n", requested);
bool minedAll = true;
for (unsigned long long i = 0; i < requested; ++i) {
block_t* block = BuildNextBlock(chain, difficultyTarget);
if (!block) {
fprintf(stderr, "failed to create block\n");
minedAll = false;
break;
}
AddCoinbaseTransaction(block, minerAddress, currentReward);
if (!MineAndAppendBlock(chain, block, &currentSupply, &currentReward, &difficultyTarget)) {
Block_Destroy(block);
minedAll = false;
break;
}
free(block); // Chain stores block by value and owns copied transaction array.
}
if (minedAll) {
(void)FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward);
printf("mine finished and chain flushed\n");
}
continue;
}
if (strcmp(cmd, "send") == 0) {
char* addressStr = strtok(NULL, " \t");
char* amountStr = strtok(NULL, " \t");
if (!addressStr || !amountStr) {
printf("usage: send <address> <amount>\n");
continue;
}
uint8_t recipientAddress[32];
if (!ParseHexAddress32(addressStr, recipientAddress)) {
printf("invalid address: expected 64 hex chars (optionally prefixed with 0x)\n");
continue;
}
char* endptr = NULL;
unsigned long long amount = strtoull(amountStr, &endptr, 10);
if (*amountStr == '\0' || amountStr[0] == '-' || (endptr && *endptr != '\0') || amount == 0) {
printf("invalid amount\n");
continue;
}
balance_sheet_entry_t senderEntry;
if (!BalanceSheet_Lookup(minerAddress, &senderEntry)) {
printf("send failed: miner address has no balance\n");
continue;
}
uint256_t spend = uint256_from_u64((uint64_t)amount);
if (uint256_cmp(&senderEntry.balance, &spend) < 0) {
printf("send failed: insufficient balance\n");
continue;
}
block_t* block = BuildNextBlock(chain, difficultyTarget);
if (!block) {
fprintf(stderr, "failed to create block\n");
Chain_Destroy(chain);
Block_ShutdownPowContext();
return 1;
continue;
}
block->header.version = 1;
block->header.blockNumber = (uint64_t)Chain_Size(chain);
if (Chain_Size(chain) > 0) {
if (!isFirstBlockOfLoadedChain) {
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 {
memcpy(block->header.prevHash, lastSavedHash, sizeof(lastSavedHash));
}
} else {
memset(block->header.prevHash, 0, sizeof(block->header.prevHash));
}
block->header.timestamp = (uint64_t)time(NULL);
block->header.difficultyTarget = difficultyTarget;
block->header.nonce = 0;
signed_transaction_t coinbaseTx;
Transaction_Init(&coinbaseTx);
coinbaseTx.transaction.version = 1;
coinbaseTx.transaction.amount1 = currentReward;
coinbaseTx.transaction.fee = 0;
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);
uint8_t merkleRoot[32];
Block_CalculateMerkleRoot(block, merkleRoot);
memcpy(block->header.merkleRoot, merkleRoot, sizeof(block->header.merkleRoot));
if (!MineBlock(block)) {
fprintf(stderr, "failed to mine block within nonce range\n");
Block_Destroy(block);
Chain_Destroy(chain);
Block_ShutdownPowContext();
return 1;
}
if (!Chain_AddBlock(chain, block)) {
fprintf(stderr, "failed to append block to chain\n");
Block_Destroy(block);
Chain_Destroy(chain);
Block_ShutdownPowContext();
return 1;
}
(void)uint256_add_u64(&currentSupply, coinbaseTx.transaction.amount1);
char supplyStr[80];
Uint256ToDecimal(&currentSupply, supplyStr, sizeof(supplyStr));
uint8_t canonicalHash[32];
uint8_t powHash[32];
Block_CalculateHash(block, canonicalHash);
Block_CalculateAutolykos2Hash(block, powHash);
printf("Mined block %llu/%llu (height=%llu) nonce=%llu reward=%llu supply=%s diff=%#x merkle=%02x%02x%02x%02x... pow=%02x%02x%02x%02x... canonical=%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.amount1,
supplyStr,
(unsigned int)block->header.difficultyTarget,
block->header.merkleRoot[0], block->header.merkleRoot[1], block->header.merkleRoot[2], block->header.merkleRoot[3],
powHash[0], powHash[1], powHash[2], powHash[3],
canonicalHash[0], canonicalHash[1], canonicalHash[2], canonicalHash[3]);
free(block); // chain stores blocks by value; transactions are owned by chain copy
currentReward = CalculateBlockReward(currentSupply, chain); // Update the global currentReward for the next block
// 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
// Persist the reward for the *next* block so restart behavior is correct.
printf("Saving chain at height %zu...\n", Chain_Size(chain));
Chain_SaveToFile(chain, chainDataDir, currentSupply, currentReward);
if (Chain_Size(chain) % DIFFICULTY_ADJUSTMENT_INTERVAL == 0) {
difficultyTarget = Chain_ComputeNextTarget(chain, difficultyTarget);
}
if (Chain_Size(chain) % EPOCH_LENGTH == 0 && Chain_Size(chain) > 0) {
uint8_t dagSeed[32];
GetNextDAGSeed(chain, dagSeed);
(void)Block_RebuildAutolykos2Dag(CalculateTargetDAGSize(chain), dagSeed);
}
isFirstBlockOfLoadedChain = false;
}
// Post-loop test: spend some coins from the miner address to a different address.
// This validates sender balance checks, transaction signing, merkle root generation,
// and PoW mining for a non-coinbase transaction.
signed_transaction_t spends[100];
for (int i = 0; i < 100; i++) {
int rng = rand() % 10; // Random amount between 0 and 9 (inclusive)
const uint64_t spendAmount = rng * DECIMALS;
uint8_t recipientAddress[32];
MakeTestRecipientAddress(recipientAddress);
AddCoinbaseTransaction(block, minerAddress, currentReward);
signed_transaction_t spendTx;
Transaction_Init(&spendTx);
spendTx.transaction.version = 1;
spendTx.transaction.fee = 0;
spendTx.transaction.amount1 = spendAmount;
spendTx.transaction.amount1 = (uint64_t)amount;
spendTx.transaction.amount2 = 0;
memcpy(spendTx.transaction.senderAddress, minerAddress, sizeof(minerAddress));
memcpy(spendTx.transaction.recipientAddress1, recipientAddress, sizeof(recipientAddress));
memset(spendTx.transaction.recipientAddress2, 0, sizeof(spendTx.transaction.recipientAddress2));
memcpy(spendTx.transaction.compressedPublicKey, minerCompressedPubkey, sizeof(minerCompressedPubkey));
Transaction_Sign(&spendTx, minerPrivateKey);
spends[i] = spendTx;
Block_AddTransaction(block, &spendTx);
if (!MineAndAppendBlock(chain, block, &currentSupply, &currentReward, &difficultyTarget)) {
Block_Destroy(block);
continue;
}
free(block);
printf("send committed in mined block\n");
continue;
}
block_t* spendBlock = Block_Create();
if (!spendBlock) {
fprintf(stderr, "failed to create test spend block\n");
Chain_Destroy(chain);
Block_ShutdownPowContext();
BalanceSheet_Destroy();
return 1;
}
if (strcmp(cmd, "balance") == 0) {
char* addressStr = strtok(NULL, " \t");
char* extra = strtok(NULL, " \t");
if (extra) {
printf("usage: balance [address]\n");
continue;
}
spendBlock->header.version = 1;
spendBlock->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, spendBlock->header.prevHash);
uint8_t queryAddress[32];
uint8_t* effectiveAddress = minerAddress;
if (addressStr) {
if (!ParseHexAddress32(addressStr, queryAddress)) {
printf("invalid address: expected 64 hex chars (optionally prefixed with 0x)\n");
continue;
}
effectiveAddress = queryAddress;
}
balance_sheet_entry_t entry;
char balanceStr[80];
if (!BalanceSheet_Lookup(effectiveAddress, &entry)) {
uint256_t zero = uint256_from_u64(0);
Uint256ToDecimal(&zero, balanceStr, sizeof(balanceStr));
} else {
memset(spendBlock->header.prevHash, 0, sizeof(spendBlock->header.prevHash));
Uint256ToDecimal(&entry.balance, balanceStr, sizeof(balanceStr));
}
} else {
memset(spendBlock->header.prevHash, 0, sizeof(spendBlock->header.prevHash));
}
spendBlock->header.timestamp = (uint64_t)time(NULL);
spendBlock->header.difficultyTarget = difficultyTarget;
spendBlock->header.nonce = 0;
signed_transaction_t testCoinbaseTx;
Transaction_Init(&testCoinbaseTx);
memset(&testCoinbaseTx, 0, sizeof(testCoinbaseTx));
testCoinbaseTx.transaction.version = 1;
testCoinbaseTx.transaction.amount1 = currentReward;
testCoinbaseTx.transaction.fee = 0;
memcpy(testCoinbaseTx.transaction.recipientAddress1, minerAddress, sizeof(minerAddress));
testCoinbaseTx.transaction.recipientAddress2[0] = 0;
testCoinbaseTx.transaction.amount2 = 0;
memset(testCoinbaseTx.transaction.compressedPublicKey, 0, sizeof(testCoinbaseTx.transaction.compressedPublicKey));
memset(testCoinbaseTx.transaction.senderAddress, 0xFF, sizeof(testCoinbaseTx.transaction.senderAddress));
Block_AddTransaction(spendBlock, &testCoinbaseTx);
for (int i = 0; i < 100; i++) {
Block_AddTransaction(spendBlock, &spends[i]);
char addrHex[65];
AddressToHexString(effectiveAddress, addrHex);
printf("Balance %s: %s unit(s)\n", addrHex, balanceStr);
continue;
}
uint8_t merkleRoot[32];
Block_CalculateMerkleRoot(spendBlock, merkleRoot);
memcpy(spendBlock->header.merkleRoot, merkleRoot, sizeof(spendBlock->header.merkleRoot));
if (!MineBlock(spendBlock)) {
fprintf(stderr, "failed to mine test spend block\n");
Block_Destroy(spendBlock);
Chain_Destroy(chain);
Block_ShutdownPowContext();
BalanceSheet_Destroy();
return 1;
}
if (!Chain_AddBlock(chain, spendBlock)) {
fprintf(stderr, "failed to append test spend block to chain\n");
Block_Destroy(spendBlock);
Chain_Destroy(chain);
Block_ShutdownPowContext();
BalanceSheet_Destroy();
return 1;
}
(void)uint256_add_u64(&currentSupply, testCoinbaseTx.transaction.amount1);
currentReward = CalculateBlockReward(currentSupply, chain);
//printf("Mined test spend block (height=%llu) sending %llu base units to a new address\n",
// (unsigned long long)spendBlock->header.blockNumber,
// (unsigned long long)spendAmount);
free(spendBlock);
bool chainSaved = Chain_SaveToFile(chain, chainDataDir, currentSupply, currentReward);
bool sheetSaved = BalanceSheet_SaveToFile(chainDataDir);
if (!chainSaved || !sheetSaved) {
if (!chainSaved) {
fprintf(stderr, "failed to save chain to %s\n", chainDataDir);
if (strcmp(cmd, "flushchain") == 0) {
if (FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward)) {
printf("chain flushed\n");
}
if (!sheetSaved) {
fprintf(stderr, "failed to save balance sheet to %s\n", chainDataDir);
continue;
}
if (strcmp(cmd, "fullverify") == 0) {
blockchain_t* verifyChain = Chain_Create();
if (!verifyChain) {
printf("Chain Not OK\n");
continue;
}
} else {
char supplyStr[80];
Uint256ToDecimal(&currentSupply, supplyStr, sizeof(supplyStr));
printf("Saved chain with %zu blocks to %s (supply=%s)\n",
Chain_Size(chain),
uint256_t verifySupply = uint256_from_u64(0);
uint32_t verifyDifficulty = INITIAL_DIFFICULTY;
uint64_t verifyReward = 0;
uint8_t verifyLastHash[32] = {0};
bool loaded = Chain_LoadFromFile(
verifyChain,
chainDataDir,
supplyStr);
&verifySupply,
&verifyDifficulty,
&verifyReward,
verifyLastHash,
true
);
bool ok = false;
if (loaded) {
uint8_t dagSeed[32];
GetNextDAGSeed(verifyChain, dagSeed);
(void)Block_RebuildAutolykos2Dag(CalculateTargetDAGSize(verifyChain), dagSeed);
ok = VerifyChainFully(verifyChain);
}
printf("%s\n", ok ? "Chain OK" : "Chain Not OK");
Chain_Destroy(verifyChain);
continue;
}
} else {
char supplyStr[80];
Uint256ToDecimal(&currentSupply, supplyStr, sizeof(supplyStr));
printf("Current chain has %zu blocks, total supply %s\n", Chain_Size(chain), supplyStr);
if (strcmp(cmd, "wipechain") == 0) {
WipeChainFiles(chainDataDir);
Chain_Wipe(chain);
BalanceSheet_Destroy();
BalanceSheet_Init();
currentSupply = uint256_from_u64(0);
difficultyTarget = INITIAL_DIFFICULTY;
currentReward = CalculateBlockReward(currentSupply, chain);
uint8_t dagSeed[32];
memset(dagSeed, DAG_GENESIS_SEED, sizeof(dagSeed));
(void)Block_RebuildAutolykos2Dag(DAG_BASE_SIZE, dagSeed);
printf("chain data wiped\n");
continue;
}
if (strcmp(cmd, "genaddr") == 0) {
uint8_t testAddress[32];
if (!GenerateRandomTestAddress(testAddress)) {
printf("failed to generate address\n");
continue;
}
char addrHex[65];
AddressToHexString(testAddress, addrHex);
printf("%s\n", addrHex);
continue;
}
if (strcmp(cmd, "exit") == 0 || strcmp(cmd, "quit") == 0) {
break;
}
printf("Unknown command. Available: mine, send, balance, flushchain, fullverify, wipechain, genaddr, exit\n");
}
// Print chain
/*for (size_t i = 0; i < Chain_Size(chain); i++) {
block_t* blk = Chain_GetBlock(chain, i);
if (blk) {
Block_Print(blk);
}
}*/
BalanceSheet_Print();
if (!BalanceSheet_SaveToFile(chainDataDir)) {
fprintf(stderr, "failed to save balance sheet to %s\n", chainDataDir);
}
(void)FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward);
Chain_Destroy(chain);
Block_ShutdownPowContext();
Node_Destroy(node);
BalanceSheet_Destroy();
return 0;
}