Skip to content
Snippets Groups Projects
dap_chain_mine.c 10.23 KiB
/*
 * Authors:
 * Dmitriy A. Gearasimov <kahovski@gmail.com>
 * DeM Labs Inc.   https://demlabs.net
 * DeM Labs Open source community https://github.com/demlabsinc
 * Copyright  (c) 2017-2018
 * All rights reserved.

 This file is part of DAP (Deus Applications Prototypes) the open source project

    DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DAP is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#define _GNU_SOURCE
#endif

#include <sys/time.h>
#include <unistd.h>
#include <sched.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#include <inttypes.h>
#include <errno.h>

#include "dap_common.h"
#include "dap_chain_block.h"
#include <pthread.h>
#include "dap_chain_mine.h"
#include "dap_chain_mine_task.h"

#define LOG_TAG "dap_chain_mine"

int get_cpu_count()
{
    long nprocs = -1;
    long nprocs_max = -1;
#ifdef _WIN32
  #ifndef _SC_NPROCESSORS_ONLN
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    #define sysconf(a) info.dwNumberOfProcessors
    #define _SC_NPROCESSORS_ONLN
  #endif
#endif
#ifdef _SC_NPROCESSORS_ONLN
    nprocs = sysconf(_SC_NPROCESSORS_ONLN);
    if (nprocs < 1) {
      log_it(L_ERROR, "Could not determine number of CPUs online: %s ", strerror (errno));
      return -1;
    }
    nprocs_max = sysconf(_SC_NPROCESSORS_CONF);
    if (nprocs_max < 1){
      log_it(L_ERROR, "Could not determine number of CPUs configured: %s",strerror (errno));
      return -2;
    }
    log_it(L_INFO, "%ld of %ld processors online",nprocs, nprocs_max);
    return nprocs;
  #else
    log_it(L_ERROR, "Could not determine number of CPUs");
    return -3;
  #endif
}

/**
 * @brief s_mine_thread
 * @param a_arg
 * @return
 */
static void * s_mine_thread(void * a_arg)
{
    dap_chain_mine_task_t * l_task =  (dap_chain_mine_task_t *) a_arg;
    dap_chain_hash_t l_hash;
    dap_chain_hash_kind_t l_hash_kind = HASH_USELESS;
    uint64_t l_difficulty = l_task->block->header.difficulty;
    uint_fast64_t l_nonce;
    uint_fast64_t l_hash_count = 0;
    log_it(L_INFO, "Th#%u:  started",l_task->id);

    // Set CPU affininty and nice level
#ifndef NO_POSIX_SHED
    uint32_t l_cpu_count = get_cpu_count();
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET( l_task->id % l_cpu_count , &mask);

    if ( pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &mask) != 0 ){
        log_it(L_CRITICAL, "Error pthread_setaffinity_np() You really have %d or more core in CPU?", l_task->id % l_cpu_count );
        abort();
    }else
        log_it(L_DEBUG, "set affinity to CPU %u", l_task->id % l_cpu_count );

