区块链源码学习(2)-Bytecoin节点程序及其数据库初探

上一节区块链源码学习(1)-Bytecoin源码编译,生成3个可执行文件,分别是bytecoind,walletd和tests。bytecoind是核心节点程序,与其他节点进行点对点通信,记录区块信息。walletd顾名思义,用于钱包管理的程序,tests为测试程序。本节主要介绍bytecoind程序。

bytecoind默认会在当前用户的根目录下创建一个隐藏的目录.bytecoin,用于区块数据、日志数据、缓存数据等的存储。其中blockchain目录是使用lmdb数据库存储的区块数据,bytecoin在源码编译时,也可以指定使用SQLite来存储区块数据,默认情况下使用lmdb数据库,关于lmdb库的使用可参考链接(lmdb简介及示例代码)。logs目录用于存储日志文件,peer_db目录存储p2p通信节点信息,后面会详细讲述peer_db存储数据的格式。

➜  .bytecoin ls -ahl
总用量 20K
drwxr-xr-x 5 root root 4.0K 6月  12 21:48 .
drwxr-xr-x 3 root root 4.0K 6月  12 21:46 ..
drwxr-xr-x 2 root root 4.0K 6月  12 21:48 blockchain
-rw------- 1 root root    0 6月  12 21:48 bytecoind.lock
drwxr-xr-x 2 root root 4.0K 6月  12 21:49 logs
drwxr-xr-x 2 root root 4.0K 6月  12 21:48 peer_db

运行bytecoind,看到输出信息如下。

➜  bin git:(master) ✗ ./bytecoind
Starting multicore ring checker using 2/2 cpus
BlockChain::db_commit started... tip_height=0 header_cache.size=1
BlockChain::db_commit finished...
21:48:16.138649 I BlockChainState BlockChainState::BlockChainState height=0 cumulative_difficulty=1 bid=a742885cb01d11b7b36fb8bf14616d42cd3d8c1429a224df41afa81b86b8a3a8
21:48:16.246808 I P2P Connecting to=45.76.29.96:8080
21:48:16.247016 I P2P No peers to connect to, will try again after 10 seconds
Starting multicore POW checker using 2/2 cpus
bytecoind started seconds=0.218
21:48:26.247250 I P2P No peers to connect to, will try again after 10 seconds
21:48:36.247641 I P2P No peers to connect to, will try again after 10 seconds
21:48:46.247706 I P2P Connecting to=207.246.127.160:8080
21:48:46.248063 I P2P Connecting to=108.61.174.232:8080
21:48:46.248284 I P2P No peers to connect to, will try again after 10 seconds
P2p COMMAND_HANDSHAKE response version=1 unique_number=18227590769910343221 current_height=0 local_peerlist.size=250
21:48:49.969575 I P2P Connecting to=72.228.0.160:8080
21:48:49.969923 I P2P Connecting to=94.232.78.204:8080
21:48:49.970208 I P2P Connecting to=196.217.81.244:8080
21:48:49.970483 I P2P Connecting to=74.192.143.134:8080
21:48:49.970758 I P2P Connecting to=104.199.238.247:8080
21:48:49.971036 I P2P Connecting to=83.181.211.60:8080
21:48:49.971813 I P2P Connecting to=45.32.156.183:8080
P2p COMMAND_HANDSHAKE response version=1 unique_number=9744417281224826320 current_height=1549945 local_peerlist.size=250
21:48:50.088996 I Node DownloaderV11::advance_chain Requesting chain from 104.199.238.247:8080 remote height=1549945 our height=0
Received chain from 104.199.238.247:8080 start_height=0 length=10000
Requesting block 1 from 104.199.238.247:8080
Received block with height=1 (queue=399) from 104.199.238.247:8080
redo_block {0} height=1 bid=13a537627c969dfda1bf6a2688694aa139f66d643073d49afc5cdd75d3c1e400
Requesting block 564 from 104.199.238.247:8080
redo_block {1} height=181 bid=40f90f4272773369fabbf795e9030fb331c2d8d7d78cda32c47aab2d86d9f4ba

其中P2P连接信息,有一些ip地址,如45.76.29.96:8080、207.246.127.160:8080等,在运行bytecoind没有指定任何配置信息,那么猜测这些ip地址应该是写死在代码中的。搜索代码源文件,果然在 CryptoNoteConfig.hpp 文件中看这些ip:

//CryptoNoteConfig.hpp
const char *const SEED_NODES[] = {
    "207.246.127.160:8080", "108.61.174.232:8080", "45.32.156.183:8080", "45.76.29.96:8080"};

也就是说bytecoind节点程序在p2p点对点通信时,需要一些初始的节点ip,通过这些节点,能够在短时间内迅速地扩散到全网。

下面来看一下bytecoind的参数,执行./bytecoind --help

➜  bin git:(master) ✗ ./bytecoind --help
bytecoind 3.1.1.

Usage:
  bytecoind [options]
  bytecoind --help | -h
  bytecoind --version | -v

