TCP Node boilerplate; CLI interface
This commit is contained in:
778
src/main.c
778
src/main.c
@@ -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, ¤tSupply, &difficultyTarget, ¤tReward, lastSavedHash)) {
|
||||
uint8_t lastSavedHash[32] = {0};
|
||||
if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply, &difficultyTarget, ¤tReward, 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(¤tSupply, 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, ¤tSupply, ¤tReward, &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(¤tSupply, coinbaseTx.transaction.amount1);
|
||||
char supplyStr[80];
|
||||
Uint256ToDecimal(¤tSupply, 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, ¤tSupply, ¤tReward, &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(¤tSupply, 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(¤tSupply, 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(¤tSupply, 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user