#else
#warning "No SHED affinity, mining could be uneffective"
#endif
    // Set thread priority
    struct sched_param l_prio_param;
    int l_prio_policy=SCHED_RR;
    l_prio_param.__sched_priority= 99;
    pthread_t self_id= pthread_self();
    if( pthread_setschedparam(self_id,l_prio_policy,&l_prio_param)== 0 ){
        log_it(L_DEBUG, "Set priority Round-Robin 99 lvl");
    }else
        log_it(L_WARNING, "Can't set priority to Round-Robin 99 lvl");

    for( l_nonce = l_task->nonce_from ; l_nonce < l_task->nonce_to; ++l_nonce ){
        //log_it(L_DEBUG, "Th#%u: nonce = %llu hash_count = %llu", l_task->id,  l_task->block->header.nonce,
        //       l_hash_count);
        l_task->block->header.nonce = l_nonce;
        dap_chain_block_hash_calc(l_task->block,&l_hash);
        l_hash_count++;

        char l_hash_str[140];
        dap_chain_hash_to_str(&l_hash,l_hash_str,sizeof (l_hash_str) );
        //log_it(L_DEBUG, "Th#%u: block hash %s ",l_task->id, l_hash_str);

        // Update task structure every 10 hashes to prevent often context switch
        if (l_hash_count % 10 == 0){ // TODO Make automatic growing value, depending from hash rate
            atomic_uint_fast64_t l_hash_count_atomic = ATOMIC_VAR_INIT(l_hash_count);
            atomic_exchange(&l_task->hash_count, l_hash_count_atomic);

            if( atomic_load(& l_task->tasks->is_mined) ){
                log_it(L_INFO, "Th#%u: Stop the process", l_task->id);
                break;
            }
        }

        l_hash_kind = dap_chain_hash_kind_check(&l_hash,l_difficulty );
        if (l_task->gold_only){
            if (  l_hash_kind == HASH_GOLD ){
                char l_hash_str[140];
                dap_chain_hash_to_str(&l_hash,l_hash_str,sizeof (l_hash_str) );
                log_it(L_INFO, "Th#%u: !!! Mined GOLD token !!! block hash %s ",l_task->id, l_hash_str);
                break;
            }
        }else if (  l_hash_kind != HASH_USELESS ){
            char l_hash_str[140];
            dap_chain_hash_to_str(&l_hash,l_hash_str,sizeof (l_hash_str) );
            log_it(L_INFO, "Th#%u: !!! Mined SILVER token !!! block hash %s", l_task->id, l_hash_str);
            break;
        }
    }

    if ( l_hash_kind != HASH_USELESS ){
        log_it(L_INFO, "Th#%u: !!! Mined nonce = %" PRIuFAST64 " on try %" PRIuFAST64,l_nonce, l_hash_count );
        atomic_bool l_is_mined_atomic = ATOMIC_VAR_INIT(true);
        atomic_uint_fast64_t l_mined_nonce_atomic = ATOMIC_VAR_INIT(l_nonce);

        memcpy(&l_task->tasks->mined_hash,&l_hash, sizeof(l_task->tasks->mined_hash) );

        atomic_exchange(&l_task->tasks->is_mined, l_is_mined_atomic );
        atomic_exchange(&l_task->tasks->mined_nonce ,l_mined_nonce_atomic);
    }else
        log_it(L_DEBUG, "Th#%u: Mined nothing");

    DAP_DELETE(l_task->block);
    return NULL;
}

static void * s_stats_thread(void * a_arg)
{
    dap_chain_mine_tasks_t * l_tasks =  (dap_chain_mine_tasks_t *) a_arg;

    struct dap_chain_mine_task_result * l_result = DAP_NEW_Z(struct dap_chain_mine_task_result);
    struct timespec l_tm_start;
    struct timespec l_tm_end ;
    clock_gettime(CLOCK_MONOTONIC_RAW,&l_tm_start);
    uint64_t l_tm_diff;
    double l_tm_diff_secs;

    while(true){
        uint_fast64_t l_hash_count_total = 0;
        uint64_t i;
        log_it(L_DEBUG, "Statistic:");

        for( i = 0; i<l_tasks->tasks_count ; ++i){
            //log_it(L_DEBUG, "Thread #%u:  hash_count = %llu", i, atomic_load(&l_tasks->task[i].hash_count));
            l_hash_count_total += atomic_load(& l_tasks->task[i].hash_count );
        }

        clock_gettime(CLOCK_MONOTONIC_RAW,&l_tm_end);
        l_tm_diff =  (l_tm_end.tv_sec - l_tm_start.tv_sec) * 1000000 +  (l_tm_end.tv_nsec - l_tm_start.tv_nsec)/ 1000;
        l_tm_diff_secs = ( (double) l_tm_diff)/ 1000000.0;

        log_it(L_INFO, "Mining time: %04.03lf seconds, %llu hashes, %.03lf H/s ", l_tm_diff_secs
               , l_hash_count_total ,
                ( (double) l_hash_count_total) /  ((double) l_tm_diff_secs ) );
        if(atomic_load(&l_tasks->is_mined) ){
            l_result->success = true;
            l_result->nonce = atomic_load(&l_tasks->mined_nonce);
            memcpy(&l_result->mined_hash, &l_tasks->mined_hash, sizeof(l_tasks->mined_hash) );
            l_result->mined_time = l_tm_diff;
            l_result->hashrate_middle = ( (double) l_hash_count_total) /  ((double) l_tm_diff_secs ) ;
            break;
        }
        sleep(2);
    }
    return l_result;
}


/**
 * @brief dap_chain_mine_block
 * @param a_block_cache
 * @return
 */
int dap_chain_mine_block(dap_chain_block_cache_t * a_block_cache, bool a_mine_gold_only, uint32_t a_threads)
{
    pthread_t stats_pid;
    struct dap_chain_mine_task_result * l_result = NULL;
    dap_chain_mine_tasks_t * l_tasks = DAP_NEW_Z ( dap_chain_mine_tasks_t);

    uint32_t i;

    if( a_threads == 0 ){
        int rval=  get_cpu_count();
        if(rval<0 )
            return -4;
        else
            a_threads = rval;
    }

    l_tasks->task = DAP_NEW_Z_SIZE( struct dap_chain_mine_task, (sizeof(struct dap_chain_mine_task)*a_threads+16) );
    l_tasks->tasks_count = a_threads;
    l_tasks->is_mined = ATOMIC_VAR_INIT(false);
    l_tasks->mined_nonce = ATOMIC_VAR_INIT(0);
    l_tasks->block_cache = a_block_cache;
    uint64_t l_nonce_task_length = UINT64_MAX / a_threads;
    for(i = 0; i< a_threads; i++){ // Creates mining threads
        dap_chain_mine_task_t *l_task = &l_tasks->task[i];
        l_task->tasks = l_tasks;
        l_task->id = i;
        l_task->gold_only = a_mine_gold_only;
        l_task->hash_count = ATOMIC_VAR_INIT(0);
        // Each thread has its own copy of the block for mining
        l_task->block = DAP_NEW_Z_SIZE(dap_chain_block_t,a_block_cache->block->header.size);
        memcpy(l_task->block, a_block_cache->block,a_block_cache->block->header.size );
        // spread nonce between threads
        l_task->nonce_from = i *l_nonce_task_length;
        l_task->nonce_to =  (i==a_threads-1)? UINT64_MAX: (i+1)*l_nonce_task_length;
        pthread_create(& l_task->task_pid ,NULL,s_mine_thread, l_task);
    }
    // Create statistic collector thread
    pthread_create(&stats_pid,NULL,s_stats_thread, l_tasks);

    // Join to it, waiting for results
    pthread_join(stats_pid,(void**) &l_result);
    log_it(L_DEBUG,"Finishing mining threads, free memory...");
    for(i = 0; i< a_threads; i++){ // Creates mining threads
        pthread_join(l_tasks->task[i].task_pid,NULL);
    }

    DAP_DELETE(l_tasks->task);
    //}
    DAP_DELETE (l_tasks);
    if(l_result){
        if(l_result->success){
            log_it(L_INFO,"Mined nonce = 0x%016x Hashrate  %.03lf H/s",l_result->nonce, l_result->hashrate_middle);
            a_block_cache->block->header.nonce = l_result->nonce;
            a_block_cache->block_mine_time = l_result->mined_time;
            memcpy(&a_block_cache->block_hash, &l_result->mined_hash,sizeof(l_result->mined_hash));
            DAP_DELETE(l_result);
            return 0;
        }else{
            log_it(L_INFO,"Minded nothing");
            DAP_DELETE(l_result);
            return 1;
        }
    }else{
        log_it(L_ERROR,"No result! Its NULL!");
        return -2;
    }


}