Skip to content
Snippets Groups Projects
dap_chain_cs_dag.c 56.47 KiB
/*
 * Authors:
 * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
 * DeM Labs Inc.   https://demlabs.net
 * Kelvin Project https://github.com/kelvinblockchain
 * Copyright  (c) 2017-2018
 * 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 <stdlib.h>
#include <time.h>
#include <pthread.h>
#include "uthash.h"

#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <io.h>
#include <time.h>
#include <wepoll.h>
#include <pthread.h>
#endif

#include "dap_common.h"
#include "dap_string.h"
#include "dap_strfuncs.h"
#include "dap_hash.h"
#include "dap_chain_datum.h"
#include "dap_chain_cs.h"
#include "dap_chain_cs_dag.h"
#include "dap_chain_global_db.h"
#include "dap_chain_node_cli.h"
#include "dap_chain_cell.h"
#include "dap_chain_net.h"

#define LOG_TAG "dap_chain_cs_dag"


typedef struct dap_chain_cs_dag_event_item {
    dap_chain_hash_fast_t hash;
    time_t ts_added;
    dap_chain_cs_dag_event_t *event;
    UT_hash_handle hh;
} dap_chain_cs_dag_event_item_t;

typedef struct dap_chain_cs_dag_pvt {
    dap_enc_key_t* datum_add_sign_key;


    pthread_rwlock_t events_rwlock;

    dap_chain_cs_dag_event_item_t * events;
    dap_chain_cs_dag_event_item_t * events_treshold;
    dap_chain_cs_dag_event_item_t * events_lasts_unlinked;

} dap_chain_cs_dag_pvt_t;

#define PVT(a) ((dap_chain_cs_dag_pvt_t *) a->_pvt )

// Atomic element organization callbacks
static int s_chain_callback_atom_add(dap_chain_t * a_chain, dap_chain_atom_ptr_t );                      //    Accept new event in dag
static int s_chain_callback_atom_verify(dap_chain_t * a_chain, dap_chain_atom_ptr_t );                   //    Verify new event in dag
static size_t s_chain_callback_atom_hdr_get_size(dap_chain_atom_ptr_t );                                 //    Get dag event size
static size_t s_chain_callback_atom_get_static_hdr_size(void);                               //    Get dag event header size

static dap_chain_atom_iter_t* s_chain_callback_atom_iter_create(dap_chain_t * a_chain );
static dap_chain_atom_iter_t* s_chain_callback_atom_iter_create_from(dap_chain_t * a_chain ,
                                                                     dap_chain_atom_ptr_t a);


static dap_chain_atom_ptr_t s_chain_callback_atom_iter_find_by_hash(dap_chain_atom_iter_t * a_atom_iter ,
                                                                       dap_chain_hash_fast_t * a_atom_hash);

static dap_chain_datum_t* s_chain_callback_atom_get_datum(dap_chain_atom_ptr_t a_event);
//    Get event(s) from dag
static dap_chain_atom_ptr_t s_chain_callback_atom_iter_get_first( dap_chain_atom_iter_t * a_atom_iter ); //    Get the fisrt event from dag
static dap_chain_atom_ptr_t s_chain_callback_atom_iter_get_next( dap_chain_atom_iter_t * a_atom_iter );  //    Get the next event from dag
static dap_chain_atom_ptr_t *s_chain_callback_atom_iter_get_links( dap_chain_atom_iter_t * a_atom_iter ,
                                                                  size_t * a_links_size_ptr );  //    Get list of linked events
static dap_chain_atom_ptr_t *s_chain_callback_atom_iter_get_lasts( dap_chain_atom_iter_t * a_atom_iter ,
                                                                  size_t * a_lasts_size_ptr );  //    Get list of linked events

// Delete iterator
static void s_chain_callback_atom_iter_delete(dap_chain_atom_iter_t * a_atom_iter );                  //    Get the fisrt event from dag

static size_t s_chain_callback_datums_pool_proc(dap_chain_t * a_chain, dap_chain_datum_t ** a_datums, size_t a_datums_size);
// Datum ops
/*
static dap_chain_datum_iter_t* s_chain_callback_datum_iter_create(dap_chain_t * a_chain );
static void s_chain_callback_datum_iter_delete(dap_chain_datum_iter_t * a_iter );
static dap_chain_datum_t* s_chain_callback_datum_iter_get_first( dap_chain_datum_iter_t * a_datum_iter ); // Get the fisrt datum from dag
static dap_chain_datum_t* s_chain_callback_datum_iter_get_next( dap_chain_datum_iter_t * a_datum_iter ); // Get the next datum from dag
*/

static int s_cli_dag(int argc, char ** argv, char **str_reply);

static bool s_seed_mode = false;
/**
 * @brief dap_chain_cs_dag_init
 * @return
 */
int dap_chain_cs_dag_init(void)
{
    srand((unsigned int) time(NULL));
    dap_chain_class_add( "dag", dap_chain_cs_dag_new );
    s_seed_mode = dap_config_get_item_bool_default(g_config,"general","seed_mode",false);
    dap_chain_node_cli_cmd_item_create ("dag", s_cli_dag, "DAG commands",
        "dag -net <chain net name> -chain <chain name> event create -datum <datum hash>\n"
            "\tCreate event from datum mempool element\n\n"
        "dag -net <chain net name> -chain <chain name> event cancel -event <event hash>\n"
            "\tRemove event from forming new round and put back its datum to mempool\n\n"
        "dag -net <chain net name> -chain <chain name> event sign -event <event hash>\n"
            "\tAdd sign to event <event hash> in round.new. Hash doesn't include other signs so event hash\n"
            "\tdoesn't changes after sign add to event. \n\n"
        "dag -net <chain net name> -chain <chain name> event dump -event <event hash> -from < events | events_lasts | round.new  | round.<Round id in hex> >\n"
            "\tDump event info\n\n"
        "dag -net <chain net name> -chain <chain name> event list -from < events | events_lasts | round.new  | round.<Round id in hex> \n\n"
            "\tShow event list \n\n"
        "dag -net <chain net name> -chain <chain name> round complete\n\n"
                                        "\tComplete the current new round, verify it and if everything is ok - publish new events in chain\n\n"
                                        );
    log_it(L_NOTICE,"Initialized DAG chain items organization class");
    return 0;
}

/**
 * @brief dap_chain_cs_dag_deinit
 */
void dap_chain_cs_dag_deinit(void)
{

}

/**
 * @brief dap_chain_cs_dag_new
 * @param a_chain
 * @param a_chain_cfg
 */