Options:
  --allow-local-ip                     Allow local ip add to peer list, mostly in debug purposes.
  --p2p-bind-address=<ip:port>         Interface and port for P2P network protocol [default: 0.0.0.0:8080].
  --p2p-external-port=<port>           External port for P2P network protocol, if port forwarding used with NAT [default: 8080].
  --bytecoind-bind-address=<ip:port>   Interface and port for bytecoind RPC [default: 127.0.0.1:8081].
  --seed-node-address=<ip:port>        Specify list (one or more) of nodes to start connecting to.
  --priority-node-address=<ip:port>    Specify list (one or more) of nodes to connect to and attempt to keep the connection open.
  --exclusive-node-address=<ip:port>   Specify list (one or more) of nodes to connect to only. All other nodes including seed nodes will be ignored.
  --export-blocks=<folder>             Perform hot export of blockchain into specified folder as blocks.bin and blockindexes.bin, then exit. This overwrites existing files.
  --backup-blockchain=<folder>         Perform hot backup of blockchain into specified backup data folder, then exit.
  --data-folder=<full-path>            Folder for blockchain, logs and peer DB [default: ~/.bytecoin].
  --bytecoind-authorization=<usr:pass> HTTP authorization for RPC.
  --ssl-certificate-pem-file=<file>    Full path to file containing both server SSL certificate and private key in PEM format.
  --ssl-certificate-password=<pass>    DEPRECATED. Will read password from stdin if not specified.

参数信息中有默认值的基本都不需要修改,其中--data-folder指定数据目录,如果不想将数据保存在默认的.bytecoin目录,可以指定该参数。--seed-node-address指定初始的p2p连接节点ip列表,如果源码中写死的节点ip都无法使用时,可以手动指定该参数,设置可用的p2p初始节点ip。其他参数通过其名称和参数说明,大概可以猜测其作用,这里不再赘述。

正常情况下,bytecoind程序运行起来后,会一直通过周围的节点,以点对点通信的方式下载区块历史信息,直到同步到最新的区块。

bytecoin源码中有两个地方使用到了数据库,一个是PeerDB类,用于存储点对点通信时节点的信息, 主要包括ip、端口等。另一个是BlockChain,用来存储区块信息。下面是这两个类的定义。

// src/p2p/PeerDB.hpp
class PeerDB {
public:
      typedef platform::DB DB;
private:
      DB db;
};

// src/Core/BlockChain.hpp
class BlockChain {
public:
      typedef platform::DB DB;
      ...
      DB m_db;
      ...
};

platform::DB 这个类,根据是否定义platform_USE_SQLITE宏,来决定使用sqlite还是lmdb,默认情况下使用lmdb。使用cmake编译时,指定-DUSE_SQLITE=1,则使用sqlite数据库存储数据。

// src/platform/DB.hpp

#pragma once
#if platform_USE_SQLITE
#include "platform/DBsqlite3.hpp"
namespace platform {
typedef DBsqlite DB;
}
#else
#include "platform/DBlmdb.hpp"
namespace platform {
typedef DBlmdb DB;
}
#endif

lmdb可以看成是一个key-value数据库。分析PeerDB类的实现代码,每一条记录都是一个key-value数据。
key的值为prefix + 节点ip + 端口, prefix 一般为 "graylist/" 或者 "whitelist/"
value的值为一个Entry对象的二进制序列化值
看下源码:

// src/p2p/PeerDB.cpp

static const std::string GRAY_LIST("graylist/");
static const std::string WHITE_LIST("whitelist/");
...
void PeerDB::update_db(const std::string &prefix, const Entry &entry) {
      auto key = prefix + common::to_string(entry.adr.ip) + ":" +  common::to_string(entry.adr.port);
      db.put(key, seria::to_binary(entry), false);
}
...
update_db(GRAY_LIST, new_entry);

再看下Entry结构的定义,主要就是一个存储ip地址和端口信息的结构体:

// src/p2p/P2pProtocolTypes.hpp
struct NetworkAddress {
    uint32_t ip   = 0;
    uint32_t port = 0;
};
struct PeerlistEntry {
     NetworkAddress adr;
     PeerIdType id      = 0;
     uint32_t last_seen = 0;  // coincides with Timestamp
     uint32_t reserved  = 0;  // High part of former 64-bit last_seen
};

// src/p2p/PeerDB.hpp
struct Entry : public PeerlistEntry {
            Entry()
                : PeerlistEntry{}  // Initialize all fields
            {}
            Timestamp ban_until               = 0;
            Timestamp next_connection_attempt = 0;
            uint64_t shuffle_random = 0;  // We assign random number to  each record, for deterministic order of equal items
            std::string error;            // last ban reason
      };

使用lmdb库,编写了一个简单的C语言程序,读取peer_db数据库中的内容,将读取到的数据转成可读的文本打印输出如下,这里只打印了前5行:

key:/whitelist/1289242423:8080, value:Entry(adr.ip:10000,adr.port:8080,id:0,last_seen:0,reserved:0,ban_until:0,next_connection_attempt:0,shuffle_random:0,error:)
key:graylist/1006109508:8080, value:Entry(adr.ip:1006109508,adr.port:8080,id:964940239481739396,last_seen:0,reserved:0,ban_until:0,next_connection_attempt:0,shuffle_random:12844025959447800046,error:)
key:graylist/1006990104:8080, value:Entry(adr.ip:1006990104,adr.port:8080,id:10284378849939737697,last_seen:0,reserved:0,ban_until:0,next_connection_attempt:0,shuffle_random:6511915129648608432,error:)
key:graylist/1020507475:8080, value:Entry(adr.ip:1020507475,adr.port:8080,id:8440730593804793034,last_seen:0,reserved:0,ban_until:0,next_connection_attempt:0,shuffle_random:10276282581105767265,error:)
key:graylist/103674199:8080, value:Entry(adr.ip:103674199,adr.port:8080,id:16166886514328276571,last_seen:0,reserved:0,ban_until:0,next_connection_attempt:0,shuffle_random:6207942281420898257,error:)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容