#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Les variables globales */ char* simple_db_path = "data/simpledb"; FILE* blockchain_fd = NULL; /* * Les outils */ static void signal_handler(int sig){ switch(sig){ case SIGUSR1: // signal USR1 break; case SIGUSR2: // Signal USR2 break; case SIGHUP: // Reload conf and reinitialize processus break; case SIGINT: // CTRL+C case SIGTERM: // Terminate : kill -15 exit(0); break; } } static btc_bool file_exist(const char *filename) { struct stat buffer; return (stat (filename, &buffer) == 0); } /* * Les fonctions spécfiques */ void request_header_or_block_from_hashbin(btc_node *node, uint256* hash, btc_bool blocks) { if (node == NULL) return; if (hash == NULL) return; // request next headers vector* blocklocators = vector_new(1, NULL); cstring* getheader_msg = cstr_new_sz(256); cstring* p2p_msg = NULL; vector_add(blocklocators, (void *)hash); btc_p2p_msg_getheaders(blocklocators, NULL, getheader_msg); /* create p2p message */ p2p_msg = btc_p2p_message_new(node->nodegroup->chainparams->netmagic, (blocks ? BTC_MSG_GETBLOCKS : BTC_MSG_GETHEADERS), getheader_msg->str, getheader_msg->len); /* send message */ btc_node_send(node, p2p_msg); node->state |= ( blocks ? NODE_BLOCKSYNC : NODE_HEADERSYNC); /* remember last headers request time */ if (blocks) { node->time_last_request = time(NULL); } /* cleanup */ vector_free(blocklocators, true); cstr_free(p2p_msg, true); cstr_free(getheader_msg, true); } void request_header_or_block_from_hashhex(btc_node *node, const char* hex, btc_bool blocks) { if (node == NULL) return; if (hex == NULL) return; uint256 *hash = btc_calloc(1, sizeof(uint256)); if (hash == NULL) return; utils_uint256_sethex((char *)hex, (uint8_t *)hash); request_header_or_block_from_hashbin(node,hash,blocks); } uint256 request_headers_or_blocks_hash; btc_bool request_headers_or_blocks_hash_init = false; void request_headers_or_blocks(btc_node *node, btc_bool blocks) { /* Sans initialisation, on repart du Genesis Block */ if (!request_headers_or_blocks_hash_init) { node->nodegroup->log_write_cb("STATUS : Setting requested hash with genesis block\n"); memcpy(&request_headers_or_blocks_hash, node->nodegroup->chainparams->genesisblockhash, sizeof(uint256)); request_headers_or_blocks_hash_init = true; } // On lance la recherche la synchro if (request_headers_or_blocks_hash_init) request_header_or_block_from_hashbin(node, &request_headers_or_blocks_hash, blocks); } static void find_and_add_nodes(btc_node_group* group) { const btc_dns_seed seed = btc_chainparams_main.dnsseeds[0]; vector* ips; /* collect and add some btc nodes to the group */ ips = vector_new(group->desired_amount_connected_nodes + 5, free); btc_get_peers_from_dns(seed.domain, ips, btc_chainparams_main.default_port, AF_INET); for (unsigned int i = 0; ilen; i++) { /* create a node */ btc_node *node = btc_node_new(); char *ip = (char *)vector_idx(ips, i); btc_node_set_ipport(node, ip); btc_node_group_add_node(group, node); } vector_free(ips, true); /* connect to the next node */ btc_node_group_connect_next_nodes(group); } static void check_connected_nodes(btc_node_group* group) { int nb_connected = btc_node_group_amount_of_connected_nodes(group, NODE_CONNECTED); if (nb_connected < group->desired_amount_connected_nodes) { group->log_write_cb("STATUS : new connection needed (%d node connected)\n", nb_connected); find_and_add_nodes(group); } } /* * Les callbacks */ static int cb_default_write_log(const char *format, ...) { va_list args; va_start(args, format); vprintf(format, args); va_end(args); return 1; } FILE* cb_file_write_log_fp = NULL; btc_bool cb_file_write_log_flag = false; char* cb_file_write_log_filter = ""; static int cb_file_write_log(const char *format, ...) { if (cb_file_write_log_flag) { // Appliquer le filtre unsigned int limit = strlen(cb_file_write_log_filter); for(unsigned int i=0; i < limit; i++) if (format[i] != cb_file_write_log_filter[i]) return 1; // Ouvrir le flix si besoin if (cb_file_write_log_fp == NULL) cb_file_write_log_fp = fopen("log.txt","a+"); // Impossible d'ouvrir le fichier de log if (cb_file_write_log_fp == NULL) return 0; va_list args; va_start(args, format); vfprintf(cb_file_write_log_fp, format, args); va_end(args); fflush(cb_file_write_log_fp); } else { if (cb_file_write_log_fp != NULL) { // Fermer le flux fclose(cb_file_write_log_fp); cb_file_write_log_fp = NULL; } } return 1; } static btc_bool cb_timer(btc_node *node, uint64_t *now) { check_connected_nodes(node->nodegroup); /* return true = run internal timer logic (ping, disconnect-timeout, etc.) */ return true; } static btc_bool cb_parse_cmd(struct btc_node_ *node, btc_p2p_msg_hdr *hdr, struct const_buffer *buf) { (void)(node); (void)(hdr); (void)(buf); return true; } static void cb_node_connection_state_changed(struct btc_node_ *node) { int nb_connected = btc_node_group_amount_of_connected_nodes(node->nodegroup, NODE_CONNECTED); node->nodegroup->log_write_cb("STATUS : connexion change status (%d node connected)\n", nb_connected); check_connected_nodes(node->nodegroup); } unsigned int cb_handshake_done_bestknownheight = 0; static void cb_handshake_done(struct btc_node_ *node) { int nb_connected = btc_node_group_amount_of_connected_nodes(node->nodegroup, NODE_CONNECTED); node->nodegroup->log_write_cb("STATUS : handshake (%d node connected)\n", nb_connected); /* make sure only one node is used for header sync */ for(size_t i =0;i< node->nodegroup->nodes->len; i++) { btc_node *check_node = vector_idx(node->nodegroup->nodes, i); if ( ( (check_node->state & NODE_HEADERSYNC) == NODE_HEADERSYNC || (check_node->state & NODE_BLOCKSYNC) == NODE_BLOCKSYNC ) && (check_node->state & NODE_CONNECTED) == NODE_CONNECTED) return; } /* * Si le nouveau noeud a une meilleur connaissance de la blockchain que nous * On relance une synchro de la blockchain * Si possible on repart de 20 blocks en arrière * Cela pour éviter de rester coincés dans un fork * * De même, on garde une marge de manoeuvre dans les positions * du fichier */ if (node->bestknownheight > cb_handshake_done_bestknownheight) { if (blockchain_fd != NULL) { /* * Définir et intialiser un tampon de 20 blocks */ #define MARGE_TAMPON 25 char buffer[MARGE_TAMPON][65]; uint32_t read_height[MARGE_TAMPON]; fpos_t fpos[MARGE_TAMPON]; (void) fseek(blockchain_fd, 0L, SEEK_SET); for(uint i = 0; i < MARGE_TAMPON; i++) { fgetpos(blockchain_fd, fpos+i); strcpy(buffer[i],""); read_height[i] = 0; } request_headers_or_blocks_hash_init = false; while(fscanf(blockchain_fd, "%s %d\n", buffer[0], read_height)) { /* Décaler le tampon d'un cran vers le bas */ for(uint i = (MARGE_TAMPON-1); i > 0 ; i--) { strcpy(buffer[i], buffer[i-1]); read_height[i] = read_height[i-1]; memcpy(fpos+i, fpos+(i-1), sizeof(fpos_t)); } if (feof(blockchain_fd)) break; } if (read_height[0] > MARGE_TAMPON) { int outlen; node->nodegroup->log_write_cb("STATUS : Setting requested hash with %s\n", buffer[19]); utils_reverse_hex(buffer[19], 64); utils_hex_to_bin(buffer[19], request_headers_or_blocks_hash, 64, &outlen); cb_handshake_done_bestknownheight = read_height[19]; request_headers_or_blocks_hash_init = true; fsetpos(blockchain_fd, fpos+(MARGE_TAMPON-1)); } } node->nodegroup->log_write_cb("STATUS : starting header sync from node %d\n", node->nodeid); node->state |= NODE_HEADERSYNC; request_headers_or_blocks(node, false); } else { node->state &= ~NODE_HEADERSYNC; } } int btc_header_hash_compare(uint8_t *hashA, uint8_t *hashB) { /* byte per byte compare */ for (unsigned int i = 0; i < sizeof(uint256); i++) { uint8_t iA = hashA[i]; uint8_t iB = hashB[i]; if (iA > iB) return -1; else if (iA < iB) return 1; } return 0; } static void post_inv_cmd(struct btc_node_ *node, struct const_buffer *buf) { struct const_buffer buf_copy = { buf->p, buf->len }; btc_bool contains_block = false; uint32_t vsize; // directly create a getdata message cstring *p2p_msg = btc_p2p_message_new(node->nodegroup->chainparams->netmagic, BTC_MSG_GETDATA, buf->p, buf->len); if (!deser_varlen(&vsize, &buf_copy)) return; for (unsigned int i = 0; i < vsize; i++) { uint8_t hash[36]; uint32_t type; if (!deser_u32(&type, &buf_copy)) return; if (!deser_u256(hash, &buf_copy)) return; contains_block |= (type == BTC_INV_TYPE_BLOCK); } /* * This a MSG for inventory one block */ if (contains_block && (vsize == 1)) { node->time_last_request = time(NULL); btc_node_send(node, p2p_msg); } /* cleanup */ cstr_free(p2p_msg, true); } static void cb_post_cmd(struct btc_node_ *node, btc_p2p_msg_hdr *hdr, struct const_buffer *buf) { struct const_buffer buf_copy = { buf->p, buf->len }; uint256 hash_bin_val; char hash_hex_str[65]; /* * MSG is inventory */ if (strcmp(hdr->command, BTC_MSG_INV) == 0) { post_inv_cmd(node, buf); } /* * MSG is block data */ if (strcmp(hdr->command, BTC_MSG_BLOCK) == 0) { FILE* block_fd; char block_filename[2000]; char prev_hex_str[65]; btc_bool is_a_known_block = false; const char* coinbase_hash = "0000000000000000000000000000000000000000000000000000000000000000"; btc_block_header header; uint32_t nb_tx; uint32_t block_height = 0; uint64_t total_outputs = 0; uint64_t total_fees = 0; uint64_t reward = 5000000000; /* * La synchro des ent^tes n'est pas terminée * Dans ce cas on ne traite pas le bloc ... */ if (node->bestknownheight > cb_handshake_done_bestknownheight) return; /* * Désérialisation du bloc */ if (!btc_block_header_deserialize(&header, &buf_copy)) return; if (!deser_varlen(&nb_tx, &buf_copy)) return; btc_block_header_hash(&header, (uint8_t *)&hash_bin_val); memset(hash_hex_str, 0, sizeof(hash_hex_str)); utils_bin_to_hex(hash_bin_val, sizeof(hash_bin_val), hash_hex_str); utils_reverse_hex(hash_hex_str, 64); memset(prev_hex_str, 0, sizeof(prev_hex_str)); utils_bin_to_hex(header.prev_block, sizeof(header.prev_block), prev_hex_str); utils_reverse_hex(prev_hex_str, 64); // Nous connaissons déjà ce block comme étant le sommet de la blockchain if (0 == btc_header_hash_compare((uint8_t *)&hash_bin_val, (uint8_t *)&request_headers_or_blocks_hash)) return; // Est-ce un nouveau sommet de la blockchain ? if (0 == btc_header_hash_compare((uint8_t *)&header.prev_block, (uint8_t *)&request_headers_or_blocks_hash)) { cb_handshake_done_bestknownheight += 1; memcpy(&request_headers_or_blocks_hash, &hash_bin_val, sizeof(uint256)); block_height = cb_handshake_done_bestknownheight; node->nodegroup->log_write_cb("STATUS : New chain top detected (height is now %d) !\n",cb_handshake_done_bestknownheight); } else { node->nodegroup->log_write_cb("STATUS : Orphan block detected !\n"); /* * TODO * Conserver une liste des blocks orphelins * Grâce au timer, tenter de les rattacher après coup * * On peut recevoir un nouveau block alors que nous sommes * en train de se sycnhroniser. * * Il peut également s'agir d'un début de fork. */ if (blockchain_fd != NULL) { char buffer[65]; uint32_t read_height = 0; (void) fseek(blockchain_fd, 0L, SEEK_SET); while(fscanf(blockchain_fd, "%s %d\n", buffer, &read_height)) { if (!strcmp(buffer, hash_hex_str)) { /* * Ce block est déjà connu */ is_a_known_block = true; block_height = read_height; (void) fseek(blockchain_fd, 0L, SEEK_END); node->nodegroup->log_write_cb("STATUS : Known block detected (%d) !\n", block_height); break; } if (!strcmp(buffer, prev_hex_str)) { /* * Ce block est potentiellement un début de fork * Il n'est pas au sommet * Mais on connait son parent */ block_height = read_height + 1; } if (feof(blockchain_fd)) break; } (void) fseek(blockchain_fd, 0L, SEEK_END); } if (!is_a_known_block) { if (block_height > 0) { /* * Ce block est potentiellement un début de fork * On le place au sommet * Attention : * Je ne suis pas sûr que c'est ce qu'il faut faire ... */ memcpy(&request_headers_or_blocks_hash, &hash_bin_val, sizeof(uint256)); cb_handshake_done_bestknownheight = block_height; node->nodegroup->log_write_cb("STATUS : Fork block detected (%d) !\n", block_height); } else { /* * Le block a-t-il moins de 2 heures ? */ if (difftime(time(NULL), header.timestamp) < (2*3600)) { /* * Ce block est un orphelin * TODO * Keep in memory and use the timer to try to connect it again to the blockchain */ if (node->bestknownheight > cb_handshake_done_bestknownheight) { /* * La synchronisation n'est pas finie */ node->nodegroup->log_write_cb("STATUS : Orphan block while synchro !\n"); } else { node->nodegroup->log_write_cb("STATUS : Orphan block detected !\n"); } } return; } } } if (!is_a_known_block) { if (blockchain_fd != NULL) { (void) fseek(blockchain_fd, 0L, SEEK_END); fprintf(blockchain_fd, "%s %d\n", hash_hex_str, block_height); fflush(blockchain_fd); } } sprintf(block_filename,"%s/blocks/%u_%s", simple_db_path, block_height, hash_hex_str); if (file_exist(block_filename)) return; // Save the block in a file block_fd = fopen(block_filename,"wb"); if (block_fd == NULL) return; fwrite((const void*) &block_height, sizeof(uint32_t), 1, block_fd); fwrite((const void*) &hash_bin_val, sizeof(uint256), 1, block_fd); fwrite((const void*) &header.version, sizeof(int32_t), 1, block_fd); fwrite((const void*) &header.timestamp, sizeof(uint32_t), 1, block_fd); fwrite((const void*) &header.nonce, sizeof(uint32_t), 1, block_fd); fwrite((const void*) &header.prev_block, sizeof(uint256), 1, block_fd); fwrite((const void*) &header.merkle_root, sizeof(uint256), 1, block_fd); fwrite((const void*) &nb_tx, sizeof(uint32_t), 1, block_fd); for (unsigned int i = 0; i < nb_tx; i++) { uint64_t tx_total_outputs = 0; size_t consummed = 0; btc_tx* tx =btc_tx_new(); //needs to be on the heap btc_tx_deserialize(buf_copy.p, buf_copy.len, tx, &consummed, true); deser_skip(&buf_copy, consummed); btc_tx_hash(tx, hash_bin_val); utils_bin_to_hex(hash_bin_val, sizeof(hash_bin_val), hash_hex_str); utils_reverse_hex(hash_hex_str, 64); for (size_t victx = 0; victx < tx->vin->len; victx++) { btc_tx_in* tx_in = vector_idx(tx->vin, victx); char* hex_txin = utils_uint8_to_hex(tx_in->prevout.hash, 32); utils_reverse_hex(hex_txin, strlen(hex_txin)); if (!strncmp(hex_txin,coinbase_hash,strlen(coinbase_hash))) strcpy(hex_txin,"COINBASE"); } for (size_t voctx = 0; voctx < tx->vout->len; voctx++) { btc_tx_out* tx_out = vector_idx(tx->vout, voctx); tx_total_outputs += tx_out->value; } fwrite((const void*) &hash_bin_val, sizeof(uint256), 1, block_fd); fwrite((const void*) &tx->locktime, sizeof(uint32_t), 1, block_fd); fwrite((const void*) &tx_total_outputs, sizeof(uint64_t), 1, block_fd); if (i == 0) { // Compute fees while(reward > 0) { if (reward < tx_total_outputs) break; reward /= 2; } total_fees = tx_total_outputs - reward; } total_outputs += tx_total_outputs; btc_tx_free(tx); } fclose(block_fd); fflush(stdout); } /* * MSG is headers data */ if (strcmp(hdr->command, BTC_MSG_HEADERS) == 0) { uint32_t amount_of_headers; if (!deser_varlen(&amount_of_headers, &buf_copy)) return; node->nodegroup->log_write_cb("STATUS : (%d) headers received\n", amount_of_headers); while(amount_of_headers--) { uint256 hash_bin_val; btc_block_header header; if (!btc_block_header_deserialize(&header, &buf_copy)) return; if (!deser_skip(&buf_copy, 1)) return; btc_block_header_hash(&header, (uint8_t *)&hash_bin_val); if (0 == btc_header_hash_compare((uint8_t *)&header.prev_block, (uint8_t *)&request_headers_or_blocks_hash)) { cb_handshake_done_bestknownheight += 1; memcpy(&request_headers_or_blocks_hash, &hash_bin_val, sizeof(uint256)); memset(hash_hex_str, 0, sizeof(hash_hex_str)); utils_bin_to_hex(hash_bin_val, sizeof(hash_bin_val), hash_hex_str); utils_reverse_hex(hash_hex_str, 64); if (blockchain_fd != NULL) { // Ne rajouter un bloc que si on ne le connait pas char buffer[65]; uint32_t read_height; btc_bool flag_local = false; /* * Cette ligen a été mise en commentaire * Car elle ralenti énormément le processus de synchro * Elle fait repartir du début du fichier à chaque fois. * Pour accélérer le processus, on se cale en début de synchro (cd_handcheck_done) * Puis on ne fait qu'avancer dans le fichier * Attention : * Cela suppose que le noeud nous envoie les entêtes de blocks dans l'ordre */ // (void) fseek(blockchain_fd, 0L, SEEK_SET); while(fscanf(blockchain_fd, "%s %d\n", buffer, &read_height)) { if (!strcmp(buffer, hash_hex_str)) { flag_local = true; break; } if (feof(blockchain_fd)) break; } if (!flag_local) { (void) fseek(blockchain_fd, 0L, SEEK_END); fprintf(blockchain_fd, "%s %d\n", hash_hex_str,cb_handshake_done_bestknownheight ); } } } else { node->nodegroup->log_write_cb("STATUS : Orphan block header !\n"); } } if (node->bestknownheight > cb_handshake_done_bestknownheight) { /* * La synchronisation n'est pas finie * Demander d'autres blocs */ node->nodegroup->log_write_cb("STATUS : bestknownheigt (%d / %d)\n", cb_handshake_done_bestknownheight, node->bestknownheight); request_headers_or_blocks(node, false); } else { node->nodegroup->log_write_cb("STATUS : Headers sync is finished !\n", cb_handshake_done_bestknownheight, node->bestknownheight); node->state &= ~NODE_HEADERSYNC; if (blockchain_fd != NULL) fflush(blockchain_fd); } } } /* * Gestion du fichier de chaine de blocks */ int init_blockchain_file() { const char* blockchain_filename = "blockchain.txt"; char* blockchain_full_filename = NULL; char buffer[65]; uint32_t block_height; blockchain_full_filename = calloc(strlen(simple_db_path)+strlen(blockchain_filename)+2, sizeof(char)); if (blockchain_full_filename == NULL) return 1; sprintf(blockchain_full_filename,"%s/%s", simple_db_path, blockchain_filename); if (!file_exist(blockchain_full_filename)) { blockchain_fd = fopen(blockchain_full_filename,"w"); if (blockchain_fd != NULL) { int outlen; /* * Init with Genesis Block */ sprintf(buffer, "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); block_height = 0; fprintf(blockchain_fd, "%s %d\n", buffer, block_height); fclose(blockchain_fd); blockchain_fd = NULL; utils_reverse_hex(buffer, 64); utils_hex_to_bin(buffer, request_headers_or_blocks_hash, 64, &outlen); request_headers_or_blocks_hash_init = true; cb_handshake_done_bestknownheight = block_height; } } blockchain_fd = fopen(blockchain_full_filename,"r+"); if (blockchain_fd != NULL) { while(fscanf(blockchain_fd, "%s %d\n", buffer, &block_height)) { if (block_height > cb_handshake_done_bestknownheight) { int outlen; utils_reverse_hex(buffer, 64); utils_hex_to_bin(buffer, request_headers_or_blocks_hash, 64, &outlen); request_headers_or_blocks_hash_init = true; cb_handshake_done_bestknownheight = block_height; } if (feof(blockchain_fd)) break; } } free(blockchain_full_filename); return 0; } int main(int ac, char** av) { btc_node_group* group = NULL; cb_file_write_log_flag = false; cb_file_write_log_filter = "STATUS"; /* * Daemonize */ daemonize(); /* * Trapper les signaux pour fermer correctement le programme */ if (signal(SIGCHLD,SIG_IGN) == SIG_ERR) { printf("\ncan't catch SIGCHLD\n"); return 1; } if (signal(SIGTSTP,SIG_IGN) == SIG_ERR) { printf("\ncan't catch SIGTSTP\n"); return 1; } if (signal(SIGTTOU,SIG_IGN) == SIG_ERR) { printf("\ncan't catch SIGTTOU\n"); return 1; } if (signal(SIGTTIN,SIG_IGN) == SIG_ERR) { printf("\ncan't catch SIGTTIN\n"); return 1; } if (signal(SIGHUP,signal_handler) == SIG_ERR) { printf("\ncan't catch SIGHUP\n"); return 1; } if (signal(SIGTERM,signal_handler) == SIG_ERR) { printf("\ncan't catch SIGTERM\n"); return 1; } if (signal(SIGINT, signal_handler) == SIG_ERR) { printf("\ncan't catch SIGINT\n"); return 1; } /* * Gestion du fichier de chaine de blocks */ if (ac > 1) simple_db_path = av[1]; if (0 != init_blockchain_file()) return 1; /* * Create a node group */ group = btc_node_group_new(NULL); group->desired_amount_connected_nodes = 1; group->periodic_timer_cb = cb_timer; group->log_write_cb = cb_file_write_log; group->parse_cmd_cb = cb_parse_cmd; group->postcmd_cb = cb_post_cmd; group->node_connection_state_changed_cb = cb_node_connection_state_changed; group->handshake_done_cb = cb_handshake_done; sprintf(group->clientstr,"TOPISTO is using libbtc"); find_and_add_nodes(group); /* connect to the next node */ btc_node_group_connect_next_nodes(group); /* start the event loop */ btc_node_group_event_loop(group); /* cleanup */ btc_node_group_free(group); //will also free the nodes structures from the heap return 0; }