int dap_chain_cs_dag_new(dap_chain_t * a_chain, dap_config_t * a_chain_cfg)
{
    dap_chain_cs_dag_t * l_dag = DAP_NEW_Z(dap_chain_cs_dag_t);
    l_dag->_pvt = DAP_NEW_Z(dap_chain_cs_dag_pvt_t);
    l_dag->chain = a_chain;

    pthread_rwlock_init(& PVT(l_dag)->events_rwlock,NULL);

    a_chain->callback_delete = dap_chain_cs_dag_delete;

    // Atom element callbacks
    a_chain->callback_atom_add = s_chain_callback_atom_add ;  // Accept new element in chain
    a_chain->callback_atom_verify = s_chain_callback_atom_verify ;  // Verify new element in chain
    a_chain->callback_atom_get_size  = s_chain_callback_atom_hdr_get_size; // Get dag event size
    a_chain->callback_atom_get_hdr_static_size = s_chain_callback_atom_get_static_hdr_size; // Get dag event hdr size

    a_chain->callback_atom_iter_create = s_chain_callback_atom_iter_create;
    a_chain->callback_atom_iter_create_from = s_chain_callback_atom_iter_create_from;
    a_chain->callback_atom_iter_delete = s_chain_callback_atom_iter_delete;

    // Linear pass through
    a_chain->callback_atom_iter_get_first = s_chain_callback_atom_iter_get_first; // Get the fisrt element from chain
    a_chain->callback_atom_iter_get_next = s_chain_callback_atom_iter_get_next; // Get the next element from chain from the current one
    a_chain->callback_atom_get_datum = s_chain_callback_atom_get_datum;

    a_chain->callback_atom_iter_get_links = s_chain_callback_atom_iter_get_links; // Get the next element from chain from the current one
    a_chain->callback_atom_iter_get_lasts = s_chain_callback_atom_iter_get_lasts;

    a_chain->callback_atom_find_by_hash = s_chain_callback_atom_iter_find_by_hash;


    a_chain->callback_datums_pool_proc = s_chain_callback_datums_pool_proc;

    // Datum operations callbacks
/*
    a_chain->callback_datum_iter_create = s_chain_callback_datum_iter_create; // Datum iterator create
    a_chain->callback_datum_iter_delete = s_chain_callback_datum_iter_delete; // Datum iterator delete
    a_chain->callback_datum_iter_get_first = s_chain_callback_datum_iter_get_first; // Get the fisrt datum from chain
    a_chain->callback_datum_iter_get_next = s_chain_callback_datum_iter_get_next; // Get the next datum from chain from the current one
*/
    // Others
    a_chain->_inheritor = l_dag;

    const char * l_static_genesis_event_hash_str = dap_config_get_item_str_default(a_chain_cfg,"dag","static_genesis_event",NULL);
    if ( l_static_genesis_event_hash_str ){
        int lhr;
        if ( (lhr= dap_chain_str_to_hash_fast(l_static_genesis_event_hash_str,&l_dag->static_genesis_event_hash) )!= 0 ){
            log_it( L_ERROR, "Can't read hash from static_genesis_event \"%s\", ret code %d ", l_static_genesis_event_hash_str, lhr);
        }
    }

    l_dag->is_static_genesis_event = dap_config_get_item_bool_default(a_chain_cfg,"dag","is_static_genesis_event",false);
    l_dag->is_single_line = dap_config_get_item_bool_default(a_chain_cfg,"dag","is_single_line",false);
    l_dag->is_celled = dap_config_get_item_bool_default(a_chain_cfg,"dag","is_celled",false);
    l_dag->is_add_directy = dap_config_get_item_bool_default(a_chain_cfg,"dag","is_add_directly",false);
    l_dag->datum_add_hashes_count = dap_config_get_item_uint16_default(a_chain_cfg,"dag","datum_add_hashes_count",1);
    l_dag->gdb_group_events_round_new = strdup( dap_config_get_item_str_default(a_chain_cfg,"dag","gdb_group_events_round_new",
                                                                        "events.round.new"));
    if ( l_dag->is_single_line ) {
        log_it (L_NOTICE, "DAG chain initialized (single line)");
    } else {
        log_it (L_NOTICE, "DAG chain initialized (multichain)");
    }

    return 0;
}

/**
 * @brief dap_chain_cs_dag_delete
 * @param a_dag
 * @return
 */
void dap_chain_cs_dag_delete(dap_chain_t * a_chain)
{
    dap_chain_cs_dag_t * l_dag = DAP_CHAIN_CS_DAG ( a_chain );
    pthread_rwlock_destroy(& PVT(l_dag)->events_rwlock);

    if(l_dag->callback_delete )
        l_dag->callback_delete(l_dag);
    if(l_dag->_inheritor)
        DAP_DELETE(l_dag->_inheritor);
    if(l_dag->_pvt)
        DAP_DELETE(l_dag->_pvt);
}

/**
 * @brief s_chain_callback_atom_add Accept new event in dag
 * @param a_chain DAG object
 * @param a_atom
 * @return 0 if verified and added well, otherwise if not
 */
static int s_chain_callback_atom_add(dap_chain_t * a_chain, dap_chain_atom_ptr_t a_atom)
{
    int ret = s_chain_callback_atom_verify (a_chain, a_atom);
    if ( ret < 0 ){
        log_it(L_WARNING,"Wrong event, can't accept, verification returned %d",ret);
        return  -1;
    }
    dap_chain_cs_dag_t * l_dag = DAP_CHAIN_CS_DAG(a_chain);
    dap_chain_cs_dag_event_t * l_event = (dap_chain_cs_dag_event_t *) a_atom;

    // verification was already in s_chain_callback_atom_verify()
    ret = l_dag->callback_cs_verify(l_dag,l_event);
    if ( ret != 0 ){
        log_it(L_WARNING,"Consensus can't accept the event, verification returned %d",ret);
        return  -2;
    }
    dap_chain_cs_dag_event_item_t * l_event_item = DAP_NEW_Z(dap_chain_cs_dag_event_item_t);
    l_event_item->event = l_event;
    l_event_item->ts_added = time(NULL);
    dap_hash_fast(l_event, dap_chain_cs_dag_event_calc_size(l_event),&l_event_item->hash );

    // Put in main table or in the treshhold if not all the rest linked event are present
    dap_chain_cs_dag_event_item_t * l_event_search = NULL;
    dap_chain_cs_dag_event_item_t * l_events =( ret==0 ? PVT(l_dag)->events : PVT(l_dag)->events_treshold );
    pthread_rwlock_t * l_events_rwlock = &PVT(l_dag)->events_rwlock ;
    pthread_rwlock_wrlock( l_events_rwlock );
    HASH_FIND(hh, l_events,&l_event_item->hash,sizeof (l_event_search->hash),  l_event_search);
    if ( l_event_search ) {
        pthread_rwlock_unlock( l_events_rwlock );
        char * l_hash_str = dap_chain_hash_fast_to_str_new(&l_event_item->hash);
        log_it(L_ERROR, "Dag event %s is already present in dag",l_hash_str);
        DAP_DELETE(l_event_item);
        DAP_DELETE(l_hash_str);
        return -3;
    }
    HASH_ADD(hh, l_events,hash,sizeof (l_event_item->hash),  l_event_item);
    // save l_events to dag_pvt
    if(ret==0)
        PVT(l_dag)->events = l_events;
    else
        PVT(l_dag)->events_treshold = l_events;
    //HASH_ADD(hh, PVT(l_dag)->events_treshold, hash, sizeof(l_event_item->hash), l_event_item);

    if ( l_events == PVT(l_dag)->events){
        dap_chain_cs_dag_event_item_t * l_event_last = NULL;
        // Check the events and update the lasts
        for ( dap_chain_hash_fast_t * l_link_hash = (dap_chain_hash_fast_t *) l_event->hashes_n_datum_n_signs ;
                  l_link_hash < ( dap_chain_hash_fast_t *) (
                  l_event->hashes_n_datum_n_signs + l_event->header.hash_count*sizeof (*l_link_hash) );
                  l_link_hash += sizeof (dap_chain_hash_fast_t ) ) {
            l_event_last = NULL;
            HASH_FIND(hh,PVT(l_dag)->events_lasts_unlinked,&l_link_hash,sizeof(l_link_hash), l_event_last);
            if ( l_event_last ){ // If present in unlinked - remove
                HASH_DEL(PVT(l_dag)->events_lasts_unlinked,l_event_last);
                DAP_DELETE(l_event_last);
            }

        }
        // and then adds itself
        l_event_last= DAP_NEW_Z(dap_chain_cs_dag_event_item_t);
        l_event_last->ts_added = l_event_item->ts_added;
        l_event_last->event = l_event;
        HASH_ADD(hh,PVT(l_dag)->events_lasts_unlinked,hash,sizeof (l_event_last->hash),l_event_last);
    }

    // add datum from event to ledger
    dap_chain_datum_t *l_datum = (dap_chain_datum_t*) dap_chain_cs_dag_event_get_datum(l_event);
    switch (l_datum->header.type_id) {
    case DAP_CHAIN_DATUM_TOKEN_DECL: {
        dap_chain_datum_token_t *l_token = (dap_chain_datum_token_t*) l_datum->data;
        dap_chain_ledger_token_add(a_chain->ledger, l_token, l_datum->header.data_size);
    }
        break;
    case DAP_CHAIN_DATUM_TOKEN_EMISSION: {
        dap_chain_datum_token_emission_t *l_token_emission = (dap_chain_datum_token_emission_t*) l_datum->data;
        dap_chain_ledger_token_emission_add(a_chain->ledger, l_token_emission, l_datum->header.data_size);
    }
        break;
    case DAP_CHAIN_DATUM_TX: {
        dap_chain_datum_tx_t *l_tx = (dap_chain_datum_tx_t*) l_datum->data;
        //if ( !l_gdb_priv->is_load_mode ) // If its not load module but mempool proc
        //    l_tx->header.ts_created = time(NULL);
        //if(dap_chain_datum_tx_get_size(l_tx) == l_datum->header.data_size){

        // don't save bad transactions to base
        if(dap_chain_ledger_tx_add(a_chain->ledger, l_tx) != 1) {
            pthread_rwlock_unlock(l_events_rwlock);
            return -1;
        }
        //}else
        //    return -2;
    }
        break;
    default:
        pthread_rwlock_unlock(l_events_rwlock);
        return -1;
    }

    pthread_rwlock_unlock( l_events_rwlock );
    // Now check the treshold if some events now are ready to move to the main table
    dap_chain_cs_dag_proc_treshold(l_dag);
    return 0;
}

/**
 * @brief s_chain_callback_datums_add
 * @param a_chain
 * @param a_datums
 * @param a_datums_size
 */
static size_t s_chain_callback_datums_pool_proc(dap_chain_t * a_chain, dap_chain_datum_t ** a_datums, size_t a_datums_count)
{
    dap_chain_cs_dag_t * l_dag = DAP_CHAIN_CS_DAG(a_chain);
    size_t l_datum_processed =0;
    size_t l_events_round_new_size = 0;
    // Load current events new round pool
    dap_global_db_obj_t * l_events_round_new = dap_chain_global_db_gr_load(l_dag->gdb_group_events_round_new, &l_events_round_new_size );
    // Prepare hashes
    size_t l_hashes_int_size = min(l_events_round_new_size + a_datums_count, l_dag->datum_add_hashes_count);
//            ( l_events_round_new_size + a_datums_count ) > l_dag->datum_add_hashes_count ?
//                                   l_dag->datum_add_hashes_count :
//                                   l_events_round_new_size+a_datums_count;

    if (l_dag->is_single_line ) // If single line - no any link inside
        l_hashes_int_size = 0;

    size_t l_hashes_ext_size = 1; // Change in cfg
    size_t l_hashes_size = l_hashes_int_size+l_hashes_ext_size;
    dap_chain_hash_fast_t * l_hashes = DAP_NEW_Z_SIZE(dap_chain_hash_fast_t,
                                             sizeof(dap_chain_hash_fast_t) * l_hashes_size);
    size_t l_hashes_linked = 0;

    for (size_t d = 0; d <a_datums_count ; d++){
        dap_chain_datum_t * l_datum = a_datums[d];

        if ( l_hashes_int_size && l_events_round_new_size){
            // Linking randomly with current new round set
            size_t l_rnd_steps;
            // Linking events inside round
            l_rnd_steps = 0;
            do{
                int l_index = rand() % (int) l_events_round_new_size;
                dap_chain_hash_fast_t l_hash;
                dap_chain_cs_dag_event_t * l_event = (dap_chain_cs_dag_event_t *) l_events_round_new[l_index].value;
                size_t l_event_size = dap_chain_cs_dag_event_calc_size(l_event);
                dap_hash_fast(l_event, l_event_size,&l_hash);

                bool l_is_already_in_event = false;
                for (uint16_t i=0; i<l_hashes_linked;i++ ){ // check if we already added it
                    if (memcmp(&l_hashes[i],&l_hash,sizeof (l_hash) )==0 ){
                        l_is_already_in_event = true;
                        break;
                    }
                }

                if ( ! l_is_already_in_event ){
                    if(l_hashes_linked < l_hashes_size) {
                        memcpy(&l_hashes[l_hashes_linked], &l_hash, sizeof(l_hash));
                        l_hashes_linked++;
                    }
                }
                l_rnd_steps++;
                if (l_rnd_steps > 100) // Too many attempts
                    break;
            } while (l_hashes_linked <(l_events_round_new_size) );

            // Check if we have enought hash links
            if (l_hashes_linked<l_events_round_new_size ){
                log_it(L_ERROR,"Can't link new events randomly for 100 attempts");
                break;
            }
        }
        // Now link with ext events
        dap_chain_cs_dag_event_item_t *l_event_ext_item = NULL;
        // is_single_line - no any link inside
        if(!l_dag->is_single_line)
        if( PVT(l_dag)->events_lasts_unlinked) { // Take then the first one if any events_lasts are present
                l_event_ext_item = PVT(l_dag)->events_lasts_unlinked;
                memcpy(&l_hashes[l_hashes_linked], &l_event_ext_item->hash, sizeof(l_event_ext_item->hash));
                l_hashes_linked++;
            }

        if (l_hashes_linked || s_seed_mode ) {
            dap_chain_cs_dag_event_t * l_event = NULL;
            if(l_dag->callback_cs_event_create)
                l_event = l_dag->callback_cs_event_create(l_dag,l_datum,l_hashes,l_hashes_linked);
            if ( l_event){ // Event is created

                // add directly to file
                if(l_dag->is_add_directy) {
                    if(!s_chain_callback_atom_add(a_chain, l_event)) {
                        l_datum_processed++;
                    }
                    else {
                        log_it(L_ERROR, "Can't add new event");
                        continue;
                    }
                }
                // add to new round into global_db
                else {
                    dap_chain_hash_fast_t l_event_hash;
                    dap_chain_cs_dag_event_calc_hash(l_event, &l_event_hash);
                    char * l_event_hash_str = dap_chain_hash_fast_to_str_new(&l_event_hash);
                    if(dap_chain_global_db_gr_set(l_event_hash_str, (uint8_t *) l_event,
                            dap_chain_cs_dag_event_calc_size(l_event),
                            l_dag->gdb_group_events_round_new)) {
                        log_it(L_INFO, "Event %s placed in the new forming round", l_event_hash_str);
                        // Clear old ext link and place itself as event_lasts

                        dap_chain_cs_dag_event_item_t * l_event_unlinked_item = DAP_NEW_Z(
                                dap_chain_cs_dag_event_item_t);
                        if(l_event_ext_item)
                            memcpy(&l_event_unlinked_item->hash, &l_event_ext_item->hash,
                                    sizeof(l_event_ext_item->hash));
                        l_event_unlinked_item->event = l_event;
                        l_event_unlinked_item->ts_added = (time_t) l_event->header.ts_created;
                        pthread_rwlock_wrlock(&PVT(l_dag)->events_rwlock);
                        HASH_ADD(hh, PVT(l_dag)->events_lasts_unlinked, hash, sizeof(l_event_unlinked_item->hash),
                                l_event_unlinked_item);
                        if(l_event_ext_item) {
                            HASH_DEL(PVT(l_dag)->events_lasts_unlinked, l_event_ext_item);
                            DAP_DELETE(l_event_ext_item);
                        }
                        pthread_rwlock_unlock(&PVT(l_dag)->events_rwlock);

                        l_datum_processed++;
                    }else {
                        log_it(L_ERROR,"Can't add new event to the new events round");
                        break;
                    }
                }
            }else {
                log_it(L_ERROR,"Can't create new event!");
                break;
            }
        }
    }
    // add events to file
    if(l_dag->is_add_directy && l_datum_processed>0) {
        dap_chain_cell_t *l_cell = dap_chain_cell_create();
        int l_res = -1;
        if(l_cell) {
            dap_chain_net_t *l_net = dap_chain_net_by_id(a_chain->net_id);
            l_cell->chain = a_chain;
            l_cell->id.uint64 = l_net ? l_net->pub.cell_id.uint64 : 0;
            l_cell->file_storage_path = dap_strdup_printf("%0llx.dchaincell", l_cell->id.uint64);
            l_res = dap_chain_cell_file_update(l_cell);
        }
        if(!l_cell || l_res < 0) {
            log_it(L_ERROR, "Can't add new %d events to the file '%s'", l_datum_processed,
                    l_cell ? l_cell->file_storage_path : "");
            l_datum_processed = 0;
        }
        dap_chain_cell_delete(l_cell);
    }
    dap_chain_global_db_objs_delete(l_events_round_new, l_events_round_new_size);
    return  l_datum_processed;
}


