Skip to content
Snippets Groups Projects
dap_chain_mempool.c 41.25 KiB
/*
 * Authors:
 * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
 * Alexander Lysikov <alexander.lysikov@demlabs.net>
 * DeM Labs Inc.   https://demlabs.net
 * Kelvin Project https://github.com/kelvinblockchain
 * 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 <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <memory.h>

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

#include "dap_common.h"
#include "dap_hash.h"
#include "dap_http_client.h"
#include "dap_http_simple.h"
//#include "dap_enc_http.h"
#include "dap_enc_http.h"
//#include "dap_http.h"
#include "http_status_code.h"
#include "dap_chain_common.h"
#include "dap_chain_node.h"
#include "dap_chain_global_db.h"
#include "dap_enc.h"
#include <dap_enc_http.h>
#include <dap_enc_key.h>
#include <dap_enc_ks.h>
#include "dap_chain_mempool.h"

#include "dap_common.h"
#include "dap_list.h"
#include "dap_chain.h"
#include "dap_chain_net.h"
#include "dap_sign.h"
#include "dap_chain_datum_tx.h"
#include "dap_chain_datum_tx_items.h"

#define LOG_TAG "dap_chain_mempool"

typedef struct list_used_item {
    dap_chain_hash_fast_t tx_hash_fast;
    int num_idx_out;
    uint8_t padding[4];
    uint64_t value;
//dap_chain_tx_out_t *tx_out;
} list_used_item_t;


int dap_datum_mempool_init(void)
{
    return 0;
}

/**
 * @brief dap_chain_mempool_datum_add
 * @param a_datum
 * @return
 */
int dap_chain_mempool_datum_add(dap_chain_datum_t * a_datum)
{
    // TODO
    (void) a_datum;
    return -1;
}

/**
 * Make transfer transaction & insert to cache
 *
 * return 0 Ok, -2 not enough funds to transfer, -1 other Error
 */
int dap_chain_mempool_tx_create(dap_chain_t * a_chain, dap_enc_key_t *a_key_from,
        const dap_chain_addr_t* a_addr_from, const dap_chain_addr_t* a_addr_to,
        const dap_chain_addr_t* a_addr_fee,
        const char a_token_ticker[DAP_CHAIN_TICKER_SIZE_MAX],
        uint64_t a_value, uint64_t a_value_fee)
{
    // check valid param
    if(!a_chain | !a_key_from || ! a_addr_from || !a_key_from->priv_key_data || !a_key_from->priv_key_data_size ||
            !dap_chain_addr_check_sum(a_addr_from) || !dap_chain_addr_check_sum(a_addr_to) ||
            (a_addr_fee && !dap_chain_addr_check_sum(a_addr_fee)) || !a_value)
        return -1;

    // find the transactions from which to take away coins
    dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items
    uint64_t l_value_transfer = 0; // how many coins to transfer
    {
        dap_chain_hash_fast_t l_tx_cur_hash = { 0 };
        uint64_t l_value_need = a_value + a_value_fee;
        while(l_value_transfer < l_value_need)
        {
            // Get the transaction in the cache by the addr in out item
            dap_chain_datum_tx_t *l_tx = dap_chain_ledger_tx_find_by_addr(a_chain->ledger,a_token_ticker, a_addr_from,
                                                                                 &l_tx_cur_hash);
            if(!l_tx)
                break;
            // Get all item from transaction by type
            int l_item_count = 0;
            dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get( l_tx, TX_ITEM_TYPE_OUT,
                    &l_item_count);
            dap_list_t *l_list_tmp = l_list_out_items;
            int l_out_idx_tmp = 0; // current index of 'out' item
            while(l_list_tmp) {
                dap_chain_tx_out_t *out_item = l_list_tmp->data;
                // if 'out' item has addr = a_addr_from
                if(out_item && !memcmp(a_addr_from, &out_item->addr, sizeof(dap_chain_addr_t))) {

                    // Check whether used 'out' items
                    if(!dap_chain_ledger_tx_hash_is_used_out_item (a_chain->ledger, &l_tx_cur_hash, l_out_idx_tmp)) {

                        list_used_item_t *item = DAP_NEW(list_used_item_t);
                        memcpy(&item->tx_hash_fast, &l_tx_cur_hash, sizeof(dap_chain_hash_fast_t));
                        item->num_idx_out = l_out_idx_tmp;
                        item->value = out_item->header.value;
                        l_list_used_out = dap_list_append(l_list_used_out, item);
                        l_value_transfer += item->value;
                        // already accumulated the required value, finish the search for 'out' items
                        if(l_value_transfer >= l_value_need) {
                            break;
                        }
                    }
                }
                // go to the next 'out' item in l_tx transaction
                l_out_idx_tmp++;
                l_list_tmp = dap_list_next(l_list_tmp);
            }
            dap_list_free(l_list_out_items);
        }

        // nothing to tranfer (not enough funds)
        if(!l_list_used_out || l_value_transfer < l_value_need) {
            dap_list_free_full(l_list_used_out, free);
            return -2;
        }
    }

    // create empty transaction
    dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create();
    // add 'in' items
    {
        dap_list_t *l_list_tmp = l_list_used_out;
        uint64_t l_value_to_items = 0; // how many datoshi to transfer
        while(l_list_tmp) {
            list_used_item_t *item = l_list_tmp->data;
            if(dap_chain_datum_tx_add_in_item(&l_tx, &item->tx_hash_fast,(uint32_t) item->num_idx_out) == 1) {
                l_value_to_items += item->value;
            }
            l_list_tmp = dap_list_next(l_list_tmp);
        }
        assert(l_value_to_items == l_value_transfer);
        dap_list_free_full(l_list_used_out, free);
    }
    // add 'out' items
    {
        uint64_t l_value_pack = 0; // how much datoshi add to 'out' items
        if(dap_chain_datum_tx_add_out_item(&l_tx, a_addr_to, a_value) == 1) {
            l_value_pack += a_value;
            // transaction fee
            if(a_addr_fee) {
                if(dap_chain_datum_tx_add_out_item(&l_tx, a_addr_fee, a_value_fee) == 1)
                    l_value_pack += a_value_fee;
            }
        }
        // coin back
        uint64_t l_value_back = l_value_transfer - l_value_pack;
        if(l_value_back) {
            if(dap_chain_datum_tx_add_out_item(&l_tx, a_addr_from, l_value_back) != 1) {
                dap_chain_datum_tx_delete(l_tx);
                return -1;
            }
        }
    }

    // add 'sign' items
    if(dap_chain_datum_tx_add_sign_item(&l_tx, a_key_from) != 1) {
        dap_chain_datum_tx_delete(l_tx);
        return -1;
    }

    size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx);
    dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size);
    dap_chain_hash_fast_t l_key_hash;
    dap_hash_fast(l_tx, l_tx_size, &l_key_hash);
    DAP_DELETE(l_tx);

    char * l_key_str = dap_chain_hash_fast_to_str_new(&l_key_hash);
    char * l_gdb_group = dap_chain_net_get_gdb_group_mempool(a_chain);
    if(dap_chain_global_db_gr_set(dap_strdup(l_key_str), (uint8_t *) l_datum, dap_chain_datum_size(l_datum)
            ,l_gdb_group)) {
        log_it(L_NOTICE, "Transaction %s placed in mempool", l_key_str);
    }
    DAP_DELETE(l_gdb_group);
    DAP_DELETE(l_key_str);

    return 0;
}

