您正在查看: Surou 发布的文章

以太坊核心存储结构分析

以太坊核心存储结构:Merkle-Patricia-Tree(前缀树与默克尔树的结合体),以太坊中的交易树、交易收据树、账户树以及合约存储树均使用该树索引。

该树分为三种类型节点:branch(分支,17个元素的元组)、extension(扩展,2个元素的元组)、leaf(叶子节点,2个元素的元组),因此为了区分 extension 与 leaf 节点,使用 key 的第一个 16 进制字符,其中 0000 与 0001 均代表扩展节点,0010 与 0011 均代表叶子节点,也就是说使用倒数第二位来区分 extension 与 leaf 节点。最后一位的 0、1 分别表示了该 key 原先为偶数个 16 进制字符与奇数个 16 进制字符,也就意味着为 0 时,需要填充另外的0000。

账户在以太坊中的存储

下面来看一下以太坊底层存储中是如何实现账户存储的:

目前以太坊中存在两个账户: 0xa3ac96fbe4b0dce5f6f89a715ca00934d68f6c37 0x0f5578914288da3b9a3f43ba41a2e6b4d3dd587a

通过使用编写的拉取以太坊底层存储的 python 脚本,拉取目前底层存储的账户数据(脚本的具体用法有所改变,详见这里):

其中前一项是以太坊在底层中真正存储的 key,与账户地址的对应关系如下:

也就是说,以太坊存储账户数据的时候,会将账户地址进行一次keccak256 哈希计算

此时,账户树的形状如下:

以太坊中会对节点使用 RLP 编码来存储在底层数据 leveldb 或 rocksdb 中,存储形式为 。所以在上图的槽中,slot 1 实际存储的是 sha3(rlp(leaf1))。

合约在以太坊中的存储

下面来看一下智能合约中的数据是如何存储在以太坊中的: 智能合约中的每一个状态变量都有一个 position,以太坊中每一个 storage slot 为 32 个字节,即 256 位,solidity 编译器会尽量将变量装到同一个 storage slot 中去,对于装不下的,会重新分配 storage slot,遇到 mapping、struct 等类型的变量时,编译器会自动重新分配 storage slot。

该代码存在于账户下,该合约的地址为 0xfe5eeb229738ab87753623a81a42656bcde30a67,contract address = sha3(rlp.encode([creator address, nonce]))

该账户在底层数据库中存储的 key 为

// geth console 环境中
> web3.sha3("0xfe5eeb229738ab87753623a81a42656bcde30a67", {encoding : "hex"})
0x886f7bfb7a4887d716ec4fbb06a8bf35fc1972d2962590248ffe6271e77ac7c1

// python
In [50]: '\xed\xa8}\x9d\xeb\xa5\xbb\xc6O\xa7\'B\xf5\x84"\xaa\xf4f\x9e\xaai)\xe2\xf2_\xa60D\x8a\x0c\x7fJ'.encode("hex")
Out[50]: 'eda87d9deba5bbc64fa72742f58422aaf4669eaa6929e2f25fa630448a0c7f4a'


我们来一一分析:

我们可以看到在相应的位置上分别存储了相应的值, 对于 mapping 来说,其中元素存储的position如下:

sha3(LeftPad32(key, 0), LeftPad32(map position, 0))

所以有:

由于 mapping 中 value 为一个 struct,size 大于 256 位,因此,在存储的位置按顺位加 1,如上图所示。 来看一下,动态数组在以太坊底层存储形式:

其中,在位置 5 存储动态数组的长度,然后以位置 sha3(5) 开始顺序存放数组元素。

--- update ---

mapping中的 key 也可以是 string 形式,假设 mapping(string => string),key = "11", value = "22", 则在 eth 底层中存储的 key 为:

// 3131 代表字符串 "11",后面的 32 个 0 代表 map 的 position
> web3.sha3("31310000000000000000000000000000000000000000000000000000000000000000", {encoding : "hex"})
"0x756ab6158180196289fbd030ff61972bf49c0e51dbf603d0dfaf6b1d3f0e49a6"