/**
 * @brief dap_chain_cs_dag_find_event_by_hash
 * @param a_dag
 * @param a_hash
 * @return
 */
dap_chain_cs_dag_event_t* dap_chain_cs_dag_find_event_by_hash(dap_chain_cs_dag_t * a_dag, dap_chain_hash_fast_t * a_hash)
{
    dap_chain_cs_dag_event_item_t* l_event_item = NULL;
    pthread_rwlock_rdlock( &PVT(a_dag)->events_rwlock );
    HASH_FIND(hh, PVT(a_dag)->events ,a_hash,sizeof(*a_hash), l_event_item);
    dap_chain_cs_dag_event_t * l_event = l_event_item->event;
    pthread_rwlock_unlock( &PVT(a_dag)->events_rwlock );
    return  l_event;
}



/**
 * @brief s_chain_callback_atom_verify Verify atomic element
 * @param a_chain
 * @param a_atom
 * @return
 */
static int s_chain_callback_atom_verify(dap_chain_t * a_chain, dap_chain_atom_ptr_t  a_atom)
{
    dap_chain_cs_dag_t * l_dag = DAP_CHAIN_CS_DAG(a_chain);
    dap_chain_cs_dag_event_t * l_event = (dap_chain_cs_dag_event_t *) a_atom;

    if (l_event->header.hash_count == 0 && l_dag->is_static_genesis_event ){
        dap_chain_hash_fast_t l_event_hash;
        dap_chain_cs_dag_event_calc_hash(l_event,&l_event_hash);
        if ( memcmp( &l_event_hash, &l_dag->static_genesis_event_hash, sizeof(l_event_hash) ) != 0 ){
            char * l_event_hash_str = dap_chain_hash_fast_to_str_new(&l_event_hash);
            char * l_genesis_event_hash_str = dap_chain_hash_fast_to_str_new(&l_dag->static_genesis_event_hash);

            log_it(L_WARNING, "Wrong genesis block %s (staticly predefined %s)",l_event_hash_str, l_genesis_event_hash_str);
            return -22;
        }
    }

    int ret = l_dag->callback_cs_verify ( l_dag, l_event );
    if (ret == 0 ){
        if ( PVT(l_dag)->events )
            for (size_t i = 0; i< l_event->header.hash_count; i++) {
                dap_chain_hash_fast_t * l_hash =  ((dap_chain_hash_fast_t *) l_event->hashes_n_datum_n_signs) + i;
                dap_chain_cs_dag_event_item_t * l_event_search = NULL;
                HASH_FIND(hh, PVT(l_dag)->events ,l_hash ,sizeof (*l_hash),  l_event_search);
                if ( l_event_search == NULL ){
                    log_it(L_DEBUG, "Hash %s wasn't in hashtable of previously parsed", l_hash);
                    return 1;
                }

            }
        return 0;
    }else {
        return  ret;
    }
}

/**
 * @brief dap_chain_cs_dag_proc_event_round_new
 * @param a_dag
 */
void dap_chain_cs_dag_proc_event_round_new(dap_chain_cs_dag_t *a_dag)
{
    (void) a_dag;
    log_it(L_WARNING,"No proc event algorythm, use manual commands for round aproving");
}


/**
 * @brief s_dag_events_lasts_delete_linked_with_event
 * @param a_dag
 * @param a_event
 */
void s_dag_events_lasts_delete_linked_with_event(dap_chain_cs_dag_t * a_dag, dap_chain_cs_dag_event_t * a_event)
{
    for (size_t i = 0; i< a_event->header.hash_count; i++) {
        dap_chain_hash_fast_t * l_hash =  ((dap_chain_hash_fast_t *) a_event->hashes_n_datum_n_signs) + i;
        dap_chain_cs_dag_event_item_t * l_event_item = NULL;
        HASH_FIND(hh, PVT(a_dag)->events_lasts_unlinked ,l_hash ,sizeof (*l_hash),  l_event_item);
        if ( l_event_item ){
            HASH_DEL(PVT(a_dag)->events_lasts_unlinked,l_event_item);
            DAP_DELETE(l_event_item);
        }
    }
}



int dap_chain_cs_dag_event_verify_hashes_with_treshold(dap_chain_cs_dag_t * a_dag, dap_chain_cs_dag_event_t * a_event)
{
    bool l_is_events_all_hashes = true;
    bool l_is_events_main_hashes = true;
    for (size_t i = 0; i< a_event->header.hash_count; i++) {
        dap_chain_hash_fast_t * l_hash =  ((dap_chain_hash_fast_t *) a_event->hashes_n_datum_n_signs) + i;
        dap_chain_cs_dag_event_item_t * l_event_search = NULL;
        HASH_FIND(hh, PVT(a_dag)->events ,l_hash ,sizeof (*l_hash),  l_event_search);
        if ( l_event_search == NULL ){ // If not found in events - search in treshhold
            l_is_events_main_hashes = false;
            HASH_FIND(hh, PVT(a_dag)->events_treshold ,l_hash ,sizeof (*l_hash),  l_event_search);
            if( l_event_search == NULL ){ // Hash is not in events or treshold table, keep the current item where it is
                l_is_events_all_hashes = false;
                break;
            }
        }
    }
    if( l_is_events_all_hashes && l_is_events_main_hashes ){
        return  0;
    }else if ( ! l_is_events_all_hashes) {
        return  -1;
    }else {
        return  1;
    }
}

/**
 * @brief dap_chain_cs_dag_proc_treshold
 * @param a_dag
 */