/**
 * Make transfer transaction & insert to cache
 *
 * return 0 Ok, -2 not enough funds to transfer, -1 other Error
 */
int dap_chain_mempool_tx_create_massive( dap_chain_t * a_chain, dap_enc_key_t *a_key_from,
        const dap_chain_addr_t* a_addr_from, const dap_chain_addr_t* a_addr_to,
        const dap_chain_addr_t* a_addr_fee,
        const char a_token_ticker[10],
        uint64_t a_value, uint64_t a_value_fee,size_t a_tx_num)
{
    // check valid param
    if(!a_chain | !a_key_from || !a_addr_from || !a_key_from->priv_key_data || !a_key_from->priv_key_data_size ||
            !dap_chain_addr_check_sum(a_addr_from) || !dap_chain_addr_check_sum(a_addr_to) ||
            (a_addr_fee && !dap_chain_addr_check_sum(a_addr_fee)) || !a_value || !a_tx_num){
        log_it(L_ERROR, "Wrong parameters in dap_chain_mempool_tx_create_massive() call");
        return -1;

    }
    dap_global_db_obj_t * l_objs = DAP_NEW_Z_SIZE(dap_global_db_obj_t, (a_tx_num+1)*sizeof (dap_global_db_obj_t) );



    // Search unused out:
    uint64_t l_value_need =a_tx_num*( a_value + a_value_fee );
    dap_chain_hash_fast_t l_tx_prev_hash = { 0 };
    dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items
    uint64_t l_value_transfer = 0; // how many coins to transfer

    log_it(L_DEBUG,"Create %lu transactions, summary %Lf.7", a_tx_num,dap_chain_balance_to_coins(l_value_need) ) ;

    while(l_value_transfer < l_value_need){
        // Get the transaction in the cache by the addr in out item
        dap_chain_datum_tx_t *l_tx = dap_chain_ledger_tx_find_by_addr(a_chain->ledger, a_token_ticker,a_addr_from,
                &l_tx_prev_hash);
        if(!l_tx)
            break;
        // Get all item from transaction by type
        int l_item_count = 0;
        dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get( l_tx, TX_ITEM_TYPE_OUT,
                &l_item_count);
        dap_list_t *l_list_tmp = l_list_out_items;
        int l_out_idx_tmp = 0; // current index of 'out' item
        while(l_list_tmp) {
            dap_chain_tx_out_t *out_item = l_list_tmp->data;
            // if 'out' item has addr = a_addr_from
            if(out_item && out_item->header.value && !memcmp(a_addr_from, &out_item->addr, sizeof(dap_chain_addr_t))) {

                // Check whether used 'out' items
                if(!dap_chain_ledger_tx_hash_is_used_out_item (a_chain->ledger, &l_tx_prev_hash, l_out_idx_tmp)) {

                    list_used_item_t *l_it = DAP_NEW(list_used_item_t);
                    memcpy(&l_it->tx_hash_fast, &l_tx_prev_hash, sizeof(dap_chain_hash_fast_t));
                    l_it->num_idx_out = l_out_idx_tmp;
                    l_it->value = out_item->header.value;
                    log_it(L_DEBUG," Found output with value %llu", l_it->value );
                    l_list_used_out = dap_list_append(l_list_used_out, l_it);
                    l_value_transfer += l_it->value;
                    // already accumulated the required value, finish the search for 'out' items
                    if(l_value_transfer >= l_value_need) {
                        l_out_idx_tmp++;
                        break;
                    }
                }
            }
            // go to the next 'out' item in l_tx transaction
            l_out_idx_tmp++;
            l_list_tmp = dap_list_next(l_list_tmp);
        }
    }

    // nothing to tranfer (not enough funds)
    if(!l_list_used_out || l_value_transfer < l_value_need) {
        log_it(L_WARNING,"Not enough funds to transfer");
        dap_list_free_full(l_list_used_out, free);
        return -2;
    }

    for (size_t i=0; i< a_tx_num ; i++){
        log_it(L_DEBUG, "Prepare tx %u",i);
        // find the transactions from which to take away coins

        // create empty transaction
        dap_chain_datum_tx_t *l_tx_new = dap_chain_datum_tx_create();
        uint64_t l_value_back=0;
        // add 'in' items
        dap_list_t *l_list_tmp = l_list_used_out;
        uint64_t l_value_to_items = 0; // how many coins to transfer

        // Add in and remove out used items
        while(l_list_tmp) {
            list_used_item_t *item = l_list_tmp->data;
            char l_in_hash_str[70];

            dap_chain_hash_fast_to_str(&item->tx_hash_fast,l_in_hash_str,sizeof (l_in_hash_str) );

            if(dap_chain_datum_tx_add_in_item(&l_tx_new, &item->tx_hash_fast, (uint32_t) item->num_idx_out) == 1) {
                l_value_to_items += item->value;
                log_it(L_DEBUG,"Added input %s with %llu datoshi",l_in_hash_str, item->value);
            }else{
                log_it(L_WARNING,"Can't add input from %s with %llu datoshi",l_in_hash_str, item->value);
            }
            l_list_used_out = l_list_tmp->next;
            DAP_DELETE(l_list_tmp->data);
            dap_list_free1(l_list_tmp);
            l_list_tmp = l_list_used_out;
            if ( l_value_to_items >= l_value_transfer )
                break;
        }
        if ( l_value_to_items <  (a_value + a_value_fee) ){
            log_it(L_ERROR,"Not enought values on output %llu to produce enought ins %llu when need %llu",
                   l_value_to_items, l_value_transfer,
                   l_value_need);
            return -5;
        }

        // add 'out' items
        uint64_t l_value_pack = 0; // how much coin add to 'out' items
        if(dap_chain_datum_tx_add_out_item(&l_tx_new, a_addr_to, a_value) == 1) {
            l_value_pack += a_value;
            // transaction fee
            if(a_addr_fee) {
                if(dap_chain_datum_tx_add_out_item(&l_tx_new, a_addr_fee, a_value_fee) == 1)
                    l_value_pack += a_value_fee;
            }
        }
        // coin back
        l_value_back = l_value_transfer - l_value_pack;
        if(l_value_back) {
            //log_it(L_DEBUG,"Change back %llu", l_value_back);
            if(dap_chain_datum_tx_add_out_item(&l_tx_new, a_addr_from, l_value_back) != 1) {
                dap_chain_datum_tx_delete(l_tx_new);
                return -3;
            }
        }

        // add 'sign' items
        if(dap_chain_datum_tx_add_sign_item(&l_tx_new, a_key_from) != 1) {
            dap_chain_datum_tx_delete(l_tx_new);
            return -1;
        }
        // now tx is formed - calc size and hash
        size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx_new);

        dap_chain_hash_fast_t l_tx_new_hash;
        dap_hash_fast(l_tx_new, l_tx_size, &l_tx_new_hash);
        // If we have value back - update balance cache
        if(l_value_back) {
            //log_it(L_DEBUG,"We have value back %llu now lets see how many outputs we have", l_value_back);
            int l_item_count = 0;
            dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get( l_tx_new, TX_ITEM_TYPE_OUT,
                    &l_item_count);
            dap_list_t *l_list_tmp = l_list_out_items;
            int l_out_idx_tmp = 0; // current index of 'out' item
            //log_it(L_DEBUG,"We have %d outputs in new TX", l_item_count);
            while(l_list_tmp) {
                dap_chain_tx_out_t * l_out = l_list_tmp->data ;
                if( ! l_out){
                    log_it(L_WARNING, "Output is NULL, continue check outputs...");
                    l_out_idx_tmp++;
                    continue;
                }
                if ( memcmp(&l_out->addr, a_addr_from, sizeof (*a_addr_from))==0 ){
                    list_used_item_t *l_item_back = DAP_NEW(list_used_item_t);
                    memcpy(&l_item_back->tx_hash_fast, &l_tx_new_hash, sizeof(dap_chain_hash_fast_t));
                    l_item_back->num_idx_out = l_out_idx_tmp;
                    l_item_back->value = l_value_back;
                    l_list_used_out = dap_list_prepend(l_list_used_out, l_item_back);
                    log_it(L_DEBUG,"Found change back output, stored back in UTXO table");
                    break;
                 }
                l_list_tmp = l_list_tmp->next;
                l_out_idx_tmp++;
            }
            //log_it(L_DEBUG,"Checked all outputs");
            dap_list_free( l_list_out_items);
        }
        l_value_transfer -= l_value_pack;

        // Now produce datum
        dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx_new, l_tx_size);

        dap_chain_datum_tx_delete(l_tx_new);
        //dap_chain_ledger_tx_add( a_chain->ledger, l_tx);

        l_objs[i].key = dap_chain_hash_fast_to_str_new(&l_tx_new_hash);
        //continue;
        l_objs[i].value = (uint8_t*) l_datum;
        l_objs[i].value_len = l_tx_size + sizeof(l_datum->header);
        log_it(L_DEBUG, "Prepared obj with key %s (value_len = %llu)",
               l_objs[i].key? l_objs[i].key :"NULL" , l_objs[i].value_len );

    }
    dap_list_free_full(l_list_used_out, free);
    char * l_gdb_group = dap_chain_net_get_gdb_group_mempool(a_chain);

    //return 0;
    if( dap_chain_global_db_gr_save(l_objs,a_tx_num,l_gdb_group) ) {
        log_it(L_NOTICE, "%u transaction are placed in mempool", a_tx_num);
        //DAP_DELETE(l_objs);
        DAP_DELETE(l_gdb_group);
        return 0;
    }else{
        log_it(L_ERROR, "Can't place %u transactions  in mempool", a_tx_num);
        //DAP_DELETE(l_objs);
        DAP_DELETE(l_gdb_group);
        return -4;
    }


}

dap_chain_hash_fast_t* dap_chain_mempool_tx_create_cond_input(dap_chain_net_t * a_net,dap_chain_hash_fast_t *a_tx_prev_hash,

        const dap_chain_addr_t* a_addr_to, dap_enc_key_t *l_key_tx_sign, dap_chain_datum_tx_receipt_t * l_receipt, size_t l_receipt_size)
{
    dap_ledger_t * l_ledger = a_net ? dap_chain_ledger_by_net_name( a_net->pub.name ) : NULL;
    if ( ! a_net || ! l_ledger || ! a_addr_to )
        return NULL;
    if ( ! dap_chain_addr_check_sum (a_addr_to) ){
        log_it(L_ERROR, "Wrong address_to checksum");
        return NULL;
    }

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

    uint16_t pos=0;
    dap_chain_datum_tx_add_item(&l_tx, (byte_t*) l_receipt);
    pos++;
    // add 'in_cond' items
    // TODO - find first cond_item occurance, not just set it to 1
    if (dap_chain_datum_tx_add_in_cond_item(&l_tx,a_tx_prev_hash,1,pos-1) != 0 ){
        dap_chain_datum_tx_delete(l_tx);
        log_it( L_ERROR, "Cant add tx cond input");
        return NULL;
    }

    if(dap_chain_datum_tx_add_out_item(&l_tx, a_addr_to, l_receipt->receipt_info.value_datoshi) != 1) {
        dap_chain_datum_tx_delete(l_tx);
        log_it( L_ERROR, "Cant add tx output");
        return NULL;
    }

    // add 'sign' items
    if (l_key_tx_sign){
        if(dap_chain_datum_tx_add_sign_item(&l_tx, l_key_tx_sign) != 1) {
            dap_chain_datum_tx_delete(l_tx);
            log_it( L_ERROR, "Can't add sign output");
            return NULL;
        }
    }
    size_t l_tx_size = dap_chain_datum_tx_get_size( l_tx );
    dap_chain_datum_t *l_datum = dap_chain_datum_create( DAP_CHAIN_DATUM_TX, l_tx, l_tx_size );

    dap_chain_hash_fast_t *l_key_hash = DAP_NEW_Z( dap_chain_hash_fast_t );
    dap_hash_fast( l_tx, l_tx_size, l_key_hash );
    DAP_DELETE( l_tx );

    char * l_key_str = dap_chain_hash_fast_to_str_new( l_key_hash );
    char * l_gdb_group = dap_chain_net_get_gdb_group_mempool_by_chain_type( a_net ,CHAIN_TYPE_TX);
    if( dap_chain_global_db_gr_set( dap_strdup(l_key_str), (uint8_t *) l_datum, dap_chain_datum_size(l_datum)
                                   , l_gdb_group ) ) {
        log_it(L_NOTICE, "Transaction %s placed in mempool", l_key_str);
    }
    DAP_DELETE(l_gdb_group);
    DAP_DELETE(l_key_str);

    return l_key_hash;
}


