最近一段时间区块链挺火的, 不管是涨是跌, 牵动人心.
每当涨的时候, 总有人后悔为啥没有早买, 每当跌时, 也能听见 "背后有没有机构做空" 疑问.
当看到有人在买卖中受打击而顿挫时, 当看到有媒体在别人赔了很多还要去嘲讽时, 我就想是不是应该做点什么, 这世界本就不完美, 为什么要禁止大家向往富有的心呢.
区块链的网络是 P2P 的, 交易来自四面八方, 很难知道交易的双方在什么地方, 哪个方位. 我打算从监测流量入手, 来探知这张网每天的异动.
没有思路, 在区块链上研究了 2 个月, 仍然找不到方法去看看到底哪些人在背后 "影响" 着市场.
直到 2 天前的从化之行, 泡着温泉, 看到几个泉眼里冒出来的热浪, 感觉发现了一些规律.
大局着手, 我们来分析一下这张网的基本数据
谁最有钱
中本聪, 李笑来... 个个都是炒币高手, 甚至身边的 90 后也声称炒币赚了 100 万, 那么到底谁最有钱, 如果不能知道是谁, 是不是可以通过技术手段算得一个排行榜.
哪些人在交易
交易数据满天飞, 几乎每一秒钟都有很多交易, 我想在里边寻找一些规律, 看看有没有可能是一部分人的交易优先被网络认可.
区块链的交易从发生到被网络认可, 是需要一段过程的, 整个时间从分钟到小时不等, 一般认为被认可的条件:
交易被成功写入一个区块. 此区块后又产生了 5 到 6 个区块.
这张网有多大
炒币的人越来越多, 矿机也越来越多, 网络中的节点到底有多少, 是不是可以找到一个通用的方法以收集全网的节点数量与动态.
带着这 2 个目标, 我以比特币为例, 来开始这趟旅程.
为什么要这些数据
先问大家一个问题, 都是交易, 如何判断现在有没有人在砸盘呢.
一定量的抛空, 会让群体散户产生恐具, 进而导致新一步的下跌, 每天都有无数双眼睛盯着交易大盘
从当前的大盘表现, 如何预测下一时刻的表现, 是一个很难的事.
一般交易所里交易的是代币, 产生的与发放的并不发生在用户的本地钱包里, 而是交易所的远程钱包. 出于安全性考虑, 在一定量币的交易完后, 用户一般会将币传回到
本地的钱包中, 在下次交易前上传到交易所钱包, 我们可以将这个上传与传回当成一次交易来统计.
另一方面, 如果所有的交易都发生在交易所, 没有本地的买卖, 就没有了网络的数据, 这部分每天的交易情况是可以从交易所得到, 因此我想了解一下全网每天的非代持交易情况,
并且为算法分析提供接口, 为预测未来的价格走势提供可能.
比特币是一把双刃剑, 可以做为其他币种的交换者, 也可以成为大量洗钱的暗黑空间, 因此对大宗交易的监测成了 goverment 或者经济体一件很重要又麻烦的事情.
节点收集
连接限制
比特币只能向外最多连接 8 个点, 连接的数量不能超过 125,
既使改变了这个数字, 还是会受到并发模型的限制. 比特币的网络模型是 select:
在 linux 上 select 的 fd 限制是 1024.
重构 bitcoin 事件模型
如果要让比特币客户端支持更多的连接, 可以有 2 种方式, 一种是多次 select 遍历所有的连接.
如果要让比特币客户端支持更多的连接, 可以有 2 种方式, 一种是多次 select 遍历所有的连接.
nodes[10000];
select(nodes, 0, 1024);
select(nodes, 1024, 2048);
....
select(nodes, 10000-1024, 10000);
另一种是 epoll 模式, 可以支持 100 万甚至更多的连接, 我选择了这种方式 btch_net_event.h
主要是几个函数
AddEvent 添加事件
DelEvent 删除事件
Process 事件循环
当有新连接建立时调用 AddEvent
有了新事件模型做保障, 接下来我重写了连接线程 CConnman::ThreadOpenConnectionsWithEvent, 去掉了连接限制. 详见 btch_net_event.cpp
关系型存储
bitcoin 内建的存储用的是 leveldb, 其独有的 key value 本地存储性能很高, 但不利于关系型的查询, 因为我们之后需要对数据做分析, 所以我选择了 mysql.
为了能更好的获取某个连接地址, 我重载了 CService::GetSockAddr
bool CService: :GetSockAddr(int & type, char * ip, int & _port) const {
_port = port;
if (IsIPv4()) {
type = 4;
size_t addrlen = sizeof(struct sockaddr_in);
struct sockaddr_in _addr;
struct sockaddr_in * paddrin = (struct sockaddr_in * ) & _addr;
memset(paddrin, 0, addrlen);
if (!GetInAddr( & paddrin - >sin_addr)) return false;
inet_ntop(AF_INET, &_addr.sin_addr, ip, INET_ADDRSTRLEN);
return true;
}
if (IsIPv6()) {
type = 6;
size_t addrlen = sizeof(struct sockaddr_in6);
struct sockaddr_in6 _addr;
struct sockaddr_in6 * paddrin6 = (struct sockaddr_in6 * ) & _addr;
memset(paddrin6, 0, addrlen);
if (!GetIn6Addr( & paddrin6 - >sin6_addr)) return false;
inet_ntop(AF_INET6, &_addr.sin6_addr, ip, INET6_ADDRSTRLEN);
return true;
}
return false;
}
当有新地址收入时, 会记载的数据库.
节点动态展示
可以看到节点主要分布在欧洲, 美国, 中国沿海与日韩也有一定的分布.
为了得到上面的展示, 我选择了 google map marker, 并使用 headless 浏览器 puppeteer.
交易收集
"你有多少比特币", 这个数字在比特币的网络并没有一个这样的数字记录.
要算出持有量需要对以往的交易推算, 用以往总获得减去总支出. 因此要算出排行榜, 比须对以往所有数据做一次运算.
为了更方便分析, 我仍然将原来的 leveldb 存储到 mysql 中.
我设计了一系列的表, 以对实时网络传播的交易入库, 并将被写进块的数据加上标记, 这样便有了一个结构型的交易数据. 感兴趣的同志戳这里
表架构
交易分 2 种, 一种给矿工奖励的 coinbase, 一种是普通的交易, 包括 tx_in(钱来自哪里),tx_out(发多少钱, 给谁)
产生交易数据
我在比特币代码接受交易并处理时, 插入一条转存的指令:
static void CheckInputsAndUpdateCoins()
{
InsertCoinDB(tx);
...
}
逻辑实现
产生区块数据
在区块存储时, 注入一条指令 SaveBlock2DB 以保存区块信息.
static CDiskBlockPos SaveBlockToDisk(const CBlock& block, int nHeight) {
SaveBlock2DB(block, nHeight);
...
}
调用实现
int SaveBlock2DB(const CBlock& block, int height)
{
...
BtchTxDB::GetInstance()->AddBlock(&bbk, height);
}
这里的 BtchTxDB 是我新增的类, 用来处理比特币里的交易数据.
经过改造过后的比特币的程序跑起来, 一天后就有了所有的线上交易数据.
我们就可以分析这些数据了.
最近最大一笔交易发送了多少币
mysql> select id, tx_id, out_value, out_address from tx_out order by out_value desc limit 1;
+-------+-------+--------------+------------------------------------+
| id | tx_id | out_value | out_address |
+-------+-------+--------------+------------------------------------+
| 14847 | 5141 | 331824606426 | 1LJWwgDdWMYZGUYRhszGqL1FkrsftEeckP |
bitcoin 最小单位是聪, 10^8 个聪为一比特币, 因此最近最大一笔交易为比特币发送了 3318 个比特币, (我的天哪), 接收地址为 1LJWwgDdWMYZGUYRhszGqL1FkrsftEeckP, 这是哪个土豪还是交易所.
当前最小一笔交易
mysql> select id, tx_id, out_value, out_address from tx_out where out_value > 0 order by out_value asc limit 1;
+-------+-------+-----------+------------------------------------+
| id | tx_id | out_value | out_address |
+-------+-------+-----------+------------------------------------+
| 10462 | 3457 | 540 | 3JU1MWabud2iENRtydJXd9LMrUrSFxXbFy |
+-------+-------+-----------+------------------------------------+
1 row in set (0.03 sec)
统计到的最小交易数目为 540 聪.
谁接收的次数最多
mysql> select count(*), out_address from tx_out where out_address <> '' group by out_address order by count(*) desc limit 2;
+----------+------------------------------------+
| count(*) | out_address |
+----------+------------------------------------+
| 343 | 392t5a1Sy5wy4hghGCBzdTht7rsjECAwPN |
| 203 | 1JwgCVCnw8ziAnXA1c2VqUaMVkV4jtfDmw |
+----------+------------------------------------+
2 rows in set (0.05 sec)
谁接收的钱最多
mysql> select sum(out_value) ov, out_address from tx_out where out_address <> '' and status <> 3 group by out_address order by ov desc limit 5;
+---------------+------------------------------------+
| ov | out_address |
+---------------+------------------------------------+
| 3053342387036 | 1MN37fphKuQepWqvM7UuKFaSxW5bX8nhoR |
| 1860288937639 | 17A16QmavnUfCW11DAApiJxp7ARnxN5pGX |
| 469584006559 | 1Kr6QSydW9bFQG1mXiPNNu6WpJGmUa9i1g |
| 331824606426 | 1LJWwgDdWMYZGUYRhszGqL1FkrsftEeckP |
| 222882357474 | 1DcKsGnjpD38bfj6RMxz945YwohZUTVLby |
(status=3 代表已经消费,<>3 代表还没消费).
可以看到接收比特币最多的地址是
1MN37fphKuQepWqvM7UuKFaSxW5bX8nhoR
总共接收了 30533 个比特币, 我猜它是一个交易所.
哪个地址钱最多
在比特币的交易里, 输入与输出决定了币的流动, 别人和你交易, 就要把你的地址放到输出中 (out_address), 而你要花钱, 也就是花费掉上一个交易的 out_value.
比特币的币运算采用 UTXO, 即只运算没花费出去的, 且一笔交易输出最多只能被消费一次, 在我的代码里, 用户的币放在 tx_out 表里, 是否被消费用 status 字段来区分.
status 值 | 含义 |
---|---|
1 | 被记录 |
2 | 被确认 |
3 | 被消费 |
在库里选择一下, 看看哪个地址最有钱, 只要查询 status=2 即可, 因此我们拉出一个排行榜:
mysql> select sum(out_value) ov, out_address from tx_out where out_address <> '' and status = 2 group by out_address order by ov desc limit 10;
+---------------+------------------------------------+
| ov | out_address |
+---------------+------------------------------------+
| 3053342387036 | 1MN37fphKuQepWqvM7UuKFaSxW5bX8nhoR |
| 920423595045 | 17A16QmavnUfCW11DAApiJxp7ARnxN5pGX |
| 331824606426 | 1LJWwgDdWMYZGUYRhszGqL1FkrsftEeckP |
| 323000000000 | 373BRdPtfMycB1yYhzZ2XNPDjvBbYQBsU7 |
| 312453740813 | 1Kr6QSydW9bFQG1mXiPNNu6WpJGmUa9i1g |
| 115881899983 | 1N52wHoVR79PMDishab2XmRHsbekCdGquK |
| 109540316825 | 3PA3gFEfTCXU7EAJDeaKpVLUX7EBF5xX4m |
| 102507542525 | 1DcKsGnjpD38bfj6RMxz945YwohZUTVLby |
| 85003959092 | 3EC6GCRBCbLGBTHL3xJNUeDeQMELmZsUcD |
| 81224138674 | 1EEqRvnS7XqMoXDcaGL7bLS3hzZi1qUZm1 |
+---------------+------------------------------------+
10 rows in set (0.05 sec)
结语
上个月帐上又多了 5 万, 当然我这只是简单玩一下, 纯属娱乐. 在抄币的同时, 做些研究, 以反馈给同样爱币的你.
谨希望此文能帮助那些在黑暗中研究和人们, 同样给那些对币世界充满好奇的人们.
来源: https://yq.aliyun.com/articles/422693