crypto-mining-monero

本期精读的文章是: coinhive 官方文档Monero 官方文档

懒得看文章?没关系。

咦,怎么是官方文档?

本期精读有所不同,注重实操,先操作获取感性认识,然后再介绍相关的概念,由浅入深,力求不纠缠细节,但不遮盖环节。阅读本文需要对加密货币有一些基本常识 (如果你是一个开发者但是完全没了解过加密货币,可以参考这里)。希望同学们看完本文能对加密货币领域有一个更深更切实的感受。如果要了解更多细节,文末总结的延伸阅读链接列表是最好的开始。

要注意一点,文中很多说明是默认基于 XMR 和 BTC 的,他们两个又同源,机制非常相似。所以很多命题判断并不适用于所有的成千上万的加密货币. 正相反,新的币种层出不穷,几乎所有的惯例都被打破,所有可能性都被尝试。这一点以下不再做说明。

1 引言

首先,干货。10 行代码,5 分钟,不需部署不需构建直接浏览器开挖。

  • 本地创建文件 test.html,粘贴如下代码:
    <!doctype html>
    <html>
    <head>
        <title>Mining</title>
    </head>
    <body>
        <div class="coinhive-miner" 
            style="width: 256px; height: 310px"
            data-key="MUtCJzIDhrs01ERrf3qlqdawo35N0CYD">
            <em>Loading...</em>
        </div>
        <script src="https://authedmine.com/lib/simple-ui.min.js" async></script>
    </body>
    </html>
    
  • 本地双击打开。等待 JS 加载,点击 widget “Start Mining”。开始挖矿! 如下图

    mining

    不错,数字已经在跳动,风扇开始工作,永无尽头的挖矿已经开始了。那么重要的是,挖出来的加密货币在哪呢?原来上面的代码里用的还是我的 API key,所以还没挖到你自己那里,所以请继续下面的步骤:

  • 在 coinhive 注册账号并登陆。它是做什么的?别急,后面会详细讲。在 coinhive/settings 找到自己的 API keypair,把 public key 复制出来,形如 MUtCJzIDhrs01ERrf3qlqdawo35N0CYD

  • 替换上面代码中的 data-key 部分,重新开始挖矿。好了,现在挖出的 Monero (这是啥?详见下一节) 已经会到你自己的 coinhive 账户中。用下图来说明,你名下总计算过的 Hash 个数为 264K,当前难度换算为 0.00002 个 Monero(Symbol:XMR)。当前难度为 66G 一个 block,一个 block reward 5.87 XMR,得到一个 XMR 是 11.268G。264K/11.268G = 0.0000234。这就是你目前的收益。

    coinhive dashbaord

  • 查 bitfinex 可得现在 XMR 价格在 375 美元 (当你看到本文的时候,价格可能早就又波动到不知哪里去了),所以你 (以及你忠实勤劳的电脑) 获得的实际收益为 0.000234 * 375 USD = 0.008775 USD,快到一分钱了 :)

怎么样,有没有一种浏览器点开即玩一刀 999 级的感觉。以上操作的便捷直接,是建立在无数前人大量的开发和基础设施建设之上的。越是领域早期的工作,越步履维艰,收货也越丰厚。如今加密货币已经走到了一个成年期,逐渐稳定成熟起来。

接下来我们聚焦到上面过程的每个环节,了解下拼图的每一块是怎样被构成全图的。

2 聚焦

让我们从最终端最接近用户的环节开始,逐一聚焦,最后走完整条链路。

2.1 从浏览器说起

本文标题叫浏览器挖矿,也是和贴合前端的部分。那么为什么可以在浏览器里挖矿?为什么可以很多用户在多个终端浏览器同时为同一个人 (你) 挖矿?

我们知道,挖矿是对加密货币产生机制的俗称。主流大多采取 Proof of Work (PoW) 机制。最常见的 PoW 方式就是由网络中所有节点作为矿工,每个节点都基于 blockchain 前面 block 已有信息计算一个新信息。这个新信息的计算方式往往是某种 hash function,并且人为地被设置为需要巨大计算力和时间才能完成 (其具体难度一般也会实时调整)。当一个节点幸运地 (也依靠强大的算力) 第一个计算出结果后,会把这个结果广播到网络中。其他所有节点会验证这个结果 (我们知道非对称加密算法,验证便宜而计算昂贵),一旦证实就会停下手里的计算,承认这个计算结果。新的计算结果创造出新的块,区块链的高度增加一层,然后计算继续下去。每一个块的生成一般在 2-10 分钟。这个过程就被叫做挖矿.