/**
 * Make transfer transaction & insert to cache
 *
 * return 0 Ok, -2 not enough funds to transfer, -1 other Error
 */
dap_chain_hash_fast_t* dap_chain_mempool_tx_create_cond(dap_chain_net_t * a_net,
        dap_enc_key_t *a_key_from, dap_enc_key_t *a_key_cond,
        const dap_chain_addr_t* a_addr_from,
        const char a_token_ticker[DAP_CHAIN_TICKER_SIZE_MAX],
        uint64_t a_value,uint64_t a_value_per_unit_max, dap_chain_net_srv_price_unit_uid_t a_unit,
        dap_chain_net_srv_uid_t a_srv_uid, uint64_t a_value_fee, const void *a_cond, size_t a_cond_size)
{
    dap_ledger_t * l_ledger = a_net ? dap_chain_ledger_by_net_name( a_net->pub.name ) : NULL;
    // check valid param
    if(!a_net || ! l_ledger || !a_key_from || !a_key_from->priv_key_data || !a_key_from->priv_key_data_size ||
            !dap_chain_addr_check_sum(a_addr_from) ||
            !a_value)
        return NULL;

    // find the transactions from which to take away coins
    dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items
    uint64_t l_value_transfer = 0; // how many coins to transfer
    {
        dap_chain_hash_fast_t l_tx_cur_hash = { 0 };
        uint64_t l_value_need = a_value + a_value_fee;
        while(l_value_transfer < l_value_need)
        {
            // Get the transaction in the cache by the addr in out item
            dap_chain_datum_tx_t *l_tx = dap_chain_ledger_tx_find_by_addr(l_ledger, a_token_ticker,a_addr_from,
                    &l_tx_cur_hash);
            if(!l_tx)
                break;
            // Get all item from transaction by type
            int l_item_count = 0;
            dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get( l_tx, TX_ITEM_TYPE_OUT,
                    &l_item_count);
            dap_list_t *l_list_tmp = l_list_out_items;
            int l_out_idx_tmp = 0; // current index of 'out' item
            while(l_list_tmp) {
                dap_chain_tx_out_t *out_item = l_list_tmp->data;
                // if 'out' item has addr = a_addr_from
                if(out_item &&  !memcmp(a_addr_from, &out_item->addr, sizeof(dap_chain_addr_t))) {

                    // Check whether used 'out' items
                    if(!dap_chain_ledger_tx_hash_is_used_out_item(l_ledger, &l_tx_cur_hash, l_out_idx_tmp)) {

                        list_used_item_t *item = DAP_NEW(list_used_item_t);
                        memcpy(&item->tx_hash_fast, &l_tx_cur_hash, sizeof(dap_chain_hash_fast_t));
                        item->num_idx_out = l_out_idx_tmp;
                        item->value = out_item->header.value;
                        l_list_used_out = dap_list_append(l_list_used_out, item);
                        l_value_transfer += item->value;
                        // already accumulated the required value, finish the search for 'out' items
                        if(l_value_transfer >= l_value_need) {
                            break;
                        }
                    }
                }
                // go to the next 'out' item in l_tx transaction
                l_out_idx_tmp++;
                l_list_tmp = dap_list_next(l_list_tmp);
            }
            dap_list_free(l_list_out_items);
        }

        // nothing to tranfer (not enough funds)
        if(!l_list_used_out || l_value_transfer < l_value_need) {
            dap_list_free_full(l_list_used_out, free);
            log_it( L_ERROR, "nothing to tranfer (not enough funds)");
            return NULL;
        }
    }

    // create empty transaction
    dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create();
    // add 'in' items
    {
        dap_list_t *l_list_tmp = l_list_used_out;
        uint64_t l_value_to_items = 0; // how many coins to transfer
        while(l_list_tmp) {
            list_used_item_t *item = l_list_tmp->data;
            if(dap_chain_datum_tx_add_in_item(&l_tx, &item->tx_hash_fast,(uint32_t) item->num_idx_out) == 1) {
                l_value_to_items += item->value;
            }
            l_list_tmp = dap_list_next(l_list_tmp);
        }
        assert(l_value_to_items == l_value_transfer);
        dap_list_free_full(l_list_used_out, free);
    }
    // add 'out_cond' and 'out' items
    {
        uint64_t l_value_pack = 0; // how much coin add to 'out' items
        if(dap_chain_datum_tx_add_out_cond_item(&l_tx, a_key_cond, a_srv_uid, a_value, a_value_per_unit_max, a_unit, a_cond,
                a_cond_size) == 1) {
            l_value_pack += a_value;
            // transaction fee
            if(a_value_fee) {
                // TODO add condition with fee for mempool-as-service
            }
        }
        // coin back
        uint64_t l_value_back = l_value_transfer - l_value_pack;
        if(l_value_back) {
            if(dap_chain_datum_tx_add_out_item(&l_tx, a_addr_from, l_value_back) != 1) {
                dap_chain_datum_tx_delete(l_tx);
                log_it( L_ERROR, "Cant add coin back output");
                return NULL;
            }
        }
    }

    // add 'sign' items
    if(dap_chain_datum_tx_add_sign_item(&l_tx, a_key_from) != 1) {
        dap_chain_datum_tx_delete(l_tx);
        log_it( L_ERROR, "Can't add sign output");
        return NULL;
    }

    size_t l_tx_size = dap_chain_datum_tx_get_size( l_tx );
    dap_chain_datum_t *l_datum = dap_chain_datum_create( DAP_CHAIN_DATUM_TX, l_tx, l_tx_size );

    dap_chain_hash_fast_t *l_key_hash = DAP_NEW_Z( dap_chain_hash_fast_t );
    dap_hash_fast( l_tx, l_tx_size, l_key_hash );
    DAP_DELETE( l_tx );

    char * l_key_str = dap_chain_hash_fast_to_str_new( l_key_hash );
    char * l_gdb_group = dap_chain_net_get_gdb_group_mempool_by_chain_type( a_net ,CHAIN_TYPE_TX);
    if( dap_chain_global_db_gr_set( dap_strdup(l_key_str), (uint8_t *) l_datum, dap_chain_datum_size(l_datum)
                                   , l_gdb_group ) ) {
        log_it(L_NOTICE, "Transaction %s placed in mempool", l_key_str);
    }
    DAP_DELETE(l_gdb_group);
    DAP_DELETE(l_key_str);

    return l_key_hash;
}