// 0x3232...04 是 bytes(string) 在底层的存储表达形式,后面的 04 代表字符串的长度为 2, 前面的 3232 代表真正存储的字符串 "22"
> eth.getStorageAt("0xf8a7e4fb488d5e0426012592c5d66e44dffa6cb7", "0x756ab6158180196289fbd030ff61972bf49c0e51dbf603d0dfaf6b1d3f0e49a6")
"0x3232000000000000000000000000000000000000000000000000000000000004"
key = "1111111111111111111111111111111111", len(key) = 34; value = "2222222222222222222222222222222", len(value) = 31
> web3.sha3("313131313131313131313131313131313131313131313131313131313131313131310000000000000000000000000000000000000000000000000000000000000000", {encoding : "hex"})
"0xeb5b36d98f0c746023b3b0e91319a7ee8cb743f75df9f8ce513c5120487cdac3"

// 最后的 3e 代表 31 个字节长
> eth.getStorageAt("0xf8a7e4fb488d5e0426012592c5d66e44dffa6cb7", "0xeb5b36d98f0c746023b3b0e91319a7ee8cb743f75df9f8ce513c5120487cdac3")
"0x323232323232323232323232323232323232323232323232323232323232323e"
key = "1111111111111111111111111111111111", len(key) = 34; value = "22222222222222222222222222222222", len(value) = 32
// 41 代表字符串长度,为了与小于32个字节的长度区分,这里加了 1,所以算长度时:(0x41-1)/2 = 32 个字节
> eth.getStorageAt("0xf8a7e4fb488d5e0426012592c5d66e44dffa6cb7", "0xeb5b36d98f0c746023b3b0e91319a7ee8cb743f75df9f8ce513c5120487cdac3")
"0x0000000000000000000000000000000000000000000000000000000000000041"

// 对该 value 对应的 key 再次进行哈希,用于存放真正字符串
> web3.sha3("0xeb5b36d98f0c746023b3b0e91319a7ee8cb743f75df9f8ce513c5120487cdac3", {encoding : "hex"})
"0x2b3b0a6d0771d1a8fa6f89276ead655b7a0684e2a22d9290bcf4f8944f05b504"

> eth.getStorageAt("0xf8a7e4fb488d5e0426012592c5d66e44dffa6cb7", "0x2b3b0a6d0771d1a8fa6f89276ead655b7a0684e2a22d9290bcf4f8944f05b504")
"0x3232323232323232323232323232323232323232323232323232323232323232"

转载自:https://ethereum.iethpay.com/ethereum-core-storage.html

VRF可验证随机函数

Why VRF?

场景

在区块链场景中,有的框架会用算法随机产生出块节点与验证节点(如Algorand),甚至解决分叉。按传统的随机算法,按一定的哈希规则随机轮询,选出一个节点来记账/验证。如果这个随机轮询的规则是谁都可以复现的,那么可以推测出将来的某个记账/验证节点,集中攻击它。
为了解决这个问题,就引入了VRF,只有自己能够完成这个哈希过程,而别人只能在他声明之后验证这个过程,防止有人可以提前推测出将来的记账节点。

POS中的权益研磨(Grinding)

(以下来源于以太坊Github上的《Proof of Stake FAQ》)
在任何基于区块链的权益证明算法中,都需要某种机制,来随机从当前活跃验证者集合中选择能够产生下一个区块的验证者。举个例子,如果当前活跃的验证者集合由持有40以太币的Alice,持有30以太币的Bob,持有20以太币的Charlie与持有10以太币的David组成,那么你想让Alice成为下一个区块的创建者的概率为40%,而Bob的概率为30%等(在实践中,不仅要随机选择一个验证者,而是要(随机产生)一个无限验证者序列,只有这样如果Alice不在线的时候,就可以有其他人在过段时间替代她,但是这并没有改变问题的本质)。在非基于区块链的算法中,出于不同的原因也经常需要考虑随机性。
(以下来源Ouroboros白皮书《Ouroboros: A Provably Secure Proof-of-Stake Blockchain Protocol》)
基于PoS的区块链协议最基本的一个问题就是模拟领导者选举过程。为了在股东们之间的选举达到一个真正的随机性,系统中就必须要引入熵(entropy),但引入熵的机制可能会容易被敌手操作。例如,一个控制一群股东的敌手可能会试图模拟协议的执行,尝试不同的股东参与者的顺序以此来找到对敌对股东有力的继续者。这会导致一个叫做"grinding"的致命弱点,敌对参与者可能会使用计算资源来倾斜领导者选举。

VRF的目的

VRF的目的就是要生成随机值,且无法被预测,同时还要可验证,可重放。

VRF是什么?

VRF是可验证随机函数(verifiable random function),一方面具有伪随机性,另一方面它还具有可验证性(输出包括一个非交互零知识证明)

  • 伪随机性
  • 可验证性
    VRF的方式是,实现本地抽签,各个节点自己抽签,如果抽中了之后,大家可以很容易地验证这个结果确实是你生成的。

