diff --git a/configure.ac b/configure.ac index 228897a16..f3c3913d4 100644 --- a/configure.ac +++ b/configure.ac @@ -233,11 +233,13 @@ fi if test "x$with_net" = xyes; then AC_CHECK_HEADER([event2/event.h],, AC_MSG_ERROR(libevent headers missing),) - AC_CHECK_LIB([event_core],[main],EVENT_LIBS=-levent_core,AC_MSG_ERROR(libevent_core missing)) + AC_CHECK_LIB([event],[main],EVENT_LIBS=-levent,AC_MSG_ERROR(libevent missing)) + AC_CHECK_LIB([event_core],[main],EVENT_CORE_LIBS=-levent_core,AC_MSG_ERROR(libevent_core missing)) + AC_CHECK_LIB([event_extra],[main],EVENT_EXTRA_LIBS=-levent_extra,AC_MSG_ERROR(libevent_extra missing)) if test "$host" = "mingw"; then AC_CHECK_LIB([event_pthreads],[main],EVENT_PTHREADS_LIBS=-levent_pthreads,AC_MSG_ERROR(libevent_pthreads missing)) fi - LIBS="$LIBS $EVENT_LIBS $EVENT_PTHREADS_LIBS" + LIBS="$LIBS $EVENT_LIBS $EVENT_CORE_LIBS $EVENT_EXTRA_LIBS $EVENT_PTHREADS_LIBS" fi if test "x$with_logdb" = xyes; then @@ -259,6 +261,8 @@ AC_CONFIG_FILES([Makefile libdogecoin.pc]) AC_SUBST(LIBTOOL_APP_LDFLAGS) AC_SUBST(BUILD_EXEEXT) AC_SUBST(EVENT_LIBS) +AC_SUBST(EVENT_CORE_LIBS) +AC_SUBST(EVENT_EXTRA_LIBS) AC_SUBST(EVENT_PTHREADS_LIBS) AC_SUBST(LIBUNISTRING) AC_SUBST(NASMFLAGS) diff --git a/include/dogecoin/net.h b/include/dogecoin/net.h index 84b39ecae..7b8da916b 100644 --- a/include/dogecoin/net.h +++ b/include/dogecoin/net.h @@ -60,6 +60,7 @@ typedef struct dogecoin_node_group_ { char clientstr[1024]; int desired_amount_connected_nodes; const dogecoin_chainparams* chainparams; + struct evhttp* http_server; /* HTTP server for processing API requests */ /* callbacks */ int (*log_write_cb)(const char* format, ...); /* log callback, default=printf */ @@ -103,6 +104,17 @@ typedef struct dogecoin_node_ { uint32_t hints; /* can be use for user defined state */ } dogecoin_node; +/* =================================== */ +/* HTTP SERVER */ +/* =================================== */ + +LIBDOGECOIN_API void dogecoin_http_server_init(dogecoin_node_group* group, const char* bindaddr, int port); +LIBDOGECOIN_API void dogecoin_http_server_shutdown(dogecoin_node_group* group); + +/* =================================== */ +/* LOGGING */ +/* =================================== */ + LIBDOGECOIN_API int net_write_log_printf(const char* format, ...); LIBDOGECOIN_API int net_write_log_null(const char* format, ...); diff --git a/include/dogecoin/spv.h b/include/dogecoin/spv.h index b176b189a..600196681 100644 --- a/include/dogecoin/spv.h +++ b/include/dogecoin/spv.h @@ -64,7 +64,7 @@ typedef struct dogecoin_spv_client_ void *sync_transaction_ctx; } dogecoin_spv_client; -LIBDOGECOIN_API dogecoin_spv_client* dogecoin_spv_client_new(const dogecoin_chainparams *params, dogecoin_bool debug, dogecoin_bool headers_memonly, dogecoin_bool use_checkpoints, dogecoin_bool full_sync, int maxnodes); +LIBDOGECOIN_API dogecoin_spv_client* dogecoin_spv_client_new(const dogecoin_chainparams *params, dogecoin_bool debug, dogecoin_bool headers_memonly, dogecoin_bool use_checkpoints, dogecoin_bool full_sync, int maxnodes, const char *http_server); LIBDOGECOIN_API void dogecoin_spv_client_free(dogecoin_spv_client *client); LIBDOGECOIN_API dogecoin_bool dogecoin_spv_client_load(dogecoin_spv_client *client, const char *file_path, dogecoin_bool prompt); LIBDOGECOIN_API void dogecoin_spv_client_discover_peers(dogecoin_spv_client *client, const char *ips); diff --git a/src/cli/spvnode.c b/src/cli/spvnode.c index 3dfab6321..9ea9a671c 100644 --- a/src/cli/spvnode.c +++ b/src/cli/spvnode.c @@ -48,6 +48,9 @@ #include #include +#include +#include + #if defined(HAVE_CONFIG_H) #include "libdogecoin-config.h" #endif @@ -57,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -184,6 +188,7 @@ static struct option long_options[] = { {"encrypted_file", required_argument, NULL, 'y'}, {"use_tpm", no_argument, NULL, 'j'}, {"master_key", no_argument, NULL, 'k'}, + {"http_server", required_argument, NULL, 'u'}, {"daemon", no_argument, NULL, 'z'}, {NULL, 0, NULL, 0} }; @@ -199,10 +204,10 @@ static void print_version() { */ static void print_usage() { print_version(); - printf("Usage: spvnode (-c|continuous) (-i|-ips ) (-m[--maxpeers] ) (-f ) \ -(-a[--address]
) (-n|-mnemonic ) (-s|-pass_phrase) (-y|-encrypted_file ) \ -(-w|-wallet_file ) (-h|-headers_file ) (-l|[--no_prompt]) (-b[--full_sync]) (-p[--checkpoint]) (-k[--master_key] (-j[--use_tpm]) \ -(-t[--testnet]) (-r[--regtest]) (-d[--debug]) \n"); + printf("Usage: spvnode (-c|continuous) (-i|--ips ) (-m[--maxpeers] ) (-f ) \ +(-a|--address
) (-n|--mnemonic ) (-s|[--pass_phrase]) (-y|--encrypted_file ) \ +(-w|--wallet_file ) (-h|--headers_file ) (-l|[--no_prompt]) (-b[--full_sync]) (-p[--checkpoint]) (-k[--master_key]) (-j[--use_tpm]) \ +(-u|--http_server ) (-t[--testnet]) (-r[--regtest]) (-d[--debug]) \n"); printf("Supported commands:\n"); printf(" scan (scan blocks up to the tip, creates header.db file)\n"); printf("\nExamples: \n"); @@ -238,6 +243,12 @@ static void print_usage() { printf("> ./spvnode -d -c -w \"./main_wallet.db\" -h \"./main_headers.db\" -y 0 -b scan\n\n"); printf("Sync up, with a wallet file \"main_wallet.db\", with encrypted mnemonic 0, show debug info, with a headers file \"main_headers.db\", wait for new blocks, use TPM:\n"); printf("> ./spvnode -d -c -w \"./main_wallet.db\" -h \"./main_headers.db\" -y 0 -j -b scan\n\n"); + printf("Sync up, with a wallet file \"main_wallet.db\", with encrypted mnemonic 0, show debug info, with a headers file \"main_headers.db\", wait for new blocks, use master key:\n"); + printf("> ./spvnode -d -c -w \"./main_wallet.db\" -h \"./main_headers.db\" -y 0 -k -b scan\n\n"); + printf("Sync up, with a wallet file \"main_wallet.db\", with encrypted mnemonic 0, show debug info, with a headers file \"main_headers.db\", wait for new blocks, use master key, use TPM:\n"); + printf("> ./spvnode -d -c -w \"./main_wallet.db\" -h \"./main_headers.db\" -y 0 -k -j -b scan\n\n"); + printf("Sync up, with a wallet file \"main_wallet.db\", show debug info, wait for new blocks, enable http server:\n"); + printf("> ./spvnode -d -c -w \"./main_wallet.db\" -u \"0.0.0.0:8080\" -b scan\n\n"); } @@ -285,6 +296,181 @@ void handle_sigint() { exit(0); } +/** + * This function is called when an http request is received + * It handles the request and sends a response + * + * @param req the request + * @param arg the client + * + * @return Nothing. + */ + +void dogecoin_http_request_cb(struct evhttp_request *req, void *arg) { + dogecoin_spv_client* client = (dogecoin_spv_client*)arg; + dogecoin_wallet* wallet = (dogecoin_wallet*)client->sync_transaction_ctx; + if (!wallet) { + evhttp_send_error(req, HTTP_INTERNAL, "Internal Server Error"); + return; + } + + const struct evhttp_uri* uri = evhttp_request_get_evhttp_uri(req); + const char* path = evhttp_uri_get_path(uri); + + struct evbuffer *evb = NULL; + evb = evbuffer_new(); + if (!evb) { + evhttp_send_error(req, HTTP_INTERNAL, "Internal Server Error"); + return; + } + + if (strcmp(path, "/getBalance") == 0) { + int64_t balance = dogecoin_wallet_get_balance(wallet); + char balance_str[32] = {0}; + koinu_to_coins_str(balance, balance_str); + evbuffer_add_printf(evb, "Wallet balance: %s\n", balance_str); + } else if (strcmp(path, "/getAddresses") == 0) { + vector* addresses = vector_new(10, dogecoin_free); + dogecoin_wallet_get_addresses(wallet, addresses); + for (unsigned int i = 0; i < addresses->len; i++) { + char* address = vector_idx(addresses, i); + evbuffer_add_printf(evb, "address: %s\n", address); + } + vector_free(addresses, true); + } else if (strcmp(path, "/getTransactions") == 0) { + char wallet_total[21]; + dogecoin_mem_zero(wallet_total, 21); + uint64_t wallet_total_u64 = 0; + + if (HASH_COUNT(utxos) > 0) { + dogecoin_utxo* utxo; + dogecoin_utxo* tmp; + HASH_ITER(hh, utxos, utxo, tmp) { + if (!utxo->spendable) { + // For spent UTXOs + evbuffer_add_printf(evb, "%s\n", "----------------------"); + evbuffer_add_printf(evb, "txid: %s\n", utils_uint8_to_hex(utxo->txid, sizeof utxo->txid)); + evbuffer_add_printf(evb, "vout: %d\n", utxo->vout); + evbuffer_add_printf(evb, "address: %s\n", utxo->address); + evbuffer_add_printf(evb, "script_pubkey: %s\n", utxo->script_pubkey); + evbuffer_add_printf(evb, "amount: %s\n", utxo->amount); + evbuffer_add_printf(evb, "confirmations: %d\n", utxo->confirmations); + evbuffer_add_printf(evb, "spendable: %d\n", utxo->spendable); + evbuffer_add_printf(evb, "solvable: %d\n", utxo->solvable); + wallet_total_u64 += coins_to_koinu_str(utxo->amount); + } + } + } + + // Convert and print totals for spent UTXOs. + koinu_to_coins_str(wallet_total_u64, wallet_total); + evbuffer_add_printf(evb, "Spent Balance: %s\n", wallet_total); + } else if (strcmp(path, "/getUTXOs") == 0) { + char wallet_total[21]; + dogecoin_mem_zero(wallet_total, 21); + uint64_t wallet_total_u64_unspent = 0, wallet_total_u64_spent = 0; + + dogecoin_utxo* utxo; + dogecoin_utxo* tmp; + + HASH_ITER(hh, wallet->utxos, utxo, tmp) { + if (utxo->spendable) { + // For unspent UTXOs + evbuffer_add_printf(evb, "----------------------\n"); + evbuffer_add_printf(evb, "Unspent UTXO:\n"); + evbuffer_add_printf(evb, "txid: %s\n", utils_uint8_to_hex(utxo->txid, sizeof(utxo->txid))); + evbuffer_add_printf(evb, "vout: %d\n", utxo->vout); + evbuffer_add_printf(evb, "address: %s\n", utxo->address); + evbuffer_add_printf(evb, "script_pubkey: %s\n", utxo->script_pubkey); + evbuffer_add_printf(evb, "amount: %s\n", utxo->amount); + evbuffer_add_printf(evb, "spendable: %d\n", utxo->spendable); + evbuffer_add_printf(evb, "solvable: %d\n", utxo->solvable); + wallet_total_u64_unspent += coins_to_koinu_str(utxo->amount); + } + } + + // Convert and print totals for unspent UTXOs. + koinu_to_coins_str(wallet_total_u64_unspent, wallet_total); + evbuffer_add_printf(evb, "Total Unspent: %s\n", wallet_total); + } else if (strcmp(path, "/getWallet") == 0) { + // Get the wallet file + FILE* file = wallet->dbfile; + if (file == NULL) { + evhttp_send_error(req, HTTP_NOTFOUND, "Wallet file not found"); + evbuffer_free(evb); + return; + } + + // Get the size of the wallet file + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + rewind(file); + + // Read the wallet file into a buffer + char* buffer = malloc(file_size); + if (buffer == NULL) { + fclose(file); + evhttp_send_error(req, HTTP_INTERNAL, "Internal Server Error"); + evbuffer_free(evb); + return; + } + fread(buffer, 1, file_size, file); + + // Add the buffer to the response buffer + evbuffer_add(evb, buffer, file_size); + + // Set the Content-Type header to "application/octet-stream" for binary data + evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "application/octet-stream"); + + // Clean up + free(buffer); + } else if (strcmp(path, "/getHeaders") == 0) { + // Get the headers file + dogecoin_headers_db* headers_db = (dogecoin_headers_db *)(client->headers_db_ctx); + FILE* file = headers_db->headers_tree_file; + if (file == NULL) { + evhttp_send_error(req, HTTP_NOTFOUND, "Headers file not found"); + evbuffer_free(evb); + return; + } + + // Get the size of the headers file + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + rewind(file); + + // Read the headers file into a buffer + char* buffer = malloc(file_size); + if (buffer == NULL) { + fclose(file); + evhttp_send_error(req, HTTP_INTERNAL, "Internal Server Error"); + evbuffer_free(evb); + return; + } + fread(buffer, 1, file_size, file); + + // Add the buffer to the response buffer + evbuffer_add(evb, buffer, file_size); + + // Set the Content-Type header to "application/octet-stream" for binary data + evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "application/octet-stream"); + + // Clean up + free(buffer); + } else if (strcmp(path, "/getChaintip") == 0) { + dogecoin_blockindex* tip = client->headers_db->getchaintip(client->headers_db_ctx); + evbuffer_add_printf(evb, "Chain tip: %d\n", tip->height); + } else { + evhttp_send_error(req, HTTP_NOTFOUND, "Not Found"); + evbuffer_free(evb); + return; + } + + evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "text/plain"); + evhttp_send_reply(req, HTTP_OK, "OK", evb); + evbuffer_free(evb); +} + int main(int argc, char* argv[]) { int ret = 0; int long_index = 0; @@ -307,6 +493,7 @@ int main(int argc, char* argv[]) { dogecoin_bool encrypted = false; dogecoin_bool master_key = false; dogecoin_bool tpm = false; + char* http_server = NULL; int file_num = NO_FILE; if (argc <= 1 || strlen(argv[argc - 1]) == 0 || argv[argc - 1][0] == '-') { @@ -317,7 +504,7 @@ int main(int argc, char* argv[]) { data = argv[argc - 1]; /* get arguments */ - while ((opt = getopt_long_only(argc, argv, "i:ctrdsm:n:f:y:w:h:a:lbpzkj:", long_options, &long_index)) != -1) { + while ((opt = getopt_long_only(argc, argv, "i:ctrdsm:n:f:y:u:w:h:a:lbpzkj:", long_options, &long_index)) != -1) { switch (opt) { case 'c': quit_when_synced = false; @@ -374,6 +561,9 @@ int main(int argc, char* argv[]) { case 'w': name = optarg; break; + case 'u': + http_server = optarg; + break; case 'z': have_decl_daemon = true; break; @@ -389,7 +579,10 @@ int main(int argc, char* argv[]) { if (strcmp(data, "scan") == 0) { dogecoin_ecc_start(); - dogecoin_spv_client* client = dogecoin_spv_client_new(chain, debug, (dbfile && (dbfile[0] == '0' || (strlen(dbfile) > 1 && dbfile[0] == 'n' && dbfile[0] == 'o'))) ? true : false, use_checkpoint, full_sync, maxnodes); + dogecoin_spv_client* client = dogecoin_spv_client_new(chain, debug, (dbfile && (dbfile[0] == '0' || (strlen(dbfile) > 1 && dbfile[0] == 'n' && dbfile[0] == 'o'))) ? true : false, use_checkpoint, full_sync, maxnodes, http_server); + if (http_server) { + evhttp_set_gencb(client->nodegroup->http_server, dogecoin_http_request_cb, client); + } client->header_message_processed = spv_header_message_processed; client->sync_completed = spv_sync_completed; signal(SIGINT, handle_sigint); diff --git a/src/net.c b/src/net.c index 00cf2c632..4076fcf7a 100644 --- a/src/net.c +++ b/src/net.c @@ -58,6 +58,7 @@ #include #include #include +#include #include #include @@ -74,6 +75,44 @@ static const int DOGECOIN_PERIODICAL_NODE_TIMER_S = 3; static const int DOGECOIN_PING_INTERVAL_S = 120; static const int DOGECOIN_CONNECT_TIMEOUT_S = 10; +/** + * Initializes the HTTP server part of the node group. + * + * @param group The node group containing the nodes and event base. + * @param bindaddr The address on which the HTTP server listens. + * @param port The port number on which the HTTP server listens. + */ +void dogecoin_http_server_init(dogecoin_node_group* group, const char* bindaddr, int port) { + assert(group != NULL); + assert(group->event_base != NULL); + + group->http_server = evhttp_new(group->event_base); + if (!group->http_server) { + fprintf(stderr, "Could not create a new HTTP server.\n"); + exit(1); + } + + if (evhttp_bind_socket(group->http_server, bindaddr, port) != 0) { + fprintf(stderr, "Could not bind HTTP server to port %d.\n", port); + exit(1); + } + + printf("HTTP server initialized on port %d\n", port); +} + +/** + * Shuts down the HTTP server. + * + * @param group The node group containing the nodes and event base. + */ +void dogecoin_http_server_shutdown(dogecoin_node_group* group) { + assert(group != NULL); + assert(group->http_server != NULL); + + evhttp_free(group->http_server); + group->http_server = NULL; +} + /** * This function is used to print debug messages to the log file * @@ -452,6 +491,7 @@ dogecoin_node_group* dogecoin_node_group_new(const dogecoin_chainparams* chainpa node_group->handshake_done_cb = NULL; node_group->log_write_cb = net_write_log_null; node_group->desired_amount_connected_nodes = 8; + node_group->http_server = NULL; return node_group; } @@ -467,6 +507,10 @@ void dogecoin_node_group_shutdown(dogecoin_node_group *group) { dogecoin_node* node = vector_idx(group->nodes, i); dogecoin_node_disconnect(node); } + + if (group->http_server) { + dogecoin_http_server_shutdown(group); + } } /** diff --git a/src/spv.c b/src/spv.c index 04c21b30e..de3e87162 100644 --- a/src/spv.c +++ b/src/spv.c @@ -114,10 +114,14 @@ void dogecoin_net_set_spv(dogecoin_node_group *nodegroup) * @param params The chainparams struct that we created earlier. * @param debug If true, the node will print out debug messages to stdout. * @param headers_memonly If true, the headers database will not be loaded from disk. + * @param use_checkpoints If true, the client will use checkpoints. + * @param full_sync If true, the client will do a full sync. + * @param maxnodes The maximum amount of nodes that the client will connect to. + * @param http_server The IP and port for the HTTP server; if NULL, the HTTP server will not be initialized. * * @return A pointer to a dogecoin_spv_client object. */ -dogecoin_spv_client* dogecoin_spv_client_new(const dogecoin_chainparams *params, dogecoin_bool debug, dogecoin_bool headers_memonly, dogecoin_bool use_checkpoints, dogecoin_bool full_sync, int maxnodes) +dogecoin_spv_client* dogecoin_spv_client_new(const dogecoin_chainparams *params, dogecoin_bool debug, dogecoin_bool headers_memonly, dogecoin_bool use_checkpoints, dogecoin_bool full_sync, int maxnodes, const char* http_server) { dogecoin_spv_client* client; client = dogecoin_calloc(1, sizeof(*client)); @@ -155,6 +159,18 @@ dogecoin_spv_client* dogecoin_spv_client_new(const dogecoin_chainparams *params, client->header_message_processed = NULL; client->sync_transaction = NULL; + if (http_server) { + // split ip and port + char* http_server_copy = strdup(http_server); + char* ip = strtok(http_server_copy, ":"); + char* port = strtok(NULL, ":"); + + // HTTP server initialization + dogecoin_http_server_init(client->nodegroup, ip, atoi(port)); + client->nodegroup->log_write_cb("HTTP server initialized\n"); + free(http_server_copy); + } + return client; } diff --git a/src/wallet.c b/src/wallet.c index 1ecd903df..98b3128d7 100644 --- a/src/wallet.c +++ b/src/wallet.c @@ -374,7 +374,7 @@ dogecoin_wallet* dogecoin_wallet_new(const dogecoin_chainparams *params) wallet->chain = params; set_wallet_filename(wallet, params); wallet->hdkeys_rbtree = 0; - wallet->utxos = utxos; + wallet->utxos = 0; wallet->unspent_rbtree = 0; wallet->spends_rbtree = 0; wallet->vec_wtxes = vector_new(10, (void (*)(void *)) dogecoin_wallet_wtx_free); @@ -992,6 +992,8 @@ dogecoin_bool dogecoin_wallet_load(dogecoin_wallet* wallet, const char* file_pat } } + wallet->utxos = utxos; + return true; } diff --git a/test/spv_tests.c b/test/spv_tests.c index 4ef8ea359..7b35097fe 100644 --- a/test/spv_tests.c +++ b/test/spv_tests.c @@ -72,7 +72,12 @@ void test_spv() unlink(headersfile); // init new spv client with debugging off and syncing to memory: - dogecoin_spv_client* client = dogecoin_spv_client_new(chain, false, true, true, false, 8); +#ifndef __APPLE__ + // due to TBD anomaly in ci environment, enable http server if not running Apple + dogecoin_spv_client* client = dogecoin_spv_client_new(chain, false, true, true, false, 8, "localhost:8888"); +#else + dogecoin_spv_client* client = dogecoin_spv_client_new(chain, false, true, true, false, 8, NULL); +#endif client->header_message_processed = test_spv_header_message_processed; client->sync_completed = test_spv_sync_completed; dogecoin_spv_client_load(client, headersfile, false); @@ -99,7 +104,7 @@ void test_reorg() { unlink(headersfile); // Initialize SPV client - dogecoin_spv_client* client = dogecoin_spv_client_new(chain, false, true, false, false, 8); + dogecoin_spv_client* client = dogecoin_spv_client_new(chain, false, true, false, false, 8, NULL); client->header_message_processed = test_spv_header_message_processed; client->sync_completed = test_spv_sync_completed; dogecoin_spv_client_load(client, headersfile, false);