使用VC学习BTC:(一)了解block的存盘文件blk*.dat

虽然首先使用win10的linux layer在Windows上先编译并成功运行了bitcoin core,但是对于更习惯VS开发工具的人来说,总是在VS下跟踪调试更顺手,因此准备建立一个VS工程来编译bitcoin,并逐步学习了解它的实现比特币存盘文件blkxxxx.dat格式

为了便于VS上编译运行,先从存盘的数据文件开始,这部分涉及的代码应比较容易在VS上运行起来。

工程建立步骤如下:

  • 从官方开源代码库https://github.com/bitcoin/bitcoin下载源代码;
  • 在下载的源代码的build-aux目录下建立vs目录;
  • 在新建的vs目录下创建vs2017的工程(Solution),并添加一个windows控制台的C++项目bitcoin(Project);
  • 在bitcoin项目中添加部分下载的源文件,添加后项目文件如下

        

  • 添加一个新的CPP文件,增加LoadExternalBlockFile函数
#define MESSAGE_START_SIZE 4
unsigned char pchMessageStart[MESSAGE_START_SIZE] = { 0xf9, 0xbe, 0xb4, 0xd9 };

bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp)
{
	int nLoaded = 0;
	try {
		// This takes over fileIn and calls fclose() on it in the CBufferedFile destructor
		CBufferedFile blkdat(fileIn, 2 * MAX_BLOCK_SERIALIZED_SIZE, MAX_BLOCK_SERIALIZED_SIZE + 8, SER_DISK, CLIENT_VERSION);
		uint64_t nRewind = blkdat.GetPos();
		while (!blkdat.eof()) {
			blkdat.SetPos(nRewind);
			nRewind++; // start one byte further next time, in case of failure
			blkdat.SetLimit(); // remove former limit
			unsigned int nSize = 0;
			try {
				// locate a header
				unsigned char buf[MESSAGE_START_SIZE];
				blkdat.FindByte(pchMessageStart[0]);
				nRewind = blkdat.GetPos() + 1;
				blkdat >> FLATDATA(buf);
				if (memcmp(buf, pchMessageStart, MESSAGE_START_SIZE))
					continue;
				// read size
				blkdat >> nSize;
				if (nSize < 80 || nSize > MAX_BLOCK_SERIALIZED_SIZE)
					continue;
			}
			catch (const std::exception&) {
				// no valid block header found; don't complain
				break;
			}
			try {
				// read block
				uint64_t nBlockPos = blkdat.GetPos();
				if (dbp)
					dbp->nPos = nBlockPos;
				blkdat.SetLimit(nBlockPos + nSize);
				blkdat.SetPos(nBlockPos);
				std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
				CBlock& block = *pblock;
				blkdat >> block;
				nRewind = blkdat.GetPos();

				printf("%s\n\n", block.ToString().c_str());
			}
			catch (const std::exception& e) {
				printf("%s: Deserialize or I/O error - %s\n", __func__, e.what());
			}
			nLoaded++;
		}
	}
	catch (const std::runtime_error& e) {
		printf("System error: %s\n", e.what());
	}
	if (nLoaded > 0)
		printf("Loaded %i blocks from external file\n", nLoaded);
	return nLoaded > 0;
}

这个函数的主体内容是从比特币的验证代码部分抄的,之所以抄是因为一旦引入验证代码,整个工程会复杂很多。

  • 增加main函数
int main(void)
{
	// Todo: Change to the real path where you store the bitcoin block file
	char path[] = "G:\\blockchain\\bitcoin\\bin\\Bitcoin\\blocks\\blk00000.dat";

	FILE *file = fsbridge::fopen(path, "rb");
	LoadExternalBlockFile(file, NULL);
	return 0;
}

这里我偷懒了,直接写死了一个存盘的block文件路径,反正只是为了学习嘛。

到这里,工程文件都有了,但是编译还有问题,因为比特币的源代码有依赖。得益于vs集成的nuget包管理工具,可以不用自己下载依赖的源代码编译(也是累死人的工作哦,开源代码在vs下的编译都很烦)。

  • 引入依赖包:

解决方案处点击鼠标右键,在弹出的菜单中选择“管理解决方案的NuGet程序包”

搜索boost_filesystem-vc141及boost_system-vc141,选择



  • 修改比特币代码错误

写本文时此错误的修复已经提交,但是仍未被接受,在被接受后再下载源代码将不用修改。


  • 编译运行

至此,就可以跟踪调试来看整个文件的格式了!

  • 文件是由一系列的block组成的,没有额外的文件头,chain.h中定义的CBlockFileInfo类并非用于这里;
  • 每个block包括:
字节数 内容 说明
4字节 0xF9 0xBE 0xB4 0xD9 所谓的Magic,用于确认block开始
4字节 Block Length Little-Endian Little-Endian的unsigned int
Block Length CBlock类序列化后的数据 CBlock类定义在block.h文件中
  • CBlock数据包括CBlockHeader以及一些列的CTransaction数据,其中CBlockHeader包括:
字节数 内容 说明
4字节 版本号 Little-Endian的int32_t
32字节 前一个block的hash值 Little-Endian的uint256
32字节 MerkleRoot Little-Endian的uint256
4字节 时间戳 块生成时的网络时间
4字节 目标值 当前区块生成所达成目标值的特征
4字节 随机数 用于挖矿时生成符合要求的块哈希
  • 在CBlockHerder之后,是交易数量及每个交易的记录
  • 交易数量为紧缩格式(CompactSize)的无符号整数

紧缩格式整数的读取,在serialize.h的ReadCompactSize模板中,其规则为若第一个字节是255,则读取后续8字节作为uint64;若第一字节是254,则读取后续4字节作为uint32;若第一字节是253,则读取后续2字节作为uint16;否则将第一个字节作为uint8。

因此,这是一个可变长度的数据,最小占用1字节,最大占用9字节。

  • 之后是按照交易数量来读取交易数据,实际的交易数据读取跟踪定位在transaction.h的函数inline void UnserializeTransaction(TxType& tx, Stream& s) 中,这个后面分析交易时再来分析细节。
不想自己从头建工程的,可以从csdn下载,地址为 http://download.csdn.net/download/lazypiggy/10265510


【原创首发地址:http://http://blog.csdn.net/lazypiggy/article/details/79414709,转发请保留此链接】


阅读更多

更多精彩内容