void dap_chain_cs_dag_proc_treshold(dap_chain_cs_dag_t * a_dag)
{
    // TODO Process finish treshold. For now - easiest from possible
    pthread_rwlock_rdlock(&PVT(a_dag)->events_rwlock);
    dap_chain_cs_dag_event_item_t * l_event_item = NULL, * l_event_item_tmp = NULL;
    HASH_ITER(hh,PVT(a_dag)->events_treshold,l_event_item, l_event_item_tmp){
        dap_chain_cs_dag_event_t * l_event = l_event_item->event;
        int ret = dap_chain_cs_dag_event_verify_hashes_with_treshold (a_dag,l_event);
        if ( ret == 0){ // All its hashes are in main table, move thats one too into it
            pthread_rwlock_unlock(&PVT(a_dag)->events_rwlock);
            pthread_rwlock_wrlock(&PVT(a_dag)->events_rwlock);
            HASH_DEL(PVT(a_dag)->events_treshold,l_event_item);
            HASH_ADD(hh, PVT(a_dag)->events, hash,sizeof (l_event_item->hash),  l_event_item);
            s_dag_events_lasts_delete_linked_with_event(a_dag, l_event);
        }
    }
    pthread_rwlock_unlock(&PVT(a_dag)->events_rwlock);
}


/**
 * @brief s_chain_callback_atom_get_size Get size of atomic element
 * @param a_atom
 * @return
 */
static size_t s_chain_callback_atom_hdr_get_size(dap_chain_atom_ptr_t  a_atom)
{
    return dap_chain_cs_dag_event_calc_size( (dap_chain_cs_dag_event_t * ) a_atom);
}

/**
 * @brief s_chain_callback_atom_get_static_hdr_size
 * @param a_chain
 * @return
 */
static size_t s_chain_callback_atom_get_static_hdr_size()
{
   return sizeof (dap_chain_class_dag_event_hdr_t);
}

/**
 * @brief s_chain_callback_atom_iter_create_from
 * @param a_chain
 * @param a_atom
 * @return
 */
static dap_chain_atom_iter_t* s_chain_callback_atom_iter_create_from(dap_chain_t * a_chain ,
                                                                     dap_chain_atom_ptr_t a_atom)
{
    dap_chain_atom_iter_t * l_atom_iter = DAP_NEW_Z(dap_chain_atom_iter_t);
    l_atom_iter->chain = a_chain;
    l_atom_iter->cur = a_atom;

    dap_chain_hash_fast_t l_atom_hash;
    dap_hash_fast(a_atom, a_chain->callback_atom_get_size(a_atom), &l_atom_hash );

    dap_chain_cs_dag_event_item_t  * l_atom_item;
    HASH_FIND(hh, PVT(DAP_CHAIN_CS_DAG(a_chain))->events, &l_atom_hash, sizeof(l_atom_hash),l_atom_item );
    l_atom_iter->cur_item = l_atom_item;
    return l_atom_iter;

}

/**
 * @brief s_chain_callback_atom_iter_create Create atomic element iterator
 * @param a_chain
 * @return
 */
static dap_chain_atom_iter_t* s_chain_callback_atom_iter_create(dap_chain_t * a_chain )
{
    dap_chain_atom_iter_t * l_atom_iter = DAP_NEW_Z(dap_chain_atom_iter_t);
    l_atom_iter->chain = a_chain;
    return l_atom_iter;
}

/**
 * @brief s_chain_callback_atom_get_datum Get the datum from event
 * @param a_atom_iter
 * @return
 */
static dap_chain_datum_t* s_chain_callback_atom_get_datum(dap_chain_atom_ptr_t a_event)
{
    if(a_event)
        return dap_chain_cs_dag_event_get_datum((dap_chain_cs_dag_event_t*) a_event);
    return NULL;
}

/**
 * @brief s_chain_callback_atom_iter_get_first Get the first dag event
 * @param a_atom_iter
 * @return
 */
static dap_chain_atom_ptr_t s_chain_callback_atom_iter_get_first(dap_chain_atom_iter_t * a_atom_iter )
{
    dap_chain_cs_dag_t * l_dag = DAP_CHAIN_CS_DAG(a_atom_iter->chain);
    dap_chain_cs_dag_pvt_t *l_dag_pvt = l_dag ? PVT(l_dag) : NULL;
    a_atom_iter->cur_item = l_dag_pvt->events;
    a_atom_iter->cur = (dap_chain_cs_dag_event_t*) (l_dag_pvt->events ? l_dag_pvt->events->event : NULL);

//    a_atom_iter->cur =  a_atom_iter->cur ?
//                (dap_chain_cs_dag_event_t*) PVT (DAP_CHAIN_CS_DAG( a_atom_iter->chain) )->events->event : NULL;
//    a_atom_iter->cur_item = PVT (DAP_CHAIN_CS_DAG( a_atom_iter->chain) )->events;
    return a_atom_iter->cur;
}

/**
 * @brief s_chain_callback_atom_iter_get_lasts
 * @param a_atom_iter
 * @param a_lasts_size_ptr
 * @return
 */
static dap_chain_atom_ptr_t* s_chain_callback_atom_iter_get_lasts( dap_chain_atom_iter_t * a_atom_iter ,
                                                                  size_t * a_lasts_size_ptr )
{
    dap_chain_cs_dag_t * l_dag = DAP_CHAIN_CS_DAG( a_atom_iter->chain );

    *a_lasts_size_ptr = HASH_COUNT( PVT(l_dag)->events_lasts_unlinked );
    if ( *a_lasts_size_ptr > 0 ) {
        dap_chain_atom_ptr_t * l_ret = DAP_NEW_Z_SIZE( dap_chain_atom_ptr_t,
                                           sizeof (dap_chain_atom_ptr_t*) * (*a_lasts_size_ptr) );

        dap_chain_cs_dag_event_item_t * l_event_item = NULL, *l_event_item_tmp = NULL;
        size_t i = 0;
        HASH_ITER(hh,PVT(l_dag)->events_lasts_unlinked, l_event_item,l_event_item_tmp){
            l_ret[i] = l_event_item->event;
            i++;
        }
        return l_ret;
    }
    return NULL;
}

/**
 * @brief s_chain_callback_atom_iter_get_links
 * @param a_atom_iter
 * @param a_links_size_ptr
 * @return
 */
static dap_chain_atom_ptr_t* s_chain_callback_atom_iter_get_links( dap_chain_atom_iter_t * a_atom_iter ,
                                                                  size_t * a_links_size_ptr )
{
    if ( a_atom_iter->cur ){
        dap_chain_cs_dag_t * l_dag = DAP_CHAIN_CS_DAG( a_atom_iter->chain );
        dap_chain_cs_dag_event_t * l_event =(dap_chain_cs_dag_event_t *) a_atom_iter->cur;
        dap_chain_cs_dag_event_item_t * l_event_item = (dap_chain_cs_dag_event_item_t *) a_atom_iter->cur_item;
        if ( l_event->header.hash_count > 0){
            dap_chain_atom_ptr_t * l_ret = DAP_NEW_Z_SIZE(dap_chain_atom_ptr_t,
                                               sizeof (dap_chain_atom_ptr_t*) * l_event->header.hash_count );
            *a_links_size_ptr = l_event->header.hash_count;
            for (uint16_t i = 0; i < l_event->header.hash_count; i++){
                dap_chain_cs_dag_event_item_t * l_link_item = NULL;
                dap_chain_hash_fast_t * l_link_hash = (dap_chain_hash_fast_t *)
                        (l_event->hashes_n_datum_n_signs +
                        i*sizeof(*l_link_hash));
                HASH_FIND(hh, PVT(l_dag)->events,l_link_hash,sizeof(*l_link_hash),l_link_item);
                if ( l_link_item ){
                    l_ret[i] = l_link_item->event;
                }else {
                    char * l_link_hash_str = dap_chain_hash_fast_to_str_new(l_link_hash);
                    char * l_event_hash_str = dap_chain_hash_fast_to_str_new(&l_event_item->hash);
                    log_it(L_ERROR,"Can't find %s->%s links", l_event_hash_str, l_link_hash_str);
                    DAP_DELETE(l_event_hash_str);
                    DAP_DELETE(l_link_hash_str);
                }
            }
            return l_ret;
        }
    }
    return  NULL;
}