/**
 * Make receipt transaction & insert to cache
 *
 * return 0 Ok, -2 not enough funds to transfer, -1 other Error
 */
int dap_chain_mempool_tx_create_receipt(uint64_t a_value)
//(dap_enc_key_t *a_key_from, dap_enc_key_t *a_key_cond,
//        const dap_chain_addr_t* a_addr_from, const dap_chain_addr_t* a_addr_cond,
//        const dap_chain_addr_t* a_addr_fee, const char a_token_ticker[DAP_CHAIN_TICKER_SIZE_MAX],
//        uint64_t a_value, uint64_t a_value_fee, const void *a_cond, size_t a_cond_size)
{
    // check valid param
/*    if(!a_key_from || !a_key_from->priv_key_data || !a_key_from->priv_key_data_size ||
            !dap_chain_addr_check_sum(a_addr_from) || !dap_chain_addr_check_sum(a_addr_cond) ||
            (a_addr_fee && !dap_chain_addr_check_sum(a_addr_fee)) || !a_value)
        return -1;*/
/*
    // find the transactions from which to take away coins
    dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items
    uint64_t l_value_transfer = 0; // how many coins to transfer
    {
        dap_chain_hash_fast_t l_tx_cur_hash = { 0 };
        uint64_t l_value_need = a_value + a_value_fee;
        while(l_value_transfer < l_value_need)
        {
            // Get the transaction in the cache by the addr in out item
            const dap_chain_datum_tx_t *l_tx = dap_chain_ledger_tx_find_by_addr(a_addr_from,
                    &l_tx_cur_hash);
            if(!l_tx)
                break;
            // Get all item from transaction by type
            int l_item_count = 0;
            dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) l_tx, TX_ITEM_TYPE_OUT,
                    &l_item_count);
            dap_list_t *l_list_tmp = l_list_out_items;
            int l_out_idx_tmp = 0; // current index of 'out' item
            while(l_list_tmp) {
                dap_chain_tx_out_t *out_item = l_list_tmp->data;
                // if 'out' item has addr = a_addr_from
                if(out_item && &out_item->addr && !memcmp(a_addr_from, &out_item->addr, sizeof(dap_chain_addr_t))) {

                    // Check whether used 'out' items
                    if(!dap_chain_ledger_tx_hash_is_used_out_item(&l_tx_cur_hash, l_out_idx_tmp)) {

                        list_used_item_t *item = DAP_NEW(list_used_item_t);
                        memcpy(&item->tx_hash_fast, &l_tx_cur_hash, sizeof(dap_chain_hash_fast_t));
                        item->num_idx_out = l_out_idx_tmp;
                        item->value = out_item->header.value;
                        l_list_used_out = dap_list_append(l_list_used_out, item);
                        l_value_transfer += item->value;
                        // already accumulated the required value, finish the search for 'out' items
                        if(l_value_transfer >= l_value_need) {
                            break;
                        }
                    }
                }
                // go to the next 'out' item in l_tx transaction
                l_out_idx_tmp++;
                l_list_tmp = dap_list_next(l_list_tmp);
            }
            dap_list_free(l_list_out_items);
        }

        // nothing to tranfer (not enough funds)
        if(!l_list_used_out || l_value_transfer < l_value_need) {
            dap_list_free_full(l_list_used_out, free);
            return -2;
        }
    }

    // create empty transaction
    dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create();
    // add 'in' items
    {
        dap_list_t *l_list_tmp = l_list_used_out;
        uint64_t l_value_to_items = 0; // how many coins to transfer
        while(l_list_tmp) {
            list_used_item_t *item = l_list_tmp->data;
            if(dap_chain_datum_tx_add_in_item(&l_tx, &item->tx_hash_fast, item->num_idx_out) == 1) {
                l_value_to_items += item->value;
            }
            l_list_tmp = dap_list_next(l_list_tmp);
        }
        assert(l_value_to_items == l_value_transfer);
        dap_list_free_full(l_list_used_out, free);
    }
    // add 'out_cond' and 'out' items
    {
        uint64_t l_value_pack = 0; // how much coin add to 'out' items
        if(dap_chain_datum_tx_add_out_cond_item(&l_tx, a_key_cond, (dap_chain_addr_t*) a_addr_cond, a_value, a_cond,
                a_cond_size) == 1) {
            l_value_pack += a_value;
            // transaction fee
            if(a_addr_fee) {
                if(dap_chain_datum_tx_add_out_item(&l_tx, a_addr_fee, a_value_fee) == 1)
                    l_value_pack += a_value_fee;
            }
        }
        // coin back
        uint64_t l_value_back = l_value_transfer - l_value_pack;
        if(l_value_back) {
            if(dap_chain_datum_tx_add_out_item(&l_tx, a_addr_from, l_value_back) != 1) {
                dap_chain_datum_tx_delete(l_tx);
                return -1;
            }
        }
    }

    // add 'sign' items
    if(dap_chain_datum_tx_add_sign_item(&l_tx, a_key_from) != 1) {
        dap_chain_datum_tx_delete(l_tx);
        return -1;
    }

    size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx);
    dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size);

    dap_chain_hash_fast_t l_key_hash;
    dap_hash_fast(l_tx, l_tx_size, &l_key_hash);
    DAP_DELETE(l_tx);

    char * l_key_str = dap_chain_hash_fast_to_str_new(&l_key_hash);
    if(dap_chain_global_db_gr_set(dap_strdup(l_key_str), (uint8_t *) l_datum, dap_chain_datum_size(l_datum)
            , c_dap_datum_mempool_gdb_group)) {
        log_it(L_NOTICE, "Transaction %s placed in mempool", l_key_str);
        // add transaction to ledger
        if(dap_chain_ledger_tx_add((dap_chain_datum_tx_t*) l_datum->data) < 0)
            log_it(L_ERROR, "Transaction %s not placed in LEDGER", l_key_str);
    }
    DAP_DELETE(l_key_str);*/

    return 0;
}