eg. 假设现在是round 10(第10 轮),节点们可能会轮流抽签,以节点自己的私钥+ 一个全网都知道的随机数(比如是这轮的轮次10)作为输入,生成了一个随机数(0-100);设置一个条件:100 个节点轮流抽签,谁先抽出来的随机数大于10,就是这一轮的打包者。假设5 号节点抽到了11,可是只有5 号知道其他人不知道,因此他在广播这个随机的同时还需要广播一个零知识证明。通过零知识证明,全网只需要通过5 号的公钥就可以验证,接受5 号为这轮打包者。图解如下:

VRF具体的操作流程?

  • 证明者生成一对密钥,PK、SK;
  • 证明者计算result = VRF_Hash(SK,info),proof = VRF_Proof(SK,info);
  • 证明者把result,proof,PK递交给验证者;
  • 验证者计算result = VRF_P2H(proof),True/False = VRF_Verify(PK, info, proof)

True表示验证通过,False表示验证未通过。所谓的验证通过,就是指proof是否是通过info生成的,通过proof是否可以计算出result,从而推导出info和result是否对应匹配、证明者给出的材料是否有问题。

抽签有没有必要用VRF?

相比随机预言机

  1. 普通哈希Hash(a)=b,所有人都可以重现,检验正确性;
  2. VRF是Hash(SIG(sk, a))=b,别人无法复现这个过程。但是可以拿b,pk,和中间信息验证b是跟a对应的。

    相比非对称加密

  3. 在密码学签名算法中,大都会引入随机性,也就是对相同信息的多次签名会得到不同的签名值,因此矿工可以不断对相同的输入SK和block,计算签名,以满足结果小于D。那么理论上任何人都会成为出块者,只要计算足够多次的签名。
  4. 有些非对称加密方式得到的随机数不是均匀分布的,如RSA
  5. 缺乏零知识,不管使用确定性签名还是随机性签名,都存在个安全隐患。就是一旦将自己的出块凭证公布,任何人都可以公开验证,包括攻击者。那么攻击者可以对出块节点进行攻击,使其不能出块。使用VRFs的方式,矿工只需要公布自己的R表明自己的出块权,当出完块的时候再公布P,那么攻击者就无法在出块之前知道谁具有出块权,因此也就无法实施针对性的攻击。

应用

  1. Consensus:共识算法中安全性
    VRF Sortition,Smart Contracts,例如本体,Cardano,Dfinity,Algorand等,不同点在于如何产生输入以及输出怎样用。VRF的返回结果可以用来公开或私密地完成节点或节点群体的选择。eg. Dfinity利用mod操作来唯一,公开的确定一个group。Algorand,Ouroboros Praos是私密选择,即计算出哈希值后,如果哈希值小于某个阈值,节点可以私密地知道自己被选中。

本体-VBFT共识算法:

  1. 根据VRF 从共识网络中选择备选提案节点,各个备选节点将独立提出备选区块;
  2. 根据VRF 从共识网络中选择多个验证节点,每个验证节点将从网络中收集备选的区块,进行验证,然后对最高优先级的备选区块进行投票;
  3. 根据VRF 从共识网络中选择多个确认节点,对上述验证节点的投票结果进行统计验证,并确定出最终的共识结果。
  4. 所有节点都将接收确认节点的共识结果,并在一轮共识确认后开启新的共识。

Algorand中:

  1. 先选打包者,选完打包者选委员会,委员会用BA*进行选区块。
  2. 输入值由前一个随机数(最初的随机数是协议给定的)和某种代表高度,轮次的变量进行组合,然后用私钥对之进行签名(或者先签名再组合),最后哈希一下得出最新的随机数。
  3. 条件:①签名算法应当具有唯一性;②避免在生成新随机数时将当前块的数据作为随机性来源之一。

Dfinity中:

交保证金提高门槛,并降低参与节点的数量,然后选打包者,选完打包者选公证人,对区块权重进行排序,选出区块。

Cardano的共识机制-Ouroboros Praos:

在根据Random seed选举slot leader时,通过VRF确保slot leader不被事先计算出来被攻击。


  1. IOST的高效分布式分配片
    使用了VRF来进行领头节点的选举,通过VRF得到随机数之后,会将结果进行广播,然后其他节点会进行统计,得到随机数值最小的作为分片领头节点。是一种交互式的选举方式。
  2. Key Transparency
    密钥管理系统,使消息传递在不相信服务端的情况下做到点对点的安全上的提升。
  3. DNSSEC
    DNS服务的安全性。