/**
 * @brief s_chain_callback_atom_iter_find_by_hash
 * @param a_atom_iter
 * @param a_atom_hash
 * @return
 */
static dap_chain_atom_ptr_t s_chain_callback_atom_iter_find_by_hash(dap_chain_atom_iter_t * a_atom_iter ,
                                                                       dap_chain_hash_fast_t * a_atom_hash)
{
    dap_chain_cs_dag_t * l_dag = DAP_CHAIN_CS_DAG( a_atom_iter->chain );
    dap_chain_cs_dag_event_item_t * l_event_item = NULL;
    HASH_FIND(hh, PVT(l_dag)->events,a_atom_hash,sizeof(*a_atom_hash),l_event_item);
    if ( l_event_item ){
        a_atom_iter->cur_item = l_event_item;
        a_atom_iter->cur = l_event_item->event;
        return  l_event_item->event;
    }else
        return NULL;
}

/**
 * @brief s_chain_callback_atom_iter_get_next Get the next dag event
 * @param a_atom_iter
 * @return
 */
static dap_chain_atom_ptr_t s_chain_callback_atom_iter_get_next( dap_chain_atom_iter_t * a_atom_iter )
{
    if (a_atom_iter->cur ){
        dap_chain_cs_dag_event_item_t * l_event_item = (dap_chain_cs_dag_event_item_t*) a_atom_iter->cur_item;
        a_atom_iter->cur_item = l_event_item->hh.next;
        l_event_item = (dap_chain_cs_dag_event_item_t*) a_atom_iter->cur_item;
        // if l_event_item=NULL then items are over
        a_atom_iter->cur = l_event_item ? l_event_item->event : NULL;
    }
    return a_atom_iter->cur;
}

/**
 * @brief s_chain_callback_atom_iter_delete Delete dag event iterator
 * @param a_atom_iter
 */
static void s_chain_callback_atom_iter_delete(dap_chain_atom_iter_t * a_atom_iter )
{
    DAP_DELETE(a_atom_iter);
}

/**
 * @brief s_cli_dag
 * @param argc
 * @param argv
 * @param str_reply
 * @return
 */