uint8_t* dap_datum_mempool_serialize(dap_datum_mempool_t *datum_mempool, size_t *size)
{
    size_t a_request_size = 2 * sizeof(uint16_t), shift_size = 0;
    for(int i = 0; i < datum_mempool->datum_count; i++) {
        a_request_size += dap_chain_datum_size(datum_mempool->data[i]) + sizeof(uint16_t);
    }
    uint8_t *a_request = DAP_NEW_SIZE(uint8_t, a_request_size);
    memcpy(a_request + shift_size, &(datum_mempool->version), sizeof(uint16_t));
    shift_size += sizeof(uint16_t);
    memcpy(a_request + shift_size, &(datum_mempool->datum_count), sizeof(uint16_t));
    shift_size += sizeof(uint16_t);
    for(int i = 0; i < datum_mempool->datum_count; i++) {
        size_t size_one = dap_chain_datum_size(datum_mempool->data[i]);
        memcpy(a_request + shift_size, &size_one, sizeof(uint16_t));
        shift_size += sizeof(uint16_t);
        memcpy(a_request + shift_size, datum_mempool->data[i], size_one);
        shift_size += size_one;
    }
    assert(shift_size == a_request_size);
    if(size)
        *size = a_request_size;
    return a_request;
}

dap_datum_mempool_t * dap_datum_mempool_deserialize(uint8_t *a_datum_mempool_ser, size_t a_datum_mempool_ser_size)
{
    size_t shift_size = 0;
    //uint8_t *a_datum_mempool_ser = DAP_NEW_Z_SIZE(uint8_t, datum_mempool_size / 2 + 1);
    //datum_mempool_size = hex2bin(a_datum_mempool_ser, datum_mempool_str_in, datum_mempool_size) / 2;
    dap_datum_mempool_t *datum_mempool = DAP_NEW_Z(dap_datum_mempool_t);
    memcpy(&(datum_mempool->version), a_datum_mempool_ser + shift_size, sizeof(uint16_t));
    shift_size += sizeof(uint16_t);
    memcpy(&(datum_mempool->datum_count), a_datum_mempool_ser + shift_size, sizeof(uint16_t));
    shift_size += sizeof(uint16_t);
    datum_mempool->data = DAP_NEW_Z_SIZE(dap_chain_datum_t*, datum_mempool->datum_count * sizeof(dap_chain_datum_t*));
    for(int i = 0; i < datum_mempool->datum_count; i++) {
        size_t size_one = 0;
        memcpy(&size_one, a_datum_mempool_ser + shift_size, sizeof(uint16_t));
        shift_size += sizeof(uint16_t);
        datum_mempool->data[i] = (dap_chain_datum_t*) DAP_NEW_Z_SIZE(uint8_t, size_one);
        memcpy(datum_mempool->data[i], a_datum_mempool_ser + shift_size, size_one);
        shift_size += size_one;
        datum_mempool->data[i];
    }
    assert(shift_size == a_datum_mempool_ser_size);
    DAP_DELETE(a_datum_mempool_ser);
    return datum_mempool;
}

