Copied TCP impl from other project, basic Block implementation, randomx pow, signing via secp256k1
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
Makefile
|
||||
svrtest
|
||||
main
|
||||
CMakeFiles/
|
||||
.vscode/
|
||||
build/
|
||||
.DS_Store
|
||||
128
CMakeLists.txt
Normal file
128
CMakeLists.txt
Normal file
@@ -0,0 +1,128 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(miniboinc
|
||||
VERSION 0.1.0
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
set(CMAKE_C_STANDARD 23)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# OpenSSL
|
||||
find_package(OpenSSL QUIET)
|
||||
if(NOT OpenSSL_FOUND)
|
||||
if(APPLE)
|
||||
execute_process(
|
||||
COMMAND brew --prefix openssl@3
|
||||
OUTPUT_VARIABLE HOMEBREW_OPENSSL_PREFIX
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(HOMEBREW_OPENSSL_PREFIX)
|
||||
set(OPENSSL_ROOT_DIR "${HOMEBREW_OPENSSL_PREFIX}")
|
||||
endif()
|
||||
endif()
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
|
||||
# secp256k1 (Bitcoin Core library)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(SECP256K1 QUIET IMPORTED_TARGET libsecp256k1)
|
||||
endif()
|
||||
|
||||
if(NOT SECP256K1_FOUND)
|
||||
find_path(SECP256K1_INCLUDE_DIR NAMES secp256k1.h)
|
||||
find_library(SECP256K1_LIBRARY NAMES secp256k1)
|
||||
if(NOT SECP256K1_INCLUDE_DIR OR NOT SECP256K1_LIBRARY)
|
||||
message(FATAL_ERROR "secp256k1 not found. Install with: brew install secp256k1")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# RandomX
|
||||
set(RANDOMX_ROOT "" CACHE PATH "Root directory of a RandomX installation")
|
||||
set(RANDOMX_USING_FETCHCONTENT OFF)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(RANDOMX QUIET IMPORTED_TARGET randomx)
|
||||
endif()
|
||||
|
||||
if(NOT RANDOMX_FOUND)
|
||||
find_path(RANDOMX_INCLUDE_DIR
|
||||
NAMES randomx.h
|
||||
HINTS ${RANDOMX_ROOT}
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
find_library(RANDOMX_LIBRARY
|
||||
NAMES randomx
|
||||
HINTS ${RANDOMX_ROOT}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
|
||||
if(NOT RANDOMX_INCLUDE_DIR OR NOT RANDOMX_LIBRARY)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
randomx_src
|
||||
GIT_REPOSITORY https://github.com/tevador/RandomX.git
|
||||
GIT_TAG master
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(randomx_src)
|
||||
set(RANDOMX_USING_FETCHCONTENT ON)
|
||||
set(RANDOMX_INCLUDE_DIR ${randomx_src_SOURCE_DIR}/src)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Output directories
|
||||
# ---------------------------------------------------------
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
|
||||
foreach(OUTPUTCONFIG DEBUG RELEASE RELWITHDEBINFO MINSIZEREL)
|
||||
string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG_UPPER)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/lib)
|
||||
endforeach()
|
||||
|
||||
# Node
|
||||
file(GLOB_RECURSE NODE_SRC CONFIGURE_DEPENDS src/*.c)
|
||||
add_executable(node ${NODE_SRC})
|
||||
target_link_libraries(node PRIVATE
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
)
|
||||
|
||||
if(SECP256K1_FOUND)
|
||||
target_link_libraries(node PRIVATE PkgConfig::SECP256K1)
|
||||
else()
|
||||
target_include_directories(node PRIVATE ${SECP256K1_INCLUDE_DIR})
|
||||
target_link_libraries(node PRIVATE ${SECP256K1_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(RANDOMX_FOUND)
|
||||
target_link_libraries(node PRIVATE PkgConfig::RANDOMX)
|
||||
elseif(RANDOMX_USING_FETCHCONTENT)
|
||||
target_link_libraries(node PRIVATE randomx)
|
||||
target_include_directories(node PRIVATE ${RANDOMX_INCLUDE_DIR})
|
||||
else()
|
||||
target_include_directories(node PRIVATE ${RANDOMX_INCLUDE_DIR})
|
||||
target_link_libraries(node PRIVATE ${RANDOMX_LIBRARY})
|
||||
endif()
|
||||
|
||||
target_include_directories(node PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_compile_options(node PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
-g
|
||||
)
|
||||
target_compile_definitions(node PRIVATE)
|
||||
set_target_properties(node PROPERTIES OUTPUT_NAME "minicoin_node")
|
||||
8
README.md
Normal file
8
README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# MiniCoin
|
||||
|
||||
Basic blockchain currency implementation in C, for educational purposes. Not intended for production use.
|
||||
|
||||
cmake:
|
||||
```bash
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
|
||||
```
|
||||
1
TODO.txt
Normal file
1
TODO.txt
Normal file
@@ -0,0 +1 @@
|
||||
Eventually move the Proof of Work to RandomX. Keep the SHA-256 for Block IDs, TX IDs, etc. But nonce and mining should be RandomX. This will make it more resistant to ASICs and more accessible for CPU mining.
|
||||
11
include/balance_sheet.h
Normal file
11
include/balance_sheet.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef BALANCE_SHEET_H
|
||||
#define BALANCE_SHEET_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t address[32]; // For now just the SHA-256 of the public key; allows representation in different encodings (base58, bech32, etc) without changing the underlying data structure
|
||||
uint64_t balance;
|
||||
} balance_sheet_entry_t;
|
||||
|
||||
#endif
|
||||
36
include/block/block.h
Normal file
36
include/block/block.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef BLOCK_H
|
||||
#define BLOCK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <dynarr.h>
|
||||
#include <block/transaction.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <randomx/librx_wrapper.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t version;
|
||||
uint32_t blockNumber;
|
||||
uint8_t prevHash[32];
|
||||
uint8_t merkleRoot[32];
|
||||
uint64_t timestamp;
|
||||
uint32_t difficultyTarget; // Encoding: [1 byte exponent][3 byte coefficient]; Target = coefficient * 256^(exponent-3)
|
||||
uint64_t nonce; // Higher nonce for RandomX
|
||||
} block_header_t;
|
||||
|
||||
typedef struct {
|
||||
block_header_t header;
|
||||
DynArr* transactions; // Array of signed_transaction_t
|
||||
} block_t;
|
||||
|
||||
block_t* Block_Create();
|
||||
void Block_CalculateHash(const block_t* block, uint8_t* outHash);
|
||||
void Block_CalculateRandomXHash(const block_t* block, uint8_t* outHash);
|
||||
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);
|
||||
void Block_Destroy(block_t* block);
|
||||
|
||||
#endif
|
||||
20
include/block/chain.h
Normal file
20
include/block/chain.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef CHAIN_H
|
||||
#define CHAIN_H
|
||||
|
||||
#include <block/block.h>
|
||||
#include <dynarr.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
DynArr* blocks;
|
||||
size_t size;
|
||||
} blockchain_t;
|
||||
|
||||
blockchain_t* Chain_Create();
|
||||
void Chain_Destroy(blockchain_t* chain);
|
||||
bool Chain_AddBlock(blockchain_t* chain, block_t* block);
|
||||
block_t* Chain_GetBlock(blockchain_t* chain, size_t index);
|
||||
size_t Chain_Size(blockchain_t* chain);
|
||||
bool Chain_IsValid(blockchain_t* chain);
|
||||
|
||||
#endif
|
||||
48
include/block/transaction.h
Normal file
48
include/block/transaction.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef TRANSACTION_H
|
||||
#define TRANSACTION_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <crypto/crypto.h>
|
||||
|
||||
// Special sender/recipient address marker for coinbase logic: 32 bytes of 0xFF.
|
||||
static inline bool Address_IsCoinbase(const uint8_t address[32]) {
|
||||
if (!address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
if (address[i] != 0xFF) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 178 bytes total for v1
|
||||
typedef struct {
|
||||
uint8_t version;
|
||||
uint8_t senderAddress[32];
|
||||
uint8_t recipientAddress[32];
|
||||
uint64_t amount;
|
||||
uint64_t fee; // Rewarded to the miner; can be zero, but the miner may choose to ignore transactions with very low fees
|
||||
uint8_t compressedPublicKey[33];
|
||||
// Timestamp is dictated by the block
|
||||
} transaction_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t txHash[32];
|
||||
uint8_t signature[64]; // Signature of the hash
|
||||
} transaction_sig_t;
|
||||
|
||||
typedef struct {
|
||||
transaction_t transaction;
|
||||
transaction_sig_t signature;
|
||||
} signed_transaction_t;
|
||||
|
||||
void Transaction_CalculateHash(const signed_transaction_t* tx, uint8_t* outHash);
|
||||
void Transaction_Sign(signed_transaction_t* tx, const uint8_t* privateKey);
|
||||
bool Transaction_Verify(const signed_transaction_t* tx);
|
||||
|
||||
#endif
|
||||
13
include/crypto/crypto.h
Normal file
13
include/crypto/crypto.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef CRYPTO_H
|
||||
#define CRYPTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <secp256k1.h>
|
||||
|
||||
bool Crypto_VerifySignature(const uint8_t* data, size_t len, const uint8_t* signature, const uint8_t* publicKey);
|
||||
void Crypto_SignData(const uint8_t* data, size_t len, const uint8_t* privateKey, uint8_t* outSignature);
|
||||
|
||||
#endif
|
||||
60
include/dynarr.h
Normal file
60
include/dynarr.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef DYNARR_H
|
||||
#define DYNARR_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DYNARR_MAX_CAPACITY ((size_t)0x7FFFFFFF)
|
||||
|
||||
typedef struct {
|
||||
size_t size;
|
||||
size_t elemSize;
|
||||
size_t capacity;
|
||||
void* data;
|
||||
} DynArr;
|
||||
|
||||
// Do not use; Use DYNARR_CREATE macro instead.
|
||||
DynArr* DynArr_create(size_t elemSize, size_t capacity);
|
||||
|
||||
// Reserve n blocks in arary; New size will be n, NOT size + n; Reserving less memory that current will fail, use prune instead.
|
||||
void DynArr_reserve(DynArr* p, size_t n);
|
||||
|
||||
// Push data into a new block at the end of the array
|
||||
void* DynArr_push_back(DynArr* p, void* value);
|
||||
|
||||
// Remove the last block in the array.
|
||||
void DynArr_pop_back(DynArr* p);
|
||||
|
||||
// Remove first block from array.
|
||||
void DynArr_pop_front(DynArr* p);
|
||||
|
||||
// Remove index from array. This moves all blocks after the index block.
|
||||
void DynArr_remove(DynArr* p, size_t index);
|
||||
|
||||
// Erase the array. This will not free unused blocks.
|
||||
void DynArr_erase(DynArr* p);
|
||||
|
||||
// Prune and free unused blocks. If pruning to zero, ensure to reserve after.
|
||||
void DynArr_prune(DynArr* p);
|
||||
|
||||
// Get a pointer to a block by index
|
||||
void* DynArr_at(DynArr* p, size_t index);
|
||||
|
||||
// Get the index by block pointer
|
||||
size_t DynArr_at_ptr(DynArr* p, void* ptr);
|
||||
|
||||
// Get size
|
||||
size_t DynArr_size(DynArr* p);
|
||||
|
||||
// Get element size
|
||||
size_t DynArr_elemSize(DynArr* p);
|
||||
|
||||
// Get capacity
|
||||
size_t DynArr_capacity(DynArr* p);
|
||||
|
||||
void DynArr_destroy(DynArr* p);
|
||||
|
||||
#define DYNARR_CREATE(T, initialCapacity) DynArr_create(sizeof(T), initialCapacity)
|
||||
|
||||
#endif
|
||||
20
include/dynset.h
Normal file
20
include/dynset.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef DYNSET_H
|
||||
#define DYNSET_H
|
||||
|
||||
#include <dynarr.h>
|
||||
|
||||
// Dynamic Set structure - basically DynArr with uniqueness enforced
|
||||
typedef struct {
|
||||
DynArr* arr;
|
||||
} DynSet;
|
||||
|
||||
// Function prototypes
|
||||
DynSet* DynSet_Create(size_t elemSize);
|
||||
void DynSet_Destroy(DynSet* set);
|
||||
int DynSet_Insert(DynSet* set, const void* element);
|
||||
int DynSet_Contains(DynSet* set, const void* element);
|
||||
size_t DynSet_Size(DynSet* set);
|
||||
void* DynSet_Get(DynSet* set, size_t index);
|
||||
void DynSet_Remove(DynSet* set, const void* element);
|
||||
|
||||
#endif
|
||||
14
include/numgen.h
Normal file
14
include/numgen.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef NUMGEN_H
|
||||
#define NUMGEN_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
unsigned char random_byte(void);
|
||||
uint16_t random_two_byte(void);
|
||||
uint32_t random_four_byte(void);
|
||||
uint64_t random_eight_byte(void);
|
||||
|
||||
#endif
|
||||
18
include/packettype.h
Normal file
18
include/packettype.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef PACKETTYPE_H
|
||||
#define PACKETTYPE_H
|
||||
|
||||
typedef enum {
|
||||
PACKET_TYPE_NONE = 0,
|
||||
PACKET_TYPE_HELLO = 1,
|
||||
PACKET_TYPE_TASK_ASSIGN = 2,
|
||||
PACKET_TYPE_TASK_RESULT = 3,
|
||||
PACKET_TYPE_STATUS_UPDATE = 4,
|
||||
PACKET_TYPE_CLIENT_CAPABILITIES = 5,
|
||||
PACKET_TYPE_TASK_REQUEST = 6,
|
||||
PACKET_TYPE_MISSING_INFO = 7,
|
||||
PACKET_TYPE_ACKNOWLEDGE = 8,
|
||||
PACKET_TYPE_TASK_REJECT = 9,
|
||||
PACKET_TYPE_TASK_NONE_AVAILABLE = 10
|
||||
} PacketType;
|
||||
|
||||
#endif
|
||||
21
include/randomx/librx_wrapper.h
Normal file
21
include/randomx/librx_wrapper.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef LIBRX_WRAPPER_H
|
||||
#define LIBRX_WRAPPER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <randomx.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool RandomX_Init(const char* key);
|
||||
void RandomX_Destroy();
|
||||
void RandomX_CalculateHash(const uint8_t* input, size_t inputLen, uint8_t* output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
29
include/tcpd/tcpclient.h
Normal file
29
include/tcpd/tcpclient.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef TCPCLIENT_H
|
||||
#define TCPCLIENT_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <dynarr.h>
|
||||
|
||||
#define MTU 1500
|
||||
|
||||
struct TcpClient {
|
||||
int clientFd;
|
||||
struct sockaddr_in clientAddr;
|
||||
uint32_t clientId;
|
||||
|
||||
unsigned char dataBuf[MTU];
|
||||
ssize_t dataBufLen;
|
||||
void (*on_data)(struct TcpClient* client);
|
||||
void (*on_disconnect)(struct TcpClient* client);
|
||||
|
||||
pthread_t clientThread;
|
||||
};
|
||||
|
||||
typedef struct TcpClient TcpClient;
|
||||
|
||||
#endif
|
||||
56
include/tcpd/tcpserver.h
Normal file
56
include/tcpd/tcpserver.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef TCPSERVER_H
|
||||
#define TCPSERVER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <tcpd/tcpclient.h>
|
||||
#include <numgen.h>
|
||||
#include <dynarr.h>
|
||||
|
||||
typedef struct {
|
||||
int sockFd;
|
||||
struct sockaddr_in addr;
|
||||
int opt;
|
||||
|
||||
// Called before the client thread runs
|
||||
void (*on_connect)(TcpClient* client);
|
||||
// Called when data is received
|
||||
void (*on_data)(TcpClient* client);
|
||||
// Called before the socket and client thread are killed; Do NOT free client manually
|
||||
void (*on_disconnect)(TcpClient* client);
|
||||
|
||||
// max clients
|
||||
size_t clients;
|
||||
TcpClient** clientsArrPtr;
|
||||
|
||||
pthread_t svrThread;
|
||||
} TcpServer;
|
||||
|
||||
struct tcpclient_thread_args {
|
||||
TcpClient* clientPtr;
|
||||
TcpServer* serverPtr;
|
||||
};
|
||||
|
||||
typedef struct tcpclient_thread_args tcpclient_thread_args;
|
||||
|
||||
TcpServer* TcpServer_Create();
|
||||
void TcpServer_Destroy(TcpServer* ptr);
|
||||
|
||||
void TcpServer_Init(TcpServer* ptr, unsigned short port, const char* addr);
|
||||
void TcpServer_Start(TcpServer* ptr, int maxcons);
|
||||
void TcpServer_Stop(TcpServer* ptr);
|
||||
void TcpServer_Send(TcpServer* ptr, TcpClient* cli, void* data, size_t len);
|
||||
void Generic_SendSocket(int sock, void* data, size_t len);
|
||||
void TcpServer_Disconnect(TcpServer* ptr, TcpClient* cli);
|
||||
void TcpServer_KillClient(TcpServer* ptr, TcpClient* cli);
|
||||
|
||||
size_t Generic_FindClientInArrayByPtr(TcpClient** arr, TcpClient* ptr, size_t len);
|
||||
|
||||
#endif
|
||||
163
src/block/block.c
Normal file
163
src/block/block.c
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <block/block.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
block_t* Block_Create() {
|
||||
block_t* block = (block_t*)malloc(sizeof(block_t));
|
||||
if (!block) {
|
||||
return NULL;
|
||||
}
|
||||
memset(&block->header, 0, sizeof(block_header_t));
|
||||
block->transactions = DYNARR_CREATE(signed_transaction_t, 1);
|
||||
if (!block->transactions) {
|
||||
free(block);
|
||||
return NULL;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
void Block_CalculateHash(const block_t* block, uint8_t* outHash) {
|
||||
if (!block || !outHash || !block->transactions || DynArr_size(block->transactions) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merkle root TODO
|
||||
|
||||
// Flatten the block header and transactions into a single buffer for hashing (assume that txs are verified - usually on receive)
|
||||
uint8_t buffer[sizeof(block_header_t) + (DynArr_size(block->transactions) * DynArr_elemSize(block->transactions))];
|
||||
memcpy(buffer, &block->header, sizeof(block_header_t));
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||
void* txPtr = (char*)DynArr_at(block->transactions, i);
|
||||
memcpy(buffer + sizeof(block_header_t) + (i * DynArr_elemSize(block->transactions)), txPtr, DynArr_elemSize(block->transactions));
|
||||
}
|
||||
|
||||
SHA256((const unsigned char*)buffer, sizeof(buffer), outHash);
|
||||
SHA256(outHash, 32, outHash); // Double-Hash
|
||||
}
|
||||
|
||||
void Block_CalculateRandomXHash(const block_t* block, uint8_t* outHash) {
|
||||
if (!block || !outHash || !block->transactions || DynArr_size(block->transactions) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merkle root TODO
|
||||
|
||||
// Flatten the block header and transactions into a single buffer for hashing (assume that txs are verified - usually on receive)
|
||||
uint8_t buffer[sizeof(block_header_t) + (DynArr_size(block->transactions) * DynArr_elemSize(block->transactions))];
|
||||
memcpy(buffer, &block->header, sizeof(block_header_t));
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||
void* txPtr = (char*)DynArr_at(block->transactions, i);
|
||||
memcpy(buffer + sizeof(block_header_t) + (i * DynArr_elemSize(block->transactions)), txPtr, DynArr_elemSize(block->transactions));
|
||||
}
|
||||
|
||||
RandomX_CalculateHash(buffer, sizeof(buffer), outHash);
|
||||
}
|
||||
|
||||
void Block_AddTransaction(block_t* block, signed_transaction_t* tx) {
|
||||
if (!block || !tx || !block->transactions) {
|
||||
return;
|
||||
}
|
||||
|
||||
DynArr_push_back(block->transactions, tx);
|
||||
}
|
||||
|
||||
void Block_RemoveTransaction(block_t* block, uint8_t* txHash) {
|
||||
if (!block || !txHash || !block->transactions) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||
signed_transaction_t* currentTx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
if (memcmp(currentTx->signature.txHash, txHash, 32) == 0) {
|
||||
DynArr_remove(block->transactions, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int Uint256_CompareBE(const uint8_t a[32], const uint8_t b[32]) {
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
if (a[i] < b[i]) return -1;
|
||||
if (a[i] > b[i]) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool DecodeCompactTarget(uint32_t nBits, uint8_t target[32]) {
|
||||
memset(target, 0, 32);
|
||||
|
||||
uint32_t exponent = nBits >> 24;
|
||||
uint32_t mantissa = nBits & 0x007fffff; // ignore sign bit for now
|
||||
bool negative = (nBits & 0x00800000) != 0;
|
||||
|
||||
if (negative || mantissa == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute: target = mantissa * 256^(exponent - 3)
|
||||
if (exponent <= 3) {
|
||||
mantissa >>= 8 * (3 - exponent);
|
||||
|
||||
target[29] = (mantissa >> 16) & 0xff;
|
||||
target[30] = (mantissa >> 8) & 0xff;
|
||||
target[31] = mantissa & 0xff;
|
||||
} else {
|
||||
uint32_t byte_index = exponent - 3; // number of zero-bytes appended on right
|
||||
|
||||
if (byte_index > 29) {
|
||||
return false; // overflow 256 bits
|
||||
}
|
||||
|
||||
target[32 - byte_index - 3] = (mantissa >> 16) & 0xff;
|
||||
target[32 - byte_index - 2] = (mantissa >> 8) & 0xff;
|
||||
target[32 - byte_index - 1] = mantissa & 0xff;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Block_HasValidProofOfWork(const block_t* block) {
|
||||
if (!block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t target[32];
|
||||
if (!DecodeCompactTarget(block->header.difficultyTarget, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hash[32];
|
||||
Block_CalculateRandomXHash(block, hash);
|
||||
|
||||
return Uint256_CompareBE(hash, target) <= 0;
|
||||
}
|
||||
|
||||
bool Block_AllTransactionsValid(const block_t* block) {
|
||||
if (!block || !block->transactions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasCoinbase = false;
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
if (!Transaction_Verify(tx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
if (hasCoinbase) {
|
||||
return false; // More than one coinbase transaction
|
||||
}
|
||||
|
||||
hasCoinbase = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase)
|
||||
}
|
||||
|
||||
void Block_Destroy(block_t* block) {
|
||||
if (!block) return;
|
||||
DynArr_destroy(block->transactions);
|
||||
free(block);
|
||||
}
|
||||
53
src/block/chain.c
Normal file
53
src/block/chain.c
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <block/chain.h>
|
||||
|
||||
blockchain_t* Chain_Create() {
|
||||
blockchain_t* ptr = (blockchain_t*)malloc(sizeof(blockchain_t));
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ptr->blocks = DYNARR_CREATE(block_t, 1);
|
||||
ptr->size = 0;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Chain_Destroy(blockchain_t* chain) {
|
||||
if (chain) {
|
||||
if (chain->blocks) {
|
||||
DynArr_destroy(chain->blocks);
|
||||
}
|
||||
free(chain);
|
||||
}
|
||||
}
|
||||
|
||||
bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
||||
if (chain && block && chain->blocks) {
|
||||
DynArr_push_back(chain->blocks, block);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
block_t* Chain_GetBlock(blockchain_t* chain, size_t index) {
|
||||
if (chain) {
|
||||
return DynArr_at(chain->blocks, index);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t Chain_Size(blockchain_t* chain) {
|
||||
if (chain) {
|
||||
return DynArr_size(chain->blocks);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Chain_IsValid(blockchain_t* chain) {
|
||||
if (!chain || !chain->blocks) {
|
||||
return false;
|
||||
}
|
||||
// Add validation logic here
|
||||
return true;
|
||||
}
|
||||
76
src/block/transaction.c
Normal file
76
src/block/transaction.c
Normal file
@@ -0,0 +1,76 @@
|
||||
#include <block/transaction.h>
|
||||
#include <string.h>
|
||||
|
||||
void Transaction_CalculateHash(const signed_transaction_t* tx, uint8_t* outHash) {
|
||||
if (!tx || !outHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t buffer[sizeof(transaction_t)];
|
||||
memcpy(buffer, &tx->transaction, sizeof(transaction_t));
|
||||
|
||||
SHA256(buffer, sizeof(buffer), outHash);
|
||||
SHA256(outHash, 32, outHash); // Double-Hash
|
||||
}
|
||||
|
||||
void Transaction_Sign(signed_transaction_t* tx, const uint8_t* privateKey) {
|
||||
if (!tx || !privateKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
Transaction_CalculateHash(tx, tx->signature.txHash);
|
||||
Crypto_SignData(
|
||||
(const uint8_t*)&tx->transaction,
|
||||
sizeof(transaction_t),
|
||||
privateKey,
|
||||
tx->signature.signature
|
||||
);
|
||||
}
|
||||
|
||||
bool Transaction_Verify(const signed_transaction_t* tx) {
|
||||
if (!tx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
// Coinbase transactions are valid if the signature is correct for the block (handled in Block_Verify)
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t computeAddress[32];
|
||||
SHA256(tx->transaction.compressedPublicKey, 33, computeAddress); // Address is hash of public key
|
||||
if (memcmp(computeAddress, tx->transaction.senderAddress, 32) != 0) {
|
||||
return false; // Sender address does not match public key
|
||||
}
|
||||
|
||||
if (tx->transaction.amount == 0) {
|
||||
return false; // Zero-amount transactions are not valid
|
||||
}
|
||||
|
||||
if (tx->transaction.fee > tx->transaction.amount) {
|
||||
return false; // Fee cannot exceed amount
|
||||
}
|
||||
|
||||
if (tx->transaction.version != 1) {
|
||||
return false; // Unsupported version
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.recipientAddress)) {
|
||||
return false; // Cannot send to coinbase address
|
||||
}
|
||||
|
||||
uint8_t txHash[32];
|
||||
Transaction_CalculateHash(tx, txHash);
|
||||
|
||||
if (memcmp(txHash, tx->signature.txHash, 32) != 0) {
|
||||
return false; // Hash does not match signature hash
|
||||
}
|
||||
|
||||
// If all checks pass, verify the signature
|
||||
return Crypto_VerifySignature(
|
||||
(const uint8_t*)&tx->transaction,
|
||||
sizeof(transaction_t),
|
||||
tx->signature.signature,
|
||||
tx->transaction.compressedPublicKey
|
||||
);
|
||||
}
|
||||
77
src/crypto/crypto.c
Normal file
77
src/crypto/crypto.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <crypto/crypto.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool crypto_hash_to_32(const uint8_t* data, size_t len, uint8_t out32[32]) {
|
||||
if (!data || !out32) {
|
||||
return false;
|
||||
}
|
||||
return SHA256(data, len, out32) != NULL;
|
||||
}
|
||||
|
||||
bool Crypto_VerifySignature(const uint8_t* data, size_t len, const uint8_t* signature, const uint8_t* publicKey) {
|
||||
if (!data || !signature || !publicKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t digest[32];
|
||||
if (!crypto_hash_to_32(data, len, digest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
secp256k1_pubkey pubkey;
|
||||
secp256k1_ecdsa_signature sig;
|
||||
|
||||
const int pub_ok = secp256k1_ec_pubkey_parse(ctx, &pubkey, publicKey, 33);
|
||||
const int sig_ok = secp256k1_ecdsa_signature_parse_compact(ctx, &sig, signature);
|
||||
const int verified = (pub_ok && sig_ok) ? secp256k1_ecdsa_verify(ctx, &sig, digest, &pubkey) : 0;
|
||||
|
||||
secp256k1_context_destroy(ctx);
|
||||
return verified == 1;
|
||||
}
|
||||
|
||||
void Crypto_SignData(const uint8_t* data, size_t len, const uint8_t* privateKey, uint8_t* outSignature) {
|
||||
if (!data || !privateKey || !outSignature) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t digest[32];
|
||||
if (!crypto_hash_to_32(data, len, digest)) {
|
||||
memset(outSignature, 0, 64);
|
||||
return;
|
||||
}
|
||||
|
||||
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
|
||||
if (!ctx) {
|
||||
memset(outSignature, 0, 64);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secp256k1_ec_seckey_verify(ctx, privateKey)) {
|
||||
memset(outSignature, 0, 64);
|
||||
secp256k1_context_destroy(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
secp256k1_ecdsa_signature sig;
|
||||
const int sign_ok = secp256k1_ecdsa_sign(
|
||||
ctx,
|
||||
&sig,
|
||||
digest,
|
||||
privateKey,
|
||||
secp256k1_nonce_function_default,
|
||||
NULL
|
||||
);
|
||||
if (!sign_ok) {
|
||||
memset(outSignature, 0, 64);
|
||||
secp256k1_context_destroy(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
secp256k1_ecdsa_signature_serialize_compact(ctx, outSignature, &sig);
|
||||
secp256k1_context_destroy(ctx);
|
||||
}
|
||||
175
src/dynarr.c
Normal file
175
src/dynarr.c
Normal file
@@ -0,0 +1,175 @@
|
||||
#include <dynarr.h>
|
||||
|
||||
DynArr* DynArr_create(size_t elemSize, size_t capacity) {
|
||||
DynArr* p = (DynArr*)malloc(sizeof(DynArr));
|
||||
if (!p) return NULL;
|
||||
|
||||
p->elemSize = elemSize;
|
||||
p->capacity = capacity;
|
||||
p->data = malloc(elemSize * capacity);
|
||||
if (!p->data) {
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
p->size = 0;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// Reserve n blocks in arary; New size will be n, NOT size + n; Reserving less memory that current will fail, use prune instead.
|
||||
void DynArr_reserve(DynArr* p, size_t n) {
|
||||
if (n <= p->capacity) {
|
||||
printf("reserve ignored; attempted to reserve less or equal to current capacity\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (n > DYNARR_MAX_CAPACITY) {
|
||||
printf("reserve ignored; attempted to reserve over 32 bits\n");
|
||||
return;
|
||||
}
|
||||
|
||||
void* new_data = realloc(p->data, n * p->elemSize);
|
||||
if (!new_data) {
|
||||
printf("reserve failed\n");
|
||||
exit(1);
|
||||
}
|
||||
p->data = new_data;
|
||||
p->capacity = n;
|
||||
}
|
||||
|
||||
// Push data into a new block at the end of the array; If value is NULL, the new block will be zeroed.
|
||||
void* DynArr_push_back(DynArr* p, void* value) {
|
||||
//if (value == NULL) {
|
||||
// printf("push_back ignored; value is null");
|
||||
// return NULL;
|
||||
//}
|
||||
|
||||
if (p->size >= p->capacity) {
|
||||
size_t new_cap = (p->capacity == 0) ? 1 : p->capacity * 2;
|
||||
|
||||
if (new_cap < p->capacity || new_cap > DYNARR_MAX_CAPACITY) {
|
||||
printf("push_back ignored; capacity overflow\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* new_data = realloc(p->data, new_cap * p->elemSize);
|
||||
if (!new_data) {
|
||||
printf("push failed\n");
|
||||
exit(1);
|
||||
}
|
||||
p->capacity = new_cap;
|
||||
p->data = new_data;
|
||||
}
|
||||
|
||||
void* dst = (void*)((char*)p->data + (p->size * p->elemSize));
|
||||
|
||||
if (value == NULL) {
|
||||
memset(dst, 0, p->elemSize); // Handle NULL value.
|
||||
} else {
|
||||
memcpy((char*)dst, value, p->elemSize);
|
||||
}
|
||||
|
||||
p->size++;
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Remove the last block in the array.
|
||||
void DynArr_pop_back(DynArr* p) {
|
||||
if (p->size == 0) {
|
||||
printf("pop_back ignored; size is 0\n");
|
||||
return;
|
||||
}
|
||||
|
||||
p->size--; // Will automatically overwrite that memory naturally
|
||||
}
|
||||
|
||||
// Remove first block from array.
|
||||
void DynArr_pop_front(DynArr* p) {
|
||||
if (p->size == 0) {
|
||||
printf("pop_front ignored; size is 0\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memmove(
|
||||
(char*)p->data,
|
||||
(char*)p->data + p->elemSize,
|
||||
(p->size - 1) * p->elemSize
|
||||
);
|
||||
|
||||
p->size--;
|
||||
}
|
||||
|
||||
// Remove index from array. This moves all blocks after the index block.
|
||||
void DynArr_remove(DynArr* p, size_t index) {
|
||||
if (index >= p->size) return;
|
||||
|
||||
memmove(
|
||||
(char*)p->data + (index * p->elemSize),
|
||||
(char*)p->data + (index + 1) * p->elemSize,
|
||||
(p->size - index - 1) * p->elemSize
|
||||
);
|
||||
|
||||
p->size--;
|
||||
}
|
||||
|
||||
// Erase the array. This will not free unused blocks.
|
||||
void DynArr_erase(DynArr* p) {
|
||||
p->size = 0;
|
||||
}
|
||||
|
||||
// Prune and free unused blocks. If pruning to zero, ensure to reserve after.
|
||||
void DynArr_prune(DynArr* p) {
|
||||
void* new_data = realloc(p->data, (p->size == 0 ? 1 : p->size) * p->elemSize);
|
||||
if (!new_data) {
|
||||
printf("pruning failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
p->data = new_data;
|
||||
p->capacity = p->size;
|
||||
}
|
||||
|
||||
// Get a pointer to a block by index
|
||||
void* DynArr_at(DynArr* p, size_t index) {
|
||||
if (index >= p->size) return NULL;
|
||||
return (char*)p->data + (index * p->elemSize);
|
||||
}
|
||||
|
||||
// Get the index by block pointer
|
||||
size_t DynArr_at_ptr(DynArr* p, void* ptr) {
|
||||
if (!p || !ptr) {
|
||||
printf("invalid pointer\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < p->size; i++) {
|
||||
if ((void*)(((char*)p->data) + (i * p->elemSize)) == ptr) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// If for some reason the array has 2^64 elements in it, then fuck it, I guess we'll just crash, I don't care.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get size
|
||||
size_t DynArr_size(DynArr* p) {
|
||||
return p->size;
|
||||
}
|
||||
|
||||
// Get element size
|
||||
size_t DynArr_elemSize(DynArr* p) {
|
||||
return p->elemSize;
|
||||
}
|
||||
|
||||
// Get capacity
|
||||
size_t DynArr_capacity(DynArr* p) {
|
||||
return p->capacity;
|
||||
}
|
||||
|
||||
void DynArr_destroy(DynArr* p) {
|
||||
if (!p) return;
|
||||
free(p->data);
|
||||
free(p);
|
||||
}
|
||||
58
src/dynset.c
Normal file
58
src/dynset.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <dynset.h>
|
||||
|
||||
DynSet* DynSet_Create(size_t elemSize) {
|
||||
DynSet* set = (DynSet*)malloc(sizeof(DynSet));
|
||||
if (!set) {
|
||||
return NULL;
|
||||
}
|
||||
set->arr = DynArr_create(elemSize, 1);
|
||||
if (!set->arr) {
|
||||
free(set);
|
||||
return NULL;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
void DynSet_Destroy(DynSet* set) {
|
||||
if (set) {
|
||||
DynArr_destroy(set->arr);
|
||||
free(set);
|
||||
}
|
||||
}
|
||||
|
||||
int DynSet_Insert(DynSet* set, const void* element) {
|
||||
if (DynSet_Contains(set, element)) {
|
||||
return 0; // Element already exists
|
||||
}
|
||||
return DynArr_push_back(set->arr, element) != NULL;
|
||||
}
|
||||
|
||||
int DynSet_Contains(DynSet* set, const void* element) {
|
||||
size_t size = DynArr_size(set->arr);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
void* current = DynArr_at(set->arr, i);
|
||||
if (memcmp(current, element, set->arr->elemSize) == 0) {
|
||||
return 1; // Found
|
||||
}
|
||||
}
|
||||
return 0; // Not found
|
||||
}
|
||||
|
||||
size_t DynSet_Size(DynSet* set) {
|
||||
return DynArr_size(set->arr);
|
||||
}
|
||||
|
||||
void* DynSet_Get(DynSet* set, size_t index) {
|
||||
return DynArr_at(set->arr, index);
|
||||
}
|
||||
|
||||
void DynSet_Remove(DynSet* set, const void* element) {
|
||||
size_t size = DynArr_size(set->arr);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
void* current = DynArr_at(set->arr, i);
|
||||
if (memcmp(current, element, set->arr->elemSize) == 0) {
|
||||
DynArr_remove(set->arr, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
250
src/main.c
Normal file
250
src/main.c
Normal file
@@ -0,0 +1,250 @@
|
||||
#include <block/chain.h>
|
||||
#include <block/transaction.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <secp256k1.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <randomx/librx_wrapper.h>
|
||||
#include <signal.h>
|
||||
|
||||
void handle_sigint(int sig) {
|
||||
printf("Caught signal %d, exiting...\n", sig);
|
||||
RandomX_Destroy();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static double MonotonicSeconds(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (double)ts.tv_sec + ((double)ts.tv_nsec / 1000000000.0);
|
||||
}
|
||||
|
||||
static double MeasureRandomXHashrate(void) {
|
||||
uint8_t input[80] = {0};
|
||||
uint8_t outHash[32];
|
||||
uint64_t counter = 0;
|
||||
|
||||
const double start = MonotonicSeconds();
|
||||
double now = start;
|
||||
do {
|
||||
memcpy(input, &counter, sizeof(counter));
|
||||
RandomX_CalculateHash(input, sizeof(input), outHash);
|
||||
counter++;
|
||||
now = MonotonicSeconds();
|
||||
} while ((now - start) < 0.75); // short local benchmark window
|
||||
|
||||
const double elapsed = now - start;
|
||||
if (elapsed <= 0.0 || counter == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return (double)counter / elapsed;
|
||||
}
|
||||
|
||||
static uint32_t CompactTargetForExpectedHashes(double expectedHashes) {
|
||||
if (expectedHashes < 1.0) {
|
||||
expectedHashes = 1.0;
|
||||
}
|
||||
|
||||
// For exponent 0x1f: target = mantissa * 2^(8*(0x1f-3)) = mantissa * 2^224
|
||||
// So expected hashes ~= 2^256 / target = 2^32 / mantissa.
|
||||
double mantissaF = 4294967296.0 / expectedHashes;
|
||||
if (mantissaF < 1.0) {
|
||||
mantissaF = 1.0;
|
||||
}
|
||||
if (mantissaF > 8388607.0) {
|
||||
mantissaF = 8388607.0; // 0x007fffff
|
||||
}
|
||||
|
||||
const uint32_t mantissa = (uint32_t)mantissaF;
|
||||
return (0x1fU << 24) | (mantissa & 0x007fffffU);
|
||||
}
|
||||
|
||||
static bool GenerateKeypair(
|
||||
const secp256k1_context* ctx,
|
||||
uint8_t outPrivateKey[32],
|
||||
uint8_t outCompressedPublicKey[33]
|
||||
) {
|
||||
if (!ctx || !outPrivateKey || !outCompressedPublicKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
secp256k1_pubkey pubkey;
|
||||
for (size_t i = 0; i < 1024; ++i) {
|
||||
arc4random_buf(outPrivateKey, 32);
|
||||
if (!secp256k1_ec_seckey_verify(ctx, outPrivateKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, outPrivateKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t serializedLen = 33;
|
||||
if (!secp256k1_ec_pubkey_serialize(
|
||||
ctx,
|
||||
outCompressedPublicKey,
|
||||
&serializedLen,
|
||||
&pubkey,
|
||||
SECP256K1_EC_COMPRESSED
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return serializedLen == 33;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool MineBlock(block_t* block) {
|
||||
if (!block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint64_t nonce = 0;; ++nonce) {
|
||||
block->header.nonce = nonce;
|
||||
if (Block_HasValidProofOfWork(block)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nonce == UINT64_MAX) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
signal(SIGINT, handle_sigint);
|
||||
|
||||
// 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.
|
||||
fprintf(stderr, "failed to initialize RandomX\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
blockchain_t* chain = Chain_Create();
|
||||
if (!chain) {
|
||||
fprintf(stderr, "failed to create chain\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
secp256k1_context* secpCtx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
||||
if (!secpCtx) {
|
||||
fprintf(stderr, "failed to create secp256k1 context\n");
|
||||
Chain_Destroy(chain);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t senderPrivateKey[32];
|
||||
uint8_t receiverPrivateKey[32];
|
||||
uint8_t senderCompressedPublicKey[33];
|
||||
uint8_t receiverCompressedPublicKey[33];
|
||||
|
||||
if (!GenerateKeypair(secpCtx, senderPrivateKey, senderCompressedPublicKey) ||
|
||||
!GenerateKeypair(secpCtx, receiverPrivateKey, receiverCompressedPublicKey)) {
|
||||
fprintf(stderr, "failed to generate keypairs\n");
|
||||
secp256k1_context_destroy(secpCtx);
|
||||
Chain_Destroy(chain);
|
||||
return 1;
|
||||
}
|
||||
|
||||
signed_transaction_t tx;
|
||||
memset(&tx, 0, sizeof(tx));
|
||||
tx.transaction.version = 1;
|
||||
tx.transaction.amount = 100;
|
||||
tx.transaction.fee = 1;
|
||||
|
||||
SHA256(senderCompressedPublicKey, 33, tx.transaction.senderAddress);
|
||||
SHA256(receiverCompressedPublicKey, 33, tx.transaction.recipientAddress);
|
||||
memcpy(tx.transaction.compressedPublicKey, senderCompressedPublicKey, 33);
|
||||
|
||||
Transaction_Sign(&tx, senderPrivateKey);
|
||||
if (!Transaction_Verify(&tx)) {
|
||||
fprintf(stderr, "signed transaction did not verify\n");
|
||||
secp256k1_context_destroy(secpCtx);
|
||||
Chain_Destroy(chain);
|
||||
RandomX_Destroy();
|
||||
return 1;
|
||||
}
|
||||
|
||||
block_t* block = Block_Create();
|
||||
if (!block) {
|
||||
fprintf(stderr, "failed to create block\n");
|
||||
secp256k1_context_destroy(secpCtx);
|
||||
Chain_Destroy(chain);
|
||||
RandomX_Destroy();
|
||||
return 1;
|
||||
}
|
||||
|
||||
block->header.version = 1;
|
||||
block->header.blockNumber = (uint32_t)Chain_Size(chain);
|
||||
memset(block->header.prevHash, 0, sizeof(block->header.prevHash));
|
||||
memset(block->header.merkleRoot, 0, sizeof(block->header.merkleRoot));
|
||||
block->header.timestamp = (uint64_t)time(NULL);
|
||||
|
||||
const double hps = MeasureRandomXHashrate();
|
||||
const double targetSeconds = 60.0;
|
||||
const double expectedHashes = (hps > 0.0) ? (hps * targetSeconds) : 65536.0;
|
||||
block->header.difficultyTarget = CompactTargetForExpectedHashes(expectedHashes);
|
||||
block->header.nonce = 0;
|
||||
|
||||
printf("RandomX benchmark: %.2f H/s, target %.0fs, nBits=0x%08x\n",
|
||||
hps,
|
||||
targetSeconds,
|
||||
block->header.difficultyTarget);
|
||||
|
||||
Block_AddTransaction(block, &tx);
|
||||
printf("Added transaction to block: sender %02x... -> recipient %02x..., amount %lu, fee %lu\n",
|
||||
tx.transaction.senderAddress[0], tx.transaction.senderAddress[31],
|
||||
tx.transaction.recipientAddress[0], tx.transaction.recipientAddress[31],
|
||||
tx.transaction.amount, tx.transaction.fee);
|
||||
|
||||
if (!MineBlock(block)) {
|
||||
fprintf(stderr, "failed to mine block within nonce range\n");
|
||||
Block_Destroy(block);
|
||||
secp256k1_context_destroy(secpCtx);
|
||||
Chain_Destroy(chain);
|
||||
RandomX_Destroy();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Chain_AddBlock(chain, block)) {
|
||||
fprintf(stderr, "failed to append block to chain\n");
|
||||
Block_Destroy(block);
|
||||
secp256k1_context_destroy(secpCtx);
|
||||
Chain_Destroy(chain);
|
||||
RandomX_Destroy();
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Mined block %u with nonce %llu and chain size %zu\n",
|
||||
block->header.blockNumber,
|
||||
(unsigned long long)block->header.nonce,
|
||||
Chain_Size(chain));
|
||||
|
||||
printf("Block hash (SHA256): ");
|
||||
uint8_t blockHash[32];
|
||||
Block_CalculateHash(block, blockHash);
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
printf("%02x", blockHash[i]);
|
||||
}
|
||||
printf("\nBlock hash (RandomX): ");
|
||||
uint8_t randomXHash[32];
|
||||
Block_CalculateRandomXHash(block, randomXHash);
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
printf("%02x", randomXHash[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Chain currently stores a copy of block_t that references the same tx array pointer,
|
||||
// so we do not destroy `block` here to avoid invalidating chain data.
|
||||
secp256k1_context_destroy(secpCtx);
|
||||
|
||||
Chain_Destroy(chain);
|
||||
return 0;
|
||||
}
|
||||
41
src/numgen.c
Normal file
41
src/numgen.c
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <numgen.h>
|
||||
|
||||
unsigned char random_byte(void) {
|
||||
return (unsigned char)(rand() % 256);
|
||||
}
|
||||
|
||||
uint16_t random_two_byte(void) {
|
||||
uint16_t x;
|
||||
unsigned char bytes[2];
|
||||
for (unsigned char i = 0; i < 2; i++) {
|
||||
bytes[i] = random_byte();
|
||||
}
|
||||
|
||||
memcpy(&x, bytes, sizeof(x));
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
uint32_t random_four_byte(void) {
|
||||
uint32_t x;
|
||||
unsigned char bytes[4];
|
||||
for (unsigned char i = 0; i < 4; i++) {
|
||||
bytes[i] = random_byte();
|
||||
}
|
||||
|
||||
memcpy(&x, bytes, sizeof(x));
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
uint64_t random_eight_byte(void) {
|
||||
uint64_t x;
|
||||
unsigned char bytes[8];
|
||||
for (unsigned char i = 0; i < 8; i++) {
|
||||
bytes[i] = random_byte();
|
||||
}
|
||||
|
||||
memcpy(&x, bytes, sizeof(x));
|
||||
|
||||
return x;
|
||||
}
|
||||
73
src/randomx/librx_wrapper.c
Normal file
73
src/randomx/librx_wrapper.c
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <randomx/librx_wrapper.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static randomx_cache* rxCache = NULL;
|
||||
static randomx_dataset* rxDataset = NULL;
|
||||
static randomx_vm* rxVm = NULL;
|
||||
|
||||
bool RandomX_Init(const char* key) {
|
||||
if (!key || rxCache || rxVm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const randomx_flags baseFlags = randomx_get_flags() | RANDOMX_FLAG_JIT;
|
||||
randomx_flags vmFlags = baseFlags | RANDOMX_FLAG_FULL_MEM;
|
||||
|
||||
rxCache = randomx_alloc_cache(baseFlags);
|
||||
if (!rxCache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomx_init_cache(rxCache, key, strlen(key));
|
||||
|
||||
// Prefer full-memory mode. If dataset allocation fails, fall back to light mode.
|
||||
rxDataset = randomx_alloc_dataset(vmFlags);
|
||||
if (rxDataset) {
|
||||
const unsigned long datasetItems = randomx_dataset_item_count();
|
||||
randomx_init_dataset(rxDataset, rxCache, 0, datasetItems);
|
||||
rxVm = randomx_create_vm(vmFlags, NULL, rxDataset);
|
||||
if (rxVm) {
|
||||
printf("RandomX initialized in full-memory mode\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
randomx_release_dataset(rxDataset);
|
||||
rxDataset = NULL;
|
||||
}
|
||||
|
||||
vmFlags = baseFlags;
|
||||
rxVm = randomx_create_vm(vmFlags, rxCache, NULL);
|
||||
if (!rxVm) {
|
||||
randomx_release_cache(rxCache);
|
||||
rxCache = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("RandomX initialized in light mode\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void RandomX_Destroy() {
|
||||
if (rxVm) {
|
||||
randomx_destroy_vm(rxVm);
|
||||
rxVm = NULL;
|
||||
}
|
||||
if (rxDataset) {
|
||||
randomx_release_dataset(rxDataset);
|
||||
rxDataset = NULL;
|
||||
}
|
||||
if (rxCache) {
|
||||
randomx_release_cache(rxCache);
|
||||
rxCache = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void RandomX_CalculateHash(const uint8_t* input, size_t inputLen, uint8_t* output) {
|
||||
if (!rxVm || !input || !output) {
|
||||
return;
|
||||
}
|
||||
randomx_calculate_hash(rxVm, input, inputLen, output);
|
||||
}
|
||||
317
src/tcpd/tcpserver.c
Normal file
317
src/tcpd/tcpserver.c
Normal file
@@ -0,0 +1,317 @@
|
||||
#include <tcpd/tcpserver.h>
|
||||
|
||||
TcpServer* TcpServer_Create() {
|
||||
TcpServer* svr = (TcpServer*)malloc(sizeof(TcpServer));
|
||||
|
||||
if (!svr) {
|
||||
perror("tcpserver - creation failure");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
svr->sockFd = -1;
|
||||
svr->svrThread = 0;
|
||||
svr->on_connect = NULL;
|
||||
svr->on_data = NULL;
|
||||
svr->on_disconnect = NULL;
|
||||
|
||||
svr->clients = 0;
|
||||
svr->clientsArrPtr = NULL;
|
||||
|
||||
return svr;
|
||||
}
|
||||
|
||||
void TcpServer_Destroy(TcpServer* ptr) {
|
||||
if (ptr) {
|
||||
if (ptr->clientsArrPtr) {
|
||||
for (size_t i = 0; i < ptr->clients; i++) {
|
||||
if (ptr->clientsArrPtr[i]) {
|
||||
free(ptr->clientsArrPtr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(ptr->clientsArrPtr);
|
||||
}
|
||||
|
||||
close(ptr->sockFd);
|
||||
free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void TcpServer_Init(TcpServer* ptr, unsigned short port, const char* addr) {
|
||||
if (ptr) {
|
||||
// Create socket
|
||||
ptr->sockFd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (ptr->sockFd < 0) {
|
||||
perror("tcpserver - socket");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Allow quick port resue
|
||||
ptr->opt = 1;
|
||||
setsockopt(ptr->sockFd, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(int));
|
||||
|
||||
// Fill address structure
|
||||
memset(&ptr->addr, 0, sizeof(ptr->addr));
|
||||
ptr->addr.sin_family = AF_INET;
|
||||
ptr->addr.sin_port = htons(port);
|
||||
inet_pton(AF_INET, addr, &ptr->addr.sin_addr);
|
||||
|
||||
// Bind
|
||||
if (bind(ptr->sockFd, (struct sockaddr*)&ptr->addr, sizeof(ptr->addr)) < 0) {
|
||||
perror("tcpserver - bind");
|
||||
close(ptr->sockFd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not call outside of func.
|
||||
void* TcpServer_clientthreadprocess(void* ptr) {
|
||||
if (!ptr) {
|
||||
perror("Client ptr is null!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tcpclient_thread_args* args = (tcpclient_thread_args*)ptr;
|
||||
|
||||
TcpClient* cli = args->clientPtr;
|
||||
TcpServer* svr = args->serverPtr;
|
||||
|
||||
if (args) {
|
||||
free(args);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
memset(cli->dataBuf, 0, MTU); // Reset buffer
|
||||
ssize_t n = recv(cli->clientFd, cli->dataBuf, MTU, 0);
|
||||
cli->dataBufLen = n;
|
||||
|
||||
if (n == 0) {
|
||||
break; // Client disconnected
|
||||
} else if (n > 0) {
|
||||
if (cli->on_data) {
|
||||
cli->on_data(cli);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_testcancel(); // Check for thread death
|
||||
}
|
||||
|
||||
cli->on_disconnect(cli);
|
||||
|
||||
// Close on exit
|
||||
close(cli->clientFd);
|
||||
|
||||
// Destroy
|
||||
TcpClient** arr = svr->clientsArrPtr;
|
||||
size_t idx = Generic_FindClientInArrayByPtr(arr, cli, svr->clients);
|
||||
if (idx != SIZE_MAX) {
|
||||
if (arr[idx]) {
|
||||
free(arr[idx]);
|
||||
arr[idx] = NULL;
|
||||
}
|
||||
} else {
|
||||
perror("tcpserver (client thread) - something already freed the client!");
|
||||
}
|
||||
|
||||
//free(ptr);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Do not call outside of func.
|
||||
void* TcpServer_threadprocess(void* ptr) {
|
||||
if (!ptr) {
|
||||
perror("Client ptr is null!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TcpServer* svr = (TcpServer*)ptr;
|
||||
while (1) {
|
||||
TcpClient tempclient;
|
||||
socklen_t clientsize = sizeof(tempclient.clientAddr);
|
||||
int client = accept(svr->sockFd, (struct sockaddr*)&tempclient.clientAddr, &clientsize);
|
||||
if (client >= 0) {
|
||||
tempclient.clientFd = client;
|
||||
tempclient.on_data = svr->on_data;
|
||||
tempclient.on_disconnect = svr->on_disconnect;
|
||||
|
||||
// I'm lazy, so I'm just copying the data for now (I should probably make this a better way)
|
||||
TcpClient* heapCli = (TcpClient*)malloc(sizeof(TcpClient));
|
||||
if (!heapCli) {
|
||||
perror("tcpserver - client failed to allocate");
|
||||
exit(EXIT_FAILURE); // Wtf just happened???
|
||||
}
|
||||
|
||||
heapCli->clientAddr = tempclient.clientAddr;
|
||||
heapCli->clientFd = tempclient.clientFd;
|
||||
heapCli->on_data = tempclient.on_data;
|
||||
heapCli->on_disconnect = tempclient.on_disconnect;
|
||||
heapCli->clientId = random_four_byte();
|
||||
heapCli->dataBufLen = 0;
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < svr->clients; i++) {
|
||||
if (svr->clientsArrPtr[i] == NULL) {
|
||||
// Make use of that space
|
||||
svr->clientsArrPtr[i] = heapCli; // We have now transfered the ownership :)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == svr->clients) {
|
||||
// Not found
|
||||
// RST; Thread doesn't exist yet
|
||||
struct linger so_linger;
|
||||
so_linger.l_onoff = 1;
|
||||
so_linger.l_linger = 0;
|
||||
setsockopt(heapCli->clientFd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
|
||||
close(heapCli->clientFd);
|
||||
|
||||
free(heapCli);
|
||||
heapCli = NULL;
|
||||
//svr->clientsArrPtr[i] = NULL;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
tcpclient_thread_args* arg = (tcpclient_thread_args*)malloc(sizeof(tcpclient_thread_args));
|
||||
arg->clientPtr = heapCli;
|
||||
arg->serverPtr = svr;
|
||||
|
||||
if (svr->on_connect) {
|
||||
svr->on_connect(heapCli);
|
||||
}
|
||||
|
||||
pthread_create(&heapCli->clientThread, NULL, TcpServer_clientthreadprocess, arg);
|
||||
pthread_detach(heapCli->clientThread); // May not work :(
|
||||
}
|
||||
|
||||
pthread_testcancel(); // Check for thread death
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TcpServer_Start(TcpServer* ptr, int maxcons) {
|
||||
if (ptr) {
|
||||
if (listen(ptr->sockFd, maxcons) < 0) {
|
||||
perror("tcpserver - listen");
|
||||
close(ptr->sockFd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ptr->clients = maxcons;
|
||||
ptr->clientsArrPtr = (TcpClient**)malloc(sizeof(TcpClient*) * maxcons);
|
||||
|
||||
if (!ptr->clientsArrPtr) {
|
||||
perror("tcpserver - allocation of client space fatally errored");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Fucking null out everything
|
||||
for (int i = 0; i < maxcons; i++) {
|
||||
ptr->clientsArrPtr[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn server thread
|
||||
pthread_create(&ptr->svrThread, NULL, TcpServer_threadprocess, ptr);
|
||||
}
|
||||
|
||||
void TcpServer_Stop(TcpServer* ptr) {
|
||||
if (ptr && ptr->svrThread != 0) {
|
||||
// Stop server
|
||||
pthread_cancel(ptr->svrThread);
|
||||
pthread_join(ptr->svrThread, NULL);
|
||||
|
||||
// Disconnect clients
|
||||
for (size_t i = 0; i < ptr->clients; i++) {
|
||||
TcpClient* cliPtr = ptr->clientsArrPtr[i];
|
||||
if (cliPtr) {
|
||||
close(cliPtr->clientFd);
|
||||
pthread_cancel(cliPtr->clientThread);
|
||||
}
|
||||
}
|
||||
|
||||
ptr->svrThread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TcpServer_Send(TcpServer* ptr, TcpClient* cli, void* data, size_t len) {
|
||||
if (ptr && cli && data && len > 0) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
// Ensure that all data is sent. TCP can split sends.
|
||||
ssize_t n = send(cli->clientFd, (unsigned char*)data + sent, len - sent, 0);
|
||||
if (n < 0) {
|
||||
perror("tcpserver - send error");
|
||||
break;
|
||||
}
|
||||
sent += n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Generic_SendSocket(int sock, void* data, size_t len) {
|
||||
if (sock > 0 && data && len > 0) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = send(sock, (unsigned char*)data + sent, len - sent, 0);
|
||||
if (n < 0) {
|
||||
perror("generic - send socket error");
|
||||
break;
|
||||
}
|
||||
sent += n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TcpServer_Disconnect(TcpServer* ptr, TcpClient* cli) {
|
||||
if (ptr && cli) {
|
||||
close(cli->clientFd);
|
||||
pthread_cancel(cli->clientThread);
|
||||
|
||||
size_t idx = Generic_FindClientInArrayByPtr(ptr->clientsArrPtr, cli, ptr->clients);
|
||||
if (idx != SIZE_MAX) {
|
||||
if (ptr->clientsArrPtr[idx]) {
|
||||
free(ptr->clientsArrPtr[idx]);
|
||||
}
|
||||
ptr->clientsArrPtr[idx] = NULL;
|
||||
} else {
|
||||
perror("tcpserver - didn't find client to disconnect in array!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TcpServer_KillClient(TcpServer* ptr, TcpClient* cli) {
|
||||
if (ptr && cli) {
|
||||
// RST the connection
|
||||
struct linger so_linger;
|
||||
so_linger.l_onoff = 1;
|
||||
so_linger.l_linger = 0;
|
||||
setsockopt(cli->clientFd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
|
||||
close(cli->clientFd);
|
||||
pthread_cancel(cli->clientThread);
|
||||
|
||||
size_t idx = Generic_FindClientInArrayByPtr(ptr->clientsArrPtr, cli, ptr->clients);
|
||||
if (idx != SIZE_MAX) {
|
||||
if (ptr->clientsArrPtr[idx]) {
|
||||
free(ptr->clientsArrPtr[idx]);
|
||||
}
|
||||
ptr->clientsArrPtr[idx] = NULL;
|
||||
} else {
|
||||
perror("tcpserver - didn't find client to kill in array!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t Generic_FindClientInArrayByPtr(TcpClient** arr, TcpClient* ptr, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (arr[i] == ptr) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX; // Returns max unsigned, likely improbable to be correct
|
||||
}
|
||||
Reference in New Issue
Block a user