比特币源码分析:txdb模块(三)

区块链技术巴比特2018-03-02 07:32:27  阅读 -评论 1  阅读原文

前一篇文章主要介绍了,txdb 的一个整体逻辑,本文将详细描述 txdb 模块与 leveldb 的交互,以及对 leveldb 的封装。

上一篇文章提到,在 dbwrapper.h 的 CDBWrapper 是对 leveldb 的一个简单封装,所有要写入 leveldb 的东西都会调用 CDBWrapper 这个类,下面我们就来分析一下如何调用,以及 CDBWrapper 究竟实现了哪些逻辑。

比特币源码分析:txdb模块(三)

CDBWrapper 的构造函数

class CDBWrapper {public: CDBWrapper(const boost::filesystem::path &path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool obfuscate = false); ~CDBWrapper(); };

CDBWrapper 主要有如下参数:

- path →系统中存储leveldb数据的位置

- nCacheSize →配置各种leveldb缓存设置

- fMemory → 如果为true,则使用leveldb的内存环境

- fWipe → 如果为true,则删除所有现有数据

- obfuscate → 如果为true,则通过简单的XOR存储数据; 如果为false,则与零字节数组进行异或运算

Read 函数

CDataStream 我们简单理解为一个内存 buffer,read()传入的key是经过泛化的一个K,经过 serialize 之后,相当于一个内存的 slice,ssKey << key;将这个内存 slice 写入内存 buffer→sskey 中,之后使用 leveldb 的 Slice 方法写入,ssKey.data() 代表首地址 ,ssKey.size()表示其大小。

之后我们构造一个 string 的 strValue,调用 leveldb 的 Get(),传入readoptions读选项,slKey 和构造好的 string 的 strValue。

最后,我们通过 ssValue 去 leveldb 中拿到我们所要的 value,并经过 Xor(异或运算)进行解码之后,ssValue >> value; 把值塞回给 read 的 value 参数,这样,我们就通过确定的 key 拿到其对应的 value。

在上述过程中,我们发现,我们从leveldb中拿到的值是经过 Xor 编码之后写入的,我们最后读取出来需要经过 Xor 解码的过程,那么编码的过程在哪里呢?很明显,读的逆向操作是 write 操作。

template bool Read(const K &key, V &value) const { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); std::string strValue; leveldb::Status status = pdb->Get(readoptions, slKey, &strValue); if (!status.ok()) { if (status.IsNotFound()) return false; LogPrintf("LevelDB read failure: %s\n", status.ToString()); dbwrapper_private::HandleError(status); } try { CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION); ssValue.Xor(obfuscate_key); ssValue >> value; } catch (const std::exception &) { return false; } return true; } Write 函数

在这个函数中,我们能清楚的理解到,write函数首先调用CDBBatch的write函数,最后返回WriteBatch批量写函数,所以,我们写入leveldb的过程是一个批量写的过程。

template bool Write(const K &key, const V &value, bool fSync = false) { CDBBatch batch(*this); batch.Write(key, value); return WriteBatch(batch, fSync); } CDBBatch 的 write函数

write 函数的参数同样是泛化过的 K,V,我们通过 ssKey << key;和 ssValue << value;将key 和 value 塞进内存 buffer 中,最后通过 Xor 编码之后,调用 put 函数写入 leveldb 中,leveldb::Slice slKey(ssKey.data(), ssKey.size());依旧表示的是首地址以及大小,slValue同理。

class CDBBatch { friend class CDBWrapper; private: CDataStream ssKey; CDataStream ssValue; public: template void Write(const K &key, const V &value) { ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE); ssValue << value; ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); leveldb::Slice slValue(ssValue.data(), ssValue.size()); batch.Put(slKey, slValue); // LevelDB serializes writes as: // - byte: header // - varint: key length (1 byte up to 127B, 2 bytes up to 16383B, ...) // - byte[]: key // - varint: value length // - byte[]: value // The formula below assumes the key and value are both less than 16k. size_estimate += 3 + (slKey.size() > 127) + slKey.size() + (slValue.size() > 127) + slValue.size(); ssKey.clear(); ssValue.clear(); } WriteBatch函数

WriteBatch的参数fSync 判断write的过程是否为同步write。

bool WriteBatch(CDBBatch &batch, bool fSync = false); Exists 函数

Exists 函数的参数是一个泛化后的 key,通过 key 可以判断该 key 所对应的 value 究竟是否在 leveldb 中存在。