void dap_datum_mempool_clean(dap_datum_mempool_t *datum)
{
    if(!datum)
        return;
    for(int i = 0; i < datum->datum_count; i++) {
        DAP_DELETE(datum->data[i]);
    }
    DAP_DELETE(datum->data);
    datum->data = NULL;
}

void dap_datum_mempool_free(dap_datum_mempool_t *datum)
{
    dap_datum_mempool_clean(datum);
    DAP_DELETE(datum);
}

/**
 *
 */
static char* calc_datum_hash(const char *datum_str, size_t datum_size)
{
    dap_chain_hash_fast_t a_hash;
    dap_hash_fast( datum_str, datum_size, &a_hash);
    size_t a_str_max = (sizeof(a_hash.raw) + 1) * 2 + 2; /* heading 0x */
    char *a_str = DAP_NEW_Z_SIZE(char, a_str_max);

//    size_t hash_len = dap_chain_hash_fast_to_str(&a_hash, a_str, a_str_max);
    dap_chain_hash_fast_to_str(&a_hash, a_str, a_str_max);

//    if(!hash_len) {
//        DAP_DELETE(a_str);
//        return NULL;
//    }

    return a_str;
}

static void enc_http_reply_encode_new(struct dap_http_simple *a_http_simple, dap_enc_key_t * key,
        enc_http_delegate_t * a_http_delegate)
{
    //dap_enc_key_t * key = dap_enc_ks_find_http(a_http_simple->http);
    if(key == NULL) {
        log_it(L_ERROR, "Can't find http key.");
        return;
    }
    if(a_http_delegate->response) {

        if(a_http_simple->reply)
            free(a_http_simple->reply);

        size_t l_reply_size_max = dap_enc_code_out_size(a_http_delegate->key,
                a_http_delegate->response_size,
                DAP_ENC_DATA_TYPE_RAW);

        a_http_simple->reply = DAP_NEW_SIZE(void, l_reply_size_max);
        a_http_simple->reply_size = dap_enc_code(a_http_delegate->key,
                a_http_delegate->response, a_http_delegate->response_size,
                a_http_simple->reply, l_reply_size_max,
                DAP_ENC_DATA_TYPE_RAW);

        /*/ decode test
         size_t l_response_dec_size_max = a_http_simple->reply_size ? a_http_simple->reply_size * 2 + 16 : 0;
         char * l_response_dec = a_http_simple->reply_size ? DAP_NEW_Z_SIZE(char, l_response_dec_size_max) : NULL;
         size_t l_response_dec_size = 0;
         if(a_http_simple->reply_size)
         l_response_dec_size = dap_enc_decode(a_http_delegate->key,
         a_http_simple->reply, a_http_simple->reply_size,
         l_response_dec, l_response_dec_size_max,
         DAP_ENC_DATA_TYPE_RAW);
         l_response_dec_size_max = 0;*/
    }

}

/**
 * @brief
 * @param cl_st HTTP server instance
 * @param arg for return code
 */