既然是通用计算,既然是算一个 hash 值,那么民用级 CPU 和 GPU,浏览器或任何沙盒,虚拟机,移动设备当然就都可以。在我们的例子中,计算过程被做成分布式,每个用户可以各自计算,结果按 chunk 发回 master 汇总。这样就实现了终端用户 - 浏览器 - 共同贡献计算资源 - 换为 XMR 的过程。

这就是对整个链路的一个描述。从中我们会生出一些疑问,比如:

给我看看具体算什么 hash?为什么要算 XMR 而不是比特币或者其他?既然第一个算出的赢家通吃所有,为什么我的收益却是线性的?这种描述来看岂不是算力最大的一方永远都能算出结果而其他人颗粒无收吗?

要看具体算法,没有问题。bitcoin 在这里,XMR 则看CryptoNote Standard 008

读完两个算法我们就有了以上疑问的答案:

2.1.1 为什么要算 XMR 而不是比特币或者其他

XMR 不是唯一选择,但是 BTC 是一个不可选的选择。因为 double SHA-265 在专业级 GPU 上会比 CPU 上快 10^4 倍 (更多信息)。这样一百万用户合力浏览器挖矿还不如一架子双路 Titan,就失去了分布到终端用户的意义。而 CryptoNight 在 GPU 上只比同价值 CPU 快 2 倍。另外 CryptoNight 算法也被设计为不适用ASIC

怎么实现的?CryptoNight 算法开宗明义地写明,运算主体是 Memory-Hard Loop,而不是 Computation-Hard Loop。每个循环都要在内存中检索。实际运行 CryptoNight 时,CPU 都会用最快,最接近 ALU 也是容量最小的 L3 Cache。换到 GPU,显存虽然很大,却没有 L3 Cache 一样的极致读写速度优化,而且由于内存读写成了瓶颈,GPU 中的大量 ALU 也没了用武之地。下图简略地描述了 CryptoNight 循环体的结构:

CryptoNight

2.1.2 算力最大的一方永远都能算出结果

看了比特币具体算法,你应该明白了 hash 是靠不停改变 nonce 来生成的。随机取一个值,算了不对,再随机取另一个 nonce 值..。既然是随机取,就不会存在赢家恒赢.

2.1.3 为什么我的收益却是线性的

这是一个隐蔽但是却非常重要的问题。答案是,本来确实是赢家通吃。如果你的算力足够大,挖矿时间足够长之后总会轮到你,但是收益会有大幅波动.

就是因为如此,矿工们逐渐建立了矿池组织。大家把算力都投入到一起,合力算,然后不管这次实际是谁算出来,都按照贡献的算力比例分配收益。矿池是一个加密货币建立之初,完全推崇去中心化时没有预料到的结构,也产生了深远的影响。现在来自中国矿池的算力早已超过网络 50%,他们会在挖出的块中打上矿池标记,而这些矿池在加密货币的分叉,路线图中也扮演举足轻重的角色.

所以你的算力并不是直接投入 XMR 网络中,而是投入一个矿池。在我们的例子里矿池就是 coinhive,只不过是一个比较特殊的矿池,特殊在矿池成员都运作在浏览器中。这就是为什么你会得到线性收益而不是 all or none.

自古以来各行业都会自发产生行业工会,建立类似保险和行业守则 / 规范这些人人为我我为人人的机制。在 crypto 行业也不例外。这是意料之外而情理之中.

2.2 在浏览器里发生了什么,或 coinhive 干了什么

好,我们搞清了一些基本的 Monero 挖矿机制,下面来看看 coinhive。已经知道 coinhive 帮我们接入它的矿池,让再小的算力也能按比例得到产出。但是还有什么呢?最关键的一点,coinhive 是怎样把一个完整的 mining 过程拆分成小块,让一个或许并不强大的设备上的浏览器,也能快速接收 task,快速完成并且即时上传的呢?

