diff --git a/CMakeLists.txt b/CMakeLists.txt index dd220822f..e4a69c6bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -392,10 +392,15 @@ IF(WITH_WALLET) ENDIF() IF(WITH_NET) + INSTALL(FILES + include/dogecoin/rest.h + DESTINATION include/dogecoin + ) TARGET_SOURCES(${LIBDOGECOIN_NAME} ${visibility} src/headersdb_file.c src/net.c src/protocol.c + src/rest.c src/spv.c ) diff --git a/Makefile.am b/Makefile.am index a9f3da4fd..306196a73 100644 --- a/Makefile.am +++ b/Makefile.am @@ -281,12 +281,14 @@ noinst_HEADERS += \ include/dogecoin/headersdb_file.h \ include/dogecoin/protocol.h \ include/dogecoin/net.h \ + include/dogecoin/rest.h \ include/dogecoin/spv.h libdogecoin_la_SOURCES += \ src/headersdb_file.c \ src/net.c \ src/protocol.c \ + src/rest.c \ src/spv.c libdogecoin_la_LIBADD += $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) diff --git a/include/dogecoin/rest.h b/include/dogecoin/rest.h new file mode 100644 index 000000000..3323a70a5 --- /dev/null +++ b/include/dogecoin/rest.h @@ -0,0 +1,42 @@ +/* + + The MIT License (MIT) + + Copyright (c) 2024 edtubbs, bluezr + Copyright (c) 2024 The Dogecoin Foundation + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef __LIBDOGECOIN_REST_H__ +#define __LIBDOGECOIN_REST_H__ + +#include + +#include +#include + +LIBDOGECOIN_BEGIN_DECL + +LIBDOGECOIN_API void dogecoin_http_request_cb(struct evhttp_request *req, void *arg); + +LIBDOGECOIN_END_DECL + +#endif // __LIBDOGECOIN_REST_H__ diff --git a/src/cli/spvnode.c b/src/cli/spvnode.c index 9ea9a671c..c02f11b54 100644 --- a/src/cli/spvnode.c +++ b/src/cli/spvnode.c @@ -40,6 +40,7 @@ #include #endif +#include #include #include #include @@ -67,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -296,181 +298,6 @@ 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; @@ -563,6 +390,10 @@ int main(int argc, char* argv[]) { break; case 'u': http_server = optarg; + if (!isdigit(http_server[0])) { + printf("Please add the ip and port after -u and try again. e.g. '-u 0.0.0.0:8080'\n"); + exit(EXIT_FAILURE); + } break; case 'z': have_decl_daemon = true; diff --git a/src/rest.c b/src/rest.c new file mode 100644 index 000000000..75cc7ae91 --- /dev/null +++ b/src/rest.c @@ -0,0 +1,219 @@ +/* + + The MIT License (MIT) + + Copyright (c) 2024 edtubbs, bluezr + Copyright (c) 2024 The Dogecoin Foundation + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include + +#include +#include +#include +#include +#include + + +/** + * 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; + + 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; + } + size_t result = fread(buffer, 1, file_size, file); + if (!result) { + evbuffer_free(evb); + free(buffer); + return; + } + + // 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; + } + size_t result = fread(buffer, 1, file_size, file); + if (!result) { + evbuffer_free(evb); + free(buffer); + return; + } + + // 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); +}