参考文献

  1. Randao可证公平随机数白皮书
  2. 一文看懂可验证随机函数VRF
  3. Ouroboros:一个可证明安全的PoS区块链协议 白皮书
  4. Proof of Stake FAQ
  5. 黄祺-区块链中VRF的应用及原理解析 视频资源
  6. Cardano(ADA)的共识算法Ouroboros
  7. 对可验证随机函数VRF的简明解释
  8. VRF wiki
  9. VRF原文
  10. VRF在区块链中的应用

原文链接:https://blog.csdn.net/shangsongwww/article/details/88797403

go.mod file not found in current directory or any parent directory; see 'go help modules'

go env -w GO111MODULE=auto

Besu 基于 Java 的企业级 Apache 2.0 许可以太坊客户端

Besu 是一个 Apache 2.0 许可的、兼容 MainNet 的、用 Java 编写的以太坊客户端。
https://github.com/hyperledger/besu

kaleido chain 测试链部署

由于在调研Algorand共识,所以最近阅读和测试相关的链实现,此篇将主要讲解kaleido的私链部署测试。
由于相关项目基本已经很久不更新,文档社区匮乏,只能阅读代码,反向推出运行参数和步骤。

编译源代码

参考《kaleido 编译与测试》,执行编译相关子程序

make all

最终kaleido\build\bin会生成genesis程序,下面会用到

genesis.json创建