template bool Exists(const K &key) const { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); std::string strValue; leveldb::Status status = pdb->Get(readoptions, slKey, &strValue); if (!status.ok()) { if (status.IsNotFound()) return false; LogPrintf("LevelDB read failure: %s\n", status.ToString()); dbwrapper_private::HandleError(status); } return true; } Erase 函数

Erase函数与write函数同理,首先调用CDBBatch中的Erase函数,最后返回WriteBatch。不同的是 Erase 函数用来删除传入的 key 所定位到的 value:

template bool Erase(const K &key, bool fSync = false) { CDBBatch batch(*this); batch.Erase(key); return WriteBatch(batch, fSync); } CDBBatch 的 Erase函数

思路同理,同样是泛化后的key,写入内存buffer -→ssKey中,然后通过leveldb::Slice判断这个key的首地址和大小,调用batch.Delete(slKey);将其删除。ssKey.clear();代表删除内存中的临时变量:

template void Erase(const K &key) { ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); batch.Delete(slKey); // LevelDB serializes erases as: // - byte: header // - varint: key length // - byte[]: key // The formula below assumes the key is less than 16kB. size_estimate += 2 + (slKey.size() > 127) + slKey.size(); ssKey.clear(); } Flush函数

Flush函数需要注意,它并不适用于LevelDB,不是我们想象中的将要写入的数据flush到leveldb中, 只是提供与BDB的兼容性:

bool Flush() { return true; } Sync 函数

sync函数用来判断批量写入的时候是否采用同步的方式:

bool Sync() { CDBBatch batch(*this); return WriteBatch(batch, true); } NewIterator 函数

该函数返回的返回值是CDBIterator:

CDBIterator *NewIterator() { return new CDBIterator(*this, pdb->NewIterator(iteroptions)); }

下面我们来分析一下CDBIterator:

CDBIterator

CDBIterator 包含两个参数,_parent表示父CDBWrapper的实例;_piter表示原始的leveldb迭代器:

class CDBIterator { private: const CDBWrapper &parent; leveldb::Iterator *piter; public: CDBIterator(const CDBWrapper &_parent, leveldb::Iterator *_piter) : parent(_parent), piter(_piter){}; ~CDBIterator(); }

换句话说, CDBIterator就是对leveldb::Iterator的封装,并且封装了如下函数来供使用

Seek

Seek 函数,通过泛化的 key 写入 ssKey,之后获取首地址以及大小,传入leveldb内部的seek函数来实现查找的功能:

template void Seek(const K &key) { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); piter->Seek(slKey); }

其他函数同理,主要实现有:

bool Valid(); //确认是否有效 void SeekToFirst(); //从头开始找 void Next(); //获取下一个元素 bool GetKey(K &key) //获取key int GetKeySize() //获取key的size bool GetValue(V &value) //获取value int GetValueSize() //后去value的size IsEmpty函数

IsEmpty函数返回一个bool类型,他的作用和她的字面意思是一样的,如果IsEmpty 管理的数据库不包含数据,则返回true:

bool IsEmpty(); EstimateSize函数

EstimateSize 函数用来预估从 key_begin 到 key_end 的范围占了文件系统多大的空间,pdb->GetApproximateSizes(&range, 1, &size);这个函数是 leveldb 内部的一个函数具体含义如下:

GetApproximateSizes方法是用来获取一个或多个密钥范围内使用的文件系统空间的近似大小。

leveldb::Range ranges[2]; ranges[0] = leveldb::Range("a", "c"); ranges[1] = leveldb::Range("x", "z"); uint64_t sizes[2]; leveldb::Status s = db->GetApproximateSizes(ranges, 2, sizes);

GetApproximateSizes(ranges, 2, sizes) 的第二个参数 2 就是 uint64_t sizes[2] 中的 2 ,因为在 cpp 中数组作为参数时会退化,所以第三个参数 sizes 只是一个指针,2 代表的是获取上面两个range所占的大小。

size_t EstimateSize(const K &key_begin, const K &key_end) const { CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION); ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey1 << key_begin; ssKey2 << key_end; leveldb::Slice slKey1(ssKey1.data(), ssKey1.size()); leveldb::Slice slKey2(ssKey2.data(), ssKey2.size()); uint64_t size = 0; leveldb::Range range(slKey1, slKey2); pdb->GetApproximateSizes(&range, 1, &size); return size; } 引用

