IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 区块链 -> 以太坊挖矿程序Ethminer -> 正文阅读

[区块链]以太坊挖矿程序Ethminer

概述:

区块链:本质是分布式账本,账本分布在全球各处,很难被篡改

以太坊Eth:Eth是区块链技术中的一种,是区块链中的二师兄(大哥是比特币)

区块链的金融属性:围绕不能被篡改有公信力的账本,建立线上交易系统。以太坊为例,可以在以太坊账本上进行交易的以太币已发行超过1.1亿个,每个币当前市价2.7w人民币,以太坊市值2.9万亿,这些币可以进行金融交易;交易需求刺激技术投入

区块链技术:包括了交易程序,账本程序,矿池程序,挖矿程序(账本加密打包);

矿池:矿池程序,工作原理类似包工头,接收记账任务,分发给很多矿工一起完成。矿池有其存在的理由,全网算力激增,个人矿工难以完成任务,所以有矿池的出现。

矿工:工作单纯,通常从矿池接收任务,经过大量固定流程的计算,上报结果。

Ethminer:就是以太坊开源的矿工程序,负责接收计算任务,计算结果上报,获得报酬。

综上所述,区块链技术包含了多个环节的技术。本文从主要从Ethminer的代码入手,对矿工程序进行分析。

Stratum以太坊通讯协议:

矿工从矿池接收和上报任务,都要依赖通讯协议,以太坊常用的是Stratum协议。

Stratum是在tcp协议之上封装,以Jason为格式的协议,可以选择是否用SSL进行加密。

Stratum协议主要格式:

请求:

{
  "id": 通讯ID
  "method": 方法名称,
  "params": [
    方法所带参数
  ]
}

回复:

{
  "id": ID,和请求ID对应
  "result": 回复的信息都放在result里
  "error": 请求是否出错
}

?方法:订阅

client请求:

{
    "id":1,
    "method":"eth_submitLogin",
    "worker":"xyz-test2", 
    "params":["0xcBFC5Cb9b315Cd82685c0Ba17A82db872Be66b01","x"],  
    "jsonrpc":"2.0"
}

?server回复:

{
    "id":1,
    "jsonrpc":"2.0",
    "result":true
}

worker: 是矿工的名字

params中的一串40位16进制数,是钱包地址

有些矿工的上报格式也有差异,有的worker字段为空,名字跟在钱包地址后,以点做分割如:

???0xcBFC5Cb9b315Cd82685c0Ba17A82db872Be66b01.?xyz-test2

注意,这里client请求和server的回复id都一致,client的id会递增,在client发起请求的过程都有这个规律

方法:请求任务

client发送请求:

{"id":2,"method":"eth_getWork","params":[],"jsonrpc":"2.0"}

发送用户名和密码

sever回复:

{"id":2,"jsonrpc":"2.0","result":{"status":"ok"}}

client订阅成功后,就会发起请求任务的消息

server只要简洁的回复ok后,就可以开始下放计算任务

方法:下发任务

{
    "id":0,
    "jsonrpc":"2.0",
    "result":[
    "0xe41d8d6a87364b010c92c9a4ef44965ee801faf64b90dc9b40f71ff74eea3e02",  #headerhash
    "0x0ae1dc880080eca07b1a976949eb91b4f67fa533a59767f24c2e1d332182a024",  #seedhash
    "0x00000000ffff00000000ffff00000000ffff00000000ffff00000000ffff0000",  #boundary
    "0xd1d351" #代表block编号
    ]
}

接到矿工请求任务的信息后,矿池就每隔1到2秒,下发一次计算任务,计算任务的ID都为0;

每个字段的含义如代码上注释;这部分信息就是计算所需输入;

headerhash就是每个区块的头部hash值,每个任务都不相同

seedhash这个值用于计算DAG图,每纪元(epoch大概100小时左右)更新一次,用相同的seedhash计算出来的DAG图都是相同的

boundary代表难度限制,boundary越小难度越高,计算结果要小于boundary

block编号,hashhead对应的区块编号,这个信息在计算的时候没有用处

方法:提交结果:

{
    "id":4,
    "method":"eth_submitWork",
    "params":["0x6b3c9a00673eeacc",    #nonce值         
    "0xe41d8d6a87364b010c92c9a4ef44965ee801faf64b90dc9b40f71ff74eea3e02" #headerhash,
    "0x2019a97ed06d889e93828c32fd065cf4c82de178462ae91ae9a93390e67730a4" #mix_hash满足条件的计算结果
     ],
    "jsonrpc":"2.0"
}

client提交的ID是逐一递增的

nonce值:这是通过DAG图中随机抽取的一段值计算得得出的

headerhash:和任务下发的headerhash对应,链上会通过这个值匹配到下发的任务

结果:nonce值是在DAG图中找到的一段数字;

mix_hash就是用nonce和hearderhash经过设定的计算流程后,得到的满足难度要求的结果

方法:server是否接受:

如果任务匹配并通过,Server回复:

{
    "id":4,
    "jsonrpc":"2.0",
    "result":true
}

如果Server不接受结果:

{
    "id": 123,
    "result": false,
    "error": [

      -1,
      "Job not found",
      NULL
    ]
  }

?Ethash计算原理:

首先我们关注Ethhash计算的输入参数,和计算结果;参考前面的stratum协议

输入参数:headerhash和seedhash

计算结果:nonce值和mix_hash

计算过程:

先根据输入seedhash创建dag图:

bool static ethash_compute_cache_nodes(
	node* const nodes,  //存放DAG图的缓存
	uint64_t cache_size, //DAG图缓存大小
	ethash_h256_t const* seed  //seedhash
)
{
    //先确认cache_size大小无误
	if (cache_size % sizeof(node) != 0) {
		return false;
	}

    //计算该cache_size包含多少个nodes
	uint32_t const num_nodes = (uint32_t) (cache_size / sizeof(node));

    //先把seedhash经过sha3_512存入nodes[0].bytes
	SHA3_512(nodes[0].bytes, (uint8_t*)seed, 32);

    //循环对nodes[n].bytes 做SHA3_512
	for (uint32_t i = 1; i != num_nodes; ++i) {
		SHA3_512(nodes[i].bytes, nodes[i - 1].bytes, 64);
	}

    
    //循环交叉计算hash
	for (uint32_t j = 0; j != ETHASH_CACHE_ROUNDS; j++) {
		for (uint32_t i = 0; i != num_nodes; i++) {
			uint32_t const idx = nodes[i].words[0] % num_nodes;
			node data;
			data = nodes[(num_nodes - 1 + i) % num_nodes];
			for (uint32_t w = 0; w != NODE_WORDS; ++w) {
				data.words[w] ^= nodes[idx].words[w];
			}
			SHA3_512(nodes[i].bytes, data.bytes, sizeof(data));
		}
	}

	//做大小端转换
	fix_endian_arr32(nodes->words, num_nodes * NODE_WORDS);
	return true;
}

DAG图创建好后,就可以进行mix hash计算,计算流程如下图:

从图中可以看出计算输入值是headerhash和nonce值,nonce值是随机取的,headerhash和nonce值算出一个索引到DAG图中取出索引对应的数据后反复计算hash,获得一个结果;

但是这个结果不一定是符合要求的,所以会对结果做一个比较:

bool EthashCUDAMiner::report(uint64_t _nonce)
{
	Nonce n = (Nonce)(u64)_nonce;
	Result r = EthashAux::eval(work().seedHash, work().headerHash, n);
    //r.value
	if (r.value < work().boundary) 
		return submitProof(Solution{ n, r.mixHash });
	return false;
}

如果符合难度要求就会向矿池上报计算结果,否则继续寻找符合难度要求的nonce值。

boundary越小,要找到符合的nonce值就需要经历越多的循环,难度就越高。

结合代码:

知道上述原理,我们在结合ethminer源码进行分析:

代码入口:

main

? ? ? ? ->cli.execute();

? ? ? ? ? ? ? ? ->doMiner();

doMiner中最主要的类是PoolManager

    void doMiner()
    {

        new PoolManager(m_PoolSettings);
        if (m_mode != OperationMode::Simulation)
            for (auto conn : m_PoolSettings.connections)
                cnote << "Configured pool " << conn->Host() + ":" + to_string(conn->Port());


        // Start PoolManager 包括启动和运行,PoolManager中把stratum协议处理和miner计算关联起来
        PoolManager::p().start();

        // 信息输出可以先忽略    
    m_cliDisplayTimer.expires_from_now(boost::posix_time::seconds(m_cliDisplayInterval));
        m_cliDisplayTimer.async_wait(m_io_strand.wrap(boost::bind(
            &MinerCLI::cliDisplayInterval_elapsed, this, boost::asio::placeholders::error)));

        // 正常该进程休眠,如果收到外部信号,执行退出
        unique_lock<mutex> clilock(m_climtx);
        while (g_running)
            g_shouldstop.wait(clilock);

        // 退出操作
        if (PoolManager::p().isRunning())
            PoolManager::p().stop();

        cnote << "Terminated!";
        return;
    }

PoolManager::start()中调用了rotateConnect

void PoolManager::start()
{
    m_running.store(true, std::memory_order_relaxed);
    m_async_pending.store(true, std::memory_order_relaxed);
    m_connectionSwitches.fetch_add(1, std::memory_order_relaxed);
    //这里调用了rotateConnect
    g_io_service.post(m_io_strand.wrap(boost::bind(&PoolManager::rotateConnect, this)));
}

通讯模块EthStratumClient:

rotateConnect中做了几件重要的事情:

void PoolManager::rotateConnect()
{
    //创建Stratum处理类
    p_client = std::unique_ptr<PoolClient>(
                new EthStratumClient(m_Settings.noWorkTimeout, m_Settings.noResponseTimeout));

    //setClientHandlers给p_client初始化一系列的回调函数,也就是stratum解析到哪些阶段就调用miner执行计算
    if (p_client)
            setClientHandlers();

    //连接服务器开始工作
    p_client->connect();
}

setClientHandlers 中设置了几个重要的回调函数

void PoolManager::setClientHandlers()
{
    // 跟服务器建立起连接时调用
    p_client->onConnected([&]() {
        // Farm start会创建启动各计算模块(CUDAMINER,CPUMINER)
        Farm::f().start();
    })
        
    // 跟服务断开连接时调用
    p_client->onDisconnected([&]() {
    }

    p_client->onWorkReceived([&](WorkPackage const& wp) {
        //stratum(p_client)通讯获取的任务信息交个计算模块
        Farm::f().setWork(m_currentWp);
    });

    p_client->onSolutionAccepted([&]() {
        //计算模块算出结果被服务器成功接受时,通知计算模块进行一些统计工作
        Farm::f().accountSolution(_minerIdx, SolutionAccountingEnum::Accepted);
    }}
}

初始化和connect过程完成后,stratum模块接收到的报文通过以下流程最终在processResponse里处理

connect_handler

? ? ? ? ->recvSocketData

? ? ? ? ? ? ? ? ->onRecvSocketDataCompleted

? ? ? ? ? ? ? ? ? ? ? ? ->processResponse

void EthStratumClient::onRecvSocketDataCompleted(
    const boost::system::error_code& ec, std::size_t bytes_transferred)
{
                    if (jRdr.parse(line, jMsg))
                    {
                        try
                        {
                            // 处理接收到的消息,如果收到新任务将m_newjobprocessed设置为true
                            processResponse(jMsg);
                        }
                        catch (const std::exception& _ex)
                        {
                            cwarn << "Stratum got invalid Json message : " << _ex.what();
                        }
                    }


        //如果有新任务,调用onWorkReceived处理,这个回调就是上文提到在setClientHandlers中设置的
        //到这里通讯和计算模块的工作就串起来了
        //上面processResponse中设置m_newjobprocessed为true,到这里就处理新任务
        if (m_newjobprocessed)
            if (m_onWorkReceived)
                m_onWorkReceived(m_current);
}

计算模块(各miner):?

我们前文讲过通讯模块和计算模块关联的函数在onWorkReceived,调用了Farm::f().setWork(m_currentWp);函数让计算模块工作

Farm::f().setWork的工作如下:

void Farm::setWork(WorkPackage const& _newWp)
{
    for (unsigned int i = 0; i < m_miners.size(); i++)
    {
        m_currentWp.startNonce = _startNonce + ((uint64_t)i << m_nonce_segment_with);
        // 调用Farm管理的各miner模块,setWork
        m_miners.at(i)->setWork(m_currentWp);
    }
}

Miner是一个抽象类,Miner::setWork就做了一件事,把work赋值给了成员变量:

void Miner::setWork(WorkPackage const& _work){
    ..............
    // 赋值给成员变量等待使用
    m_work = _work;
}

继承Miner的类,通过调用Miner::work(),取得m_work

WorkPackage Miner::work() const
{
    boost::mutex::scoped_lock l(x_work);
    return m_work;
}

有很多Miner的实现继承了miner类,如CUDAMiner CPUMiner等类;CUDAMiner是调用NvidiaGpu加速计算过程的类,是经典实现,我们以该类为例继续分析

CUDAMiner中最重要的就是workLoop,在反复计算:

void CUDAMiner::workLoop()
{
        while (!shouldStop())
        {
            if (current.epoch != w.epoch)
            {
                // 前文说过3000个块差不多100小时1个世纪,每次世纪更迭,要调用initEpoch
                // 在initEpoch中会最终调用到函数ethash_generate_dag 生成dag图
                if (!initEpoch())
                    break;  // This will simply exit the thread

                // As DAG generation takes a while we need to
                // ensure we're on latest job, not on the one
                // which triggered the epoch change
                current = w;
                continue;
            }

            // 寻找符合难度的nonce
            search(current.header.data(), upper64OfBoundary, current.startNonce, w);

        }
}

?看看search中做的事情:

void CUDAMiner::search(
    uint8_t const* header, uint64_t target, uint64_t start_nonce, const dev::eth::WorkPackage& w){
        while (!done)
        {
            volatile Search_results& buffer(*m_search_buf[current_index]);
            // 如果计算过程有找到符合的结果,m_search_buf会被填充赋值
            uint32_t found_count = std::min((unsigned)buffer.count, MAX_SEARCH_RESULTS);


            // run_ethash_search调用GPU进行计算
            if (!done)
                run_ethash_search(
                    m_settings.gridSize, m_settings.blockSize, stream, &buffer, start_nonce);

            if (found_count)
            {
                uint64_t nonce_base = start_nonce - m_streams_batch_size;
                for (uint32_t i = 0; i < found_count; i++)
                {
                    uint64_t nonce = nonce_base + gids[i];
                    
                    // 计算结果上报服务器,和我们在stratum协议中说讲的输出值对应,nonce,mixes结果,w中有header函数
                    Farm::f().submitProof(
                        Solution{nonce, mixes[i], w, std::chrono::steady_clock::now(), m_index});
                }
            }
            
        }
}

ethash_search中做的事情:

__global__ void ethash_search(volatile Search_results* g_output, uint64_t start_nonce)
{
    uint32_t const gid = blockIdx.x * blockDim.x + threadIdx.x;
    uint2 mix[4];
    // 用start_nonce没找到符合的结果,退出,更换nonce继续计算
    if (compute_hash(start_nonce + gid, mix)) 
        return;


    // 如果找到符合结果的值
    uint32_t index = atomicInc((uint32_t*)&g_output->count, 0xffffffff);
    if (index >= MAX_SEARCH_RESULTS)
        return;
    // 填充output buffer中的值,让外层函数可以获取结果
    g_output->result[index].gid = gid;
    g_output->result[index].mix[0] = mix[0].x;
    g_output->result[index].mix[1] = mix[0].y;
    g_output->result[index].mix[2] = mix[1].x;
    g_output->result[index].mix[3] = mix[1].y;
    g_output->result[index].mix[4] = mix[2].x;
    g_output->result[index].mix[5] = mix[2].y;
    g_output->result[index].mix[6] = mix[3].x;
    g_output->result[index].mix[7] = mix[3].y;
}

compute_hash 调用GPU计算,过程复杂,这里就提结果判断这点

DEV_INLINE bool compute_hash(uint64_t nonce, uint2* mix_hash)
{
    // 如果计算结果大于目标值,代表这次计算失败,返回true,进入下一轮计算   
    if (cuda_swab64(keccak_f1600_final(state)) > d_target)
        return true;

    // 如果计算结果小于目标值,代表这次计算结果有笑,填充到带出参数中,并返回
    mix_hash[0] = state[8];
    mix_hash[1] = state[9];
    mix_hash[2] = state[10];
    mix_hash[3] = state[11];

    return false;
}

到此代码的主要过程就分析完了,涉及的算法的部分是最难的部分,也是精髓;希望有兴趣的朋友可以钻研分享;

CUDA编程:

说道Ethminer,很难不提起GPU编程;详细的部分朋友们可以在网上找相关资料;我研究不是很深,简单说下自己的理解;

GPU设计就是用很多小硬件核心,完成并行计算;既然是并行计算,要满足

1、计算过程,可以把数据分拆成解耦的小块,每个核完成一小块计算,最后拼成结果;核排列是二维的,所以最后的数据处理很像线性代数中的矩阵处理

2、GPU计算很快,但是内存跟不上上GPU计算速度;所以要把数据提前放入显存;并且一组GPU核可以高速同时访问同一块内存;所以在编程上出现每个GPU挪动一小块数据到内存中,一组GPU同时完成一组数据的迁移,这样可以加快内存访问速度

基于以上两点考虑,就让GPU编程的复杂度提升太多,很多CPU上可以理解的过程到GPU上就一头懵

ethminer的GPU算法我也没完全看明白,希望看懂得高手分享。但是dag图生成的代码相对简单,对照CPU的dag图代码还是可以看出一些端倪

?请擅长CUDA编程的朋友多指导

  区块链 最新文章
盘点具备盈利潜力的几大加密板块,以及潜在
阅读笔记|让区块空间成为商品,打造Web3云
区块链1.0-比特币的数据结构
Team Finance被黑分析|黑客自建Token“瞒天
区块链≠绿色?波卡或成 Web3“生态环保”标
期货从入门到高深之手动交易系列D1课
以太坊基础---区块验证
进入以太坊合并的五个数字
经典同态加密算法Paillier解读 - 原理、实现
IPFS/Filecoin学习知识科普(四)
上一篇文章      下一篇文章      查看所有文章
加:2022-01-01 13:56:56  更:2022-01-01 13:58:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:25:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码