Skip to content

BTCPOOL的常见名词及数据库结构详解

YihaoPeng edited this page Aug 23, 2019 · 1 revision

BTCPOOL的常见名词及数据库结构详解

名词解释

Target(目标)

是一个256bit(32字节,64个hex字符)的大整数,具有特定数量的前导0,用于控制挖矿的困难程度。PoW(工作量证明)挖矿时,矿机计算出来的区块头hash需要小于等于目标(即具有比目标更多的前导0)才能向矿池提交。

比如,Target为0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF表示区块头hash(的hex表示)至少要有8个前导0才能向矿池提交。

Bits

是Target的压缩形式,为比特币等区块链采用。以下Python代码展示了如何通过Bits求Target,用其逆运算就可以通过Target求Bits:

bits=0x187fffff
exp=bits >> 24
base = bits & 0x00ffff
target = base * 2**(8*(exp-3))
print '%064x' % target

也可以参考比特币的Wiki:https://en.bitcoin.it/wiki/Difficulty

Difficulty(难度)

是Target的另一种表达方式。在比特币挖矿中,我们把Target0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF的难度定义为1,则另一个Target对应的难度就等于0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF与该target的比。

例如,0x000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF的难度为

0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF / 0x000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF = 65536

Share(Share Difficulty,份额难度)

份额(份额难度),是实际完成的挖矿工作量。比如,在挖矿难度为65536时完成了一次符合Target要求的提交,则得到了一份难度为65536的Share,之后这些Share会用于收益分配。此外,Share也可以用于估算矿机的算力。

在BTCPool中,我们会简单的把一段时间内所有Share的难度累加在一起然后写入数据库。所以数据库中出现的所有share_xxx字段都是指这段时间内矿机提交的Share的难度之和。

需要注意的是,Share的难度是在矿机开始挖之前就已经由矿池指定好了的,Share的难度就是矿机所挖的这个Share的任务的难度,与Share的实际区块hash具有多少前导0没有关系。如果矿池下发任务时难度为65536,则矿机挖到的任何Share的难度都是65536,无论这些Share是具有12个前导0还是20个前导0。

Hashrate(hash速率,算力)

hash速率是矿机单位时间内可以进行的hash运算的次数。矿池没有办法直接得到这一速度,只能通过矿机提交Share的情况进行估测。以下是通过share求hash速率的公式:

hash速率 (hash/s) = 一段时间内的share难度之和 * 达到share难度1所需的hash次数 / 这段时间的秒数

“完成share难度1所需的hash次数”(以下简称“难度1 hash次数”)对于特定算法的币种来说是确定值,参考以下表格:

区块链 算法 难度1的Bits 难度1 hash次数
BTC/BCH/UBTC/SBTC SHA256d 0x1d00ffff 2 ^ 32
ETH Ethash 0x2100ffff 1
DCR Blake256 0x1d00ffff 2 ^ 32
LTC Scrypt 0x1f00ffff 2 ^ 16
Grin Cuckaroo29 42
Cuckatoo31+ 42
ZCash EquiHash (n=144, k=5) 0x1f07ffff 2 ^ 13
Beam EquiHash (n=150, k=5) 1

所以对于BTC来说,如果你在15分钟内提交了难度总和为9830400的share,则你的理论算力为:

9830400 × 232 ÷ (15 × 60) = 46912496118442 (Hash/s) ≈ 46.9 TH/s

accept(接受)、reject(拒绝)、stale(过时)

矿机不一定总是给出正确的结果。有时候矿机因为芯片损坏、程序错误、或者网络连接问题,导致提交的Share不符合矿池的要求,这样的share被称为“拒绝share”。而如果提交符合矿池要求,则称为“接受Share”。只有“接受Share”会有收益,“拒绝share”不计算收益。

拒绝share和接受share一样都有一个难度,即在挖矿开始前由矿池专门指定的任务提交难度。BTCPool会把一段时间内的“接受Share”难度累加起来,记录在数据库的“share_accept”字段,再把一段时间内的“拒绝share”难度累加起来,记录在数据库的“share_reject”字段。

此外,新版的BTCPool还有“share_stale”字段,它是指矿机提交的过时share的难度之和。“过时”是指矿机提交Share时挖矿任务已经过期。“过时Share”也是“拒绝Share”,旧版累加在“share_reject”字段中,新版由于BTC.com自己的统计需要而单列出来了。

预期爆块数(score)、幸运值

根据大数定律,当爆块难度为N,矿池中的所有矿机提交的Share难度之和也达到N时,爆块的概率为1。所以,一段时间内的Share难度之和与这段时间内的平均挖矿难度的比值,就是矿池在这段时间内应该挖到的区块数量,或者称为“预期爆块数”。BTCPool把这一数值记录在数据库的“score”字段。

但是,因为挖矿的基本运算(通常为hash运算)是非确定性的,所以运气差的时候即使预期爆块数达到1也未必会爆块,但运气好的话预期爆块数没有达到1也可以爆块。我们把一段时间内的真实爆块数和预期爆块数的比称为幸运值。比如,矿池某天的预期爆块数为5个,但是爆了6个块,那么幸运值就为:

