| timezone | Asia/Shanghai |
|---|
- 自我介绍:
我是EthStorage的研究员, 主要研究ZK和Layer2欺诈证明等 - 你认为你会完成本次残酷学习吗?
Yes
Week 1: Protocol Intro
- 以太坊升级主要通过EIP提案
- 执行层和共识层规范,采用python版本更容易理解
- The Merge升级之后包括执行层和共识层
-
执行层:
- EVM执行交易;采用Merkle Patricia Trie存储账户、合约代码等状态数据,;执行层节点之间通讯采用JSON-RPC,;应用层采用JSON-RPC API与执行层交互
- 有多个客户端实现: go_ethereum, reth
-
共识层:
- LMD GHOST (Latest Message Driven - Greedy Heaviest Observed SubTree)作为fork choice rule, 它决定链的最新规范区块;共识层节点有另一个P2P网络来通讯;RANDAO
- 有多个客户端: Prysm, lighthouse等
-
执行层和共识层通过Engine API来通讯
-
- 社区讨论:
- Dev calls
- 论坛: @EthMagicians, @ethresearchbot
- Discord: Ethereum R&D discord(https://discord.com/invite/qGpsxSA)
- Week 1相关资料
Week 1的视频基本看完了, Week2的视频复习了一下。 比较感兴趣的地方在于Merkle tree的更高性能实现,看了下Layer0和MegaETH这方面的解读. 此外对于节点间如何进行P2P通讯也很感兴趣, 打算用python或者go实现一个最简单的版本。
学习Randao 以太坊beacon chain公平选取proposer, committee等都需要随机数。以太坊采用RANDAO机制来产生随机数,它能够累加来自参与方的随机性,每次出块proposal都会给RANDAO值混入一部分随机性(randao_reveal)。
这个随机数需要足够的公平,不能让proposal任意选取,否则就会给他一定的优势用来作恶。以太坊采用BLS签名作为RANDAO贡献的随机数,BLS有几个特性非常适合作为随机数:
- 签名值可以认为是均匀分布的
- 签名值对于其他验证者而言是无法预测的(由于他们不知道proposal的私钥),但是验证者很容易根据proposal的公钥验证签名
RANDAO更新的算法如下图:
<title>Diagram illustrating updating the RANDAO.</title>
- 关于RANDAO的安全性分析,可以详见Ben Edgington的文章。文中提到还可以结合VRF,让proposer在决定自己提交的值之前确定是否要提交randao_reveal,以消除RANDAO的机会主义偏差。 自己写了一个简单的[RANDAO python代码]
- 还可以参考EthStorage很好的使用RANDAO作为随机源的例子,在链上采用blockhash+proof验证randao
学习了以太坊的网络层相关内容,执行层的网络协议包括两部分:
- 网络发现: 他基于UDP协议,底层基于Kademlia实现的分布式哈希表(DHT),具体的协议实现是discv5。
- 节点通讯: 基于TCP协议,具体使用的是DevP2P
共识层与执行层的网络协议类似,不过使用的是后来开发的libP2P
P2P节点采用Ethereum Node Records (ENR)来进行标记,他主要包括如下信息:
content = [seq, key, value, ...]
signature = sign(content)
record = [signature, seq, key, value, ...]
其中预定义的key包括:
id name of identity scheme, e.g. “v4”
secp256k1 compressed secp256k1 public key, 33 bytes
ip IPv4 address, 4 bytes
tcp TCP port, big endian integer
udp UDP port, big endian integer
ip6 IPv6 address, 16 bytes
tcp6 IPv6-specific TCP port, big endian integer
udp6 IPv6-specific UDP port, big endian integer
学习了RLP编码,他是一种space-efficent的数据表示格式,用来序列化以太坊的交易、收据等数据。 写了一个编码简单数据和decode raw transaction的代码。 https://github.com/dajuguan/lab/blob/d10a269bf17ac14fbce8bfbadfd532b6d25c100a/eth/rlp_test.py
学习了EL和CL的网络层相关内容,EL采用devP2P, 而CL则采用IPFS团队开发的libP2P,这是因为libP2P发展的较晚,以太坊出来的时候libP2P还没有成熟。 以太坊The merge也对libP2P提出了很多升级建议,比如加密算法采用Noise等等 References:
- libP2P和ETH2.0的关系
- Ethereum 2.0's networking wire protocol spec
- Noise protocol
- 陈天 TLS 和 Noise protocol
学习了discv4节点网络发现的kademlia算法,该算法主要解决P2P网络中节点发现的问题。核心要解决:
- 节点ID很多,160bits的hash表空间有2^160这么多节点,那么单级肯定没法存下来,每个节点存那些节点?
- 如何获取到整个网络中的所有节点
核心思想还是用到局部性思想,通过XOR来定义节点之间的距离,每个节点只存储最多k个与自己log距离为i(1≤i≤MAX_BITS)的节点,这样存储空间只需要k*MAX_BITS。距离函数满足几个特性:
- distance(x,x) == 0
- distance(x,y) > 0
- distance(x, y) + distance(y, z) ≥ distance(x, z)
- distance(x, y) == distance(y, z) 距离可互换 为了保证查找节点收敛,每次向自己已知的节点查询与目标节点最近的k个节点,根据返回值再递归地查询,这样保证每次log距离减小1或者不变,用O(log(MAX_BITS))查询就能找到网络中的任意目标节点。非常高效而简单的算法!
需要注意的是,每个节点在所有log距离为[1,2,MAX_BITS]的各个buckets中至少要存储一个节点,否则有可能导致距离不会减小,没法迅速收敛。
用python实现了kademlia算法
学习EVM状态转换这一节,发现除了正常的用户交易外,在处理用户交易之前或者之后有几个特殊的系统合约调用,具体顺序如下: 1.调用EIP4788系统合约生成beacon root的context 2.调用EIP2935生成历史blockhash 3.处理所有正常交易 4.分别调用EIP-6110, EIP-7002,EIP-7251这三个系统合约
这几个系统合约在write的时候会使用特殊的SYTEM_ADDRESS=0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE作为caller来设置相应的合约storage,读取的时候则是任何用户可以读取合约的状态。具体可见geth相应的处理代码。
Ref:
玩了下EIP4788测试代码: https://github.com/dajuguan/lab/blob/main/eth/contracts/test/EIP4788.t.sol
阅读了EL优化相关内容,尤其是Georgios的推文和reth perf,主要有几个方面优化:
-
JIT编译:2x 提升
-
Parallel EVM: 2x提升(文中是5倍,但是实际上由于状态依赖会更小)
-
State commitments: 2-3x提升
- 并行计算account的state tire
- prefetch不改变的中间trie nodes
-
DB优化
学习了下EIP2930 access list相关内容,它定义了一种新的交易类型,可以在交易中定义需要访问的address和storage slots, 从而对相应数据进行预取,以节约gas开销。gas主要分为两部分:
- prewarm的冷数据读取gas消耗相比于直接SLOAD或者CALL更低
- warm数据读取gas只需要100 以前(EIP2929):
- COLD_SLOAD_COST 2100
- COLD_ACCOUNT_ACCESS_COST 2600
- WARM_STORAGE_READ_COST 100 现在(EIP2930):
- ACCESS_LIST_STORAGE_KEY_COST 1900
- ACCESS_LIST_ADDRESS_COST 2400 总体下来一个slot数据读取会节约200gas。
同时写了相应的forge合约来测试
Refs:
- https://eips.ethereum.org/EIPS/eip-2930
- https://www.rareskills.io/post/eip-2930-optional-access-list-ethereum
- 学习merkle patricia tree这种压缩前缀树的实现机制
- 学习merkle patricia tree这种压缩前缀树的实现机制,并写了个简单的demo
学习以太坊的数据结构,主要包括5种DB:
- ethdb: 定义了持久层的接口和leveldb/pebbledb/memorydb/remotedb对其接口的具体实现
- triedb: 介于trie和持久成之间,包括两种后端
- hashdb: key为node的hash,value为node的值
- pathdb: key为节点的path
- statedb: 在执行一个区块交易的时候需要从ethdb或triedb中读取contract和storage trie,他提供了一个thin layer来读取这些数据
- 生命周期为一个区块
- L2魔改EVM主要就是要修改statedb
- rawdb: 可以看做KV数据的schema,他定义了实际的数据在DB中的key具体是如何定义的
var CodePrefix = []byte("c") // CodePrefix + code hash -> account code
// codeKey = CodePrefix + hash
func codeKey(hash common.Hash) []byte {
return append(CodePrefix, hash.Bytes()...)
}
// ReadCodeWithPrefix retrieves the contract code of the provided code hash.
func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte {
data, _ := db.Get(codeKey(hash))
return data
}
- 仔细研究了
pathdb和hashdb的区别 - path-based state scheme的具体实现和benchmark
- 继续深入学习
pathdb和hashdb的相关代码- hashdb实际上存的是hash=> value,所以实际上不同合约地址如果存储相同的storage,那么他们有可能会引用相同的key及相应的数据(即使他们的contract地址不一样!,因为不是按照前缀地址存储数据的),这样导致不好删除数据,即使一个合约destroy了,他内部的hash对应的数据可能被其他合约引用,导致没法直接删除,所以即使是full node也可能会保存不需要的过期数据(比如hash对应的parent root已经被gc了)
- pathdb则在具体的合约上加了contract address前缀,这样不同的合约确保不会引用相同的hash对应的数据,更容易prune,所以在内存和实际数据库中(fullnode)只需要维护一个trie即可
- 因此pathdb没法做archive node,因为他需要维护所有的增量数据,这个成本很高
- 学习了EVM合约的contract storage slot的基本原理,及其相应的key存在哪里
- Solidity layout and access of storage state variables simply explained