启动私链,首先需要准备bios节点配置
阅读相关代码(github
可以使用genesis程序进行生成,首次直接运行genesis

Hint: You must make a minerkey before making genesis.

提示需要先生成minerkey,继续跟进
阅读相关代码(github

    makeMinerKeyCommand = cli.Command{
        Action:    utils.MigrateFlags(makeMinerKey),
        Name:      "makeminerkey",
        Usage:     "Generates a miner key for mining",
        ArgsUsage: " ",
        Flags: []cli.Flag{
            utils.DataDirFlag,
            utils.MinerStakeOwnerFlag,
            utils.MinerKeyCoinbaseFlag,
            utils.MinerKeyStartFlag,
            utils.MinerKeyLifespanFlag,
        },
        Category: "MINER COMMANDS",
        Description: `
The makeMinerKey command generates a miner key for mining.
If the key already exists, just return it.

可以使用kalgo加上参数makeminerkey进行生成,参考有限文档例子如下

mkdir $PWD/data
export KALEIDO_HOME=$PWD/data
echo $KALEIDO_HOME

./kalgo makeminerkey \
--miner.stakeowner 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0 \
--minerkey.coinbase 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0 \
--minerkey.start 1

0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0修改为自己的地址
执行生成后,返回

MinerKey: 0x39fb25e90000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006400000000000000000000000048f155527f25eb1d4cb2aa32b7e84692aa0025c003ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f534566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645
Details:
    miner = 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0
    coinbase = 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0
    start = 1
    end = 1000000
    lifespan = 100
    vrfVerifier = 0x03ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f53
    voteVerfier = 0x4566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645
GenesisStorage:
    0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2fefd = 0x48f155527f25eb1d4cb2aa32b7e84692aa0025c0000000640000000000000001
    0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2fefe = 0x03ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f53
    0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2feff = 0x4566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645

以上仅为测试数据,对于自己相关私有数据请注意保管

由于cmd/genesis/main.goconsensus/clique/clique.go已写死了0x0e09768B2B2e7aa534243f8bf9AFdC145DdA8EDa为测试数据,所以我们可以修改对应代码,也可以拿测试地址生成minerkey,然后生成genesis.json然后再手动修改对应的地址和数据。
切记替换地址时,(github

contracts.MinerAddress: {
        Balance: common.Big0,
        Code:    common.FromHex(contracts.MinerBinRuntime),
        Storage: map[common.Hash]common.Hash{
            common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8a"): common.HexToHash("0x45ec182edc6774c9a2926172f1fd996e59b58ced000000640000000000000001"),
            common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8b"): common.HexToHash("0xf88a8d844c217531a38d6019ea671652340fe0d899996250bccce13af99933de"),
            common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8c"): common.HexToHash("0x6e8f4a7c7651766722dd7fb9d7a97cd28678a1cefb12631580a7ffe90a910b8f"),
        },
    },

Storage中的数据,要替换成上面执行kalgo makeminerkey返回的数据中的GenesisStorage对应的数据,不然检查不通过(github)会报bad signature

此时按照常规eth启动步骤执行即可,细节不再单独讲解

./kalgo --datadir ./data init genesis.json
./kalgo --config geth.toml  --nodiscover

./kalgo attach ipc:./data/geth.ipc

添加新节点

同上为新节点创建minerkey

mkdir $PWD/data
export KALEIDO_HOME=$PWD/data
echo $KALEIDO_HOME

./kalgo makeminerkey \
--miner.stakeowner 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa \
--minerkey.coinbase 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa \
--minerkey.start 1

返回

MinerKey: 0x39fb25e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000064000000000000000000000000bb93fcc2bb90d46255c43dd398a1a372e12be6aa474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8
Details:
    miner = 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa
    coinbase = 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa
    start = 1
    end = 1000000
    lifespan = 100
    vrfVerifier = 0x474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e
    voteVerfier = 0x35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8
GenesisStorage:
    0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569857 = 0xbb93fcc2bb90d46255c43dd398a1a372e12be6aa000000640000000000000001
    0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569858 = 0x474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e
    0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569859 = 0x35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8
mkdir -p ./data/kalgo/minerkeys
cp ~/.kaleido/kalgo/minerkeys/0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa-0-1000000.bin ./data/kalgo/minerkeys
./kalgo makeminerkey \
--miner.stakeowner 0x10210572d6b4924Af7Ef946136295e9b209E1FA0 \
--minerkey.coinbase 0x10210572d6b4924Af7Ef946136295e9b209E1FA0 \
--minerkey.start 9500


mkdir -p ./data/kalgo/minerkeys
cp ~/.kaleido/kalgo/minerkeys/0x10210572d6b4924Af7Ef946136295e9b209E1FA0-0-1000000.bin ./data/kalgo/minerkeys


myaddr = '0x10210572d6b4924Af7Ef946136295e9b209E1FA0';
hash = eth.sendTransaction({
    from: myaddr, 
    to: '0x1000000000000000000000000000000000000002', 
    gas: 200000, 
    gasPrice: 20000000000, 
    data: 
'0x39fb25e9000000000000000000000000000000000000000000000000000000000000251c000000000000000000000000000000000000000000000000000000000000006400000000000000000000000010210572d6b4924af7ef946136295e9b209e1fa04141f8e905b628d3657b151a62c1414c4cc4f554d24bc16971c8233b761879d09b8f95e47a6f9662b78198c45ff986153c1e3316ede5cbfae09c6f927d374bd2'
});
admin.sleepBlocks(2);
eth.getTransactionReceipt(hash);


personal.importRawKey("8d91c6c7a494a4373...0715","j7OseEMVQ3Trna6gWdkp")
personal.unlockAccount("0x10210572d6b4924Af7Ef946136295e9b209E1FA0","j7OseEMVQ3Trna6gWdkp",0)



nohup ./kalgo --config geth.toml --networkid 1001 --unlock "0x10210572d6b4924Af7Ef946136295e9b209E1FA0" --password ./password --mine --miner.stakeowner "0x10210572d6b4924Af7Ef946136295e9b209E1FA0" --nodiscover --etherbase 0 --verbosity 4 2>> ./geth.log &

personal.unlockAccount("0xf8329ea42489c2164f44a81deb3be22c2c456109","j7OseEMVQ3Trna6gWdkp",0)
eth.sendTransaction({from: "0xf8329ea42489c2164f44a81deb3be22c2c456109", to: "0x10210572d6b4924Af7Ef946136295e9b209E1FA0", value: web3.toWei(25000000, 'ether')})


admin.addPeer("enode://e20d5cb1c726bb5f8bcc5c3b91255e7fcf459add7584cf6becbb99715f2f84334f14233843bb50ce68c71c4f1b6cfeaff3294cec85a488f363428476010651b1@172.31.204.223:32668?discport=0")
admin.addPeer("enode://95b41257c7da4bd0a98e81cb636b833c36693a3ab76ff55987663455b186dac96437d785b7bb6c5d5335bae91402f227d99a395c1dfe31663a891eb6e5d90140@172.31.204.224:32668?discport=0")



./kalgo --datadir ./data init genesis.json
./kalgo --config geth.toml  --nodiscover

./kalgo attach ipc:./data/geth.ipc

参考

https://docs.kaleidochain.io