[区块链」比特币的全球交易数据监测

[区块链」比特币的全球交易数据监测
最近一段时间区块链挺火的,不管是涨是跌,牵动人心。

每当涨的时候,总有人后悔为啥没有早买, 每当跌时,也能听见”背后有没有机构做空“疑问。

当看到有人在买卖中受打击而顿挫时,当看到有媒体在别人赔了很多还要去嘲讽时,我就想是不是应该做点什么,这世界本就不完美,为什么要禁止大家向往富有的心呢。

区块链的网络是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 | 3EC6GCRBCb*HL3xJNUeDeQMELmZsUcD |
|   81224138674 | 1EEqRvnS7XqMoXDcaGL7bLS3hzZi1qUZm1 |
+---------------+------------------------------------+
10 rows in set (0.05 sec)

结语

上个月帐上又多了5万,当然我这只是简单玩一下,纯属娱乐。在抄币的同时,做些研究,以反馈给同样爱币的你。
谨希望此文能帮助那些在黑暗中研究和人们,同样给那些对币世界充满好奇的人们。

上一篇:简单设计并开发一个移动通信程序


下一篇:令牌桶