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