/* * Authors: * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net> * Alexander Lysikov <alexander.lysikov@demlabs.net> * Roman Khlopkov <roman.khlopkov@demlabs.net> * DeM Labs Inc. https://demlabs.net * DeM Labs Open source community https://github.com/demlabsinc * Copyright (c) 2017-2024 * All rights reserved. This file is part of CellFrame SDK the open source project CellFrame SDK 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. CellFrame SDK 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 CellFrame SDK based project. If not, see <http://www.gnu.org/licenses/>. */ #include "dap_common.h" #include <dirent.h> #include "uthash.h" #include "dap_chain_ledger_pvt.h" #include "dap_chain_common.h" #include "dap_math_ops.h" #include "dap_list.h" #include "dap_hash.h" #include "dap_enc_base58.h" #include "dap_strfuncs.h" #include "dap_config.h" #include "dap_chain_datum_tx_in_ems.h" #include "dap_chain_datum_token.h" #include "dap_global_db.h" #include "dap_chain_ledger.h" #include "json_object.h" #define LOG_TAG "dap_ledger" typedef struct dap_ledger_service_info { dap_chain_srv_uid_t service_uid; // hash key char tag_str[32]; // tag string name dap_ledger_tag_check_callback_t callback; //callback for check if a tx for particular service UT_hash_handle hh; } dap_ledger_service_info_t; static dap_ledger_service_info_t *s_services; static pthread_rwlock_t s_services_rwlock = PTHREAD_RWLOCK_INITIALIZER; bool g_debug_ledger = true; static void s_threshold_txs_free(dap_ledger_t *a_ledger); static size_t s_threshold_txs_max = 10000; static size_t s_threshold_free_timer_tick = 900000; // 900000 ms = 15 minutes. //add a service declaration for tx tagging and more static bool s_tag_check_block_reward(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_chain_datum_tx_item_groups_t *a_items_grp, dap_chain_tx_tag_action_type_t *a_action) { //reward tag if (a_items_grp->items_in_reward) { if (a_action) *a_action = DAP_CHAIN_TX_TAG_ACTION_TRANSFER_REGULAR; return true; } return false; } dap_chain_tx_out_cond_t* dap_chain_ledger_get_tx_out_cond_linked_to_tx_in_cond(dap_ledger_t *a_ledger, dap_chain_tx_in_cond_t *a_in_cond) { dap_hash_fast_t *l_tx_prev_hash = &a_in_cond->header.tx_prev_hash; uint32_t l_tx_prev_out_idx = a_in_cond->header.tx_out_prev_idx; dap_chain_datum_tx_t *l_tx_prev = dap_ledger_tx_find_by_hash (a_ledger,l_tx_prev_hash); if (!l_tx_prev) return NULL; byte_t* l_item_res = dap_chain_datum_tx_item_get_nth(l_tx_prev, TX_ITEM_TYPE_OUT_ALL, l_tx_prev_out_idx); dap_chain_tx_item_type_t l_type = *(uint8_t *)l_item_res; if (l_type != TX_ITEM_TYPE_OUT_COND) return NULL; return (dap_chain_tx_out_cond_t*)l_item_res; } static dap_chain_addr_t s_get_out_addr(byte_t *out_item) { dap_chain_tx_item_type_t l_type = *(uint8_t *)out_item; switch (l_type) { case TX_ITEM_TYPE_OUT: { dap_chain_tx_out_t *l_tx_out = (dap_chain_tx_out_t *)out_item; return l_tx_out->addr; } break; case TX_ITEM_TYPE_OUT_EXT: { // 256 dap_chain_tx_out_ext_t *l_tx_out = (dap_chain_tx_out_ext_t *)out_item; return l_tx_out->addr; } break; } dap_chain_addr_t l_tx_out_to={0}; return l_tx_out_to; } static bool s_tag_check_transfer(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_chain_datum_tx_item_groups_t *a_items_grp, dap_chain_tx_tag_action_type_t *a_action) { //crosschain transfer //regular transfer //comission transfer // fee transfer: in_cond item linked to out_cond_fee if (a_items_grp->items_in_cond) { for (dap_list_t *it = a_items_grp->items_in_cond; it; it = it->next) { dap_chain_tx_in_cond_t *l_tx_in = it->data; dap_chain_tx_out_cond_t *l_tx_out_cond = dap_chain_ledger_get_tx_out_cond_linked_to_tx_in_cond(a_ledger, l_tx_in); if (l_tx_out_cond && l_tx_out_cond->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_FEE) { if (a_action) *a_action = DAP_CHAIN_TX_TAG_ACTION_TRANSFER_COMISSION; return true; } } } //crosschain transfer: outs destination net-id differs from current net-id // to differ with wrong stakes -> no ems in required if (!a_items_grp->items_in_ems) { dap_chain_addr_t addr_to = {0}; for (dap_list_t *it = a_items_grp->items_out_all; it; it = it->next) { dap_chain_addr_t l_tx_out_to = s_get_out_addr(it->data); //tag cross-chain _outputs_ transactions (recepient-tx is emission-based) if (l_tx_out_to.net_id.uint64 != a_ledger->net->pub.id.uint64 && !dap_chain_addr_is_blank(&l_tx_out_to)) { if (a_action) *a_action = DAP_CHAIN_TX_TAG_ACTION_TRANSFER_CROSSCHAIN; return true; } } } //regular transfers //have no other ins except regular in //have no OUT_COND except fee //have no vote //no TSD! //have any of those -> not regular transfer if (a_items_grp->items_in_cond || a_items_grp->items_in_ems || a_items_grp->items_in_reward ) { return false; } //have any of those -> not regular transfer if ( a_items_grp->items_out_cond_srv_pay || a_items_grp->items_out_cond_srv_stake_lock || a_items_grp->items_out_cond_srv_stake_pos_delegate || a_items_grp->items_out_cond_srv_xchange) { return false; } //not voting or vote... if (a_items_grp->items_vote || a_items_grp->items_voting || dap_list_length(a_items_grp->items_tsd) > 1 || (a_items_grp->items_tsd && !dap_chain_datum_tx_item_get_tsd_by_type(a_tx, DAP_CHAIN_DATUM_TRANSFER_TSD_TYPE_OUT_COUNT))) return false; //not tsd sects (staking!) or only batching tsd if(a_action) { *a_action = DAP_CHAIN_TX_TAG_ACTION_TRANSFER_REGULAR; } return true; } int dap_ledger_service_add(dap_chain_srv_uid_t a_uid, char *tag_str, dap_ledger_tag_check_callback_t a_callback) { dap_ledger_service_info_t *l_new_sinfo = NULL; int l_tmp = a_uid.raw_ui64; pthread_rwlock_rdlock(&s_services_rwlock); HASH_FIND_INT(s_services, &l_tmp, l_new_sinfo); pthread_rwlock_unlock(&s_services_rwlock); if (l_new_sinfo) { l_new_sinfo->callback = a_callback; return 1; } l_new_sinfo = DAP_NEW(dap_ledger_service_info_t); if (!l_new_sinfo) { log_it(L_CRITICAL, "Memory allocation error"); return -1; } l_new_sinfo->service_uid = a_uid; l_new_sinfo->callback = a_callback; dap_strncpy(l_new_sinfo->tag_str, tag_str, sizeof(l_new_sinfo->tag_str)); pthread_rwlock_wrlock(&s_services_rwlock); HASH_ADD_INT(s_services, service_uid.raw_ui64, l_new_sinfo); pthread_rwlock_unlock(&s_services_rwlock); log_it(L_NOTICE, "Successfully registered service tag %s with uid %02" DAP_UINT64_FORMAT_X, tag_str, a_uid.raw_ui64); return 0; } /** * @brief dap_ledger_init * current function version set g_debug_ledger parameter, if it define in config, and returns 0 * @return */ int dap_ledger_init() { g_debug_ledger = dap_config_get_item_bool_default(g_config, "ledger", "debug_more",false); //register native ledger services dap_chain_srv_uid_t l_uid_transfer = { .uint64 = DAP_CHAIN_NET_SRV_TRANSFER_ID }; dap_ledger_service_add(l_uid_transfer, "transfer", s_tag_check_transfer); dap_chain_srv_uid_t l_uid_breward = { .uint64 = DAP_CHAIN_NET_SRV_BLOCK_REWARD_ID }; dap_ledger_service_add(l_uid_breward, "block_reward", s_tag_check_block_reward); return 0; } /** * @brief dap_ledger_deinit * nothing do */ void dap_ledger_deinit() { pthread_rwlock_destroy(&s_services_rwlock); } /** * @brief dap_ledger_handle_new * Create empty dap_ledger_t structure * @return dap_ledger_t* */ static dap_ledger_t *dap_ledger_handle_new(void) { dap_ledger_t *l_ledger = DAP_NEW_Z_RET_VAL_IF_FAIL(dap_ledger_t, NULL); dap_ledger_private_t *l_ledger_pvt = l_ledger->_internal = DAP_NEW_Z_RET_VAL_IF_FAIL(dap_ledger_private_t, NULL, l_ledger); pthread_rwlock_init(&l_ledger_pvt->ledger_rwlock, NULL); pthread_rwlock_init(&l_ledger_pvt->tokens_rwlock, NULL); pthread_rwlock_init(&l_ledger_pvt->threshold_txs_rwlock, NULL); pthread_rwlock_init(&l_ledger_pvt->balance_accounts_rwlock, NULL); pthread_rwlock_init(&l_ledger_pvt->stake_lock_rwlock, NULL); pthread_rwlock_init(&l_ledger_pvt->rewards_rwlock, NULL); return l_ledger; } /** * @brief dap_ledger_handle_free * Remove dap_ledger_t structure * @param a_ledger */ void dap_ledger_handle_free(dap_ledger_t *a_ledger) { if(!a_ledger) return; // Destroy Read/Write Lock pthread_rwlock_destroy(&PVT(a_ledger)->ledger_rwlock); pthread_rwlock_destroy(&PVT(a_ledger)->tokens_rwlock); pthread_rwlock_destroy(&PVT(a_ledger)->threshold_txs_rwlock); pthread_rwlock_destroy(&PVT(a_ledger)->balance_accounts_rwlock); pthread_rwlock_destroy(&PVT(a_ledger)->stake_lock_rwlock); pthread_rwlock_destroy(&PVT(a_ledger)->rewards_rwlock); DAP_DEL_MULTY(PVT(a_ledger), a_ledger); log_it(L_INFO,"Ledger for network %s destroyed", a_ledger->net->pub.name); } bool dap_ledger_datum_is_enforced(dap_ledger_t *a_ledger, dap_hash_fast_t *a_hash, bool a_accept) { dap_ledger_hal_item_t *l_wanted = NULL; HASH_FIND(hh, a_accept ? PVT(a_ledger)->hal_items : PVT(a_ledger)->hrl_items, a_hash, sizeof(dap_hash_fast_t), l_wanted); debug_if(g_debug_ledger && l_wanted, L_DEBUG, "Datum %s is %slisted", dap_hash_fast_to_str_static(a_hash), a_accept ? "white" : "black"); return !!l_wanted; } /** * @brief s_tx_header_print * prepare data for print, add time * * return history string * @param a_tx * @param a_tx_hash * @param a_hash_out_type * @return a_json_out */ static void s_tx_header_print(json_object *a_json_out, dap_chain_datum_tx_t *a_tx, const char *a_hash_out_type, dap_chain_hash_fast_t *a_tx_hash) { char l_time_str[DAP_TIME_STR_SIZE] = "unknown"; if (a_tx->header.ts_created) dap_time_to_str_rfc822(l_time_str, DAP_TIME_STR_SIZE, a_tx->header.ts_created); const char *l_tx_hash_str = dap_strcmp(a_hash_out_type, "hex") ? dap_enc_base58_encode_hash_to_str_static(a_tx_hash) : dap_chain_hash_fast_to_str_static(a_tx_hash); json_object_object_add(a_json_out, "tx_hash ", json_object_new_string(l_tx_hash_str)); json_object_object_add(a_json_out, "time ", json_object_new_string(l_time_str)); } static void s_dump_datum_tx_for_addr(dap_ledger_tx_item_t *a_item, bool a_unspent, dap_ledger_t *a_ledger, dap_chain_addr_t *a_addr, const char *a_hash_out_type, json_object *json_arr_out) { if (a_unspent && a_item->cache_data.ts_spent) { // With 'unspent' flag spent ones are ignored return; } dap_chain_datum_tx_t *l_tx = a_item->tx; dap_chain_hash_fast_t l_tx_hash = a_item->tx_hash_fast; dap_chain_addr_t l_src_addr = { }, l_dst_addr = { }; bool l_base_tx = false; const char *l_src_token = NULL; int l_src_subtype = DAP_CHAIN_TX_OUT_COND_SUBTYPE_UNDEFINED; dap_hash_fast_t l_tx_prev_hash = { }; byte_t *l_item; size_t l_size; int idx, l_tx_prev_out_idx; TX_ITEM_ITER_TX_TYPE(l_item, TX_ITEM_TYPE_IN_ALL, l_size, idx, l_tx) { switch (*l_item) { case TX_ITEM_TYPE_IN: { dap_chain_tx_in_t *l_tx_in = (dap_chain_tx_in_t*)l_item; l_tx_prev_hash = l_tx_in->header.tx_prev_hash; l_tx_prev_out_idx = l_tx_in->header.tx_out_prev_idx; } break; case TX_ITEM_TYPE_IN_COND: { dap_chain_tx_in_cond_t *l_tx_in_cond = (dap_chain_tx_in_cond_t*)l_item; l_tx_prev_hash = l_tx_in_cond->header.tx_prev_hash; l_tx_prev_out_idx = l_tx_in_cond->header.tx_out_prev_idx; } break; default: continue; } if ( dap_hash_fast_is_blank(&l_tx_prev_hash) ) { l_base_tx = true; dap_chain_tx_in_ems_t *l_token = (dap_chain_tx_in_ems_t*) dap_chain_datum_tx_item_get(l_tx, NULL, NULL, TX_ITEM_TYPE_IN_EMS, NULL); if (l_token) l_src_token = l_token->header.ticker; break; } dap_chain_datum_tx_t *l_tx_prev = dap_ledger_tx_find_by_hash(a_ledger, &l_tx_prev_hash); if ( !l_tx_prev ) continue; uint8_t *l_prev_out_union = dap_chain_datum_tx_item_get_nth(l_tx_prev, TX_ITEM_TYPE_OUT_ALL, l_tx_prev_out_idx); if (!l_prev_out_union) continue; switch (*l_prev_out_union) { case TX_ITEM_TYPE_OUT: l_src_addr = ((dap_chain_tx_out_t *)l_prev_out_union)->addr; break; case TX_ITEM_TYPE_OUT_EXT: l_src_addr = ((dap_chain_tx_out_ext_t *)l_prev_out_union)->addr; l_src_token = (const char*)(((dap_chain_tx_out_ext_t *)l_prev_out_union)->token); break; case TX_ITEM_TYPE_OUT_COND: l_src_subtype = ((dap_chain_tx_out_cond_t *)l_prev_out_union)->header.subtype; default: break; } if ( !dap_chain_addr_compare(&l_src_addr, a_addr) ) break; //it's not our addr if (!l_src_token) { l_src_token = a_item->cache_data.token_ticker; } } bool l_header_printed = false; l_item = NULL; TX_ITEM_ITER_TX_TYPE(l_item, TX_ITEM_TYPE_OUT_ALL, l_size, idx, l_tx) { dap_chain_tx_item_type_t l_type = *l_item; uint256_t l_value; switch (l_type) { case TX_ITEM_TYPE_OUT: l_dst_addr = ((dap_chain_tx_out_t*)l_item)->addr; l_value = ((dap_chain_tx_out_t*)l_item)->header.value; break; case TX_ITEM_TYPE_OUT_EXT: l_dst_addr = ((dap_chain_tx_out_ext_t*)l_item)->addr; l_value = ((dap_chain_tx_out_ext_t *)l_item)->header.value; break; case TX_ITEM_TYPE_OUT_COND: l_value = ((dap_chain_tx_out_cond_t *)l_item)->header.value; default: break; } if ( !dap_chain_addr_is_blank(&l_src_addr) && !dap_chain_addr_is_blank(&l_dst_addr) && dap_chain_addr_compare(&l_dst_addr, &l_src_addr) ) continue; // send to self if ( dap_chain_addr_compare(&l_src_addr, a_addr)) { json_object * l_json_obj_datum = json_object_new_object(); if (!l_header_printed) { s_tx_header_print(l_json_obj_datum, l_tx, a_hash_out_type, &l_tx_hash); l_header_printed = true; } //const char *l_token_ticker = dap_ledger_tx_get_token_ticker_by_hash(l_ledger, &l_tx_hash); const char *l_dst_addr_str = !dap_chain_addr_is_blank(&l_dst_addr) ? dap_chain_addr_to_str_static(&l_dst_addr) : dap_chain_tx_out_cond_subtype_to_str( ((dap_chain_tx_out_cond_t *)l_item)->header.subtype ); json_object_object_add(l_json_obj_datum, "send", json_object_new_string(dap_uint256_to_char(l_value, NULL))); json_object_object_add(l_json_obj_datum, "to_addr", json_object_new_string(l_dst_addr_str)); json_object_object_add(l_json_obj_datum, "token", l_src_token ? json_object_new_string(l_src_token) : json_object_new_string("UNKNOWN")); json_object_array_add(json_arr_out, l_json_obj_datum); } if ( dap_chain_addr_compare(&l_dst_addr, a_addr) ) { json_object * l_json_obj_datum = json_object_new_object(); if (!l_header_printed) { s_tx_header_print(l_json_obj_datum, l_tx, a_hash_out_type, &l_tx_hash); l_header_printed = true; } const char *l_dst_token = (l_type == TX_ITEM_TYPE_OUT_EXT) ? (const char *)(((dap_chain_tx_out_ext_t *)l_item)->token) : NULL; const char *l_src_addr_str = l_base_tx ? "emission" : ( !dap_chain_addr_is_blank(&l_src_addr) ? dap_chain_addr_to_str_static(&l_src_addr) : dap_chain_tx_out_cond_subtype_to_str(l_src_subtype) ); json_object_object_add(l_json_obj_datum, "recv ", json_object_new_string(dap_uint256_to_char(l_value, NULL))); json_object_object_add(l_json_obj_datum, "token ", l_dst_token ? json_object_new_string(l_dst_token) : (l_src_token ? json_object_new_string(l_src_token) : json_object_new_string("UNKNOWN"))); json_object_object_add(l_json_obj_datum, "from ", json_object_new_string(l_src_addr_str)); json_object_array_add(json_arr_out, l_json_obj_datum); } } } json_object *dap_ledger_token_tx_item_list(dap_ledger_t * a_ledger, dap_chain_addr_t *a_addr, const char *a_hash_out_type, bool a_unspent_only) { json_object * json_arr_out = json_object_new_array(); if (!json_arr_out) { log_it(L_CRITICAL, "%s", c_error_memory_alloc); return NULL; } dap_ledger_tx_item_t *l_tx_item, *l_tx_tmp; dap_ledger_private_t * l_ledger_pvt = PVT(a_ledger); pthread_rwlock_rdlock(&l_ledger_pvt->ledger_rwlock); HASH_ITER(hh, l_ledger_pvt->ledger_items, l_tx_item, l_tx_tmp) { s_dump_datum_tx_for_addr(l_tx_item, a_unspent_only, a_ledger, a_addr, a_hash_out_type, json_arr_out); } pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); // if no history if(!json_arr_out) { json_object * json_obj_addr = json_object_new_object(); json_object_object_add(json_obj_addr, "status", json_object_new_string("empty")); json_object_array_add(json_arr_out, json_obj_addr); } return json_arr_out; } static bool s_pack_ledger_threshold_info_json (json_object *a_json_arr_out, dap_ledger_tx_item_t *a_tx_item) { json_object *json_obj_tx = json_object_new_object(); if (!json_obj_tx) return 1; char l_tx_prev_hash_str[DAP_HASH_FAST_STR_SIZE]={0}; char l_time[DAP_TIME_STR_SIZE] = {0}; dap_chain_hash_fast_to_str(&a_tx_item->tx_hash_fast,l_tx_prev_hash_str,sizeof(l_tx_prev_hash_str)); dap_time_to_str_rfc822(l_time, sizeof(l_time), a_tx_item->cache_data.ts_created); json_object_object_add(json_obj_tx, "thresholded_hash", json_object_new_string(l_tx_prev_hash_str)); json_object_object_add(json_obj_tx, "time_created", json_object_new_string(l_time)); json_object_object_add(json_obj_tx, "tx_item_size", json_object_new_int(a_tx_item->tx->header.tx_items_size)); json_object_array_add(a_json_arr_out, json_obj_tx); return 0; } static bool s_pack_ledger_balance_info_json (json_object *a_json_arr_out, dap_ledger_wallet_balance_t *a_balance_item) { json_object* json_obj_tx = json_object_new_object(); json_object_object_add(json_obj_tx, "ledger_balance_key", json_object_new_string(a_balance_item->key)); json_object_object_add(json_obj_tx, "token_ticker", json_object_new_string(a_balance_item->token_ticker)); json_object_object_add(json_obj_tx, "balance", json_object_new_string(dap_uint256_to_char(a_balance_item->balance, NULL))); json_object_array_add(a_json_arr_out, json_obj_tx); return 0; } json_object *dap_ledger_threshold_info(dap_ledger_t *a_ledger, size_t a_limit, size_t a_offset, dap_chain_hash_fast_t *a_threshold_hash, bool a_head) { dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); dap_ledger_tx_item_t *l_tx_item = NULL, *l_tx_tmp; json_object *json_arr_out = json_object_new_array(); if (!json_arr_out) return NULL; uint32_t l_counter = 0; size_t l_arr_start = 0; size_t l_arr_end = 0; dap_chain_set_offset_limit_json(json_arr_out, &l_arr_start, &l_arr_end, a_limit, a_offset, HASH_COUNT(l_ledger_pvt->threshold_txs)); pthread_rwlock_rdlock(&l_ledger_pvt->threshold_txs_rwlock); if (a_threshold_hash) { json_object *json_obj_tx = json_object_new_object(); if (!json_obj_tx) { pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); json_object_put(json_arr_out); return NULL; } HASH_FIND(hh, l_ledger_pvt->threshold_txs, a_threshold_hash, sizeof(dap_hash_t), l_tx_item); if (l_tx_item) { json_object_object_add(json_obj_tx, "found_hash", json_object_new_string(dap_hash_fast_to_str_static(a_threshold_hash))); json_object_array_add(json_arr_out, json_obj_tx); } else { json_object_object_add(json_obj_tx, "found_hash", json_object_new_string("empty")); json_object_array_add(json_arr_out, json_obj_tx); } } else { size_t i_tmp = 0; if (a_head) HASH_ITER(hh, l_ledger_pvt->threshold_txs, l_tx_item, l_tx_tmp) { if (i_tmp < l_arr_start || i_tmp >= l_arr_end) { i_tmp++; continue; } i_tmp++; if (s_pack_ledger_threshold_info_json(json_arr_out, l_tx_item)) { pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); json_object_put(json_arr_out); return NULL; } l_counter++; } else { l_tx_item = HASH_LAST(l_ledger_pvt->threshold_txs); for(; l_tx_item; l_tx_item = l_tx_item->hh.prev, i_tmp++){ if (i_tmp < l_arr_start || i_tmp >= l_arr_end) continue; if (s_pack_ledger_threshold_info_json(json_arr_out, l_tx_item)) { pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); json_object_put(json_arr_out); return NULL; } l_counter++; } } if (!l_counter) { json_object* json_obj_tx = json_object_new_object(); json_object_object_add(json_obj_tx, "status", json_object_new_string("0 items in ledger tx threshold")); json_object_array_add(json_arr_out, json_obj_tx); } pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); } return json_arr_out; } json_object *dap_ledger_balance_info(dap_ledger_t *a_ledger, size_t a_limit, size_t a_offset, bool a_head) { dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); json_object * json_arr_out = json_object_new_array(); pthread_rwlock_rdlock(&l_ledger_pvt->balance_accounts_rwlock); uint32_t l_counter = 0; dap_ledger_wallet_balance_t *l_balance_item, *l_balance_tmp; size_t l_arr_start = 0; size_t l_arr_end = 0; dap_chain_set_offset_limit_json(json_arr_out, &l_arr_start, &l_arr_end, a_limit, a_offset, HASH_COUNT(l_ledger_pvt->balance_accounts)); size_t i_tmp = 0; if (a_head) HASH_ITER(hh, l_ledger_pvt->balance_accounts, l_balance_item, l_balance_tmp) { if (i_tmp < l_arr_start || i_tmp >= l_arr_end) { i_tmp++; continue; } i_tmp++; s_pack_ledger_balance_info_json(json_arr_out, l_balance_item); l_counter +=1; } else { l_balance_item = HASH_LAST(l_ledger_pvt->balance_accounts); for(; l_balance_item; l_balance_item = l_balance_item->hh.prev, i_tmp++){ if (i_tmp < l_arr_start || i_tmp >= l_arr_end) continue; s_pack_ledger_balance_info_json(json_arr_out, l_balance_item); l_counter++; } } if (!l_counter){ json_object* json_obj_tx = json_object_new_object(); json_object_object_add(json_obj_tx, "info_status", json_object_new_string("No items in ledger balance_accounts")); json_object_array_add(json_arr_out, json_obj_tx); } pthread_rwlock_unlock(&l_ledger_pvt->balance_accounts_rwlock); return json_arr_out; } int dap_ledger_pvt_threshold_txs_add(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_hash_fast_t *a_tx_hash) { dap_ledger_tx_item_t *l_item = NULL; dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); unsigned l_hash_value = 0; HASH_VALUE(a_tx_hash, sizeof(*a_tx_hash), l_hash_value); pthread_rwlock_wrlock(&l_ledger_pvt->threshold_txs_rwlock); HASH_FIND_BYHASHVALUE(hh, l_ledger_pvt->threshold_txs, a_tx_hash, sizeof(*a_tx_hash), l_hash_value, l_item); unsigned long long l_threshold_txs_count = HASH_COUNT(l_ledger_pvt->threshold_txs); if (!l_item) { if (l_threshold_txs_count >= s_threshold_txs_max) { pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); debug_if(g_debug_ledger, L_WARNING, "Threshold for transactions is overfulled (%zu max), dropping down tx %s, added nothing", s_threshold_txs_max, dap_hash_fast_to_str_static(a_tx_hash)); return -2; } if (!( l_item = DAP_NEW_Z(dap_ledger_tx_item_t) )) { pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); log_it(L_CRITICAL, "%s", c_error_memory_alloc); return -1; } l_item->tx_hash_fast = *a_tx_hash; l_item->tx = is_ledger_mapped(l_ledger_pvt) ? a_tx : DAP_DUP_SIZE(a_tx, dap_chain_datum_tx_get_size(a_tx)); if ( !l_item->tx ) { DAP_DELETE(l_item); pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); log_it(L_CRITICAL, "%s", c_error_memory_alloc); return -1; } l_item->ts_added = dap_nanotime_now(); l_item->cache_data.ts_created = a_tx->header.ts_created; HASH_ADD_BYHASHVALUE(hh, l_ledger_pvt->threshold_txs, tx_hash_fast, sizeof(dap_chain_hash_fast_t), l_hash_value, l_item); debug_if(g_debug_ledger, L_DEBUG, "Tx %s added to threshold", dap_hash_fast_to_str_static(a_tx_hash)); } pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); return 0; } void dap_ledger_pvt_threshold_txs_proc(dap_ledger_t *a_ledger) { bool l_success; dap_ledger_private_t * l_ledger_pvt = PVT(a_ledger); pthread_rwlock_wrlock(&l_ledger_pvt->threshold_txs_rwlock); do { l_success = false; dap_ledger_tx_item_t *l_tx_item, *l_tx_tmp; HASH_ITER(hh, l_ledger_pvt->threshold_txs, l_tx_item, l_tx_tmp) { int l_res = dap_ledger_tx_add(a_ledger, l_tx_item->tx, &l_tx_item->tx_hash_fast, true, NULL); if (l_res != DAP_CHAIN_CS_VERIFY_CODE_TX_NO_EMISSION && l_res != DAP_CHAIN_CS_VERIFY_CODE_TX_NO_PREVIOUS) { HASH_DEL(l_ledger_pvt->threshold_txs, l_tx_item); if ( !is_ledger_mapped(l_ledger_pvt) ) DAP_DELETE(l_tx_item->tx); DAP_DELETE(l_tx_item); l_success = true; } } } while (l_success); pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); } /** * @breif s_treshold_txs_free * @param a_ledger */ static void s_threshold_txs_free(dap_ledger_t *a_ledger) { log_it(L_DEBUG, "Start free threshold txs"); dap_ledger_private_t *l_pvt = PVT(a_ledger); dap_ledger_tx_item_t *l_current = NULL, *l_tmp = NULL; dap_nanotime_t l_time_cut_off = dap_nanotime_now() - dap_nanotime_from_sec(7200); //7200 sec = 2 hours. pthread_rwlock_wrlock(&l_pvt->threshold_txs_rwlock); HASH_ITER(hh, l_pvt->threshold_txs, l_current, l_tmp) { if (l_current->ts_added < l_time_cut_off) { HASH_DEL(l_pvt->threshold_txs, l_current); char l_tx_hash_str[DAP_CHAIN_HASH_FAST_STR_SIZE]; dap_chain_hash_fast_to_str(&l_current->tx_hash_fast, l_tx_hash_str, sizeof(l_tx_hash_str)); if ( !is_ledger_mapped(l_pvt) ) DAP_DELETE(l_current->tx); DAP_DELETE(l_current); log_it(L_NOTICE, "Removed transaction %s form threshold ledger", l_tx_hash_str); } } pthread_rwlock_unlock(&l_pvt->threshold_txs_rwlock); } /** * @brief s_load_cache_gdb_loaded_balances_callback * @param a_global_db_context * @param a_rc * @param a_group * @param a_key * @param a_values_total * @param a_values_shift * @param a_values_count * @param a_values * @param a_arg */ bool dap_ledger_pvt_cache_gdb_load_balances_callback(dap_global_db_instance_t *a_dbi, int a_rc, const char *a_group, const size_t a_values_total, const size_t a_values_count, dap_global_db_obj_t *a_values, void *a_arg) { dap_ledger_t * l_ledger = (dap_ledger_t*) a_arg; dap_ledger_private_t * l_ledger_pvt = PVT(l_ledger); for (size_t i = 0; i < a_values_count; i++) { dap_ledger_wallet_balance_t *l_balance_item = DAP_NEW_Z(dap_ledger_wallet_balance_t); if (!l_balance_item) { log_it(L_CRITICAL, "%s", c_error_memory_alloc); return false; } l_balance_item->key = dap_strdup(a_values[i].key); if (!l_balance_item->key) { log_it(L_CRITICAL, "%s", c_error_memory_alloc); DAP_DEL_Z(l_balance_item); return false; } char *l_ptr = strchr(l_balance_item->key, ' '); if (l_ptr++) dap_strncpy(l_balance_item->token_ticker, l_ptr, sizeof(l_balance_item->token_ticker)); l_balance_item->balance = *(uint256_t *)a_values[i].value; HASH_ADD_KEYPTR(hh, l_ledger_pvt->balance_accounts, l_balance_item->key, strlen(l_balance_item->key), l_balance_item); } pthread_mutex_lock( &l_ledger_pvt->load_mutex ); l_ledger_pvt->load_end = true; pthread_cond_broadcast( &l_ledger_pvt->load_cond ); pthread_mutex_unlock( &l_ledger_pvt->load_mutex ); return true; } /** * @brief Load ledger from cache (stored in GDB) * @param a_ledger */ void dap_ledger_load_cache(dap_ledger_t *a_ledger) { dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); char *l_gdb_group = dap_ledger_get_gdb_group(a_ledger, DAP_LEDGER_TOKENS_STR); pthread_mutex_lock(& l_ledger_pvt->load_mutex); dap_global_db_get_all(l_gdb_group, 0, dap_ledger_pvt_cache_gdb_load_tokens_callback, a_ledger); while (!l_ledger_pvt->load_end) pthread_cond_wait(& l_ledger_pvt->load_cond, &l_ledger_pvt->load_mutex); pthread_mutex_unlock(& l_ledger_pvt->load_mutex); DAP_DELETE(l_gdb_group); } /** * @brief * create ledger for specific net * load ledger cache * @param a_check_flags checking flags * @param a_net_name char * network name, for example "kelvin-testnet" * @return dap_ledger_t* */ dap_ledger_t *dap_ledger_create(dap_chain_net_t *a_net, uint16_t a_flags) { dap_ledger_t *l_ledger = dap_ledger_handle_new(); dap_return_val_if_fail(l_ledger, NULL); l_ledger->net = a_net; dap_ledger_private_t *l_ledger_pvt = PVT(l_ledger); l_ledger_pvt->flags = a_flags; if ( is_ledger_threshld(l_ledger_pvt) ) l_ledger_pvt->threshold_txs_free_timer = dap_interval_timer_create(s_threshold_free_timer_tick, (dap_timer_callback_t)s_threshold_txs_free, l_ledger); pthread_cond_init(&l_ledger_pvt->load_cond, NULL); pthread_mutex_init(&l_ledger_pvt->load_mutex, NULL); #ifndef DAP_LEDGER_TEST for ( dap_chain_t *l_chain = a_net->pub.chains; l_chain; l_chain = l_chain->next ) { uint16_t l_whitelist_size, l_blacklist_size, i; const char **l_whitelist = dap_config_get_array_str(l_chain->config, "ledger", "hard_accept_list", &l_whitelist_size), **l_blacklist = dap_config_get_array_str(l_chain->config, "ledger", "hard_reject_list", &l_blacklist_size); for (i = 0; i < l_blacklist_size; ++i) { dap_ledger_hal_item_t *l_item = DAP_NEW_Z(dap_ledger_hal_item_t); dap_chain_hash_fast_from_str(l_blacklist[i], &l_item->hash); HASH_ADD(hh, l_ledger_pvt->hrl_items, hash, sizeof(dap_hash_fast_t), l_item); } for (i = 0; i < l_whitelist_size; ++i) { dap_ledger_hal_item_t *l_item = DAP_NEW_Z(dap_ledger_hal_item_t); dap_chain_hash_fast_from_str(l_whitelist[i], &l_item->hash); HASH_ADD(hh, l_ledger_pvt->hal_items, hash, sizeof(dap_hash_fast_t), l_item); } log_it(L_DEBUG, "Chain %s.%s has %d datums in HAL and %d datums in HRL", a_net->pub.name, l_chain->name, l_whitelist_size, l_blacklist_size); } if ( is_ledger_cached(l_ledger_pvt) ) // load ledger cache from GDB dap_ledger_load_cache(l_ledger); #endif // Decrees initializing dap_ledger_decree_init(l_ledger); return l_ledger; } static int s_callback_sign_compare(dap_list_t *a_list_elem, dap_list_t *a_sign_elem) { dap_pkey_t *l_key = (dap_pkey_t *)a_list_elem->data; dap_sign_t *l_sign = (dap_sign_t *)a_sign_elem->data; if (!l_key || !l_sign) { log_it(L_CRITICAL, "Invalid argument"); return -1; } return !dap_pkey_compare_with_sign(l_key, l_sign); } bool dap_ledger_tx_poa_signed(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx) { dap_chain_tx_sig_t *l_tx_sig = (dap_chain_tx_sig_t *)dap_chain_datum_tx_item_get(a_tx, NULL, NULL, TX_ITEM_TYPE_SIG, NULL); dap_sign_t *l_sign = dap_chain_datum_tx_item_sig_get_sign((dap_chain_tx_sig_t *)l_tx_sig); return dap_list_find(a_ledger->net->pub.keys, l_sign, s_callback_sign_compare); } /* services we know now 0x01 - VPN 0x02 - xchange 0x03, 0x13 - pos_delegate 0x04 bridge 0x.05 - custom datum 0x06 voting 0x12 - stake_lock */ const char *dap_ledger_tx_action_str(dap_chain_tx_tag_action_type_t a_tag) { if (a_tag == DAP_CHAIN_TX_TAG_ACTION_UNKNOWN) return "unknown"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_TRANSFER_REGULAR) return "regular"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_TRANSFER_COMISSION) return "comission"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_TRANSFER_CROSSCHAIN) return "crosschain"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_TRANSFER_REWARD) return "reward"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_OPEN) return "open"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_USE) return "use"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_EXTEND) return "extend"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_CLOSE) return "close"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_CHANGE) return "change"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_VOTING) return "voting"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_VOTE) return "vote"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_EMIT_DELEGATE_HOLD) return "hold"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_EMIT_DELEGATE_TAKE) return "take"; if (a_tag == DAP_CHAIN_TX_TAG_ACTION_EMIT_DELEGATE_REFILL) return "refill"; return "WTFSUBTAG"; } dap_chain_tx_tag_action_type_t dap_ledger_tx_action_str_to_action_t(const char *a_str) { if (!a_str) return DAP_CHAIN_TX_TAG_ACTION_UNKNOWN; if (strcmp("unknown", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_UNKNOWN; if (strcmp("regular", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_TRANSFER_REGULAR; if (strcmp("comission", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_TRANSFER_COMISSION; if (strcmp("crosschain", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_TRANSFER_CROSSCHAIN; if (strcmp("reward", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_TRANSFER_REWARD; if (strcmp("open", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_OPEN; if (strcmp("use", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_USE; if (strcmp("extend", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_EXTEND; if (strcmp("close", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_CLOSE; if (strcmp("change", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_CHANGE; if (strcmp("voting", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_VOTING; if (strcmp("vote", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_VOTE; if (strcmp("hold", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_EMIT_DELEGATE_HOLD; if (strcmp("take", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_EMIT_DELEGATE_TAKE; if (strcmp("refill", a_str) == 0) return DAP_CHAIN_TX_TAG_ACTION_EMIT_DELEGATE_REFILL; return DAP_CHAIN_TX_TAG_ACTION_UNKNOWN; } bool dap_ledger_tx_service_info(dap_ledger_t *a_ledger, dap_hash_fast_t *a_tx_hash, dap_chain_srv_uid_t *a_uid, char **a_service_name, dap_chain_tx_tag_action_type_t *a_action) { //find tx dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); dap_ledger_tx_item_t *l_tx_item = NULL; pthread_rwlock_rdlock(&l_ledger_pvt->ledger_rwlock); HASH_FIND(hh, l_ledger_pvt->ledger_items, a_tx_hash, sizeof(dap_chain_hash_fast_t), l_tx_item); pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); if(l_tx_item) { dap_ledger_service_info_t *l_sinfo = NULL; pthread_rwlock_rdlock(&s_services_rwlock); HASH_FIND_INT(s_services, &l_tx_item->cache_data.tag, l_sinfo); pthread_rwlock_unlock(&s_services_rwlock); if (l_sinfo) { if(a_uid) *a_uid = l_sinfo->service_uid; if (a_service_name) *a_service_name = l_sinfo->tag_str; if (a_action) *a_action = l_tx_item->cache_data.action; return true; } } if (a_action) *a_action = DAP_CHAIN_TX_TAG_ACTION_UNKNOWN; return false; } bool dap_ledger_deduct_tx_tag(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, char **a_service_name, dap_chain_srv_uid_t *a_tag, dap_chain_tx_tag_action_type_t *a_action) { dap_ledger_service_info_t *l_sinfo_current, *l_sinfo_tmp; dap_chain_datum_tx_item_groups_t l_items_groups = {0}; dap_chain_datum_tx_group_items(a_tx, &l_items_groups); bool l_res = false; int l_deductions_ok = 0; pthread_rwlock_rdlock(&s_services_rwlock); HASH_ITER(hh, s_services , l_sinfo_current, l_sinfo_tmp) { dap_chain_tx_tag_action_type_t action = DAP_CHAIN_TX_TAG_ACTION_UNKNOWN; if (l_sinfo_current->callback && l_sinfo_current->callback(a_ledger, a_tx, &l_items_groups, &action)){ if (a_tag) *a_tag = l_sinfo_current->service_uid; if (a_action) *a_action = action; if (a_service_name) *a_service_name = l_sinfo_current->tag_str; l_res = true; l_deductions_ok ++; } } pthread_rwlock_unlock(&s_services_rwlock); if (l_deductions_ok > 1) { char l_tx_hash_str[DAP_CHAIN_HASH_FAST_STR_SIZE]; dap_chain_hash_fast_t l_tx_hash = dap_chain_node_datum_tx_calc_hash(a_tx); dap_chain_hash_fast_to_str(&l_tx_hash, l_tx_hash_str, sizeof(l_tx_hash_str)); log_it(L_WARNING, "Transaction %s identyfied by multiple services (%d):", l_tx_hash_str, l_deductions_ok); pthread_rwlock_rdlock(&s_services_rwlock); HASH_ITER(hh, s_services , l_sinfo_current, l_sinfo_tmp) { dap_chain_tx_tag_action_type_t action = DAP_CHAIN_TX_TAG_ACTION_UNKNOWN; if (l_sinfo_current->callback && l_sinfo_current->callback(a_ledger, a_tx, &l_items_groups,&action)) { log_it(L_WARNING, "%s %s", l_sinfo_current->tag_str, dap_ledger_tx_action_str(action)); } } pthread_rwlock_unlock(&s_services_rwlock); } dap_chain_datum_tx_group_items_free(&l_items_groups); return l_res; } const char *dap_ledger_tx_tag_str_by_uid(dap_chain_srv_uid_t a_service_uid) { dap_ledger_service_info_t *l_new_sinfo = NULL; int l_tmp = a_service_uid.raw_ui64; pthread_rwlock_rdlock(&s_services_rwlock); HASH_FIND_INT(s_services, &l_tmp, l_new_sinfo); pthread_rwlock_unlock(&s_services_rwlock); return l_new_sinfo ? l_new_sinfo->tag_str : "unknown"; } /** * Delete all transactions from the cache */ void dap_ledger_purge(dap_ledger_t *a_ledger, bool a_preserve_db) { dap_ledger_tx_purge(a_ledger, a_preserve_db); dap_ledger_token_purge(a_ledger, a_preserve_db); dap_ledger_decree_purge(a_ledger); PVT(a_ledger)->load_end = false; } void dap_ledger_tx_purge(dap_ledger_t *a_ledger, bool a_preserve_db) { dap_return_if_fail(a_ledger); dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); pthread_rwlock_wrlock(&l_ledger_pvt->ledger_rwlock); pthread_rwlock_wrlock(&l_ledger_pvt->threshold_txs_rwlock); pthread_rwlock_wrlock(&l_ledger_pvt->balance_accounts_rwlock); pthread_rwlock_wrlock(&l_ledger_pvt->stake_lock_rwlock); /* Delete regular transactions */ dap_ledger_tx_item_t *l_item_current, *l_item_tmp; char *l_gdb_group; HASH_ITER(hh, l_ledger_pvt->ledger_items , l_item_current, l_item_tmp) { HASH_DEL(l_ledger_pvt->ledger_items, l_item_current); if (!is_ledger_mapped(l_ledger_pvt)) DAP_DELETE(l_item_current->tx); DAP_DELETE(l_item_current); } if (!a_preserve_db) { l_gdb_group = dap_ledger_get_gdb_group(a_ledger, DAP_LEDGER_TXS_STR); dap_global_db_erase_table(l_gdb_group, NULL, NULL); DAP_DELETE(l_gdb_group); } /* Delete balances */ dap_ledger_wallet_balance_t *l_balance_current, *l_balance_tmp; HASH_ITER(hh, l_ledger_pvt->balance_accounts, l_balance_current, l_balance_tmp) { HASH_DEL(l_ledger_pvt->balance_accounts, l_balance_current); DAP_DEL_MULTY(l_balance_current->key, l_balance_current); } if (!a_preserve_db) { l_gdb_group = dap_ledger_get_gdb_group(a_ledger, DAP_LEDGER_BALANCES_STR); dap_global_db_erase_table(l_gdb_group, NULL, NULL); DAP_DELETE(l_gdb_group); } /* Delete stake-lock items */ dap_ledger_stake_lock_item_t *l_stake_item_current, *l_stake_item_tmp; HASH_ITER(hh, l_ledger_pvt->emissions_for_stake_lock, l_stake_item_current, l_stake_item_tmp) { HASH_DEL(l_ledger_pvt->emissions_for_stake_lock, l_stake_item_current); DAP_DELETE(l_stake_item_current); } if (!a_preserve_db) { l_gdb_group = dap_ledger_get_gdb_group(a_ledger, DAP_LEDGER_STAKE_LOCK_STR); dap_global_db_erase_table(l_gdb_group, NULL, NULL); DAP_DELETE(l_gdb_group); } /* Delete threshold transactions */ HASH_ITER(hh, l_ledger_pvt->threshold_txs, l_item_current, l_item_tmp) { HASH_DEL(l_ledger_pvt->threshold_txs, l_item_current); if (!is_ledger_mapped(l_ledger_pvt)) DAP_DELETE(l_item_current->tx); DAP_DEL_Z(l_item_current); } l_ledger_pvt->ledger_items = NULL; l_ledger_pvt->balance_accounts = NULL; l_ledger_pvt->threshold_txs = NULL; pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); pthread_rwlock_unlock(&l_ledger_pvt->threshold_txs_rwlock); pthread_rwlock_unlock(&l_ledger_pvt->balance_accounts_rwlock); pthread_rwlock_unlock(&l_ledger_pvt->stake_lock_rwlock); } void dap_ledger_token_purge(dap_ledger_t *a_ledger, bool a_preserve_db) { dap_return_if_fail(a_ledger); dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); pthread_rwlock_wrlock(&l_ledger_pvt->tokens_rwlock); /* Delete tokens and their emissions */ dap_ledger_token_item_t *l_token_current, *l_token_tmp; dap_ledger_token_emission_item_t *l_emission_current, *l_emission_tmp; HASH_ITER(hh, l_ledger_pvt->tokens, l_token_current, l_token_tmp) { HASH_DEL(l_ledger_pvt->tokens, l_token_current); pthread_rwlock_wrlock(&l_token_current->token_emissions_rwlock); HASH_ITER(hh, l_token_current->token_emissions, l_emission_current, l_emission_tmp) { HASH_DEL(l_token_current->token_emissions, l_emission_current); DAP_DEL_MULTY(l_emission_current->datum_token_emission, l_emission_current); } pthread_rwlock_unlock(&l_token_current->token_emissions_rwlock); pthread_rwlock_destroy(&l_token_current->token_emissions_rwlock); DAP_DEL_MULTY(l_token_current->datum_token, l_token_current->datum_token, l_token_current->auth_pkeys, l_token_current->auth_pkey_hashes, l_token_current->tx_recv_allow, l_token_current->tx_recv_block, l_token_current->tx_send_allow, l_token_current->tx_send_block, l_token_current); } if (!a_preserve_db) { char *l_gdb_group = dap_ledger_get_gdb_group(a_ledger, DAP_LEDGER_TOKENS_STR); dap_global_db_erase_table(l_gdb_group, NULL, NULL); DAP_DELETE(l_gdb_group); l_gdb_group = dap_ledger_get_gdb_group(a_ledger, DAP_LEDGER_EMISSIONS_STR); dap_global_db_erase_table(l_gdb_group, NULL, NULL); DAP_DELETE(l_gdb_group); } l_ledger_pvt->tokens = NULL; pthread_rwlock_unlock(&l_ledger_pvt->tokens_rwlock); } /** * Return number transactions from the cache * According to UT_hash_handle size of return value is sizeof(unsigned int) */ unsigned dap_ledger_count(dap_ledger_t *a_ledger) { pthread_rwlock_rdlock(&PVT(a_ledger)->ledger_rwlock); unsigned long ret = HASH_COUNT(PVT(a_ledger)->ledger_items); pthread_rwlock_unlock(&PVT(a_ledger)->ledger_rwlock); return ret; } /** * @brief dap_ledger_count_from_to * @param a_ledger * @param a_ts_from * @param a_ts_to * @return */ uint64_t dap_ledger_count_from_to(dap_ledger_t * a_ledger, dap_nanotime_t a_ts_from, dap_nanotime_t a_ts_to) { uint64_t l_ret = 0; dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); dap_ledger_tx_item_t *l_iter_current, *l_item_tmp; pthread_rwlock_rdlock(&l_ledger_pvt->ledger_rwlock); if ( a_ts_from && a_ts_to) { HASH_ITER(hh, l_ledger_pvt->ledger_items , l_iter_current, l_item_tmp){ if ( l_iter_current->ts_added >= a_ts_from && l_iter_current->ts_added <= a_ts_to ) l_ret++; } } else if ( a_ts_to ){ HASH_ITER(hh, l_ledger_pvt->ledger_items , l_iter_current, l_item_tmp){ if ( l_iter_current->ts_added <= a_ts_to ) l_ret++; } } else if ( a_ts_from ){ HASH_ITER(hh, l_ledger_pvt->ledger_items , l_iter_current, l_item_tmp){ if ( l_iter_current->ts_added >= a_ts_from ) l_ret++; } } else { l_ret = HASH_COUNT(l_ledger_pvt->ledger_items); } pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); return l_ret; } /** * Calculate balance of addr * */ uint256_t dap_ledger_calc_balance(dap_ledger_t *a_ledger, const dap_chain_addr_t *a_addr, const char *a_token_ticker) { uint256_t l_ret = uint256_0; dap_ledger_wallet_balance_t *l_balance_item = NULL;// ,* l_balance_item_tmp = NULL; const char *l_addr = dap_chain_addr_to_str_static(a_addr); char *l_wallet_balance_key = dap_strjoin(" ", l_addr, a_token_ticker, (char*)NULL); pthread_rwlock_rdlock(&PVT(a_ledger)->balance_accounts_rwlock); HASH_FIND_STR(PVT(a_ledger)->balance_accounts, l_wallet_balance_key, l_balance_item); pthread_rwlock_unlock(&PVT(a_ledger)->balance_accounts_rwlock); if (l_balance_item) { debug_if(g_debug_ledger, L_INFO, "Found address in cache with balance %s", dap_uint256_to_char(l_balance_item->balance, NULL)); l_ret = l_balance_item->balance; } else { debug_if(g_debug_ledger, L_WARNING, "Balance item %s not found", l_wallet_balance_key); } DAP_DELETE(l_wallet_balance_key); return l_ret; } bool dap_ledger_tx_check_recipient(dap_ledger_t* a_ledger, dap_chain_hash_fast_t* a_tx_prev_hash, dap_chain_addr_t *a_addr) { dap_chain_datum_tx_t *l_tx = dap_ledger_tx_find_by_hash(a_ledger, a_tx_prev_hash); if ( !l_tx ) return false; dap_chain_addr_t l_dst_addr = { }; byte_t *it; size_t l_size; TX_ITEM_ITER_TX(it, l_size, l_tx) { switch (*it) { case TX_ITEM_TYPE_OUT: l_dst_addr = ((dap_chain_tx_out_t*)it)->addr; break; case TX_ITEM_TYPE_OUT_EXT: l_dst_addr = ((dap_chain_tx_out_ext_t*)it)->addr; break; case TX_ITEM_TYPE_OUT_OLD: l_dst_addr = ((dap_chain_tx_out_old_t*)it)->addr; break; default: continue; } if ( dap_chain_addr_compare(a_addr, &l_dst_addr) ) return true; } return false; } /** * @brief dap_ledger_find_pkey_by_hash * @param a_ledger to search * @param a_pkey_hash - pkey hash * @return pointer to dap_pkey_t if finded, other - NULL */ dap_pkey_t *dap_ledger_find_pkey_by_hash(dap_ledger_t *a_ledger, dap_chain_hash_fast_t *a_pkey_hash) { dap_return_val_if_pass(!a_pkey_hash || dap_hash_fast_is_blank(a_pkey_hash), NULL); dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); dap_ledger_tx_item_t *l_iter_current, *l_item_tmp; dap_pkey_t *l_ret = NULL; pthread_rwlock_rdlock(&l_ledger_pvt->ledger_rwlock); HASH_ITER(hh, l_ledger_pvt->ledger_items , l_iter_current, l_item_tmp) { dap_chain_datum_tx_t *l_tx_tmp = l_iter_current->tx; dap_chain_hash_fast_t *l_tx_hash_tmp = &l_iter_current->tx_hash_fast; // Get sign item from transaction dap_chain_tx_sig_t *l_tx_sig = (dap_chain_tx_sig_t*) dap_chain_datum_tx_item_get(l_tx_tmp, NULL, NULL, TX_ITEM_TYPE_SIG, NULL); // Get dap_sign_t from item dap_sign_t *l_sig = dap_chain_datum_tx_item_sig_get_sign(l_tx_sig); if(l_sig) { // compare public key in transaction with a_public_key dap_chain_hash_fast_t l_sign_hash = {}; dap_sign_get_pkey_hash(l_sig, &l_sign_hash); if(!memcmp(&l_sign_hash, a_pkey_hash, sizeof(dap_chain_hash_fast_t))) { l_ret = dap_pkey_get_from_sign(l_sig); break; } } } pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); return l_ret; } /** * @brief Get all transactions from the cache with the out_cond item * @param a_ledger * @param a_srv_uid * @return */ dap_list_t* dap_ledger_tx_cache_find_out_cond_all(dap_ledger_t *a_ledger, dap_chain_srv_uid_t a_srv_uid) { dap_list_t * l_ret = NULL; dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); dap_ledger_tx_item_t *l_iter_current = NULL, *l_item_tmp = NULL; HASH_ITER(hh, l_ledger_pvt->ledger_items, l_iter_current, l_item_tmp) { dap_chain_datum_tx_t *l_tx = l_iter_current->tx; byte_t *item; size_t l_size; TX_ITEM_ITER_TX(item, l_size, l_tx) { if (*item == TX_ITEM_TYPE_OUT_COND && ((dap_chain_tx_out_cond_t*)item)->header.srv_uid.uint64 == a_srv_uid.uint64) l_ret = dap_list_append(l_ret, l_tx); } } return l_ret; } dap_list_t *dap_ledger_get_list_tx_outs(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from, uint256_t *a_value_transfer) { dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items dap_chain_hash_fast_t l_tx_cur_hash = { }; uint256_t l_value_transfer = {}; dap_chain_datum_tx_t *l_tx; while (( l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from, &l_tx_cur_hash) )) { byte_t *it; size_t l_size; int i, l_out_idx_tmp = -1; dap_chain_addr_t l_out_addr = { }; TX_ITEM_ITER_TX_TYPE(it, TX_ITEM_TYPE_OUT_ALL, l_size, i, l_tx) { ++l_out_idx_tmp; uint256_t l_value = { }; switch (*it) { case TX_ITEM_TYPE_OUT_OLD: { dap_chain_tx_out_old_t *l_out = (dap_chain_tx_out_old_t*)it; l_out_addr = l_out->addr; if ( !l_out->header.value || !dap_chain_addr_compare(a_addr_from, &l_out_addr) ) continue; l_value = GET_256_FROM_64(l_out->header.value); } break; case TX_ITEM_TYPE_OUT: { dap_chain_tx_out_t *l_out = (dap_chain_tx_out_t*)it; l_out_addr = l_out->addr; if ( !dap_chain_addr_compare(a_addr_from, &l_out_addr) || dap_strcmp( dap_ledger_tx_get_token_ticker_by_hash(a_ledger, &l_tx_cur_hash), a_token_ticker ) || IS_ZERO_256(l_out->header.value)) continue; l_value = l_out->header.value; } break; case TX_ITEM_TYPE_OUT_EXT: { dap_chain_tx_out_ext_t *l_out_ext = (dap_chain_tx_out_ext_t *)it; l_out_addr = l_out_ext->addr; if ( !dap_chain_addr_compare(a_addr_from, &l_out_addr) || strcmp((char*)a_token_ticker, l_out_ext->token) || IS_ZERO_256(l_out_ext->header.value) ) continue; l_value = l_out_ext->header.value; } break; default: continue; } // Check whether used 'out' items if ( !dap_ledger_tx_hash_is_used_out_item(a_ledger, &l_tx_cur_hash, l_out_idx_tmp, NULL) ) { dap_chain_tx_used_out_item_t *l_item = DAP_NEW_Z(dap_chain_tx_used_out_item_t); *(l_item) = (dap_chain_tx_used_out_item_t) { l_tx_cur_hash, (uint32_t)l_out_idx_tmp, l_value }; l_list_used_out = dap_list_append(l_list_used_out, l_item); SUM_256_256(l_value_transfer, l_item->value, &l_value_transfer); } } } if (a_value_transfer) *a_value_transfer = l_value_transfer; return l_list_used_out; } dap_list_t *dap_ledger_get_txs(dap_ledger_t *a_ledger, size_t a_count, size_t a_page, bool a_reverse, bool a_unspent_only) { dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); pthread_rwlock_rdlock(&PVT(a_ledger)->ledger_rwlock); size_t l_offset = a_page < 2 ? 0 : a_count * (a_page - 1); if (!l_ledger_pvt->ledger_items || l_offset > HASH_COUNT(l_ledger_pvt->ledger_items)){ pthread_rwlock_unlock(&PVT(a_ledger)->ledger_rwlock); return NULL; } dap_list_t *l_list = NULL; size_t l_counter = 0; dap_ledger_tx_item_t *l_item_current, *l_item_tmp; HASH_ITER(hh, l_ledger_pvt->ledger_items, l_item_current, l_item_tmp) { if (l_counter++ >= l_offset) { if (!a_unspent_only || !l_item_current->cache_data.ts_spent) l_list = a_reverse ? dap_list_prepend(l_list, l_item_current->tx) : dap_list_append(l_list, l_item_current->tx); } } pthread_rwlock_unlock(&PVT(a_ledger)->ledger_rwlock); return l_list; } dap_ledger_datum_iter_t *dap_ledger_datum_iter_create(dap_chain_net_t *a_net) { dap_ledger_datum_iter_t *l_ret = DAP_NEW_Z(dap_ledger_datum_iter_t); if(!l_ret){ log_it(L_CRITICAL, "Memory allocation error!"); return NULL; } l_ret->net = a_net; return l_ret; } void dap_ledger_datum_iter_delete(dap_ledger_datum_iter_t *a_iter) { DAP_DEL_Z(a_iter); } dap_chain_datum_tx_t *dap_ledger_datum_iter_get_first(dap_ledger_datum_iter_t *a_iter) { if (!a_iter) return NULL; dap_ledger_private_t *l_ledger_pvt = PVT(a_iter->net->pub.ledger); pthread_rwlock_rdlock(&l_ledger_pvt->ledger_rwlock); if (!l_ledger_pvt->ledger_items) { pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); return NULL; } a_iter->cur_ledger_tx_item = l_ledger_pvt->ledger_items; a_iter->cur = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->tx; a_iter->cur_hash = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->tx_hash_fast; a_iter->is_unspent = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->cache_data.ts_spent ? false : true; a_iter->ret_code = 0; pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); return a_iter->cur; } dap_chain_datum_tx_t *dap_ledger_datum_iter_get_next(dap_ledger_datum_iter_t *a_iter) { dap_ledger_private_t *l_ledger_pvt = PVT(a_iter->net->pub.ledger); pthread_rwlock_rdlock(&l_ledger_pvt->ledger_rwlock); a_iter->cur_ledger_tx_item = a_iter->cur_ledger_tx_item ? ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->hh.next : NULL; if (a_iter->cur_ledger_tx_item){ a_iter->cur = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->tx; a_iter->cur_hash = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->tx_hash_fast; a_iter->ret_code = 0; a_iter->is_unspent = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->cache_data.ts_spent ? false : true; } else { a_iter->cur = NULL; memset(&a_iter->cur_hash, 0, sizeof(dap_hash_fast_t)); a_iter->ret_code = 0; a_iter->is_unspent = false; } pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); return a_iter->cur; } dap_chain_datum_tx_t *dap_ledger_datum_iter_get_last(dap_ledger_datum_iter_t *a_iter) { dap_ledger_private_t *l_ledger_pvt = PVT(a_iter->net->pub.ledger); pthread_rwlock_rdlock(&l_ledger_pvt->ledger_rwlock); a_iter->cur_ledger_tx_item = HASH_LAST(l_ledger_pvt->ledger_items); a_iter->cur = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->tx; a_iter->cur_hash = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->tx_hash_fast; a_iter->is_unspent = ((dap_ledger_tx_item_t *)(a_iter->cur_ledger_tx_item))->cache_data.ts_spent ? false : true; a_iter->ret_code = 0; pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); return a_iter->cur; } dap_chain_tx_used_out_item_t *dap_ledger_get_tx_cond_out(dap_ledger_t *a_ledger, const dap_chain_addr_t *a_addr_from, dap_chain_tx_out_cond_subtype_t a_subtype) { dap_chain_tx_used_out_item_t *l_item = NULL; dap_chain_hash_fast_t l_tx_cur_hash = { }; dap_chain_datum_tx_t *l_tx; while(( l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_ledger->net->pub.native_ticker, a_addr_from, &l_tx_cur_hash) )) { byte_t *it; size_t l_size; int i, l_out_idx_tmp = -1; TX_ITEM_ITER_TX_TYPE(it, TX_ITEM_TYPE_OUT_COND, l_size, i, l_tx) { ++l_out_idx_tmp; dap_chain_tx_out_cond_t *l_out_cond = (dap_chain_tx_out_cond_t *)it; if ( a_subtype != l_out_cond->header.subtype || IS_ZERO_256(l_out_cond->header.value) ) continue; if (dap_ledger_tx_hash_is_used_out_item(a_ledger, &l_tx_cur_hash, l_out_idx_tmp, NULL)) continue; // TODO Move this check to dap_ledger_tx_find_by_addr() to avoid double search l_item = DAP_NEW_Z(dap_chain_tx_used_out_item_t); *l_item = (dap_chain_tx_used_out_item_t) { l_tx_cur_hash, (uint32_t)l_out_idx_tmp, l_out_cond->header.value }; } } return l_item; } /** * Get the transaction in the cache by the addr in sig item * * a_addr[in] public key that signed the transaction * a_tx_first_hash [in/out] hash of the initial transaction/ found transaction, if 0 start from the beginning */ dap_chain_tx_out_cond_t *dap_ledger_out_cond_unspent_find_by_addr(dap_ledger_t *a_ledger, const char *a_token, dap_chain_tx_out_cond_subtype_t a_subtype, const dap_chain_addr_t *a_addr, dap_chain_hash_fast_t *a_tx_first_hash, int *a_out_idx) { if (!a_addr || !a_token) return NULL; dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); dap_chain_tx_out_cond_t *ret = NULL; dap_ledger_tx_item_t *l_iter_start = NULL, *it; pthread_rwlock_rdlock(&l_ledger_pvt->ledger_rwlock); if (a_tx_first_hash && !dap_hash_fast_is_blank(a_tx_first_hash)) { HASH_FIND(hh, l_ledger_pvt->ledger_items, a_tx_first_hash, sizeof(dap_hash_t), l_iter_start); if (!l_iter_start || !l_iter_start->hh.next) { pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); return NULL; } // start searching from the next hash after a_tx_first_hash l_iter_start = l_iter_start->hh.next; } else l_iter_start = l_ledger_pvt->ledger_items; for (it = l_iter_start; it; it = it->hh.next, ret = NULL) { // If a_token is setup we check if its not our token - miss it if (*it->cache_data.token_ticker && dap_strcmp(it->cache_data.token_ticker, a_token)) continue; ret = NULL; // Get 'out_cond' item from transaction byte_t *l_item; size_t l_size; int i, l_out_idx = 0; TX_ITEM_ITER_TX_TYPE(l_item, TX_ITEM_TYPE_OUT_ALL, l_size, i, it->tx) { if (*l_item == TX_ITEM_TYPE_OUT_COND) { dap_chain_tx_out_cond_subtype_t l_subtype = ((dap_chain_tx_out_cond_t *)l_item)->header.subtype; if (l_subtype == a_subtype || (a_subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_ALL && l_subtype != DAP_CHAIN_TX_OUT_COND_SUBTYPE_FEE)) { ret = (dap_chain_tx_out_cond_t *)l_item; break; } } l_out_idx++; } // Don't return regular tx or spent conditions if (!ret || !dap_hash_fast_is_blank(&it->out_metadata[l_out_idx].tx_spent_hash_fast)) continue; dap_hash_fast_t l_owner_tx_hash = dap_ledger_get_first_chain_tx_hash(a_ledger, a_subtype, &it->tx_hash_fast); dap_chain_datum_tx_t *l_tx = dap_hash_fast_is_blank(&l_owner_tx_hash) ? it->tx : dap_ledger_tx_find_by_hash(a_ledger, &l_owner_tx_hash); if (!l_tx) { log_it(L_ERROR, "Can't find owner for tx %s", dap_hash_fast_to_str_static(&it->tx_hash_fast)); continue; } // Get sign item from transaction dap_chain_tx_sig_t *l_tx_sig = (dap_chain_tx_sig_t*) dap_chain_datum_tx_item_get(l_tx, NULL, NULL, TX_ITEM_TYPE_SIG, NULL); // Get dap_sign_t from item dap_sign_t *l_sign = dap_chain_datum_tx_item_sig_get_sign(l_tx_sig); // compare public key in transaction with a_public_key dap_chain_hash_fast_t l_sign_hash = {}; dap_sign_get_pkey_hash(l_sign, &l_sign_hash); if (dap_hash_fast_compare(&l_sign_hash, &a_addr->data.hash_fast)) { if (a_tx_first_hash) *a_tx_first_hash = it->tx_hash_fast; if (a_out_idx) *a_out_idx = l_out_idx; break; } } pthread_rwlock_unlock(&l_ledger_pvt->ledger_rwlock); return ret; } dap_list_t *dap_ledger_get_list_tx_cond_outs(dap_ledger_t *a_ledger, dap_chain_tx_out_cond_subtype_t a_subtype, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from) { dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items dap_chain_hash_fast_t l_tx_cur_hash = { }; int l_out_idx; dap_chain_tx_out_cond_t *l_cond; while ( (l_cond = dap_ledger_out_cond_unspent_find_by_addr(a_ledger, a_token_ticker, a_subtype, a_addr_from, &l_tx_cur_hash, &l_out_idx)) ) { dap_chain_tx_used_out_item_t *l_item = DAP_NEW_Z(dap_chain_tx_used_out_item_t); *l_item = (dap_chain_tx_used_out_item_t) { l_tx_cur_hash, (uint32_t)l_out_idx, l_cond->header.value }; l_list_used_out = dap_list_append(l_list_used_out, l_item); } return l_list_used_out; } bool dap_ledger_check_condition_owner(dap_ledger_t *a_ledger, dap_hash_fast_t *a_tx_hash, dap_chain_tx_out_cond_subtype_t a_cond_subtype, int a_out_idx, dap_sign_t *a_owner_sign) { dap_return_val_if_fail(a_ledger && a_tx_hash && a_owner_sign, false); // Get first tx dap_chain_datum_tx_t *l_check_tx = dap_ledger_tx_find_by_hash(a_ledger, a_tx_hash); if (!l_check_tx) { log_it(L_ERROR, "Can't find tx %s", dap_hash_fast_to_str_static(a_tx_hash)); return false; } if (!dap_chain_datum_tx_out_cond_get(l_check_tx, a_cond_subtype, NULL)) { log_it(L_ERROR, "Can't find owner for tx %s", dap_hash_fast_to_str_static(a_tx_hash)); return false; } dap_hash_fast_t l_first_tx_hash = dap_ledger_get_first_chain_tx_hash(a_ledger, a_cond_subtype, a_tx_hash); dap_chain_datum_tx_t *l_first_tx = dap_hash_fast_is_blank(&l_first_tx_hash) ? l_check_tx : dap_ledger_tx_find_by_hash(a_ledger, &l_first_tx_hash); if (!l_first_tx) { log_it(L_ERROR, "Can't find owner tx %s", dap_hash_fast_to_str_static(&l_first_tx_hash)); return false; } dap_chain_tx_sig_t *l_first_tx_sig = (dap_chain_tx_sig_t *)dap_chain_datum_tx_item_get(l_first_tx, NULL, NULL, TX_ITEM_TYPE_SIG, NULL); dap_sign_t *l_sign = dap_chain_datum_tx_item_sig_get_sign((dap_chain_tx_sig_t *)l_first_tx_sig); if (!l_sign) { log_it(L_ERROR, "Can't find signature for tx %s", dap_hash_fast_to_str_static(&l_first_tx_hash)); return false; } return dap_sign_compare_pkeys(a_owner_sign, l_sign); } bool dap_ledger_cache_enabled(dap_ledger_t *a_ledger) { return is_ledger_cached(PVT(a_ledger)); }