static int s_cli_dag(int argc, char ** argv, char **a_str_reply)
{
    enum {
        SUBCMD_EVENT_CREATE,
        SUBCMD_EVENT_CANCEL,
        SUBCMD_EVENT_LIST,
        SUBCMD_EVENT_DUMP,
        SUBCMD_UNDEFINED
    } l_event_subcmd={0};

    const char* l_event_subcmd_str[]={
        [SUBCMD_EVENT_CREATE]="create",
        [SUBCMD_EVENT_CANCEL]="cancel",
        [SUBCMD_EVENT_LIST]="list",
        [SUBCMD_EVENT_DUMP]="dump",
        [SUBCMD_UNDEFINED]="UNDEFINED"
    };


    int arg_index = 1;

    const char * l_net_name = NULL;

    const char * l_chain_name = NULL;

    const char * l_event_cmd_str = NULL;
    const char * l_round_cmd_str = NULL;

    const char* l_event_hash_str = NULL;
    dap_chain_hash_fast_t l_event_hash = {0};

    const char * l_datum_hash_str = NULL;

    const char * l_from_events_str = NULL;

    dap_chain_t * l_chain = NULL;
    dap_chain_cs_dag_t * l_dag = NULL;
    dap_chain_net_t * l_net = NULL;

    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-net", &l_net_name);
    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-chain", &l_chain_name);
    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "event", &l_event_cmd_str);
    dap_chain_node_cli_find_option_val(argv, arg_index, argc, "round", &l_round_cmd_str);

    if ( l_net_name == NULL){
        dap_chain_node_cli_set_reply_text(a_str_reply, "Need -net <net name> param!");
        return -1;
    }
    l_net = dap_chain_net_by_name( l_net_name );
    if ( l_net == NULL ){
        dap_chain_node_cli_set_reply_text(a_str_reply, "Can't find network \"%s\"",l_net_name);
        return -2;

    }

    if ( l_chain_name == NULL){
        dap_chain_node_cli_set_reply_text(a_str_reply, "Need -chain <chain name> param!");
        return -3;
    }
    l_chain = dap_chain_net_get_chain_by_name(l_net,l_chain_name);
    if ( l_chain == NULL ){
        dap_chain_node_cli_set_reply_text(a_str_reply, "Can't find chain \"%s\" in network \"%s\"",
                                          l_chain_name, l_net_name);
        return -4;
    }
    l_dag = DAP_CHAIN_CS_DAG(l_chain);

    int ret = 0;
    if ( l_round_cmd_str ) {
        if ( strcmp(l_round_cmd_str,"complete") == 0 ){
            const char * l_cmd_mode_str = NULL;
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-mode", &l_cmd_mode_str);
            bool l_verify_only = false;
            if ( dap_strcmp(l_cmd_mode_str,"verify only") == 0 ){
                l_verify_only = true;
            }
            log_it(L_NOTICE,"Round complete command accepted, forming new events");

            size_t l_objs_size=0;
            dap_global_db_obj_t * l_objs = dap_chain_global_db_gr_load(l_dag->gdb_group_events_round_new,&l_objs_size);

            dap_string_t *l_str_ret_tmp= l_objs_size>0 ? dap_string_new("Completing round:\n") : dap_string_new("Completing round: no data");

            // list for verifed and added events
            dap_list_t *l_list_to_del = NULL;

            // Check if its ready or not
            for (size_t i = 0; i< l_objs_size; i++ ){
                dap_chain_cs_dag_event_t * l_event = (dap_chain_cs_dag_event_t*) l_objs[i].value;
                size_t l_event_size = l_objs[i].value_len;
                int l_ret_event_verify;
                if ( ( l_ret_event_verify = l_dag->callback_cs_verify (l_dag,l_event) ) !=0 ){// if consensus accept the event
                    dap_string_append_printf( l_str_ret_tmp,
                            "Error! Event %s is not passing consensus verification, ret code %d\n",
                                              l_objs[i].key, l_ret_event_verify );
                    ret = -30;
                    break;
                }else {
                    dap_string_append_printf( l_str_ret_tmp, "Event %s verification passed\n", l_objs[i].key);
                    // If not verify only mode we add
                    if ( ! l_verify_only ){
                        dap_chain_atom_ptr_t l_new_atom = (dap_chain_atom_ptr_t)dap_chain_cs_dag_event_copy(l_event); // produce deep copy of event;
                        memcpy(l_new_atom, l_event, l_event_size);
                        if(s_chain_callback_atom_add(l_chain, l_new_atom) < 0) { // Add new atom in chain
                            DAP_DELETE(l_new_atom);
                            dap_string_append_printf(l_str_ret_tmp, "Event %s not added in chain\n", l_objs[i].key);
                        }
                        else {
                            // add event to delete
                            l_list_to_del = dap_list_prepend(l_list_to_del, l_objs[i].key);
                            dap_string_append_printf(l_str_ret_tmp, "Event %s added in chain successfully\n",
                                    l_objs[i].key);
                        }
                    }
                }
            }
            // write events to file and delete events from db
            if(l_list_to_del) {
                dap_chain_cell_t *l_cell = dap_chain_cell_create();
                if(l_cell) {
                    l_cell->chain = l_chain;
                    l_cell->id.uint64 = l_net ? l_net->pub.cell_id.uint64 : 0;
                    l_cell->file_storage_path = dap_strdup_printf("%0llx.dchaincell", l_cell->id.uint64);
                    if(!dap_chain_cell_file_update(l_cell)) {
                        // delete events from db
                        dap_list_t *l_list_tmp = l_list_to_del;
                        while(l_list_tmp) {
                            const char *l_key = (const char*) l_list_tmp->data;
                            dap_chain_global_db_gr_del(l_key, l_dag->gdb_group_events_round_new);
                            l_list_tmp = dap_list_next(l_list_tmp);
                        }
                    }
                }
                dap_chain_cell_delete(l_cell);
                dap_list_free(l_list_to_del);
            }

            // Cleaning up
            dap_chain_global_db_objs_delete(l_objs, l_objs_size);
            dap_chain_node_cli_set_reply_text(a_str_reply,l_str_ret_tmp->str);
            dap_string_free(l_str_ret_tmp,false);

            // Spread new  mempool changes and  dag events in network - going to SYNC_ALL
            dap_chain_net_sync_all(l_net);
        }
    }else if ( l_event_cmd_str  ) {
        if  ( strcmp( l_event_cmd_str, "create" ) == 0  ) {
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-datum", &l_datum_hash_str);
            l_event_subcmd = SUBCMD_EVENT_CREATE;
        } else if (  strcmp( l_event_cmd_str, "cancel" ) == 0  ) {
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-event", &l_event_hash_str);
            l_event_subcmd = SUBCMD_EVENT_CANCEL;
        } else if ( strcmp( l_event_cmd_str, "list" ) == 0 ) {
            l_event_subcmd = SUBCMD_EVENT_LIST;
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-from", &l_from_events_str);
        } else if ( strcmp( l_event_cmd_str,"dump") == 0 ) {
            l_event_subcmd = SUBCMD_EVENT_DUMP;
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-from", &l_from_events_str);
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-event", &l_event_hash_str);
        } else {
            l_event_subcmd = SUBCMD_UNDEFINED;
        }

        if (l_event_hash_str)
            dap_chain_str_to_hash_fast(l_event_hash_str,&l_event_hash);

        switch ( l_event_subcmd ){
            case SUBCMD_EVENT_CREATE:{
                size_t l_datums_count=1;
                char * l_gdb_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
                dap_chain_datum_t ** l_datums = DAP_NEW_Z_SIZE(dap_chain_datum_t*,
                                                               sizeof(dap_chain_datum_t*)*l_datums_count);
                size_t l_datum_size = 0;
                dap_chain_datum_t * l_datum = (dap_chain_datum_t*) dap_chain_global_db_gr_get( l_datum_hash_str ,
                                                                                                  &l_datum_size,
                                                                   l_gdb_group_mempool);
                l_datums[0] = l_datum;
                if ( s_chain_callback_datums_pool_proc(l_chain,l_datums,l_datums_count ) == l_datums_count ){
                    for ( size_t i = 0; i <l_datums_count; i++){
                       dap_chain_hash_fast_t l_datum_hash;
                       dap_hash_fast(l_datums[i],dap_chain_datum_size(l_datums[i]),&l_datum_hash);
                       char * l_datums_datum_hash_str = dap_chain_hash_fast_to_str_new(&l_datum_hash);
                       if ( dap_chain_global_db_gr_del(l_datums_datum_hash_str,l_gdb_group_mempool ) ){
                           dap_chain_node_cli_set_reply_text(a_str_reply,
                                                             "Converted datum %s from mempool to event in the new forming round ",
                                                             l_datums_datum_hash_str);
                           DAP_DELETE(l_datums_datum_hash_str);
                           ret = 0;
                       }else {
                           dap_chain_node_cli_set_reply_text(a_str_reply,
                                                             "Warning! Can't delete datum %s from mempool after conversion to event in the new forming round ",
                                                             l_datums_datum_hash_str);
                           ret = 1;
                       }
                    }
                }else {
                    dap_chain_node_cli_set_reply_text(a_str_reply,
                                                      "Warning! Can't convert datum %s from mempool to event in the new forming round ",
                                                      l_datum_hash_str);
                    ret = -12;

                }
                DAP_DELETE(l_gdb_group_mempool);
                dap_chain_net_sync_all(l_net);
            }break;
            case SUBCMD_EVENT_CANCEL:{
                char * l_gdb_group_events = DAP_CHAIN_CS_DAG(l_chain)->gdb_group_events_round_new;
                if ( dap_chain_global_db_gr_del(l_event_hash_str ,l_gdb_group_events ) ){
                    dap_chain_node_cli_set_reply_text(a_str_reply,
                                                      "Successfuly removed event 0x%s from the new forming round ",
                                                      l_event_hash_str);
                    ret = 0;
                }else {
                    dap_chain_node_cli_set_reply_text(a_str_reply,
                                                      "Can't remove event 0x%s from the new forming round ",
                                                      l_event_hash_str);
                    ret = -1;
                }
                DAP_DELETE( l_gdb_group_events );
                dap_chain_net_sync_gdb(l_net);
            }break;
            case SUBCMD_EVENT_DUMP:{
                dap_chain_cs_dag_event_t * l_event = NULL;
                size_t l_event_size = 0;
                if ( l_from_events_str ){
                    if ( strcmp(l_from_events_str,"round.new") == 0 ){
                        const char * l_gdb_group_events = l_dag->gdb_group_events_round_new;
                        l_event = (dap_chain_cs_dag_event_t *)  dap_chain_global_db_gr_get
                                              ( l_event_hash_str ,&l_event_size,l_gdb_group_events );
                    }else if ( strncmp(l_from_events_str,"round.",6) == 0){

                    }else if ( strcmp(l_from_events_str,"events_lasts") == 0){
                        dap_chain_cs_dag_event_item_t * l_event_item = NULL;
                        HASH_FIND(hh,PVT(l_dag)->events_lasts_unlinked,&l_event_hash,sizeof(l_event_hash),l_event_item);
                        if ( l_event_item )
                            l_event = l_event_item->event;
                        else {
                            ret = -23;
                            dap_chain_node_cli_set_reply_text(a_str_reply,
                                                              "Can't find events %s in events_last table\n");
                            break;
                        }
                    }else if ( strcmp(l_from_events_str,"events") == 0){
                        dap_chain_cs_dag_event_item_t * l_event_item = NULL;
                        HASH_FIND(hh,PVT(l_dag)->events,&l_event_hash,sizeof(l_event_hash),l_event_item);
                        if ( l_event_item )
                            l_event = l_event_item->event;
                        else {
                            ret = -24;
                            dap_chain_node_cli_set_reply_text(a_str_reply,
                                                              "Can't find events %s in events table\n");
                            break;
                        }

                    }else {
                        ret = -22;
                        dap_chain_node_cli_set_reply_text(a_str_reply,
                                                          "Wrong events_from option \"%s\", need one of variant: events, round.new, events_lasts, round.0x0123456789ABCDEF");
                        break;

                    }
                } else {
                    ret = -21;
                    dap_chain_node_cli_set_reply_text(a_str_reply,
                                                      "No events_from option");
                    break;
                }
                if ( l_event ){
                    dap_string_t * l_str_tmp = dap_string_new(NULL);
                    char * l_ctime_tmp = NULL;
                    time_t l_ts_reated = (time_t) l_event->header.ts_created;
                     // Header
                    dap_string_append_printf(l_str_tmp,"Event %s:\n", l_event_hash_str);
                    dap_string_append_printf(l_str_tmp,"\t\t\t\tversion: 0x%04sX\n",l_event->header.version);
                    dap_string_append_printf(l_str_tmp,"\t\t\t\tcell_id: 0x%016llX\n",l_event->header.cell_id.uint64);
                    dap_string_append_printf(l_str_tmp,"\t\t\t\tchain_id: 0x%016llX\n",l_event->header.chain_id.uint64);
                    dap_string_append_printf(l_str_tmp,"\t\t\t\tts_created: %s\n",ctime_r(&l_ts_reated,l_ctime_tmp) );

                    // Hash links
                    dap_string_append_printf(l_str_tmp,"\t\t\t\thashes:\tcount: %us\n",l_event->header.hash_count);
                    for (uint16_t i=0; i < l_event->header.hash_count; i++){
                        dap_chain_hash_fast_t * l_hash = (dap_chain_hash_fast_t *) (l_event->hashes_n_datum_n_signs +
                                i*sizeof (dap_chain_hash_fast_t));
                        char * l_hash_str = dap_chain_hash_fast_to_str_new(l_hash);
                        dap_string_append_printf(l_str_tmp,"\t\t\t\t\t\thash: %s\n",l_hash_str);
                        DAP_DELETE(l_hash_str);
                    }
                    size_t l_offset =  l_event->header.hash_count*sizeof (dap_chain_hash_fast_t);
                    dap_chain_datum_t * l_datum = (dap_chain_datum_t*) (l_event->hashes_n_datum_n_signs + l_offset);
                    size_t l_datum_size =  dap_chain_datum_size(l_datum);
                    char buf[50];
                    time_t l_datum_ts_create = (time_t) l_datum->header.ts_create;

                    // Nested datum
                    dap_string_append_printf(l_str_tmp,"\t\t\t\tdatum:\tdatum_size: %u\n",l_datum_size);
                    dap_string_append_printf(l_str_tmp,"\t\t\t\t\t\tversion:=%0x02X\n", l_datum->header.version_id);
                    dap_string_append_printf(l_str_tmp,"\t\t\t\t\t\ttype_id:=%s\n", c_datum_type_str[l_datum->header.type_id]);
                    dap_string_append_printf(l_str_tmp,"\t\t\t\t\t\tts_create=%s\n",ctime_r( &l_datum_ts_create,buf ));
                    dap_string_append_printf(l_str_tmp,"\t\t\t\t\t\tdata_size=%u\n", l_datum->header.data_size);

                    // Signatures
                    dap_string_append_printf(l_str_tmp,"\t\t\t\tsigns:\tcount: %us\n",l_event->header.signs_count);
                    l_offset += l_datum_size;
                    while (l_offset + sizeof (l_event->header) < l_event_size ){
                        dap_chain_sign_t * l_sign =(dap_chain_sign_t *) (l_event->hashes_n_datum_n_signs +l_offset);
                        size_t l_sign_size = dap_chain_sign_get_size(l_sign);
                        if (l_sign_size == 0 ){
                            dap_string_append_printf(l_str_tmp,"\t\t\t\tERROR: wrong sign size 0, stop parsing headers\n");
                            break;
                        }
                        dap_enc_key_t * l_sign_key = dap_chain_sign_to_enc_key(l_sign);
                        dap_chain_addr_t l_addr = {0};
                        dap_chain_addr_fill(&l_addr,l_sign_key,&l_net->pub.id);
                        char * l_addr_str = dap_chain_addr_to_str(&l_addr);
                        dap_string_append_printf(l_str_tmp,"\t\t\t\t\t\ttype: %s\taddr: %s"
                                                           "n", dap_chain_sign_type_to_str( l_sign->header.type ),
                                                 l_addr_str );
                        l_offset += l_sign_size;
                        DAP_DELETE( l_addr_str);
                        dap_enc_key_delete(l_sign_key);
                    }

                    dap_chain_node_cli_set_reply_text(a_str_reply, l_str_tmp->str);
                    dap_string_free(l_str_tmp,false);
                    ret=0;
                }else {
                    dap_chain_node_cli_set_reply_text(a_str_reply,
                                                      "Can't find event 0x%s in the new forming round ",
                                                      l_event_hash_str);
                    ret=-10;
                }
            }break;
            case SUBCMD_EVENT_LIST:{
                if( (l_from_events_str == NULL) ||
                        (strcmp(l_from_events_str,"round.new") == 0) ){
                    char * l_gdb_group_events = DAP_CHAIN_CS_DAG(l_chain)->gdb_group_events_round_new;
                    dap_string_t * l_str_tmp = dap_string_new("");
                    if ( l_gdb_group_events ){
                        dap_global_db_obj_t * l_objs;
                        size_t l_objs_count = 0;
                        l_objs = dap_chain_global_db_gr_load(l_gdb_group_events,&l_objs_count);
                        dap_string_append_printf(l_str_tmp,"%s.%s: Found %u records :\n",l_net->pub.name,l_chain->name,l_objs_count);

                        for (size_t i = 0; i< l_objs_count; i++){
                            dap_chain_cs_dag_event_t * l_event = (dap_chain_cs_dag_event_t *) l_objs[i].value;
                            char buf[50];
                            time_t l_ts_create = (time_t) l_event->header.ts_created;
                            dap_string_append_printf(l_str_tmp,"\t%s: ts_create=%s",
                                                     l_objs[i].key, ctime_r( &l_ts_create,buf ) );

                        }
                        DAP_DELETE( l_gdb_group_events);
                        if (l_objs && l_objs_count )
                            dap_chain_global_db_objs_delete(l_objs, l_objs_count);
                        ret = 0;
                    } else {
                        dap_string_append_printf(l_str_tmp,"%s.%s: Error! No GlobalDB group!\n",l_net->pub.name,l_chain->name);
                        ret = -2;

                    }
                    dap_chain_node_cli_set_reply_text(a_str_reply, l_str_tmp->str);
                    dap_string_free(l_str_tmp,false);
                }else if (l_from_events_str && (strcmp(l_from_events_str,"events") == 0) ){
                    dap_string_t * l_str_tmp = dap_string_new(NULL);
                    size_t l_events_count = HASH_COUNT(PVT(l_dag)->events);
                    dap_string_append_printf(l_str_tmp,"%s.%s: Have %u events :\n",
                                             l_net->pub.name,l_chain->name,l_events_count);
                    dap_chain_cs_dag_event_item_t * l_event_item = NULL,*l_event_item_tmp = NULL;
                    HASH_ITER(hh,PVT(l_dag)->events,l_event_item, l_event_item_tmp ) {
                        char buf[50];
                        char * l_event_item_hash_str = dap_chain_hash_fast_to_str_new( &l_event_item->hash);
                        time_t l_ts_create = (time_t) l_event_item->event->header.ts_created;
                        dap_string_append_printf(l_str_tmp,"\t%s: ts_create=%s",
                                                 l_event_item_hash_str, ctime_r( &l_ts_create,buf ) );
                        DAP_DELETE(l_event_item_hash_str);
                    }

                    dap_chain_node_cli_set_reply_text(a_str_reply, l_str_tmp->str);
                    dap_string_free(l_str_tmp,false);

                }else {
                    dap_chain_node_cli_set_reply_text(a_str_reply, "Undefined events source for listing ");
                    ret=-14;

                }
            }break;

            case SUBCMD_UNDEFINED: {
                dap_chain_node_cli_set_reply_text(a_str_reply,
                                                  "Undefined event subcommand \"%s\" ",
                                                  l_event_cmd_str);
                ret=-11;
            }
        }
    }else {
        dap_chain_node_cli_set_reply_text(a_str_reply,
                                          "Undefined subcommand");
        ret = -13;
    }
    return ret;
}