/*
 * Authors:
 * Daniil Frolov <daniil.frolov@demlabs.net>
 * DeM Labs Inc.   https://demlabs.net
 * DeM Labs Open source community https://github.com/demlabsinc
 * Copyright  (c) 2017-2019
 * 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 <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

#include "dap_chain_net_voting.h"
#include "dap_chain_net_srv_stake_pos_delegate.h"
#include "dap_chain_node_cli.h"
#include "dap_chain_mempool.h"
#include "uthash.h"
#include "utlist.h"

#define LOG_TAG "chain_net_voting"

typedef struct dap_chain_net_voting_params_offsets{
    dap_chain_datum_tx_t* voting_tx;
    size_t voting_question_offset;
    size_t voting_question_length;
    dap_list_t* option_offsets_list;
    size_t voting_expire_offset;
    size_t votes_max_count_offset;
    size_t delegate_key_required_offset;
    size_t vote_changing_allowed_offset;
} dap_chain_net_voting_params_offsets_t;

typedef struct dap_chain_net_vote_option {
    size_t vote_option_offset;
    size_t vote_option_length;
} dap_chain_net_vote_option_t;

typedef struct dap_chain_net_voting_cond_outs {
    dap_chain_hash_fast_t tx_hash;
    int out_idx;

    UT_hash_handle hh;
} dap_chain_net_voting_cond_outs_t;

typedef struct dap_chain_net_vote {
    dap_chain_hash_fast_t vote_hash;
    dap_chain_hash_fast_t pkey_hash;
    uint64_t answer_idx;
    uint256_t weight;
} dap_chain_net_vote_t;

typedef struct dap_chain_net_votings {
    dap_chain_hash_fast_t voting_hash;
    dap_chain_net_voting_params_offsets_t voting_params;
    dap_list_t *votes;
    dap_chain_net_id_t net_id;

    pthread_rwlock_t s_tx_outs_rwlock;
    dap_chain_net_voting_cond_outs_t *voting_spent_cond_outs;

    UT_hash_handle hh;
} dap_chain_net_votings_t;

static dap_chain_net_votings_t *s_votings;
static pthread_rwlock_t s_votings_rwlock;

static int s_datum_tx_voting_coin_check_cond_out(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_cond_hash, int a_cond_out_idx);
/// -1 error, 0 - unspent, 1 - spent
static int s_datum_tx_voting_coin_check_spent(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_prev_hash, int a_out_idx);
static bool s_datum_tx_voting_verification_callback(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx_in, bool a_apply);
static int s_cli_voting(int argc, char **argv, void **a_str_reply);

int dap_chain_net_voting_init()
{
    pthread_rwlock_init(&s_votings_rwlock, NULL);
    dap_chain_ledger_voting_verificator_add(s_datum_tx_voting_verification_callback);
    dap_cli_server_cmd_add("voting", s_cli_voting, "Voting commands.", ""
                                                                       "voting create -net <net_name> -question <\"Question_string\"> -options <\"Option1\", \"Option2\" ... \"OptionN\"> [-expire <voting_expire_time_in_RCF822>] [-max_votes_count <Votes_count>] [-delegated_key_required] [-vote_changing_allowed] -fee <value_datoshi> -w <fee_wallet_name>\n"
                                                                       "voting vote -net <net_name> -hash <voting_hash> -option_idx <option_index> [-cert <delegate_cert_name>] -fee <value_datoshi> -w <fee_wallet_name>\n"
                                                                       "voting list -net <net_name>\n"
                                                                       "voting dump -net <net_name> -hash <voting_hash>\n");
    return 0;
}

uint64_t* dap_chain_net_voting_get_result(dap_ledger_t* a_ledger, dap_chain_hash_fast_t* a_voting_hash)
{
    if(!a_voting_hash){
        return NULL;
    }

    uint64_t* l_voting_results = NULL;

    dap_chain_net_votings_t * l_voting = NULL;
    pthread_rwlock_rdlock(&s_votings_rwlock);
    HASH_FIND(hh, s_votings, a_voting_hash, sizeof(dap_hash_fast_t), l_voting);
    pthread_rwlock_unlock(&s_votings_rwlock);
    if(!l_voting || l_voting->net_id.uint64 != a_ledger->net->pub.id.uint64){
        char* l_hash_str = dap_hash_fast_to_str_new(a_voting_hash);
        log_it(L_ERROR, "Can't find voting with hash %s in net %s", l_hash_str, a_ledger->net->pub.name);
        DAP_DEL_Z(l_hash_str);
        return NULL;
    }

    l_voting_results = DAP_NEW_Z_SIZE(uint64_t, sizeof(uint64_t)*dap_list_length(l_voting->voting_params.option_offsets_list));
    if (!l_voting_results){
        log_it(L_CRITICAL, "Memory allocation error");
        return NULL;
    }

    dap_list_t* l_temp = l_voting->votes;
    while(l_temp){
        dap_chain_net_vote_t* l_vote = l_temp->data;
        if (l_vote->answer_idx >= dap_list_length(l_voting->voting_params.option_offsets_list))
            continue;

        l_voting_results[l_vote->answer_idx]++;

        l_temp = l_temp->next;
    }


    return l_voting_results;
}



bool s_datum_tx_voting_verification_callback(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx_in, bool a_apply)
{

    dap_hash_fast_t l_hash = {};
    dap_hash_fast(a_tx_in, dap_chain_datum_tx_get_size(a_tx_in), &l_hash);

    if (a_type == TX_ITEM_TYPE_VOTING){
        if (!a_apply){
            dap_chain_net_votings_t * l_voting = NULL;
            pthread_rwlock_rdlock(&s_votings_rwlock);
            HASH_FIND(hh, s_votings, &l_hash, sizeof(dap_hash_fast_t), l_voting);
            pthread_rwlock_unlock(&s_votings_rwlock);
            if(l_voting && l_voting->net_id.uint64 == a_ledger->net->pub.id.uint64){
                char* l_hash_str = dap_hash_fast_to_str_new(&l_hash);
                log_it(L_ERROR, "Voting with hash %s is already presents in net %s", l_hash_str, a_ledger->net->pub.name);
                DAP_DEL_Z(l_hash_str);
                return false;
            }

            dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL);
            dap_list_t* l_temp = l_tsd_list;
            size_t l_question_len = 0;
            size_t l_options_count = 0;
            while (l_temp){
                dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_temp->data)->tsd;
                dap_chain_net_vote_option_t *l_vote_option = NULL;
                switch(l_tsd->type){
                case VOTING_TSD_TYPE_QUESTION:
                    l_question_len = l_tsd->size;
                    break;
                case VOTING_TSD_TYPE_ANSWER:
                    l_options_count++;
                    break;
                default:
                    break;
                }
                l_temp = l_temp->next;
            }
            dap_list_free(l_tsd_list);

            if (!l_question_len || !l_options_count)
                return false;

            return true;
        }

        dap_chain_net_votings_t *l_item = DAP_NEW_Z_SIZE(dap_chain_net_votings_t, sizeof(dap_chain_net_votings_t));
        l_item->voting_hash = l_hash;
        l_item->voting_params.voting_tx = a_tx_in;
        l_item->net_id = a_ledger->net->pub.id;
        pthread_rwlock_init(&l_item->s_tx_outs_rwlock, NULL);

        dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL);
        dap_list_t* l_temp = l_tsd_list;
        while (l_temp){
            dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_temp->data)->tsd;
            dap_chain_net_vote_option_t *l_vote_option = NULL;
            switch(l_tsd->type){
            case VOTING_TSD_TYPE_QUESTION:
                l_item->voting_params.voting_question_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
                l_item->voting_params.voting_question_length = l_tsd->size;
                break;
            case VOTING_TSD_TYPE_ANSWER:
                l_vote_option = DAP_NEW_Z(dap_chain_net_vote_option_t);
                l_vote_option->vote_option_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
                l_vote_option->vote_option_length = l_tsd->size;
                l_item->voting_params.option_offsets_list = dap_list_append(l_item->voting_params.option_offsets_list, l_vote_option);
                break;
            case VOTING_TSD_TYPE_EXPIRE:
                l_item->voting_params.voting_expire_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
                break;
            case VOTING_TSD_TYPE_MAX_VOTES_COUNT:
                l_item->voting_params.votes_max_count_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
                break;
            case VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED:
                l_item->voting_params.delegate_key_required_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
                break;
            case VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED:
                l_item->voting_params.vote_changing_allowed_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
                break;
            default:
                break;
            }
            l_temp = l_temp->next;
        }
        dap_list_free(l_tsd_list);

        pthread_rwlock_wrlock(&s_votings_rwlock);
        HASH_ADD(hh, s_votings, voting_hash, sizeof(dap_hash_fast_t), l_item);
        pthread_rwlock_unlock(&s_votings_rwlock);
        return true;
    } else if (a_type == TX_ITEM_TYPE_VOTE){
        dap_chain_tx_vote_t *l_vote_tx_item = (dap_chain_tx_vote_t *)dap_chain_datum_tx_item_get(a_tx_in, 0, TX_ITEM_TYPE_VOTE, NULL);
        if(!l_vote_tx_item){
            log_it(L_ERROR, "Can't find vote item");
            return false;
        }

        dap_chain_net_votings_t * l_voting = NULL;
        pthread_rwlock_wrlock(&s_votings_rwlock);
        HASH_FIND(hh, s_votings, &l_vote_tx_item->voting_hash, sizeof(dap_hash_fast_t), l_voting);
        pthread_rwlock_unlock(&s_votings_rwlock);
        if(!l_voting || l_voting->net_id.uint64 != a_ledger->net->pub.id.uint64) {
            log_it(L_ERROR, "Can't find voting with hash %s in net %s",
                   dap_chain_hash_fast_to_str_static(&l_hash), a_ledger->net->pub.name);
            return false;
        }

        if (!a_apply){
            if (l_vote_tx_item->answer_idx > dap_list_length(l_voting->voting_params.option_offsets_list)){
                log_it(L_ERROR, "Invalid vote option index.");
                return false;
            }

            if(l_voting->voting_params.votes_max_count_offset){
                uint64_t l_votes_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset);
                if (l_votes_max_count && dap_list_length(l_voting->votes) >= l_votes_max_count){
                    log_it(L_ERROR, "The required number of votes has been collected.");
                    return false;
                }
            }

            if(l_voting->voting_params.voting_expire_offset){
                dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset);
                if( l_expire && l_expire <= a_tx_in->header.ts_created){
                    log_it(L_ERROR, "The voting has been expired.");
                    return false;
                }
            }

            dap_hash_fast_t pkey_hash = {};
            dap_chain_tx_sig_t *l_vote_sig = NULL;
            int l_item_cnt = 0;
            dap_list_t* l_signs_list = NULL;
            l_signs_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_SIG, &l_item_cnt);

            if(!l_signs_list){
                log_it(L_ERROR, "Can't get sign.");
                return false;
            }
            l_vote_sig = (dap_chain_tx_sig_t *)(dap_list_last(l_signs_list)->data);
            dap_sign_get_pkey_hash((dap_sign_t*)l_vote_sig->sig, &pkey_hash);
            if (l_voting->voting_params.delegate_key_required_offset &&
                *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.delegate_key_required_offset)){
                if (!dap_chain_net_srv_stake_check_pkey_hash(&pkey_hash)){
                    log_it(L_ERROR, "The voting required a delegated key.");
                    return false;
                }
            }

            dap_list_t *l_temp = l_voting->votes;
            while(l_temp){
                if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &pkey_hash)){
                    if(l_voting->voting_params.vote_changing_allowed_offset &&
                        *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){
                        //delete conditional outputs
                        dap_chain_datum_tx_t *l_old_tx = dap_ledger_tx_find_by_hash(a_ledger, &((dap_chain_net_vote_t *)l_temp->data)->vote_hash);

                        dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(l_old_tx, TX_ITEM_TYPE_TSD, NULL);
                        dap_list_t* l_tsd_temp = l_tsd_list;
                        while (l_tsd_temp){
                            dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_tsd_temp->data)->tsd;
                            dap_hash_fast_t l_hash = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->tx_hash;
                            if(l_tsd->type == VOTING_TSD_TYPE_VOTE_TX_COND){
                                dap_chain_net_voting_cond_outs_t *l_tx_outs = NULL;
                                pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock);
                                HASH_FIND(hh, l_voting->voting_spent_cond_outs, &l_hash, sizeof(dap_hash_fast_t), l_tx_outs);
                                if(l_tx_outs)
                                    HASH_DELETE(hh, l_voting->voting_spent_cond_outs, l_tx_outs);
                                pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock);
                            }
                            l_tsd_temp = l_tsd_temp->next;
                        }
                        dap_list_free(l_tsd_list);


                        //delete vote
                        l_voting->votes = dap_list_remove(l_voting->votes, l_temp->data);
                        break;
                    } else {
                        log_it(L_ERROR, "The voting don't allow change your vote.");
                        return false;
                    }
                }
                l_temp = l_temp->next;
            }
        }

        uint256_t l_weight = {};

        // check out conds
        dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL);
        dap_list_t* l_tsd_temp = l_tsd_list;
        while (l_tsd_temp){
            dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_tsd_temp->data)->tsd;
            dap_hash_fast_t l_hash = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->tx_hash;
            int l_out_idx = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->out_idx;
            if(l_tsd->type == VOTING_TSD_TYPE_VOTE_TX_COND){
                if (s_datum_tx_voting_coin_check_cond_out(a_ledger->net, l_vote_tx_item->voting_hash,
                                                          l_hash, l_out_idx) != 0){
                    l_tsd_temp = l_tsd_temp->next;
                    continue;
                }
                dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(a_ledger, &l_hash);
                dap_chain_tx_out_cond_t *l_prev_out = (dap_chain_tx_out_cond_t*)dap_chain_datum_tx_item_get(l_tx_prev_temp, &l_out_idx, TX_ITEM_TYPE_OUT_COND, NULL);
                if(!l_prev_out || l_prev_out->header.subtype != DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK){
                    l_tsd_temp = l_tsd_temp->next;
                    continue;
                }
                SUM_256_256(l_weight, l_prev_out->header.value, &l_weight);

                dap_chain_net_voting_cond_outs_t *l_item = DAP_NEW_Z_SIZE(dap_chain_net_voting_cond_outs_t, sizeof(dap_chain_net_voting_cond_outs_t));
                l_item->tx_hash = l_hash;
                l_item->out_idx = l_out_idx;
                pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock);
                HASH_ADD(hh, l_voting->voting_spent_cond_outs, tx_hash, sizeof(dap_hash_fast_t), l_item);
                pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock);
            }
            l_tsd_temp = l_tsd_temp->next;
        }
        dap_list_free(l_tsd_list);
        // check inputs
        dap_list_t *l_ins_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_IN, NULL);
        if (!l_ins_list){
            log_it(L_ERROR, "Can't get inputs from tx");
            return -1;
        }
        dap_list_t *l_in_temp = l_ins_list;
        while(l_in_temp){
            dap_chain_tx_in_t *l_tx_in = (dap_chain_tx_in_t *)l_in_temp->data;
            if (s_datum_tx_voting_coin_check_spent(a_ledger->net, l_vote_tx_item->voting_hash, l_tx_in->header.tx_prev_hash, l_tx_in->header.tx_out_prev_idx) == 0){
                dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(a_ledger, &l_tx_in->header.tx_prev_hash);
                int l_out_prev_idx = (int)l_tx_in->header.tx_out_prev_idx;
                dap_chain_tx_out_t *l_prev_out_union = (dap_chain_tx_out_t *)dap_chain_datum_tx_out_get_by_out_idx(l_tx_prev_temp, l_out_prev_idx);
                if (!l_prev_out_union){
                    l_in_temp = l_in_temp->next;
                    continue;
                }

                switch (l_prev_out_union->header.type) {
                case TX_ITEM_TYPE_OUT:
                case TX_ITEM_TYPE_OUT_EXT:
                    SUM_256_256(l_weight, l_prev_out_union->header.value, &l_weight);
                }
            }
            l_in_temp = l_in_temp->next;
        }
        dap_list_free(l_ins_list);

        if (IS_ZERO_256(l_weight)){
            log_it(L_ERROR, "No unspent coins");
            return false;
        }


        if (a_apply){
            dap_hash_fast_t pkey_hash = {};
            dap_chain_tx_sig_t *l_vote_sig = NULL;
            int l_item_cnt = 0;
            dap_list_t* l_signs_list = NULL;
            l_signs_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_SIG, &l_item_cnt);

            if(!l_signs_list){
                log_it(L_ERROR, "Can't get sign.");
                return false;
            }
            l_vote_sig = (dap_chain_tx_sig_t *)(dap_list_last(l_signs_list)->data);
            dap_sign_get_pkey_hash((dap_sign_t*)l_vote_sig->sig, &pkey_hash);

            dap_chain_net_vote_t *l_vote_item = DAP_NEW_Z(dap_chain_net_vote_t);
            if (!l_vote_item){
                log_it(L_CRITICAL, "Memory allocate_error!");
                return false;
            }
            l_vote_item->vote_hash = l_hash;
            l_vote_item->pkey_hash = pkey_hash;
            l_vote_item->answer_idx = l_vote_tx_item->answer_idx;
            l_vote_item->weight = l_weight;

            dap_list_t *l_temp = l_voting->votes;
            while(l_temp){
                if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &pkey_hash)){
                    if(l_voting->voting_params.vote_changing_allowed_offset &&
                        *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){

                        l_voting->votes = dap_list_append(l_voting->votes, l_vote_item);

                        log_it(L_ERROR, "Vote is changed.");
                        return true;
                    } else {
                        log_it(L_ERROR, "The voting don't allow change your vote.");
                        DAP_DELETE(l_vote_item);
                        return false;
                    }
                }
                l_temp = l_temp->next;
            }
            log_it(L_INFO, "Vote is accepted.");
            l_voting->votes = dap_list_append(l_voting->votes, l_vote_item);
        }
        return true;
    } else {
        log_it(L_ERROR, "Item is not supported in votings.");
    }

    return false;
}

static dap_list_t* s_get_options_list_from_str(const char* a_str)
{
    dap_list_t* l_ret = NULL;
    char * l_options_str_dup = strdup(a_str);
    if (!l_options_str_dup) {
        log_it(L_ERROR, "Memory allocation error in %s, line %d", __PRETTY_FUNCTION__, __LINE__);
        return 0;
    }

    size_t l_opt_str_len = strlen(l_options_str_dup);
    char* l_option_start_ptr = l_options_str_dup;
    dap_string_t* l_option_str = dap_string_new(NULL);
    for (size_t i = 0; i <= l_opt_str_len; i++){
        if(i == l_opt_str_len){
            l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, &l_options_str_dup[i] - l_option_start_ptr);
            char* l_option = dap_string_free(l_option_str, false);
            l_option = dap_strstrip(l_option);// removes leading and trailing spaces
            l_ret = dap_list_append(l_ret, l_option);
            break;
        }
        if (l_options_str_dup [i] == ','){
            if(i > 0 && l_options_str_dup [i-1] == '\\'){
                l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, i-1);
                l_option_start_ptr = &l_options_str_dup [i];
                continue;
            }
            l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, &l_options_str_dup[i] - l_option_start_ptr);
            l_option_start_ptr = &l_options_str_dup [i+1];
            char* l_option = dap_string_free(l_option_str, false);
            l_option_str = dap_string_new(NULL);
            l_option = dap_strstrip(l_option);// removes leading and trailing spaces
            l_ret = dap_list_append(l_ret, l_option);
        }
    }

    free(l_options_str_dup);

    return l_ret;
}

static int s_cli_voting(int a_argc, char **a_argv, void **a_str_reply)
{
    enum {CMD_NONE=0, CMD_CREATE, CMD_VOTE, CMD_LIST, CMD_DUMP};

    const char* l_net_str = NULL;
    int arg_index = 1;
    dap_chain_net_t *l_net = NULL;

    const char *l_hash_out_type = NULL;
    dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-H", &l_hash_out_type);
    if(!l_hash_out_type)
        l_hash_out_type = "hex";
    if(dap_strcmp(l_hash_out_type,"hex") && dap_strcmp(l_hash_out_type, "base58"))
        return -1;


    dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str);
    // Select chain network
    if(!l_net_str) {
        dap_cli_server_cmd_set_reply_text(a_str_reply, "command requires parameter '-net'");
        return -2;
    } else {
        if((l_net = dap_chain_net_by_name(l_net_str)) == NULL) { // Can't find such network
            dap_cli_server_cmd_set_reply_text(a_str_reply,
                                              "command requires parameter '-net' to be valid chain network name");
            return -3;
        }
    }

    int l_cmd = CMD_NONE;
    if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "create", NULL))
        l_cmd = CMD_CREATE;
    else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "vote", NULL))
        l_cmd = CMD_VOTE;
    else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "list", NULL))
        l_cmd = CMD_LIST;
    else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "dump", NULL))
        l_cmd = CMD_DUMP;


    switch(l_cmd){
    case CMD_CREATE:{
        const char* l_question_str = NULL;
        const char* l_options_list_str = NULL;
        const char* l_voting_expire_str = NULL;
        const char* l_max_votes_count_str = NULL;
        const char* l_fee_str = NULL;
        const char* l_wallet_str = NULL;

        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-question", &l_question_str);
        if (!l_question_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires a question parameter to be valid.");
            return -100;
        }

        if (strlen(l_question_str) > DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "The question must contain no more than %d characters", DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH);
            return -101;
        }

        dap_list_t *l_options_list = NULL;
        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-options", &l_options_list_str);
        if (!l_options_list_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires a question parameter to be valid.");
            return -101;
        }
        // Parse options list
        l_options_list = s_get_options_list_from_str(l_options_list_str);
        if(!l_options_list || dap_list_length(l_options_list) < 2){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Number of options must be 2 or greater.");
            return -102;
        }

        if(dap_list_length(l_options_list)>DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "The voting can to contain no more than %d options", DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT);
            return -102;
        }

        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-expire", &l_voting_expire_str);
        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-max_votes_count", &l_max_votes_count_str);
        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_fee_str);
        if (!l_fee_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires paramete -fee to be valid.");
            return -102;
        }
        uint256_t l_value_fee = dap_chain_balance_scan(l_fee_str);
        if (IS_ZERO_256(l_value_fee)) {
            dap_cli_server_cmd_set_reply_text(a_str_reply,
                                              "command requires parameter '-fee' to be valid uint256");
            return -103;
        }

        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str);
        if (!l_wallet_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires parameter -w to be valid.");
            return -103;
        }

        dap_enc_key_t *l_priv_key = NULL;
        const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
        dap_chain_wallet_t *l_wallet_fee = dap_chain_wallet_open(l_wallet_str, c_wallets_path);
        if (!l_wallet_fee) {
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Wallet %s does not exist", l_wallet_str);
            return -112;
        }
        l_priv_key = dap_chain_wallet_get_key(l_wallet_fee, 0);

        const dap_chain_addr_t *l_addr_from = (const dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet_fee, l_net->pub.id);

        if(!l_addr_from) {
            dap_cli_server_cmd_set_reply_text(a_str_reply, "source address is invalid");
            return -113;
        }

        const char *l_native_ticker = l_net->pub.native_ticker;
        uint256_t l_net_fee = {}, l_total_fee = {}, l_value_transfer;
        dap_chain_addr_t l_addr_fee = {};
        bool l_net_fee_used = dap_chain_net_tx_get_fee(l_net->pub.id, &l_net_fee, &l_addr_fee);
        SUM_256_256(l_net_fee, l_value_fee, &l_total_fee);

        dap_ledger_t* l_ledger = dap_ledger_by_net_name(l_net->pub.name);
        dap_list_t *l_list_used_out = dap_ledger_get_list_tx_outs_with_val(l_ledger, l_native_ticker,
                                                                           l_addr_from, l_total_fee, &l_value_transfer);
        if (!l_list_used_out) {
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Not enough funds to transfer");
            return -113;
        }
        // create empty transaction
        dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create();

        // Add Voting item
        dap_chain_tx_voting_t* l_voting_item = dap_chain_datum_tx_item_voting_create();

        dap_chain_datum_tx_add_item(&l_tx, l_voting_item);
        DAP_DELETE(l_voting_item);

        // Add question to tsd data
        dap_chain_tx_tsd_t* l_question_tsd = dap_chain_datum_voting_question_tsd_create(l_question_str, strlen(l_question_str));
        dap_chain_datum_tx_add_item(&l_tx, l_question_tsd);
        DAP_DELETE(l_question_tsd);

        // Add options to tsd
        dap_list_t *l_temp = l_options_list;
        while(l_temp){
            if(strlen((char*)l_temp->data) > DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH){
                dap_chain_datum_tx_delete(l_tx);
                dap_list_free_full(l_options_list, NULL);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "The option must contain no more than %d characters", DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH);
                return -114;
            }
            dap_chain_tx_tsd_t* l_option = dap_chain_datum_voting_answer_tsd_create((char*)l_temp->data, strlen((char*)l_temp->data));
            if(!l_option){
                dap_chain_datum_tx_delete(l_tx);
                dap_list_free_full(l_options_list, NULL);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create option tsd item.");
                return -114;
            }
            dap_chain_datum_tx_add_item(&l_tx, l_option);
            DAP_DEL_Z(l_option);

            l_temp = l_temp->next;
        }
        dap_list_free_full(l_options_list, NULL);

        // add voting expire time if needed
        if(l_voting_expire_str){
            dap_time_t l_expired_time = dap_time_from_str_rfc822(l_voting_expire_str);
            if(!l_expired_time){
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't parse expire time");
                return -114;
            }
            if (l_expired_time < dap_time_now()){
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create voting with expired time");
                return -114;
            }

            dap_chain_tx_tsd_t* l_expired_item = dap_chain_datum_voting_expire_tsd_create(l_expired_time);
            if(!l_expired_item){
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create expired tsd item.");
                return -114;
            }
            dap_chain_datum_tx_add_item(&l_tx, l_expired_item);
            DAP_DEL_Z(l_expired_item);
        }

        // Add vote max count if needed
        if(l_max_votes_count_str){
            uint64_t l_max_votes_count = atoll(l_max_votes_count_str);
            dap_chain_tx_tsd_t* l_max_votes_item = dap_chain_datum_voting_max_votes_count_tsd_create(l_max_votes_count);
            if(!l_max_votes_item){
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create expired tsd item.");
                return -114;
            }
            dap_chain_datum_tx_add_item(&l_tx, l_max_votes_item);
            DAP_DEL_Z(l_max_votes_item);
        }

        if(dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-delegated_key_required", NULL)){
            dap_chain_tx_tsd_t* l_delegated_key_req_item = dap_chain_datum_voting_delegated_key_required_tsd_create(true);
            if(!l_delegated_key_req_item){
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create delegated key req tsd item.");
                return -114;
            }
            dap_chain_datum_tx_add_item(&l_tx, l_delegated_key_req_item);
            DAP_DEL_Z(l_delegated_key_req_item);
        }

        if(dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-vote_changing_allowed", NULL)){
            dap_chain_tx_tsd_t* l_vote_changing_item = dap_chain_datum_voting_vote_changing_allowed_tsd_create(true);
            if(!l_vote_changing_item){
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create delegated key req tsd item.");
                return -114;
            }
            dap_chain_datum_tx_add_item(&l_tx, l_vote_changing_item);
            DAP_DEL_Z(l_vote_changing_item);
        }

        // add 'in' items
        uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out);
        assert(EQUAL_256(l_value_to_items, l_value_transfer));
        dap_list_free_full(l_list_used_out, NULL);
        uint256_t l_value_pack = {};
        // Network fee
        if (l_net_fee_used) {
            if (dap_chain_datum_tx_add_out_item(&l_tx, &l_addr_fee, l_net_fee) == 1)
                SUM_256_256(l_value_pack, l_net_fee, &l_value_pack);
            else {
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out.");
                return -114;
            }
        }
        // Validator's fee
        if (!IS_ZERO_256(l_value_fee)) {
            if (dap_chain_datum_tx_add_fee_item(&l_tx, l_value_fee) == 1)
                SUM_256_256(l_value_pack, l_value_fee, &l_value_pack);
            else {
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out.");
                return -115;
            }
        }
        // coin back
        uint256_t l_value_back;
        SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back);
        if(!IS_ZERO_256(l_value_back)) {
            if(dap_chain_datum_tx_add_out_item(&l_tx, l_addr_from, l_value_back) != 1) {
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add out with value back");
                return -116;
            }
        }

        // add 'sign' items
        if(dap_chain_datum_tx_add_sign_item(&l_tx, l_priv_key) != 1) {
            dap_chain_datum_tx_delete(l_tx);
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx");
            return -117;
        }

        size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx);
        dap_hash_fast_t l_tx_hash;
        dap_hash_fast(l_tx, l_tx_size, &l_tx_hash);
        dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size);
        DAP_DELETE(l_tx);
        dap_chain_t* l_chain = dap_chain_net_get_default_chain_by_chain_type(l_net, CHAIN_TYPE_TX);

        char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain, l_hash_out_type);
        if (l_ret)
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Datum %s successfully added to mempool", l_ret);
        else
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add datum to mempool");
        DAP_DELETE(l_datum);
    }break;
    case CMD_VOTE:{
        const char* l_cert_name = NULL;
        const char* l_fee_str = NULL;
        const char* l_wallet_str = NULL;
        const char* l_hash_str = NULL;
        const char* l_option_idx_str = NULL;

        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_hash_str);
        if(!l_hash_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' require the parameter -hash");
            return -110;
        }

        dap_hash_fast_t l_voting_hash = {};
        dap_chain_hash_fast_from_str(l_hash_str, &l_voting_hash);


        dap_chain_hash_fast_t l_pkey_hash;
        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-cert", &l_cert_name);
        dap_cert_t * l_cert = dap_cert_find_by_name(l_cert_name);
        if (l_cert_name){
            if (l_cert == NULL) {
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find \"%s\" certificate", l_cert_name);
                return -7;
            }
            if (l_cert->enc_key == NULL) {
                dap_cli_server_cmd_set_reply_text(a_str_reply, "No key found in \"%s\" certificate", l_cert_name );
                return -8;
            }
            // Get publivc key hash
            size_t l_pub_key_size = 0;
            uint8_t *l_pub_key = dap_enc_key_serialize_pub_key(l_cert->enc_key, &l_pub_key_size);;
            if (l_pub_key == NULL) {
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't serialize public key of certificate \"%s\"", l_cert_name);
                return -9;
            }

            dap_hash_fast(l_pub_key, l_pub_key_size, &l_pkey_hash);
        }

        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_fee_str);
        if (!l_fee_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires paramete -fee to be valid.");
            return -102;
        }
        uint256_t l_value_fee = dap_chain_balance_scan(l_fee_str);
        if (IS_ZERO_256(l_value_fee)) {
            dap_cli_server_cmd_set_reply_text(a_str_reply,
                                              "command requires parameter '-fee' to be valid uint256");
            return -103;
        }

        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str);
        if (!l_wallet_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires parameter -w to be valid.");
            return -103;
        }

        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-option_idx", &l_option_idx_str);
        if (!l_option_idx_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires parameter -option_idx to be valid.");
            return -103;
        }

        dap_chain_net_votings_t *l_voting = NULL;
        pthread_rwlock_rdlock(&s_votings_rwlock);
        HASH_FIND(hh, s_votings, &l_voting_hash, sizeof(l_voting_hash),l_voting);
        pthread_rwlock_unlock(&s_votings_rwlock);
        if(!l_voting || l_voting->net_id.uint64 != l_net->pub.id.uint64){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find voting with hash %s", l_hash_str);
            return -111;
        }

        if(l_voting->voting_params.votes_max_count_offset){
            uint64_t l_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset);
            if (l_max_count && dap_list_length(l_voting->votes) >= l_max_count){
                dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting already received the required number of votes.");
                return -111;
            }
        }

        if(l_voting->voting_params.voting_expire_offset){
            dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset);
            dap_time_t l_time_now = dap_time_now();
            if (l_expire && l_time_now > l_expire){
                dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting already expired.");
                return -111;
            }
        }

        if(l_voting->voting_params.delegate_key_required_offset &&
            *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.delegate_key_required_offset) ){
            if (!l_cert){
                dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting required a delegated key.");
                return -111;
            } else if(!dap_chain_net_srv_stake_check_pkey_hash(&l_pkey_hash)){
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Your key is not delegated.");
                return -111;
            }
        }

        if(l_cert){
            dap_list_t *l_temp = l_voting->votes;
            while(l_temp){
                if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &l_pkey_hash)){
                    if(!l_voting->voting_params.vote_changing_allowed_offset ||
                        !*(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){
                        dap_cli_server_cmd_set_reply_text(a_str_reply, "The voting doesn't allow change your vote.");
                        return -113;
                    }
                }
                l_temp = l_temp->next;
            }
        }

        dap_enc_key_t *l_priv_key = NULL;
        const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
        dap_chain_wallet_t *l_wallet_fee = dap_chain_wallet_open(l_wallet_str, c_wallets_path);
        if (!l_wallet_fee) {
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Wallet %s does not exist", l_wallet_str);
            return -112;
        }
        l_priv_key = dap_chain_wallet_get_key(l_wallet_fee, 0);

        const dap_chain_addr_t *l_addr_from = (const dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet_fee, l_net->pub.id);

        if(!l_addr_from) {
            dap_cli_server_cmd_set_reply_text(a_str_reply, "source address is invalid");
            return -113;
        }

        const char *l_native_ticker = l_net->pub.native_ticker;
        uint256_t l_net_fee = {}, l_total_fee = {}, l_value_transfer;
        dap_chain_addr_t l_addr_fee = {};
        bool l_net_fee_used = dap_chain_net_tx_get_fee(l_net->pub.id, &l_net_fee, &l_addr_fee);
        SUM_256_256(l_net_fee, l_value_fee, &l_total_fee);

        dap_ledger_t* l_ledger = dap_ledger_by_net_name(l_net->pub.name);
        dap_list_t *l_list_used_out = dap_ledger_get_list_tx_outs(l_ledger, l_native_ticker, l_addr_from, &l_value_transfer);
        if (!l_list_used_out || compare256(l_value_transfer, l_total_fee) <= 0) {
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Not enough funds to transfer");
            return -113;
        }


        // check outputs UTXOs
        dap_list_t *l_utxo_temp = l_list_used_out;
        uint256_t l_value_transfer_new = {};
        while(l_utxo_temp){
            dap_chain_tx_used_out_item_t *l_out = (dap_chain_tx_used_out_item_t *)l_utxo_temp->data;
            if (s_datum_tx_voting_coin_check_spent(l_net, l_voting_hash, l_out->tx_hash_fast, l_out->num_idx_out) != 0 &&
                (!l_voting->voting_params.vote_changing_allowed_offset ||
                 !*(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset))){
                dap_list_t *l_temp = l_utxo_temp;
                l_utxo_temp = l_utxo_temp->next;
                dap_list_delete_link(l_list_used_out, l_temp);
                continue;
            }
            SUM_256_256(l_value_transfer_new, l_out->value, &l_value_transfer_new);
            l_utxo_temp = l_utxo_temp->next;
        }

        if (IS_ZERO_256(l_value_transfer_new) || compare256(l_value_transfer_new, l_total_fee) <= 0){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "You have not unspent UTXO for participation in this voting.");
            return -113;
        }

        l_value_transfer = l_value_transfer_new;

        // create empty transaction
        dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create();

        // Add vote item
        uint64_t l_option_idx_count = atoll(l_option_idx_str);
        if (l_option_idx_count > dap_list_length(l_voting->voting_params.option_offsets_list)){
            dap_chain_datum_tx_delete(l_tx);
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Invalid option index.");
            return -114;
        }
        dap_chain_tx_vote_t* l_vote_item = dap_chain_datum_tx_item_vote_create(&l_voting_hash, &l_option_idx_count);
        if(!l_vote_item){
            dap_chain_datum_tx_delete(l_tx);
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create vote item.");
            return -114;
        }
        dap_chain_datum_tx_add_item(&l_tx, l_vote_item);
        DAP_DEL_Z(l_vote_item);

        // add stake out conds items
        dap_list_t *l_outs = dap_ledger_get_list_tx_cond_outs(l_ledger, l_net->pub.native_ticker,  l_addr_from,
                                                              DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK, NULL);
        dap_list_t *l_temp = l_outs;
        while(l_temp){
            dap_chain_tx_used_out_item_t *l_out_item = (dap_chain_tx_used_out_item_t *)l_temp->data;
            if (dap_ledger_tx_hash_is_used_out_item(l_net->pub.ledger, &l_out_item->tx_hash_fast, l_out_item->num_idx_out, NULL) ||
                s_datum_tx_voting_coin_check_cond_out(l_net, l_voting_hash, l_out_item->tx_hash_fast, l_out_item->num_idx_out ) != 0){
                l_temp = l_temp->next;
                continue;
            }
            dap_chain_tx_tsd_t *l_item = dap_chain_datum_voting_vote_tx_cond_tsd_create(l_out_item->tx_hash_fast, l_out_item->num_idx_out);
            if(!l_item){
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create tsd tx cond item.");
                dap_list_free_full(l_outs, NULL);
                return -114;
            }
            dap_chain_datum_tx_add_item(&l_tx, l_item);
            DAP_DEL_Z(l_item);
            l_temp = l_temp->next;
        }
        dap_list_free_full(l_outs, NULL);

        // add 'in' items
        uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out);
        assert(EQUAL_256(l_value_to_items, l_value_transfer));
        dap_list_free_full(l_list_used_out, NULL);
        uint256_t l_value_pack = {};
        // Network fee
        if (l_net_fee_used) {
            if (dap_chain_datum_tx_add_out_item(&l_tx, &l_addr_fee, l_net_fee) == 1)
                SUM_256_256(l_value_pack, l_net_fee, &l_value_pack);
            else {
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out.");
                return -114;
            }
        }
        // Validator's fee
        if (!IS_ZERO_256(l_value_fee)) {
            if (dap_chain_datum_tx_add_fee_item(&l_tx, l_value_fee) == 1)
                SUM_256_256(l_value_pack, l_value_fee, &l_value_pack);
            else {
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out.");
                return -115;
            }
        }
        // coin back
        uint256_t l_value_back;
        SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back);
        if(!IS_ZERO_256(l_value_back)) {
            if(dap_chain_datum_tx_add_out_item(&l_tx, l_addr_from, l_value_back) != 1) {
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add out with value back");
                return -116;
            }
        }

        // add 'sign' items with wallet sign
        if(dap_chain_datum_tx_add_sign_item(&l_tx, l_priv_key) != 1) {
            dap_chain_datum_tx_delete(l_tx);
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx");
            return -117;
        }

        // add 'sign' items with delegated key if needed
        if(l_cert){
            if(dap_chain_datum_tx_add_sign_item(&l_tx, l_cert->enc_key) != 1) {
                dap_chain_datum_tx_delete(l_tx);
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx");
                return -117;
            }
        }

        size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx);
        dap_hash_fast_t l_tx_hash;
        dap_hash_fast(l_tx, l_tx_size, &l_tx_hash);
        dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size);
        DAP_DELETE(l_tx);
        dap_chain_t* l_chain = dap_chain_net_get_default_chain_by_chain_type(l_net, CHAIN_TYPE_TX);

        char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain, l_hash_out_type);
        if (l_ret)
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Datum %s successfully added to mempool", l_ret);
        else
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add datum to mempool");
        DAP_DELETE(l_datum);


    }break;
    case CMD_LIST:{
        dap_string_t *l_str_out = dap_string_new(NULL);
        dap_string_append_printf(l_str_out, "List of votings in net %s:\n\n", l_net->pub.name);
        dap_chain_net_votings_t *l_voting = NULL, *l_tmp;
        pthread_rwlock_rdlock(&s_votings_rwlock);
        HASH_ITER(hh, s_votings, l_voting, l_tmp){
            if (l_voting->net_id.uint64 != l_net->pub.id.uint64)
                continue;

            dap_string_append_printf(l_str_out, "Voting hash: %s\n",
                dap_chain_hash_fast_to_str_static(&l_voting->voting_hash));
            dap_string_append(l_str_out, "Voting question:\n");
            char* l_voting_question = (char*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_question_offset);
            dap_string_append_len(l_str_out,
                                  l_voting_question,
                                  l_voting->voting_params.voting_question_length > strlen(l_voting_question) ? strlen(l_voting_question) : l_voting->voting_params.voting_question_length);
            dap_string_append(l_str_out, "\n\n");
        }
        pthread_rwlock_unlock(&s_votings_rwlock);

        dap_cli_server_cmd_set_reply_text(a_str_reply, "%s", l_str_out->str);
        dap_string_free(l_str_out, true);
    }break;
    case CMD_DUMP:{
        const char* l_hash_str = NULL;

        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_hash_str);
        if(!l_hash_str){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'results' require the parameter -hash");
            return -110;
        }

        dap_hash_fast_t l_voting_hash = {};
        dap_chain_hash_fast_from_str(l_hash_str, &l_voting_hash);
        dap_chain_net_votings_t *l_voting = NULL;
        pthread_rwlock_rdlock(&s_votings_rwlock);
        HASH_FIND(hh, s_votings, &l_voting_hash, sizeof(l_voting_hash),l_voting);
        pthread_rwlock_unlock(&s_votings_rwlock);
        if(!l_voting){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find voting with hash %s", l_hash_str);
            return -111;
        }

        uint64_t l_options_count = 0;
        l_options_count = dap_list_length(l_voting->voting_params.option_offsets_list);
        if(!l_options_count){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "No options. May be datum is crashed.");
            return -111;
        }

        struct voting_results {uint64_t num_of_votes; uint256_t weights;};

        struct voting_results* l_results = DAP_NEW_Z_SIZE(struct voting_results, sizeof(struct voting_results)*l_options_count);
        if(!l_results){
            dap_cli_server_cmd_set_reply_text(a_str_reply, "Memlory allocation error!");
            return -111;
        }
        dap_list_t* l_list_tmp = l_voting->votes;
        uint256_t l_total_weight = {};
        while(l_list_tmp){
            dap_chain_net_vote_t *l_vote = l_list_tmp->data;
            l_results[l_vote->answer_idx].num_of_votes++;
            SUM_256_256(l_results[l_vote->answer_idx].weights, l_vote->weight, &l_results[l_vote->answer_idx].weights);
            l_list_tmp = l_list_tmp->next;
            SUM_256_256(l_total_weight, l_vote->weight, &l_total_weight);
        }

        uint64_t l_votes_count = 0;
        l_votes_count = dap_list_length(l_voting->votes);
        dap_string_t *l_str_out = dap_string_new(NULL);
        dap_string_append_printf(l_str_out, "Dump of voting %s:\n\n", l_hash_str);
        dap_string_append_len(l_str_out,
                              (char*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_question_offset),
                              l_voting->voting_params.voting_question_length);
        dap_string_append(l_str_out, "\n\n");

        if(l_voting->voting_params.voting_expire_offset){
            char l_tmp_buf[DAP_TIME_STR_SIZE];
            dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset);
            dap_time_to_str_rfc822(l_tmp_buf, DAP_TIME_STR_SIZE, l_expire);
            dap_string_append_printf(l_str_out, "\t Voting expire: %s", l_tmp_buf);
            dap_string_truncate(l_str_out, l_str_out->len - 1);
            dap_string_append_printf(l_str_out, " (%s)\n", l_expire > dap_time_now() ? "active" : "expired");
        }
        if (l_voting->voting_params.votes_max_count_offset){
            uint64_t l_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset);
            dap_string_append_printf(l_str_out, "\t Votes max count: %"DAP_UINT64_FORMAT_U" (%s)\n", l_max_count, l_max_count <= l_votes_count ? "closed" : "active");
        }
        dap_string_append_printf(l_str_out, "\t Changing vote is %s available.\n", l_voting->voting_params.vote_changing_allowed_offset ? "" : "not");
        dap_string_append_printf(l_str_out, "\t A delegated key is%s required to participate in voting. \n", l_voting->voting_params.delegate_key_required_offset ? "" : " not");
        dap_string_append_printf(l_str_out, "\n\nResults:\n\n");
        for (uint64_t i = 0; i < dap_list_length(l_voting->voting_params.option_offsets_list); i++){
            dap_string_append_printf(l_str_out, "%"DAP_UINT64_FORMAT_U")  ", i);
            dap_list_t* l_option = dap_list_nth(l_voting->voting_params.option_offsets_list, (uint64_t)i);
            dap_chain_net_vote_option_t* l_vote_option = (dap_chain_net_vote_option_t*)l_option->data;
            dap_string_append_len(l_str_out,
                                  (char*)((byte_t*)l_voting->voting_params.voting_tx + l_vote_option->vote_option_offset),
                                  l_vote_option->vote_option_length);
            float l_percentage = l_votes_count ? ((float)l_results[i].num_of_votes/l_votes_count)*100 : 0;
            uint256_t l_weight_percentage = {};

            DIV_256_COIN(l_results[i].weights, l_total_weight, &l_weight_percentage);
            MULT_256_COIN(l_weight_percentage, dap_chain_coins_to_balance("100.0"), &l_weight_percentage);
            char *l_weight_percentage_str = dap_uint256_decimal_to_round_char(l_weight_percentage, 2, true);
            char *l_w_coins, *l_w_datoshi = dap_uint256_to_char(l_results[i].weights, &l_w_coins);
            dap_string_append_printf(l_str_out, "\nVotes: %"DAP_UINT64_FORMAT_U" (%.2f%%)\nWeight: %s (%s) %s (%s%%)\n",
                                     l_results[i].num_of_votes, l_percentage, l_w_coins, l_w_datoshi, l_net->pub.native_ticker, l_weight_percentage_str);
        }
        DAP_DELETE(l_results);
        dap_string_append_printf(l_str_out, "\nTotal number of votes: %"DAP_UINT64_FORMAT_U, l_votes_count);
        char *l_tw_coins, *l_tw_datoshi = dap_uint256_to_char(l_total_weight, &l_tw_coins);
        dap_string_append_printf(l_str_out, "\nTotal weight: %s (%s) %s\n\n", l_tw_coins, l_tw_datoshi, l_net->pub.native_ticker);
        dap_cli_server_cmd_set_reply_text(a_str_reply, "%s", l_str_out->str);
        dap_string_free(l_str_out, true);
    }break;
    default:{

    }break;
    }

    return 0;
}

static int s_datum_tx_voting_coin_check_spent(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_prev_hash, int a_out_idx)
{
    int l_coin_is_spent = 0;


    dap_ledger_t *l_ledger = a_net->pub.ledger;
    if(!l_ledger){
        log_it(L_ERROR, "Can't find ledger");
        return -1;
    }

    dap_chain_datum_tx_t *l_voting_tx = dap_ledger_tx_find_by_hash(l_ledger, &a_voting_hash);
    const char *l_native_ticker = a_net->pub.native_ticker;

    dap_list_t *l_tx_list = NULL; // "stack" for saving txs on up level
    dap_chain_datum_tx_t *l_tx = dap_ledger_tx_find_by_hash(l_ledger, &a_tx_prev_hash);
    if (!l_tx){
        log_it(L_ERROR, "Can't find tx");
        return -1;
    }

    if (l_tx->header.ts_created < l_voting_tx->header.ts_created){
        return 0;
    }

    if (s_datum_tx_voting_coin_check_cond_out(a_net, a_voting_hash, a_tx_prev_hash, a_out_idx) != 0){
        return 1;
    }

    dap_chain_tx_vote_t *l_vote =(dap_chain_tx_vote_t *) dap_chain_datum_tx_item_get(l_tx, NULL, TX_ITEM_TYPE_VOTE, NULL);
    if(l_vote && dap_hash_fast_compare(&l_vote->voting_hash, &a_voting_hash)){
        dap_chain_net_votings_t *l_voting = NULL;
        pthread_rwlock_wrlock(&s_votings_rwlock);
        HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting);
        pthread_rwlock_unlock(&s_votings_rwlock);
        if (l_voting)
        {
            dap_list_t *l_temp = l_voting->votes;
            while (l_temp){
                dap_chain_net_vote_t *l_vote = (dap_chain_net_vote_t *)l_temp->data;
                if (dap_hash_fast_compare(&l_vote->vote_hash, &a_tx_prev_hash)){
                    l_coin_is_spent = 1;
                    return 1;
                }
                l_temp = l_temp->next;
            }
        }
    }


    dap_list_t *l_ins_list_temp = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
    dap_list_t *l_cond_ins_list = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN_COND, NULL);
    if (!l_ins_list_temp && !l_cond_ins_list){
        log_it(L_ERROR, "Can't get inputs from tx");
        return -1;
    }

    dap_list_t *l_ins_list = NULL;
    l_ins_list = dap_list_concat(l_ins_list, l_ins_list_temp);
    l_ins_list = dap_list_concat(l_ins_list, l_cond_ins_list);

    l_tx_list = dap_list_append(l_tx_list, l_ins_list);
    dap_list_t* l_tx_temp = dap_list_last(l_tx_list);

    while(l_tx_temp && !l_coin_is_spent){
        if (l_tx_temp->data == NULL){
            l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
            l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
            continue;
        }
        dap_list_t *l_ins_list = (dap_list_t*)l_tx_temp->data;
        dap_chain_tx_in_t* l_temp_in = (dap_chain_tx_in_t*)l_ins_list->data;
        dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(l_ledger, &l_temp_in->header.tx_prev_hash);
        int l_out_prev_idx = (int)l_temp_in->header.tx_out_prev_idx;

        const char* l_tx_token = NULL;
        dap_chain_tx_out_t *l_prev_out_union = (dap_chain_tx_out_t*)dap_chain_datum_tx_out_get_by_out_idx(l_tx_prev_temp, l_out_prev_idx);
        if (!l_prev_out_union){
            l_tx_temp->data = dap_list_remove(l_tx_temp->data, l_temp_in);
            if (l_tx_temp->data == NULL){
                l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
                l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
            }
            continue;
        }

        switch (l_prev_out_union->header.type) {
        case TX_ITEM_TYPE_OUT:{
            l_tx_token = dap_ledger_tx_get_token_ticker_by_hash(l_ledger, &l_temp_in->header.tx_prev_hash);
        }break;
        case TX_ITEM_TYPE_OUT_EXT:{
            dap_chain_tx_out_ext_t *l_temp_out = (dap_chain_tx_out_ext_t *)l_prev_out_union;
            l_tx_token = l_temp_out->token;
        }break;
        case TX_ITEM_TYPE_OUT_COND:{
            dap_chain_tx_out_cond_t *l_temp_out = (dap_chain_tx_out_cond_t*)l_prev_out_union;
            if (l_temp_out->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK ||
                s_datum_tx_voting_coin_check_cond_out(a_net, a_voting_hash, l_temp_in->header.tx_prev_hash, l_temp_in->header.tx_out_prev_idx) == 0)
                break;
        }
        default:
            l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in);
            if (l_tx_temp->data == NULL){
                l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
                l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
            }
            continue;
        }

        if (l_tx_prev_temp->header.ts_created < l_voting_tx->header.ts_created ||
            dap_strcmp(l_tx_token, l_native_ticker)){
            l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in);
            if (l_tx_temp->data == NULL){
                l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
                l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
            }
            continue;
        }


        dap_chain_tx_vote_t *l_vote =(dap_chain_tx_vote_t *) dap_chain_datum_tx_item_get(l_tx_prev_temp, NULL, TX_ITEM_TYPE_VOTE, NULL);
        if(l_vote && dap_hash_fast_compare(&l_vote->voting_hash, &a_voting_hash)){
            dap_chain_net_votings_t *l_voting = NULL;
            pthread_rwlock_wrlock(&s_votings_rwlock);
            HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting);
            pthread_rwlock_unlock(&s_votings_rwlock);
            dap_list_t *l_temp = NULL;
            while (l_temp){
                dap_chain_net_vote_t *l_vote = (dap_chain_net_vote_t *)l_temp->data;
                if (dap_hash_fast_compare(&l_vote->vote_hash, &l_temp_in->header.tx_prev_hash)){
                    l_coin_is_spent = 1;
                    break;
                }
                l_temp = l_temp->next;
            }
        }


        l_ins_list = dap_chain_datum_tx_items_get(l_tx_prev_temp, TX_ITEM_TYPE_IN, NULL);
        l_tx_list = dap_list_append(l_tx_list, l_ins_list);
        l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in);
        l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;

    }

    if(l_tx_list){
        l_tx_temp = l_tx_list;
        while(l_tx_temp){
            if (l_tx_temp->data)
                dap_list_free((dap_list_t*)l_tx_temp->data);
            l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
            l_tx_temp = dap_list_first(l_tx_list);
        }
    }

    return l_coin_is_spent;
}

static int s_datum_tx_voting_coin_check_cond_out(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_cond_hash, int a_cond_out_idx)
{

    dap_chain_net_votings_t * l_voting = NULL;
    pthread_rwlock_wrlock(&s_votings_rwlock);
    HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting);
    pthread_rwlock_unlock(&s_votings_rwlock);
    if(!l_voting || l_voting->net_id.uint64 != a_net->pub.id.uint64) {
        log_it(L_ERROR, "Can't find voting with hash %s in net %s",
            dap_chain_hash_fast_to_str_static(&a_voting_hash), a_net->pub.name);
        return -1;
    }

    dap_chain_net_voting_cond_outs_t *l_tx_outs = NULL;
    pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock);
    HASH_FIND(hh, l_voting->voting_spent_cond_outs, &a_tx_cond_hash, sizeof(dap_hash_fast_t), l_tx_outs);
    pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock);

    if (!l_tx_outs || l_tx_outs->out_idx != a_cond_out_idx){
        return 0;
    }

    return 1;
}