diff --git a/CMakeLists.txt b/CMakeLists.txt index 36bf31d..6a17868 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,21 +160,20 @@ if(SKALACOIN_ENABLE_AUTOLYKOS2_REF) ) endif() if(TARGET CURL::libcurl) - set(_AUTOLYKOS2_CURL_LIB CURL::libcurl) + target_link_libraries(autolykos2_ref PRIVATE + ${CMAKE_THREAD_LIBS_INIT} + OpenSSL::SSL + OpenSSL::Crypto + CURL::libcurl + ) elseif(DEFINED CURL_LIBRARIES AND CURL_LIBRARIES) - set(_AUTOLYKOS2_CURL_LIB ${CURL_LIBRARIES}) + target_link_libraries(autolykos2_ref PRIVATE + ${CMAKE_THREAD_LIBS_INIT} + OpenSSL::SSL + OpenSSL::Crypto + ${CURL_LIBRARIES} + ) else() - set(_AUTOLYKOS2_CURL_LIB "") - endif() - - target_link_libraries(autolykos2_ref PRIVATE - ${CMAKE_THREAD_LIBS_INIT} - OpenSSL::SSL - OpenSSL::Crypto - $<$:$_AUTOLYKOS2_CURL_LIB> - ) - - if(NOT _AUTOLYKOS2_CURL_LIB) message(FATAL_ERROR "autolykos2_ref requires libcurl (curl/curl.h). Install libcurl devel package or allow FetchContent to build it.") endif() set(SKALACOIN_AUTOLYKOS2_REF_AVAILABLE ON) diff --git a/include/block/block.h b/include/block/block.h index 54b0a91..0764016 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -36,6 +36,7 @@ void Block_AddTransaction(block_t* block, signed_transaction_t* tx); void Block_RemoveTransaction(block_t* block, uint8_t* txHash); bool Block_HasValidProofOfWork(const block_t* block); bool Block_AllTransactionsValid(const block_t* block); +bool Block_IsFullyValid(const block_t* block); void Block_ShutdownPowContext(void); void Block_Destroy(block_t* block); void Block_Print(const block_t* block); diff --git a/include/constants.h b/include/constants.h index 6e7405b..84cce4c 100644 --- a/include/constants.h +++ b/include/constants.h @@ -7,7 +7,7 @@ #include #include -extern uint64_t currentBlockHeight; +#include // Nets #define MAX_CONS 32 // Some baseline for now @@ -56,11 +56,8 @@ extern uint64_t currentBlockHeight; static const uint64_t M_CAP = 18446744073709551615ULL; // Max uint64 static const uint64_t TAIL_EMISSION = 750000000000ULL; // 0.75 coins per block floor -static uint64_t currentReward = 750000000000ULL; // Epoch reward cache for phase 3 // No max supply. Instead of halving, it'll follow a more gradual, Monero-like emission curve. -static uint256_t currentSupply = {{0, 0, 0, 0}}; // Global variable to track total supply; updated with each block mined - // Phase 3: update once per effective epoch and keep a fixed per-block reward for that epoch. static inline uint64_t GetInflationRateReward(uint256_t currentSupply, blockchain_t* chain) { if (!chain || !chain->blocks) { return 0x00; } // Invalid diff --git a/include/nets/net_node.h b/include/nets/net_node.h index 29a1e43..8e13706 100644 --- a/include/nets/net_node.h +++ b/include/nets/net_node.h @@ -15,6 +15,10 @@ #include +#include +#include +#include + typedef struct { tcp_server_t* server; tcp_client_t outboundClients[MAX_CONS]; diff --git a/include/runtime_state.h b/include/runtime_state.h new file mode 100644 index 0000000..db9decc --- /dev/null +++ b/include/runtime_state.h @@ -0,0 +1,17 @@ +#ifndef RUNTIME_STATE_H +#define RUNTIME_STATE_H + +#include + +#include + +#include + +extern uint64_t currentBlockHeight; +extern blockchain_t* currentChain; +extern uint256_t currentSupply; +extern uint64_t currentReward; +extern uint32_t difficultyTarget; +extern const char* chainDataDir; + +#endif diff --git a/src/block/block.c b/src/block/block.c index dc547c5..6bd817e 100644 --- a/src/block/block.c +++ b/src/block/block.c @@ -231,6 +231,17 @@ bool Block_AllTransactionsValid(const block_t* block) { return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase) } +bool Block_IsFullyValid(const block_t* block) { + bool merkleValid = false; + uint8_t calculatedMerkleRoot[32]; + if (block && block->transactions) { + Block_CalculateMerkleRoot(block, calculatedMerkleRoot); + merkleValid = (memcmp(calculatedMerkleRoot, block->header.merkleRoot, 32) == 0); + } + + return Block_HasValidProofOfWork(block) && Block_AllTransactionsValid(block) && DynArr_size(block->transactions) > 0 && merkleValid; +} + void Block_Destroy(block_t* block) { if (!block) return; DynArr_destroy(block->transactions); diff --git a/src/block/chain.c b/src/block/chain.c index f1da979..009190b 100644 --- a/src/block/chain.c +++ b/src/block/chain.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/src/main.c b/src/main.c index de99ab3..040c090 100644 --- a/src/main.c +++ b/src/main.c @@ -13,6 +13,7 @@ #include +#include #include #include @@ -21,6 +22,11 @@ #define CHAIN_DATA_DIR "chain_data" #endif +blockchain_t* currentChain = NULL; +const char* chainDataDir = CHAIN_DATA_DIR; +uint256_t currentSupply = {{0, 0, 0, 0}}; +uint64_t currentReward = 750000000000ULL; + void handle_sigint(int sig) { printf("Caught signal %d, exiting...\n", sig); Block_ShutdownPowContext(); @@ -30,9 +36,6 @@ void handle_sigint(int sig) { uint32_t difficultyTarget = INITIAL_DIFFICULTY; -// extern the currentReward from constants.h so we can update it as we mine blocks and save it to disk -extern uint64_t currentReward; - static bool MineBlock(block_t* block) { if (!block) { return false; @@ -471,20 +474,20 @@ int main(int argc, char* argv[]) { srand((unsigned int)time(NULL)); BalanceSheet_Init(); - const char* chainDataDir = CHAIN_DATA_DIR; - - uint256_t currentSupply = uint256_from_u64(0); - - net_node_t* node = Node_Create(); - if (!node) { - BalanceSheet_Destroy(); - return 1; - } blockchain_t* chain = Chain_Create(); if (!chain) { fprintf(stderr, "failed to create chain\n"); - Node_Destroy(node); + BalanceSheet_Destroy(); + return 1; + } + + currentChain = chain; + + net_node_t* node = Node_Create(); + if (!node) { + currentChain = NULL; + Chain_Destroy(chain); BalanceSheet_Destroy(); return 1; } @@ -536,8 +539,9 @@ int main(int argc, char* argv[]) { uint8_t minerCompressedPubkey[33]; if (!GenerateTestMinerIdentity(minerPrivateKey, minerCompressedPubkey, minerAddress)) { fprintf(stderr, "failed to generate test miner keypair\n"); - Chain_Destroy(chain); Node_Destroy(node); + currentChain = NULL; + Chain_Destroy(chain); Block_ShutdownPowContext(); BalanceSheet_Destroy(); return 1; @@ -867,9 +871,10 @@ int main(int argc, char* argv[]) { (void)FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward); - Chain_Destroy(chain); Block_ShutdownPowContext(); Node_Destroy(node); + currentChain = NULL; + Chain_Destroy(chain); BalanceSheet_Destroy(); return 0; diff --git a/src/nets/net_node.c b/src/nets/net_node.c index 4121427..8cd1fc6 100644 --- a/src/nets/net_node.c +++ b/src/nets/net_node.c @@ -4,6 +4,8 @@ #include #include +#include + static net_node_t* Node_FromConnection(tcp_connection_t* conn) { if (!conn) { return NULL; @@ -12,6 +14,14 @@ static net_node_t* Node_FromConnection(tcp_connection_t* conn) { return (net_node_t*)conn->owner; } +static uint64_t Node_GetCurrentBlockHeight(void) { + if (currentChain) { + return (uint64_t)Chain_Size(currentChain); + } + + return currentBlockHeight; +} + static int Node_DecodePacket(const tcp_connection_t* conn, packet_type_t* outType, const unsigned char** outPayload, size_t* outPayloadLen) { if (!conn || !outType || !outPayload || !outPayloadLen || conn->dataBufLen < 1 || !conn->dataBuf) { return -1; @@ -224,8 +234,9 @@ void Node_Server_OnData(tcp_connection_t* client) { size_t ackOffset = 0; memcpy(ackData + ackOffset, &protoVersion, sizeof(protoVersion)); ackOffset += sizeof(protoVersion); - memcpy(ackData + ackOffset, ¤tBlockHeight, sizeof(currentBlockHeight)); - ackOffset += sizeof(currentBlockHeight); + uint64_t currentHeight = Node_GetCurrentBlockHeight(); + memcpy(ackData + ackOffset, ¤tHeight, sizeof(currentHeight)); + ackOffset += sizeof(currentHeight); Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ACK_HELLO, ackData, ackOffset); @@ -241,9 +252,86 @@ void Node_Server_OnData(tcp_connection_t* client) { TcpConnection_RequestClose(client); return; } - case PACKET_TYPE_FETCH_BLOCK: - case PACKET_TYPE_BLOCK_DATA: - case PACKET_TYPE_BROADCAST_BLOCK: + case PACKET_TYPE_FETCH_BLOCK: { + // Decode FETCH_BLOCK - payload is the block height as uint64_t + if (payloadLen != sizeof(uint64_t)) { + return; + } + + uint64_t requestedHeight; + memcpy(&requestedHeight, payload, sizeof(requestedHeight)); + + printf("Received FETCH_BLOCK for height %" PRIu64 " from node %u\n", requestedHeight, client ? client->connectionId : 0U); + if (requestedHeight > Node_GetCurrentBlockHeight()) { + printf("Requested block height %" PRIu64 " is higher than current height, ignoring\n", requestedHeight); + + // Error the client, but don't kill + const char* msg = "Requested block height is higher than my current height!"; + Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ERROR, msg, strlen(msg)); + + return; + } + + // Find the block + block_t* block = Chain_GetBlock(currentChain, (size_t)requestedHeight); + if (!block) { + printf("Requested block height %" PRIu64 " not found, ignoring\n", requestedHeight); + const char* msg = "Requested block not found!"; + Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ERROR, msg, strlen(msg)); + return; + } + + if (block->transactions == NULL) { + // Just reload from disk pure + Chain_LoadBlockFromFile(chainDataDir, requestedHeight - 1, true, &block, NULL); // blockNumber = height - 1 because of 0-indexing + } + + // Serialize into a BLOCK_DATA packet [block header][tx count - 8 bytes][transactions...] + size_t blockDataSize = sizeof(block_header_t) + sizeof(uint64_t) + (block->transactions ? block->transactions->size * sizeof(signed_transaction_t) : 0); + unsigned char* blockData = (unsigned char*)malloc(blockDataSize); + if (!blockData) { + // Generic error response + printf("Failed to allocate memory for block data response to node %u\n", client ? client->connectionId : 0U); + const char* msg = "Generic error for block data!"; + Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ERROR, msg, strlen(msg)); + return; + } + + size_t offset = 0; + memcpy(blockData + offset, &block->header, sizeof(block_header_t)); + offset += sizeof(block_header_t); + uint64_t txCount = block->transactions ? (uint64_t)block->transactions->size : 0; + memcpy(blockData + offset, &txCount, sizeof(txCount)); + offset += sizeof(txCount); + if (block->transactions && block->transactions->size > 0) { + memcpy(blockData + offset, block->transactions->data, block->transactions->size * sizeof(signed_transaction_t)); + offset += block->transactions->size * sizeof(signed_transaction_t); + } + + // Send the block data + Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_BLOCK_DATA, blockData, offset); + free(blockData); + } + case PACKET_TYPE_BLOCK_DATA: { + // Server can't receive these! + printf("Received unexpected packet type %u from node %u\n", (unsigned int)packetType, client ? client->connectionId : 0U); + + // Send the error and kill the connection + const char* msg = "You can't send me BLOCK_DATA! I'm a server!"; + Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ERROR, msg, strlen(msg)); + TcpConnection_RequestClose(client); + return; + } + case PACKET_TYPE_BROADCAST_BLOCK: { + // Server cannot receive these either. + printf("Received unexpected BROADCAST_BLOCK packet from node %u\n", client ? client->connectionId : 0U); + + // Send the error and kill the connection + const char* msg = "You can't send me BROADCAST_BLOCK! I'm a server!"; + Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ERROR, msg, strlen(msg)); + TcpConnection_RequestClose(client); + return; + } case PACKET_TYPE_ACK_BLOCK: case PACKET_TYPE_BROADCAST_TX: case PACKET_TYPE_ACK_TX: @@ -287,7 +375,7 @@ void Node_Client_OnConnect(tcp_connection_t* client) { size_t offset = 0; uint32_t protoVersion = 1; // little-endian - uint64_t blockHeight = currentBlockHeight; + uint64_t blockHeight = Node_GetCurrentBlockHeight(); memcpy((unsigned char*)data + offset, &protoVersion, sizeof(protoVersion)); // This is technically "unsafe", but I honestly just don't give a shit at this point offset += sizeof(protoVersion); memcpy((unsigned char*)data + offset, &blockHeight, sizeof(blockHeight)); @@ -331,9 +419,73 @@ void Node_Client_OnData(tcp_connection_t* client) { printf("Received ACK_HELLO from node %u with protoVersion %u and blockHeight %lu\n", client ? client->connectionId : 0U, protoVersion, (unsigned long)blockHeight); break; } - case PACKET_TYPE_FETCH_BLOCK: - case PACKET_TYPE_BLOCK_DATA: - case PACKET_TYPE_BROADCAST_BLOCK: + case PACKET_TYPE_FETCH_BLOCK: { + // A client can't serve a block! + printf("Received unexpected FETCH_BLOCK packet from node %u\n", client ? client->connectionId : 0U); + + // Send the error and kill the connection (this might be too aggressive) + const char* msg = "You can't FETCH_BLOCK from me! I'm a client!"; + Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ERROR, msg, strlen(msg)); + TcpConnection_RequestClose(client); + return; + } + case PACKET_TYPE_BLOCK_DATA: { + // TODO + break; + } + case PACKET_TYPE_BROADCAST_BLOCK: { + // TODO: Handle based on the current block height of the node; if for n + 1, ignore it for now, since we're probably syncing. + // If higher than n + 1, request missing blocks. + // We just assume n + 1 for now. + + // Decode - [1 byte packet type][8 byte block height][block header][8 byte transation count][remaining bytes block data]; TODO: This is just for v1 transactions right now. + if (payloadLen < 1 + sizeof(uint64_t) + sizeof(uint64_t)) { + return; + } + + char* ptr = (char*)payload; + ptr += 1; // skip packet type, we already know it + + uint64_t blockHeight; + memcpy(&blockHeight, ptr, sizeof(blockHeight)); + ptr += sizeof(blockHeight); + + block_t blockHeader; + memcpy(&blockHeader, ptr, sizeof(blockHeader)); + ptr += sizeof(blockHeader); + + uint64_t txCount; + memcpy(&txCount, ptr, sizeof(txCount)); + ptr += sizeof(txCount); + + DynArr* transactions = DYNARR_CREATE(sizeof(signed_transaction_t), 0); + for (uint64_t i = 0; i < txCount; ++i) { + if (ptr + sizeof(signed_transaction_t) > (char*)payload + payloadLen) { + // Malformed packet - TODO: Error the sender + DynArr_destroy(transactions); + return; + } + signed_transaction_t tx; + memcpy(&tx, ptr, sizeof(tx)); + ptr += sizeof(tx); + DynArr_push_back(transactions, &tx); + } + + blockHeader.transactions = transactions; + + // Verify + if (!Block_IsFullyValid(&blockHeader)) { + // Invalid block + DynArr_destroy(transactions); + return; + } + + // Push to chain - TODO: Handle orphans, reorgs, etc. + if (currentChain) { + Chain_AddBlock(currentChain, &blockHeader); + Chain_SaveToFile(currentChain, chainDataDir, currentSupply, currentReward); // Note: this destroy the transactions inside the block automatically, so we don't have to worry about that here. + } + } case PACKET_TYPE_ACK_BLOCK: case PACKET_TYPE_BROADCAST_TX: case PACKET_TYPE_ACK_TX: