/* * Authors: * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net> * DeM Labs Inc. https://demlabs.net * Cellframe Network https://cellframe.net * Copyright (c) 2022 * All rights reserved. This file is part of DAP (Deus Applications Prototypes) the open source project DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. DAP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> #include "dap_chain_net_tx.h" #include "dap_chain_cell.h" #include "dap_chain_common.h" #include "dap_chain_datum_tx_in_cond.h" #include "dap_chain_tx.h" #include "dap_list.h" #define LOG_TAG "dap_chain_net_tx" /** * @brief For now it returns all COND_IN transactions * @param a_net * @param a_srv_uid * @param a_search_type * @return Hash lists of dap_chain_datum_tx_item_t with conditional transaction and it spending if present */ dap_chain_datum_tx_spends_items_t * dap_chain_net_get_tx_cond_all_with_spends_by_srv_uid(dap_chain_net_t * a_net, const dap_chain_net_srv_uid_t a_srv_uid, const dap_time_t a_time_from, const dap_time_t a_time_to, const dap_chain_net_tx_search_type_t a_search_type) { dap_ledger_t * l_ledger = a_net->pub.ledger; dap_chain_datum_tx_spends_items_t * l_ret = DAP_NEW_Z(dap_chain_datum_tx_spends_items_t); switch (a_search_type) { case TX_SEARCH_TYPE_NET: case TX_SEARCH_TYPE_CELL: case TX_SEARCH_TYPE_LOCAL: case TX_SEARCH_TYPE_CELL_SPENT: case TX_SEARCH_TYPE_NET_UNSPENT: case TX_SEARCH_TYPE_CELL_UNSPENT: case TX_SEARCH_TYPE_NET_SPENT: { // pass all chains for ( dap_chain_t * l_chain = a_net->pub.chains; l_chain; l_chain = l_chain->next){ dap_chain_cell_t * l_cell, *l_cell_tmp; // Go through all cells HASH_ITER(hh,l_chain->cells,l_cell, l_cell_tmp){ dap_chain_atom_iter_t * l_atom_iter = l_chain->callback_atom_iter_create(l_chain,l_cell->id, false ); // try to find transaction in chain ( inside shard ) size_t l_atom_size = 0; dap_chain_atom_ptr_t l_atom = l_chain->callback_atom_iter_get_first(l_atom_iter, &l_atom_size); // Check atoms in chain while(l_atom && l_atom_size) { size_t l_datums_count = 0; dap_chain_datum_t **l_datums = l_chain->callback_atom_get_datums(l_atom, l_atom_size, &l_datums_count); // transaction dap_chain_datum_tx_t *l_tx = NULL; for (size_t i = 0; i < l_datums_count; i++) { // Check if its transaction if (l_datums && (l_datums[i]->header.type_id == DAP_CHAIN_DATUM_TX)) { l_tx = (dap_chain_datum_tx_t *)l_datums[i]->data; } // If found TX if (l_tx){ // Check for time from if(a_time_from && l_tx->header.ts_created < a_time_from) continue; // Check for time to if(a_time_to && l_tx->header.ts_created > a_time_to) continue; if(a_search_type == TX_SEARCH_TYPE_CELL_SPENT || a_search_type == TX_SEARCH_TYPE_NET_SPENT ){ dap_hash_fast_t * l_tx_hash = dap_chain_node_datum_tx_calc_hash(l_tx); bool l_is_spent = dap_chain_ledger_tx_spent_find_by_hash(l_ledger,l_tx_hash); DAP_DELETE(l_tx_hash); if(!l_is_spent) continue; } // Go through all items uint32_t l_tx_items_pos = 0, l_tx_items_size = l_tx->header.tx_items_size; int l_item_idx = 0; while (l_tx_items_pos < l_tx_items_size) { uint8_t *l_item = l_tx->tx_items + l_tx_items_pos; int l_item_size = dap_chain_datum_item_tx_get_size(l_item); if(!l_item_size) break; // check type dap_chain_tx_item_type_t l_item_type = dap_chain_datum_tx_item_get_type(l_item); switch (l_item_type){ case TX_ITEM_TYPE_IN_COND:{ dap_chain_tx_in_cond_t * l_tx_in_cond = (dap_chain_tx_in_cond_t *) l_item; dap_chain_datum_tx_spends_item_t *l_tx_prev_out_item = NULL; HASH_FIND(hh, l_ret->tx_outs, &l_tx_in_cond->header.tx_prev_hash,sizeof(l_tx_in_cond->header.tx_prev_hash), l_tx_prev_out_item); if (l_tx_prev_out_item){ // we found previous out_cond with target srv_uid dap_chain_datum_tx_spends_item_t *l_item_in = DAP_NEW_Z(dap_chain_datum_tx_spends_item_t); size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx); dap_chain_datum_tx_t * l_tx_dup = DAP_DUP_SIZE(l_tx,l_tx_size); dap_hash_fast(l_tx_dup,l_tx_size, &l_item_in->tx_hash); l_item_in->tx = l_tx_dup; // Calc same offset from tx duplicate l_item_in->in_cond = (dap_chain_tx_in_cond_t*) (l_tx_dup->tx_items + l_tx_items_pos); HASH_ADD(hh,l_ret->tx_ins, tx_hash, sizeof(dap_chain_hash_fast_t), l_item_in); // Link previous out with current in l_tx_prev_out_item->tx_next = l_tx_dup; } }break; case TX_ITEM_TYPE_OUT_COND:{ dap_chain_tx_out_cond_t * l_tx_out_cond = (dap_chain_tx_out_cond_t *)l_item; if(l_tx_out_cond->header.srv_uid.uint64 == a_srv_uid.uint64){ dap_chain_datum_tx_spends_item_t * l_item = DAP_NEW_Z(dap_chain_datum_tx_spends_item_t); size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx); dap_chain_datum_tx_t * l_tx_dup = DAP_DUP_SIZE(l_tx,l_tx_size); dap_hash_fast(l_tx,l_tx_size, &l_item->tx_hash); l_item->tx = l_tx_dup; // Calc same offset from tx duplicate l_item->out_cond = (dap_chain_tx_out_cond_t*) (l_tx_dup->tx_items + l_tx_items_pos); HASH_ADD(hh,l_ret->tx_outs, tx_hash, sizeof(dap_chain_hash_fast_t), l_item); break; // We're seaching only for one specified OUT_COND output per transaction } } break; default:; } l_tx_items_pos += l_item_size; l_item_idx++; } } } DAP_DEL_Z(l_datums); // go to next atom l_atom = l_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size); } } } } break; } return l_ret; } /** * @brief dap_chain_datum_tx_spends_items_free * @param a_items */ void dap_chain_datum_tx_spends_items_free(dap_chain_datum_tx_spends_items_t * a_items) { assert(a_items); dap_chain_datum_tx_spends_item_free(a_items->tx_ins); dap_chain_datum_tx_spends_item_free(a_items->tx_outs); DAP_DELETE(a_items); } /** * @brief dap_chain_datum_tx_spends_item_free * @param a_items */ void dap_chain_datum_tx_spends_item_free(dap_chain_datum_tx_spends_item_t * a_items) { dap_chain_datum_tx_spends_item_t * l_item, *l_tmp; HASH_ITER(hh,a_items,l_item,l_tmp){ DAP_DELETE(l_item->tx); HASH_DELETE(hh,a_items, l_item); DAP_DELETE(l_item); } } /** * @brief dap_chain_net_get_tx_all * @param a_net * @param a_search_type * @param a_tx_callback * @param a_arg */ void dap_chain_net_get_tx_all(dap_chain_net_t * a_net, dap_chain_net_tx_search_type_t a_search_type ,dap_chain_net_tx_hash_callback_t a_tx_callback, void * a_arg) { assert(a_tx_callback); switch (a_search_type) { case TX_SEARCH_TYPE_NET_UNSPENT: case TX_SEARCH_TYPE_CELL_UNSPENT: case TX_SEARCH_TYPE_NET: case TX_SEARCH_TYPE_CELL: case TX_SEARCH_TYPE_LOCAL: case TX_SEARCH_TYPE_CELL_SPENT: case TX_SEARCH_TYPE_NET_SPENT: { // pass all chains for ( dap_chain_t * l_chain = a_net->pub.chains; l_chain; l_chain = l_chain->next){ dap_chain_cell_t * l_cell, *l_cell_tmp; // Go through all cells HASH_ITER(hh,l_chain->cells,l_cell, l_cell_tmp){ dap_chain_atom_iter_t * l_atom_iter = l_chain->callback_atom_iter_create(l_chain,l_cell->id, false ); // try to find transaction in chain ( inside shard ) size_t l_atom_size = 0; dap_chain_atom_ptr_t l_atom = l_chain->callback_atom_iter_get_first(l_atom_iter, &l_atom_size); // Check atoms in chain while(l_atom && l_atom_size) { size_t l_datums_count = 0; dap_chain_datum_t **l_datums = l_chain->callback_atom_get_datums(l_atom, l_atom_size, &l_datums_count); // transaction dap_chain_datum_tx_t *l_tx = NULL; for (size_t i = 0; i < l_datums_count; i++) { // Check if its transaction if (l_datums && (l_datums[i]->header.type_id == DAP_CHAIN_DATUM_TX)) { l_tx = (dap_chain_datum_tx_t *) l_datums[i]->data; } // If found TX if ( l_tx ) { a_tx_callback(a_net, l_tx, a_arg); } } DAP_DEL_Z(l_datums); // go to next atom l_atom = l_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size); } } } } break; } } /** * @brief The get_tx_cond_all_from_tx struct */ struct get_tx_cond_all_from_tx { dap_list_t * ret; dap_hash_fast_t * tx_begin_hash; dap_chain_datum_tx_t * tx_last; dap_hash_fast_t tx_last_hash; int tx_last_cond_idx; dap_chain_net_srv_uid_t srv_uid; }; /** * @brief s_get_tx_cond_all_from_tx_callback * @param a_net * @param a_tx * @param a_arg */ static void s_get_tx_cond_chain_callback(dap_chain_net_t* a_net, dap_chain_datum_tx_t *a_tx, void *a_arg) { struct get_tx_cond_all_from_tx * l_args = (struct get_tx_cond_all_from_tx* ) a_arg; dap_hash_fast_t * l_tx_hash = dap_chain_node_datum_tx_calc_hash(a_tx); if( l_args->ret ){ int l_item_idx = 0; byte_t *l_tx_item; // Get items from transaction while ((l_tx_item = dap_chain_datum_tx_item_get(a_tx, &l_item_idx, TX_ITEM_TYPE_IN_COND , NULL)) != NULL){ dap_chain_tx_in_cond_t * l_in_cond = (dap_chain_tx_in_cond_t *) l_tx_item; if(dap_hash_fast_compare(&l_in_cond->header.tx_prev_hash, &l_args->tx_last_hash) && (uint32_t)l_args->tx_last_cond_idx == l_in_cond->header.tx_out_prev_idx ){ // Found output // We're the next tx in tx cond chain l_args->ret = dap_list_append(l_args->ret, a_tx); } l_item_idx++; } }else if(dap_hash_fast_compare(l_tx_hash,l_args->tx_begin_hash)){ // Found condition int l_item_idx = 0; byte_t *l_tx_item; // Get items from transaction while ((l_tx_item = dap_chain_datum_tx_item_get(a_tx, &l_item_idx, TX_ITEM_TYPE_OUT_COND , NULL)) != NULL){ dap_chain_tx_out_cond_t * l_out_cond = (dap_chain_tx_out_cond_t *) l_tx_item; if ( l_out_cond->header.srv_uid.uint64 == l_args->srv_uid.uint64 ){ // We found output with target service uuid l_args->tx_last = a_tx; // Record current transaction as the last in tx chain memcpy(&l_args->tx_last_hash, l_tx_hash, sizeof(*l_tx_hash)); // Record current hash l_args->tx_last_cond_idx = l_item_idx; l_args->ret = dap_list_append(NULL, a_tx); break; } } } DAP_DELETE(l_tx_hash); } /** * @brief Return spends chain for conditioned transaction since beginning one * @param a_net Network where to search for * @param l_tx_hash TX hash of the Tx chain beginning * @param a_srv_uid Service UID from witch cond output the chain begin * @return List of conditioned transactions followin each other one by one as they do as spends */ dap_list_t * dap_chain_net_get_tx_cond_chain(dap_chain_net_t * a_net, dap_hash_fast_t * a_tx_hash, dap_chain_net_srv_uid_t a_srv_uid) { struct get_tx_cond_all_from_tx * l_args = DAP_NEW_Z(struct get_tx_cond_all_from_tx); l_args->tx_begin_hash = a_tx_hash; l_args->srv_uid = a_srv_uid; dap_chain_net_get_tx_all(a_net,TX_SEARCH_TYPE_NET,s_get_tx_cond_chain_callback, l_args); dap_list_t * l_ret = l_args->ret; DAP_DELETE(l_args); return l_ret; } /** * @brief The get_tx_cond_all_for_addr struct */ struct get_tx_cond_all_for_addr { dap_list_t * ret; dap_chain_tx_t * tx_all_hh; // Transactions hash table for target address const dap_chain_addr_t * addr; dap_chain_net_srv_uid_t srv_uid; }; /** * @brief s_get_tx_cond_all_for_addr_callback * @param a_net * @param a_tx * @param a_arg */ static void s_get_tx_cond_all_for_addr_callback(dap_chain_net_t* a_net, dap_chain_datum_tx_t *a_datum_tx, void *a_arg) { struct get_tx_cond_all_for_addr * l_args = (struct get_tx_cond_all_for_addr* ) a_arg; int l_item_idx = 0; dap_chain_datum_tx_item_t *l_tx_item; bool l_tx_for_addr = false; // TX with output related with our address bool l_tx_from_addr = false; // TX with input that take assets from our address //const char *l_tx_from_addr_token = NULL; bool l_tx_collected = false; // We already collected this TX in return list // Get in items to detect is in or in_cond from target address while ((l_tx_item = (dap_chain_datum_tx_item_t *) dap_chain_datum_tx_item_get(a_datum_tx, &l_item_idx, TX_ITEM_TYPE_ANY , NULL)) != NULL){ switch (l_tx_item->type){ case TX_ITEM_TYPE_IN:{ dap_chain_tx_in_t * l_in = (dap_chain_tx_in_t *) l_tx_item; if( l_tx_from_addr) // Already detected thats spends from addr break; dap_chain_tx_t * l_tx = dap_chain_tx_hh_find( l_args->tx_all_hh, &l_in->header.tx_prev_hash); if( l_tx ){ // Its input thats closing output for target address - we note it l_tx_from_addr = true; //l_tx_from_addr_token = dap_chain_ledger_tx_get_token_ticker_by_hash(a_net->pub.ledger, &l_tx->hash); } }break; case TX_ITEM_TYPE_IN_COND:{ if(l_tx_collected) // Already collected break; dap_chain_tx_in_cond_t * l_in_cond = (dap_chain_tx_in_cond_t *) l_tx_item; dap_chain_tx_t * l_tx = dap_chain_tx_hh_find( l_args->tx_all_hh, &l_in_cond->header.tx_prev_hash); if( l_tx ){ // Its input thats closing conditioned tx related with target address, collect it //dap_chain_tx_t *l_tx_add = dap_chain_tx_wrap_packed(a_datum_tx); l_args->ret = dap_list_append(l_args->ret, a_datum_tx); l_tx_collected = true; } }break; } l_item_idx++; } // Get out items from transaction while ((l_tx_item = (dap_chain_datum_tx_item_t *) dap_chain_datum_tx_item_get(a_datum_tx, &l_item_idx, TX_ITEM_TYPE_OUT_ALL , NULL)) != NULL){ switch (l_tx_item->type){ case TX_ITEM_TYPE_OUT:{ if(l_tx_for_addr) // Its already added break; dap_chain_tx_out_t * l_out = (dap_chain_tx_out_t*) l_tx_item; if ( memcmp(&l_out->addr, l_args->addr, sizeof(*l_args->addr)) == 0){ // Its our address tx dap_chain_tx_t * l_tx = dap_chain_tx_wrap_packed(a_datum_tx); dap_chain_tx_hh_add(l_args->tx_all_hh, l_tx); l_tx_for_addr = true; } }break; case TX_ITEM_TYPE_OUT_EXT:{ if(l_tx_for_addr) // Its already added break; dap_chain_tx_out_ext_t * l_out = (dap_chain_tx_out_ext_t*) l_tx_item; if ( memcmp(&l_out->addr, l_args->addr, sizeof(*l_args->addr)) == 0){ // Its our address tx dap_chain_tx_t * l_tx = dap_chain_tx_wrap_packed(a_datum_tx); dap_chain_tx_hh_add(l_args->tx_all_hh, l_tx); l_tx_for_addr = true; } }break; case TX_ITEM_TYPE_OUT_COND:{ dap_chain_tx_out_cond_t * l_out_cond = (dap_chain_tx_out_cond_t*) l_tx_item; if(l_tx_collected) // Already collected for return list break; // If this output spends monies from our address if(l_tx_from_addr && l_out_cond->header.srv_uid.uint64 == l_args->srv_uid.uint64){ //dap_chain_tx_t *l_tx_add = dap_chain_tx_wrap_packed(a_datum_tx); l_args->ret = dap_list_append(l_args->ret, a_datum_tx); l_tx_collected = true; } } break; } l_item_idx++; } } /** * @brief Compose list of all cond transactions with target srv_uid for specified address * @param a_net * @param a_addr * @param a_srv_uid * @return List of dap_chain_tx_t (don't forget to free it) */ dap_list_t * dap_chain_net_get_tx_cond_all_for_addr(dap_chain_net_t * a_net, dap_chain_addr_t * a_addr, dap_chain_net_srv_uid_t a_srv_uid) { struct get_tx_cond_all_for_addr * l_args = DAP_NEW_Z(struct get_tx_cond_all_for_addr); l_args->addr = a_addr; l_args->srv_uid = a_srv_uid; dap_chain_net_get_tx_all(a_net,TX_SEARCH_TYPE_NET,s_get_tx_cond_all_for_addr_callback, l_args); dap_chain_tx_hh_free(l_args->tx_all_hh); dap_list_t * l_ret = l_args->ret; DAP_DELETE(l_args); return l_ret; } /** * @brief dap_chain_net_get_tx_cond_all_by_srv_uid * @param a_net * @param a_srv_uid * @param a_search_type * @return */ dap_list_t * dap_chain_net_get_tx_cond_all_by_srv_uid(dap_chain_net_t * a_net, const dap_chain_net_srv_uid_t a_srv_uid, const dap_time_t a_time_from, const dap_time_t a_time_to, const dap_chain_net_tx_search_type_t a_search_type) { dap_ledger_t * l_ledger = a_net->pub.ledger; dap_list_t * l_ret = NULL; switch (a_search_type) { case TX_SEARCH_TYPE_NET: case TX_SEARCH_TYPE_CELL: case TX_SEARCH_TYPE_LOCAL: case TX_SEARCH_TYPE_CELL_SPENT: case TX_SEARCH_TYPE_NET_SPENT: { // pass all chains for ( dap_chain_t * l_chain = a_net->pub.chains; l_chain; l_chain = l_chain->next){ dap_chain_cell_t * l_cell, *l_cell_tmp; // Go through all cells HASH_ITER(hh,l_chain->cells,l_cell, l_cell_tmp){ dap_chain_atom_iter_t * l_atom_iter = l_chain->callback_atom_iter_create(l_chain,l_cell->id, false ); // try to find transaction in chain ( inside shard ) size_t l_atom_size = 0; dap_chain_atom_ptr_t l_atom = l_chain->callback_atom_iter_get_first(l_atom_iter, &l_atom_size); // Check atoms in chain while(l_atom && l_atom_size) { size_t l_datums_count = 0; dap_chain_datum_t **l_datums = l_chain->callback_atom_get_datums(l_atom, l_atom_size, &l_datums_count); // transaction dap_chain_datum_tx_t *l_tx = NULL; for (size_t i = 0; i < l_datums_count; i++) { // Check if its transaction if (l_datums && (l_datums[i]->header.type_id == DAP_CHAIN_DATUM_TX)) { l_tx = (dap_chain_datum_tx_t *)l_datums[i]->data; } // If found TX if (l_tx){ // Check for time from if(a_time_from && l_tx->header.ts_created < a_time_from) continue; // Check for time to if(a_time_to && l_tx->header.ts_created > a_time_to) continue; if(a_search_type == TX_SEARCH_TYPE_CELL_SPENT || a_search_type == TX_SEARCH_TYPE_NET_SPENT ){ dap_hash_fast_t * l_tx_hash = dap_chain_node_datum_tx_calc_hash(l_tx); bool l_is_spent = dap_chain_ledger_tx_spent_find_by_hash(l_ledger,l_tx_hash); DAP_DELETE(l_tx_hash); if(!l_is_spent) continue; } // Check for OUT_COND items dap_list_t *l_list_out_cond_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT_COND , NULL); if(l_list_out_cond_items){ dap_list_t *l_list_cur = l_list_out_cond_items; while(l_list_cur){ // Go through all cond items dap_chain_tx_out_cond_t * l_tx_out_cond = (dap_chain_tx_out_cond_t *)l_list_cur->data; if(l_tx_out_cond) // If we found cond out with target srv_uid if(l_tx_out_cond->header.srv_uid.uint64 == a_srv_uid.uint64) l_ret = dap_list_append(l_ret, DAP_DUP_SIZE(l_tx, dap_chain_datum_tx_get_size(l_tx))); l_list_cur = dap_list_next(l_list_cur); } dap_list_free(l_list_out_cond_items); } } } DAP_DEL_Z(l_datums); // go to next atom l_atom = l_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size); } } } } break; case TX_SEARCH_TYPE_NET_UNSPENT: case TX_SEARCH_TYPE_CELL_UNSPENT: l_ret = dap_chain_ledger_tx_cache_find_out_cond_all(l_ledger, a_srv_uid); break; } return l_ret; } /** * @brief Summarize all tx inputs * @param a_net * @param a_tx * @return */ uint256_t dap_chain_net_get_tx_total_value(dap_chain_net_t * a_net, dap_chain_datum_tx_t * a_tx) { uint256_t l_ret = {0}; int l_item_idx = 0; dap_chain_tx_in_t *l_in_item = NULL; do { l_in_item = (dap_chain_tx_in_t*) dap_chain_datum_tx_item_get(a_tx, &l_item_idx, TX_ITEM_TYPE_IN , NULL); l_item_idx++; if(l_in_item ) { //const char *token = l_out_cond_item->subtype.srv_xchange.token; dap_chain_datum_tx_t * l_tx_prev = dap_chain_net_get_tx_by_hash(a_net,&l_in_item->header.tx_prev_hash, TX_SEARCH_TYPE_NET_SPENT); if(l_tx_prev){ int l_tx_prev_out_index = l_in_item->header.tx_out_prev_idx; dap_chain_tx_out_t * l_tx_prev_out =(dap_chain_tx_out_t *) dap_chain_datum_tx_item_get(l_tx_prev,&l_tx_prev_out_index, TX_ITEM_TYPE_OUT,NULL); if ((uint32_t)l_tx_prev_out_index == l_in_item->header.tx_out_prev_idx && l_tx_prev_out) { uint256_t l_in_value = l_tx_prev_out->header.value; if(SUM_256_256(l_in_value,l_ret, &l_ret )!= 0) log_it(L_ERROR, "Overflow on inputs values calculation (summing)"); }else{ log_it(L_WARNING, "Can't find item with index %d in prev tx hash", l_tx_prev_out_index); } }else log_it(L_WARNING, "Can't find prev tx hash"); } } while(l_in_item); return l_ret; } /** * @brief dap_chain_net_tx_get_by_hash * @param a_net * @param a_tx_hash * @param a_search_type * @return */ dap_chain_datum_tx_t * dap_chain_net_get_tx_by_hash(dap_chain_net_t * a_net, dap_chain_hash_fast_t * a_tx_hash, dap_chain_net_tx_search_type_t a_search_type) { dap_ledger_t * l_ledger = a_net->pub.ledger; dap_chain_datum_tx_t * l_tx = NULL; switch (a_search_type) { case TX_SEARCH_TYPE_NET: case TX_SEARCH_TYPE_CELL: case TX_SEARCH_TYPE_LOCAL: case TX_SEARCH_TYPE_CELL_SPENT: case TX_SEARCH_TYPE_NET_SPENT: { if ( ! l_tx ){ // pass all chains for ( dap_chain_t * l_chain = a_net->pub.chains; l_chain; l_chain = l_chain->next){ if ( l_chain->callback_tx_find_by_hash ){ // try to find transaction in chain ( inside shard ) l_tx = l_chain->callback_tx_find_by_hash(l_chain, a_tx_hash); if (l_tx) { if ((a_search_type == TX_SEARCH_TYPE_CELL_SPENT || a_search_type == TX_SEARCH_TYPE_NET_SPENT) && (!dap_chain_ledger_tx_spent_find_by_hash(l_ledger, a_tx_hash))) return NULL; break; } } } } } break; case TX_SEARCH_TYPE_NET_UNSPENT: case TX_SEARCH_TYPE_CELL_UNSPENT: l_tx = dap_chain_ledger_tx_find_by_hash(l_ledger, a_tx_hash); break; } return l_tx; } static struct net_fee { dap_chain_net_id_t net_id; uint256_t value; // Network fee value dap_chain_addr_t fee_addr; // Addr collector UT_hash_handle hh; } *s_net_fees = NULL; // Governance statements for networks static pthread_rwlock_t s_net_fees_rwlock = PTHREAD_RWLOCK_INITIALIZER; bool dap_chain_net_tx_get_fee(dap_chain_net_id_t a_net_id, uint256_t *a_value, dap_chain_addr_t *a_addr) { struct net_fee *l_net_fee; HASH_FIND(hh, s_net_fees, &a_net_id, sizeof(a_net_id), l_net_fee); if (!l_net_fee || IS_ZERO_256(l_net_fee->value)) return false; if (a_value) *a_value = l_net_fee->value; if (a_addr) *a_addr = l_net_fee->fee_addr; return true; }