本篇主要是扫盲,解决一些基础概念问题,对以太坊或者BCOS有了解的可以直接略过,BCOS是在以太坊的基础上修改而来。
一:哈希
哈希是BCOS中最重要的概念,通过给定的哈希算法h=h(x)可以将一个得到一个哈希值h,该输入是不可逆的,哈希在数学上是唯一的,因此它可以作为某个对象的全局唯一标识符,因此如果我们将一个哈希值和它对应的对象以[k,v]的形式保存在键值对中,可以很方便的索引到所需要的对象;哈希的另外一个特点是不可逆的,即不能通过h反推出它的对象x的值。
在BCOS中,哈希函数的实现在\libdevcore\SHA3.cpp中,我们不关心它的具体实现,它计算哈希的函数就是简单的sha3(),返回一个h256类型的值。
需要关注的是和哈希有关的几个数据类型,即以h开头的如h2048,、h1024等等,这是BCOS里专门为哈希散列值准备的容器,在\libdevcore\FixedHash中实现,FixedHash类重载了相应的操作符,我们可以像使用内置类型一样使用h+数字的这类型的结构;这些结构的意义也很容易了解,如h64就是指64位的哈希散列值,这里的数字代表二进制数目,而不是字节数,这点要注意;该类还提供了哈希类型(h+数字)的vector容器和set容器。
在之后我们会经常遇到Address这个数据类型,它代表BCOS当中的地址,只要时刻记得它就是一个h160就可以了。
二:RLP编码
RLP也是BCOS中极为基础和重要的机制,其定义可以参见以太坊的RLP
这种编码格式可以将任意嵌套的字节数组变成一个无嵌套的字节数组bytes,它可以线性的表示任意数据,另外该编码方式是可逆的。需要指出BCOS中,大多数情况下,数据都是编码成RLP之后才进行保存或者哈希的。
这里需要注意一个数据类型byte,在libdevcore\common.h中定义,它是一个uint8_t就是一个字节,该文件还定义了bytes,它实际上是一个vector<byte>
。跑一下题,这个文件中还定义了许多名字,其中最为重要的是u+数字的类型如u128、u64等,这里的数字表示的也是位而不是字节,u代表的是无符号,以后我们会经常遇到这个类型。还有一个重要的数据类型vector_ref,定义在\libdevcore\vector_ref中,该类可以当成一个vector来用。
回到RLP,RLP的实现在\libdevcore\RLP,里面比较重要的俩个类是RLP类和RLPStream类,RLP类用来读取RLP格式的字节数组,RLPStream则提供了重要的函数append,该函数可以将给定的数据追加到字节流,该函数的重载版本几乎覆盖可能涉及的需要写入流中的数据类型,也就是说这俩个类,一个负责生产RLP字节流,一个负责读RLP字节格式;RLP类为自己提供了一个迭代器,我们可以像使用容器一样使用RLP。
RLP既然是双向的,那么RLP数据也一定可以还原成原始数据;该类重载了相应的操作符,最重要的是提供了允许数据的转换的方式,如explicit operator std::string() const { return toString(); }
,就允许将RLP格式的数据转换成字符串。图中所示的bytesConstRef实际上就是一个vector_ref<byte const>
其实我们可以不必关注其实现细节,直接把它当做内置类型使用就可以了。
三:账户
BCOS中账户是极为关键的概念,账户分为俩类:一类是外部账户,外部账户代表着外部代理人的,如节点,机构等;一类是合约账户,合约账户就是指合约。实际上这俩类账户对于BCOS来说是讲的是一个东西,它们指的都是Account类,该类在\libethereum\Account中实现。可以将账户理解成一个状态的集合。
账户主要包含几个重要的成员:
u256 m_nonce;//随机数
u256 m_balance = 0;//余额
bytes m_codeCache;//合约代码的缓存
h256 m_storageRoot = EmptyTrie; //存储
std::unordered_map<u256, u256> m_storageOverlay;
h256 m_codeHash = EmptySHA3;//合约代码的哈希值
其中由于BCOS抛弃了代币的概念,所以余额的这个字段在这里无用;随机数是对账户状态的一个计数器,保证每一笔交易只能被处理一次,如果账户是一个外部账户,那么随机数代表的就是此账户地址发送的交易的序号,如果是一个合约账户,该数字代表的就是创建合约的序号;合约代码缓存是指如果该账户是合约账户则合约的代码就保存在这里,存储那一部分中m_storageRoot而是一个MPT树的树根,m_storageOverlay键值对才是真正保存账户状态的地方,需要注意的是,树根将被保存在一个状态数据库中,如果我们想要找到这个账户的状态,需要从状态数据库中读取树根,顺着MPT树来找。
在这里需要解释的多一点,当我们通过状态树,找到了某个账户的m_storageRoot。并不意味着我们就找到了该账户保存的数据了,m_storageRoot本身就是一颗树,想要都该账户里保存的数据,还要通过m_storageRoot这颗树再去寻找,至于状态树,那是下一章的内容了~
四:MPT
MPT(Merkle-PatriciaTrie)贯穿了整个BCOS。BCOS中的MPT树,我更愿意将其理解为一种[k,v]的实现方法,我们知道在BCOS中大多数的数据都是以键值对的方式存储的,比如说我们想要找到一个账户,那么我们需要知道该账户的地址,这就是键,与其对应的账户内容,就是值。在有很多个地址的情况下,怎么快速找到我们要的那个地址,进而得到它对应的值,最笨的方法是挨个遍历。BCOS采用的方法是用MPT,它既是默克尔树也是紧凑前缀树,在\libdevcore\Triexxx中实现了这个结构,当我们在介绍BCOS具体的区块数据组织形式时可能会与它打交道,这里先不管。
先来说什么是前缀树,如图:
这就是前缀树,每个节点所携带的数据由该节点的位置来体现,子节点共享父节点的前缀,在图中键标注在节点中,值标注在节点下,在计算机中以键值对的方式保存,如[ab,I]就表示键为ab而值为I,图中的要保存的数据是I am Tire !。
紧凑前缀树是对前缀树的改进,如果一个节点有唯一的子节点,那么就与父节点合并,上图基于紧凑前缀树的表示如图: