区块链源码学习(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:)