废话不多说,打开源码。本项目没有开源,构建完成后的在https://coinhive.com/lib/coinhive.min.js。先做初步 format 处理,发现有些工作完成在后端,worker shard 一侧。以下用松散的伪码总结一下 client side 的main success scenario流程 (注意很多地方简化了):

  - start
  - loadWorkerResource
  - load worker-asmjs.min.js
  - CRYPTONIGHT_WORKER_BLOB = createObjectURL(Blob(response_of_worker-asmjs.min.js))
  - _startNow -> _connectAfterSelfTest
  - selfTest -> verify(testJob),
    testJob = {
      verify_id: "1",
      nonce: "204f150c",
      result: "6a9c7dea83b079ce0e012907dd6929bcb0aeec3c1f06c032ca7c3386432bca00",
      blob: "0606c6d8cfd005cad45b0306350a730b0354d52f1b6d671063824287ce4a82c971d109d56d1f1b00000000ee2d1d4fd7c18bdc1b24abb902ac8ecc3d201ffb5904de9e476a7bbb0f9ec1ab04"
    };
    verify = if (!this._isReady) {
        this.verifyJob = job
    } else {
        this.worker.postMessage(job)
    }
    // 实例化若干个JobThread,每个对应一个worker,worker实际执行asmjs.min.js
  - _connect // verify成功,终于建立连接。根据public key固定hash到一个shard池然后随机选一个shard,建立websocket
  - websocket.onmessage: if (type==job) work()
  - work:
    do { hash(input,output) } while !(meetTarget(output));
    websocket.postMessage({nonce,output}) // hash done successfully。submit

看一下这个过程,结合cns003 XMR blockchain specs。XMR 的整体 hash input 很小,是:

  - size of [block_header,Merkle root hash,and the number of 
    transactions] in bytes (varint)
  - block_header,
  - Merkle root hash,
  - number of transactions (varint).

这样用 websocket 发过来毫无问题。之后就是完全独立的计算,调整 nonce 来算不同的 hash 结果。target 就是当前难度的一个指示.

这样整条链路就比较清晰了。再思考一下以下问题:

2.2.1 为什么 XMR 适宜分布式客户端计算

因为能够利用每个用户的 CPU 和其中的高速 L3 Cache。这是中心化执行难以具备的条件.

任何时候当考虑要不要把某项操作推到客户端进行时,都要想明白可以利用客户端的哪个资源,这个资源在客户端是否有明显优势,是否比后端中心化执行更有利。很多时候答案是,优势并不明显。那么引入的网络通信成本,法规成本,额外开销可能就并不值得.

2.3 最后的步骤

到了这里,最后剩余步骤就很标准模式化了。coinhive 作为矿场,代管着用户生产的加密货币。用户发请求提出 XMR,就需要提到一个自己的钱包地址保管,比如 MyMonero。也可以直接提到 Exchange 交易所,在其中交易成其他币种,包括法币,然后电汇等等形式提现.

如果对钱包或者交易所感兴趣 (这两个也是很大的话题,比如钱包分硬件钱包和软件钱包,离线冷钱包和线上热钱包,private key 和 recover seeds。交易所有多种多样的交易对,有杠杆,期货,空和多,多种挂单类型等等),可以看这里这里.

3 更多讨论

讨论地址是:精读《全链路体验浏览器挖矿》 · Issue #55 · dt-fe/weekly

如果你想参与讨论,请点击这里,每周都有新的主题,每周五发布。

4 延伸阅读

http://piotrpasich.com/introduction-bitcoin-for-developers

https://en.wikipedia.org/wiki/Monero_(cryptocurrency)

http://www.righto.com/2014/02/bitcoin-mining-hard-way-algorithms.html

https://cryptonote.org/cns/cns001.txt cns001-008 是 Specs 集合

https://en.bitcoin.it/wiki/Why_a_GPU_mines_faster_than_a_CPU

https://en.wikipedia.org/wiki/Application-specific_integrated_circuit

https://github.com/txbits/txbits

Written on December 1, 2017