6 ÷ 5 × 100% = 120%

通常来说,算力越小的矿池,幸运值越不稳定。但是长期幸运值(几个月或数年)应该要稳定在100%,否则矿池可能出了什么问题(比如给多个矿机的挖矿任务或者Nonce范围重复等)。

PPS、PPS收益

PPS的全称是Pay per Share(逐份额付费),是一种矿池收益分配模式。在这种分配模式下,如果挖矿难度是N,矿机提交了一个难度为X的Share并且被接受,那么用户就可以立即获得一笔金额为以下数值的收益:

X ÷ N × 当前爆块奖励

PPS的收益发放和矿池爆块没有关系,在矿池幸运值不佳时,可能一个块也没有爆,但是只要用户在挖矿,就必须给用户发放收益。因此采用PPS模式的矿池运营风险较大,但用户的收入稳定且可预期。

而另一种收益发放模式PPLNS(Pay per Last N Share)采用在爆块时为最近提交的N个Share付款的模式计算收益。这种模式的好处是矿池不承担幸运值不佳的风险,坏处是用户的收益无法预期。如果矿池不爆块,则用户没有挖矿收益。

BTCPool是一个PPS矿池,目前的程序仅支持PPS计费模式,无法实现PPLNS。BTCPool存入数据库的“earn”字段始终是用户的PPS收益。并且,该收益只考虑基础块奖励(对比特币来说是12.5BTC),不考虑交易手续费,并且也没有扣除矿池手续费。矿池在打款前应该自行按比例调整金额(比如抽走3%的手续费,或者补贴1%的交易手续费给用户等)。

数据库结构

TODO: 加入对share_stale、reject_detail等字段的描述。

数据库 bpool_local_stats_db

存储按时间段分割的矿池算力统计数据

表 stats_pool_hour

存储矿池级别的小时算力/PPS收益等信息

字段 名称 备注
hour 时间(小时) 是个十进制整数,比如2019080811,表示2019年8月8日11点。注意BTCPool始终使用UTC时间
share_accept 接受份额 用于计算矿池每小时算力(参考上面对“Hashrate”的描述)
share_reject 拒绝份额 与接受份额相对应,只是放在这里。
reject_rate 拒绝率 这个数据的单位是1,例如值是0.001462,则拒绝率为0.00146 * 100% = 0.146%
score 预期爆块数 用来计算幸运值
earn 收益 矿池应当发给所有用户的PPS收益之和。收益的单位是各个区块链的最小可分割金额,比如比特币是聪,以太坊是wei。
created_at 创建时间
updated_at 更新时间

表 stats_users_day

存储用户级别的天级算力/PPS收益等信息。注意BTCPool的统计周期是UTC时间0点到24点(北京时间8点到次日8点)。

与上表重复的字段不再赘述。

字段 名称 备注
puid 用户id 用来区别不同的用户。BTCPool要求外界提供一个用来获取puid和用户名对应关系的web api,此后BTCPool内部就使用puid(数字)来表示特定的用户,而不再存储用户的字符串形式用户名。
day 日期 是个十进制整数,比如20190808,表示2019年8月8日。注意BTCPool始终使用UTC时间
earn 收益 矿池该日应当发给该用户的PPS收益。收益的单位是各个区块链的最小可分割金额,比如比特币是聪,以太坊是wei。

表 stats_workers_hour

字段 名称 备注
puid 用户id 用来区别不同的用户。BTCPool要求外界提供一个用来获取puid和用户名对应关系的web api,此后BTCPool内部就使用puid(数字)来表示特定的用户,而不再存储用户的字符串形式用户名。
worker_id 矿机id 用来区别不同的矿机。worker_id是矿机名的hash值。不直接存储矿机名的原因是考虑向数据库插入数值比插入字符串要快。注意不同的用户可能具有相同的矿机id。比如a.11x12b.11x12的矿机名相同(均为11x12),矿机id也会相同。但是它们的puid不同。

其他 stats_*_* 表

请参考上面的描述

数据库 bpool_local_db

存储实时算力统计信息和矿池爆块信息

表 found_blocks

存储矿池爆块信息。注意不同币种的这个表结构是不同的。这里以BTC为例。

字段 名称 备注
id 自增id 主键
puid 用户id 用来记录是哪个用户爆了块。
worker_id 矿机id 用来记录是哪个矿机爆了块。
worker_full_name 矿工全名 用于显示是谁爆了块。如果没有这个字段,需要查询其他表才能知道爆块的用户名和矿机名。
job_id 任务id 爆块的挖矿任务的id,如果爆块有问题,可以查看任务以确定是什么问题。
height 高度 挖到的区块的高度。可以在区块浏览器查看这个高度的区块是不是我们爆的,用来确认爆块是否成功。
is_orphaned 是否为孤块 BTCPool不更新这个数据,但是矿池运营者可以自行编写一个脚本,查询区块浏览器来确定爆块是否成功,然后更新这个字段。
hash 挖到的区块的hash 区块hash是区块的唯一标识符,用于在区块浏览器查询。如果你在公有区块链爆了一个块,但是在区块浏览器查不到你的hash,并且对应高度的区块是另一个hash,表明这个区块没有被区块链网络接受,不在主链上,也就是成为了“孤块”(孤立的区块)。
rewards 挖到的区块所包含的奖励 对于比特币,这个值由比特币节点给出(放置在getblocktemplate的coinbasevalue字段)。因为BTCPool是一个PPS矿池,用户收益计算与矿池爆块没有关系,所以这个字段只是用于统计矿池收到多少币,不用于计算用户收益。
size 挖到的区块的大小 可以用来统计你挖到的区块的平均大小,用来确认你的节点网络状况是否良好。如果你挖到的区块都很小,说明你的内存池交易不足,可能是你的Peer数较少,所以传播到你这里的交易较少,或者是你的节点配置有问题。通常区块越大交易手续费越多,矿池可以有更多利润。
prev_hash 挖到的这个区块的父区块hash 如果你挖到了一个孤块,你可以查看一下它的父区块hash是否在主链上。如果它的父区块hash也不在主链上,说明你的矿池配置很可能有问题(比如节点分叉了)。
bits 如上面的名词定义 bits是target的压缩形式。这里用来记录这个区块要求达到的挖矿难度,也就是“全网难度”。
version 挖到的比特币区块的版本 版本是比特币区块头中的一个字段,现在被用于AsicBoost。记录版本可以用于统计AsicBoost的使用情况。
created_at 创建时间 挖到区块的时间

表 found_nmc(uno)_blocks

存储联合挖矿币种NMC/UNO的爆块记录

字段 名称 备注
id 自增id 主键
bitcoin_block_hash 联合挖矿的父区块hash 用于在出问题时调试
aux_block_hash 联合挖矿的任务hash 用于在出问题时进行调试
aux_pow 联合挖矿的工作量证明数据 用于在出问题时进行调试
is_orphaned 是否为孤块 BTCPool不更新这个数据,但是矿池运营者可以自行编写脚本进行更新。
created_at 创建时间 挖到区块的时间

表 mining_workers

字段 名称 备注
worker_id 矿工id
puid 用户id
group_id 分组id 这是BTC.com的特有功能,其他矿池可以不考虑该字段。其更新规则为:默认值为-puid(负puid),若为0,表明矿机被隐藏。被隐藏的矿机在重新开始挖矿时,group_id会被重置为-puid。
worker_name 矿工名
accept_1m 最近1分钟内接受的share的难度之和 用于计算瞬时算力。已被新版BTCPool废弃。
accept_5m 最近1分钟内接受的share的难度之和 用于计算瞬时算力。
accept_15m 最近15分钟内接受的share的难度之和 用于计算15分钟平均算力。
accept_1h 最近1小时内接受的share的难度之和 用于计算1小时平均算力。
reject_15m 最近15分钟内拒绝的share的难度之和 用于计算15分钟拒绝率。
reject_1h 最近1小时内拒绝的share的难度之和 用于计算1小时拒绝率。
accept_count 接受的share的个数(不是难度) 用于显示给用户看,表示矿机在正常提交。
last_share_ip 最后提交share的ip地址 如果只是连接矿池没有提交share,不会被记录。
last_share_time 最后提交share的时间 如果只是连接矿池没有提交share,时间不会更新。
miner_agent 挖矿程序的名称字符串 可以用来判断矿机厂商和版本(比如蚂蚁矿机是“bmminer/xxx”)。
created_at 创建时间
updated_at 更新时间

常见问题

  1. 算力计算方式(矿池、矿工、全网)

对于矿池和矿工:

hash速率 (hash/s) = 一段时间内的share难度之和 * 难度1 hash次数 / 这段时间的秒数

对于全网:

因为:全网算力 × 平均爆块时间 = 平均爆块难度 × 难度1 hash次数
所以:全网算力 = 平均爆块难度 × 难度1 hash次数 ÷ 平均爆块时间
  1. 拒接率计算方式
share_reject ÷ (share_reject + share_accept) × 100%

对于新版BTCPool:

不含stale的拒绝率 = share_reject ÷ (share_reject + share_stale + share_accept) × 100%
stale率 = share_stale ÷ (share_reject + share_stale + share_accept) × 100%
  1. 矿池内矿工依据什么判断是哪个账号的矿机

我们使用矿机名前缀做为用户名。比如填写的矿机名为hu60.123,表明这是用户“hu60”的矿机,矿机名为123

  1. 矿工收益的基础时间单位是什么,earn的值是小时的收益还是分钟

是这段统计周期内的PPS总收益。比如puid=1的用户在挖BTC,stats_users_day表里面他在day=20190823这一天的earn123456789,表明他这天的收益是123456789聪。

其他时间段同理,stats_users_hourearn值是用户在一小时内的总收益。

基本上来说,矿池只需要根据stats_users_day里面的earn值发款就行,不需要关心其他表的earn值。