源码:bitcoin-abc(https://github.com/Bitcoin-ABC/bitcoin-abc)

版本号:v0.16.0

声明:链世界登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。此文如侵犯到您的合法权益,请联系我们100@7234.cn

    参与讨论 (1 人参与讨论)

    相关推荐

    区块链投资趋势报告:巨头入场布局行业趋于成熟

    区块链投资趋势报告:巨头入场布局行业趋于成熟

    来自:https://mp.weixin.qq.com/s?__biz=MzI4NzIxOTY1NA==&amp;mid=2650632639&amp;idx=1&amp;sn=e6d1c29731d992a80410aaee82ec3ea6&amp;chksm=f3d8db16c4af520097e4a64a71b1d4743ac326b9f027

    重新发明货币

    重新发明货币

    一、货币的演化过程 先简单回顾一下人类货币的演化过程,大概有以下阶段: a. 1.0版本:自然货币(贝壳、牲口、金银……) 这个阶段,货币基于一般等价物的稀有性或者实用性,货币不可能出现人为操纵的超发。 b. 2.0版本:早期纸币、银票到本位纸币 当贸易量越来越大,实物货币太不方便了,而且大家发现其实并不在意货币本身有什么价值,在意的只是这么多的货币能不能交换到足够的物品,于是纸币这种信用货

    从比特币交易看欧洲央行虚拟货币分类

    从比特币交易看欧洲央行虚拟货币分类

      互联网对传统社会的颠覆从未停止,在其完成对信息流、商流、物流、资金流的初步改造之后,或将以虚拟货币的形式打破现有货币体系   4月18日,在中国极客张沈鹏创办的比特币交易平台(42BTC.com)上,比特币对人民币的平均交易价为576元。当天,该平台完成了100个比特币的交易量。仅仅过去一周,4月25日上午,比特币对人民币的平均交易价已达到906元。据42BTC网站统计:在过去的32个月

    欧洲央行-比特币报告

    3.1 比特币 3.1.1 基本特征          比特币可能是最成功的,也可能是最有争议的虚拟货币方案,由日本程序员中本聪(译者注:事实上,中本聪是不是日本人,甚至是不是单个人无从考证)在2009年设计并实现。该计划基于一个类似于BitTorrent的P2P网络。BitTorrent是互联网上著名的共享文件协议,应用在电影,游戏和音乐领域。比特币在全球层面上运作,可用于各类货币交易(虚

    彻底玩转比特币地址和私匙

    彻底玩转比特币地址和私匙

    比特币地址和私匙是所有比特币初学者面对的一大难题,再加上那一串超长的字符串,让人更是摸不到头脑。 现在编者以问答的形式,带你一步步的揭开比特币地址和私匙的面纱。 还不知道什么是比特币地址和私匙的同学请点这里 问题一、比特币钱包由什么组成? 答 我们知道,比特币地址和私匙组成了比特币钱包,而私匙则决定了比特币地址上比特币的归属。 地址和私匙 问题二、如果只记得私匙我们还能还原比特币地址么? 答

    用GO语言实现比特币算法

    用GO语言实现比特币算法

    本节的这个例子展示一点点高精度数学包math/big、一点点散列包hash、一点点加密包crypto,还有一点点测试包testing的知识。这里不介绍bitcoin协议和算法——尽管它们很有趣,而是试图指出,Go对多种操作系统的支持,是实现这种跨平台应用的理想语言。 位钱(bitcoin)是一种使用加密手段制作的分布式电子货币。它最初于1998年由Wei Dai提出,并由中本聪(Satoshi

    详解比特币的找零机制

    详解比特币的找零机制

    比特币的找零机制一直让人有些迷惑,明明只向一个地址发送了比特币为什么 blockchain 上面的显示的有时是1个地址对多个地址,有时是多个地址对1个地址,有时又显示多个地址对多个地址? 为什么比特币资深用户要提醒大家当比特币钱包交易100次以上时再次交易后要重新备份钱包,恢复以前的钱包备份有可能会遭遇损失? 是的,这一切都是因为比特币的找零(Change)机制。本文参考 Bitcoin的维

    玩转比特币客户端之一:C盘转移和加速下载

    玩转比特币客户端之一:C盘转移和加速下载

    C盘空间不足?交易数据下载速度太慢?别着急,乐享比特币教你轻松玩转比特币官方客户端。 所有新人开始接触比特币时做的第一件事情大多数是安装比特币的官方客户端。 安全起见大家最好直接访问官方发布渠道sourceforge的地址进行下载:http://sourceforge.net/projects/bitcoin/files/Bitcoin/ 该网页列出了各版本的官方比特币客户端,目前

    麦妖榜
    更新日期 2019-06-18
    排名用户贡献值
    1BitettFan23992
    2等待的宿命23809
    3六叶树20309
    4区块大康18727
    5牛市来了16520
    6天下无双16192
    7linjm122716021
    8lizhen00215101
    9让时间淡忘14475
    10冷风大q11188
    返回顶部 ↑