void chain_mempool_proc(struct dap_http_simple *cl_st, void * arg)
{
    http_status_code_t * return_code = (http_status_code_t*) arg;
    // save key while it alive, i.e. still exist
    dap_enc_key_t *key = dap_enc_ks_find_http(cl_st->http);
    //dap_enc_key_serealize_t *key_ser = dap_enc_key_serealize(key_tmp);
    //dap_enc_key_t *key = dap_enc_key_deserealize(key_ser, sizeof(dap_enc_key_serealize_t));

    // read header
    dap_http_header_t *hdr_session_close_id =
            (cl_st->http) ? dap_http_header_find(cl_st->http->in_headers, "SessionCloseAfterRequest") : NULL;
    dap_http_header_t *hdr_key_id =
            (hdr_session_close_id && cl_st->http) ? dap_http_header_find(cl_st->http->in_headers, "KeyID") : NULL;

    enc_http_delegate_t *dg = enc_http_request_decode(cl_st);
    if(dg) {
        char *suburl = dg->url_path;
        char *request_str = dg->request_str;
        int request_size = (int) dg->request_size;
        //printf("!!***!!! chain_mempool_proc arg=%d suburl=%s str=%s len=%d\n", arg, suburl, request_str, request_size);
        if(request_str && request_size > 1) {
            //  find what to do
            uint8_t action = DAP_DATUM_MEMPOOL_NONE; //*(uint8_t*) request_str;
            if(dg->url_path_size > 0) {
                if(!strcmp(suburl, "add"))
                    action = DAP_DATUM_MEMPOOL_ADD;
                else if(!strcmp(suburl, "check"))
                    action = DAP_DATUM_MEMPOOL_CHECK;
                else if(!strcmp(suburl, "del"))
                    action = DAP_DATUM_MEMPOOL_DEL;
            }
            dap_datum_mempool_t *datum_mempool =
                    (action != DAP_DATUM_MEMPOOL_NONE) ?
                            dap_datum_mempool_deserialize((uint8_t*) request_str, (size_t) request_size) : NULL;
            if(datum_mempool)
            {
                dap_datum_mempool_free(datum_mempool);
                char *a_key = calc_datum_hash(request_str, (size_t) request_size);
                char *a_value;
                switch (action)
                {
                case DAP_DATUM_MEMPOOL_ADD: // add datum in base
                    //a_value = DAP_NEW_Z_SIZE(char, request_size * 2);
                    //bin2hex((char*) a_value, (const unsigned char*) request_str, request_size);
                    if(dap_chain_global_db_gr_set(dap_strdup(a_key), request_str,(size_t) request_size,
                            dap_config_get_item_str_default(g_config, "mempool", "gdb_group", "datum-pool"))) {
                        *return_code = Http_Status_OK;
                    }
                    log_it(L_INFO, "Insert hash: key=%s result:%s", a_key,
                            (*return_code == Http_Status_OK) ? "OK" : "False!");
                    DAP_DELETE(a_key);
                    break;

                case DAP_DATUM_MEMPOOL_CHECK: // check datum in base

                    strcpy(cl_st->reply_mime, "text/text");
                    char *str = dap_chain_global_db_gr_get( dap_strdup(a_key) , NULL,
                            dap_config_get_item_str_default(g_config, "mempool", "gdb_group", "datum-pool"));
                    if(str) {
                        dg->response = strdup("1");
                        DAP_DELETE(str);
                        log_it(L_INFO, "Check hash: key=%s result: Present", a_key);
                    }
                    else
                    {
                        dg->response = strdup("0");
                        log_it(L_INFO, "Check hash: key=%s result: Absent", a_key);
                    }
                    dg->response_size = strlen(dg->response);
                    *return_code = Http_Status_OK;
                    enc_http_reply_encode_new(cl_st, key, dg);
                    break;

                case DAP_DATUM_MEMPOOL_DEL: // delete datum in base
                    strcpy(cl_st->reply_mime, "text/text");
                    if(dap_chain_global_db_gr_del( dap_strdup(a_key),
                            dap_config_get_item_str_default(g_config, "mempool", "gdb_group", "datum-pool"))) {
                        dg->response = strdup("1");

                        log_it(L_INFO, "Delete hash: key=%s result: Ok", a_key);
                    }
                    else
                    {
                        dg->response = strdup("0");
                        log_it(L_INFO, "Delete hash: key=%s result: False!", a_key);
                    }
                    *return_code = Http_Status_OK;
                    enc_http_reply_encode_new(cl_st, key, dg);
                    break;

                default: // unsupported command
                    log_it(L_INFO, "Unknown request=%s! key=%s", (suburl) ? suburl : "-", a_key);
                    DAP_DELETE(a_key);
                    enc_http_delegate_delete(dg);
                    if(key)
                        dap_enc_key_delete(key);
                    return;
                }
                DAP_DELETE(a_key);
            }
            else
                *return_code = Http_Status_BadRequest;
        }
        else
            *return_code = Http_Status_BadRequest;
        enc_http_delegate_delete(dg);
    }
    else {
        *return_code = Http_Status_Unauthorized;
    }
    if(hdr_session_close_id && hdr_session_close_id->value && !strcmp(hdr_session_close_id->value, "yes")) {
        // close session
        if(hdr_key_id && hdr_key_id->value) {
            dap_enc_ks_delete(hdr_key_id->value);
        }
    }
}

/**
 * @brief chain_mempool_add_proc
 * @param sh HTTP server instance
 * @param url URL string
 */
void dap_chain_mempool_add_proc(dap_http_t * a_http_server, const char * a_url)
{
    dap_http_simple_proc_add(a_http_server, a_url, 4096, chain_mempool_proc);
}