EarnDefi被盗分析
Earn Defi 单币挖矿合约架构
入口合约
负责单笔挖矿用户接入的账户系统
地址:0x80b0eafa5aaec24c3971d55a4919e8d6a6b71c78
管理员:0x61f942b6eedd9b400aa9780637c84d2770af85b6
当前账户内剩余USDT数量:
282,095.648425891624129973 USDTHECO
248,056,556.021327376723397534 HPT
3,001.664842435153282252 HLTC
3,027.460637161641773855 HFIL
2.528440468150094002 HBTC
22.021496524943978192 ETH
3,030.812388128947611371 WHT
3,041.24777095 HUSD
合约内部用map维护了pool列表,其中USDT的pid为2
交互方法
方法 | MethodID | 备注 | 示例txId |
---|---|---|---|
deposit(uint256 _pid, uint256 _amount) | 0xe2bbb158 | 用户存入指定数量的某种token | 0xe314a2bc03a274f06bfe25f76c0f3ad1c8beadda5a899846c8a4ef905c64b709 |
depositAll(uint256 _pid) | 0xc6f678bd | 用户存入所有的某种token | 0xbd9973852e45cda3839080e8d3c6daff2e9c9db4d5f67eca2b2056719388f7fc |
withdraw(uint256 _bid, uint256 _refId) | 0x441a3e70 | 用户提出指定数量的某种token | 0xb4aa7ee92d21a3b40d5ef3f96ec8646f45c1d47a15a1d183cacf2cfc8e43cddb |
withdrawAll(uint256 _pid) | 0x958e2d31 | 用户提出所有的某种token | 0x94e889b92424a9b879bd01ff3d762ba3580b0993e8bf1089566b79639f8aadcf |
pause?(bool) | 0xe83ddd87 | 管理员可以设置参数暂停存提,true为暂停,false为开放。函数名未确定 | 0x2a9c37e9df8998e4c51f1b7446dee1baeddbb5c087c4687d4db4bc992f4470d2 |
addStrategy(address addr) | 0x223e5479 | 添加币种 | 0x7e957b90142617ead7d349456590c1a262ad2eba2727aa4c503ffd2fe3e1ff83 |
harvestAll() | 0x8ed955b9 | 0x7a5dc22804e51ff0a692b6f3ee0e52069d5fc07c0b1ed4b8f138383ac68065ac | |
unknowed(address) | 0x776d59e4 | 貌似是设置对下层合约进行重新投资的函数 | 0x2efeea460127f3f598f3836a08dacafe55c21d8ba1e99bacff5425f38710a54b |
storage 状态变量
unknown1526fe27 is array of struct at storage 4
struct poolInfo[]:
- erc20Address address
- pid?
- blockNumber
- ?
- 已投额度
- 总计额度
例如:pid为2的USDT
[
'0xa71edc38d189767582c38a3145b5873052c3e47a',
'125',
'9567307',
'10918534050',
'8999854826604473921412569',
'30000000000000000000000000'
]
unknowna267526b is array of addr at storage 5
策略列表
address[] strategys;
sid: 0, addr: 0x447e8a7f84368a762294be9a8a34f606206bde0e //boo 涉及wht的投资策略
sid: 1, addr: 0x40b910d008cebfd433154e52ce343ded3d248378 //Depth 涉及 USDT 11 0
sid: 2, addr: 0xbb8f5d4e826c32085baabb3623cffb1887dd5917 //Depth 涉及 HUSD pid 9 510853.05530818
sid: 3, addr: 0x1843f672f6d60db32b374d0eb3acd0bdd12cd3c1 //boo 涉及HFIL // 空
sid: 4, addr: 0xc19c9adc5fe02a1d91b2721c2e232e1c37527392 //coinwind 涉及HFIL // 空
sid: 5, addr: 0x996b6e9635f862c834cf50403cd33aacbfa4b207 //coinwind 涉及USDT // 空
sid: 6, addr: 0xadc0a8b9e7149d7c6a5bbecf6a4016576e942247 //coinwind 涉及HUSD //空
sid: 7, addr: 0x4f676ec7a3d75f7be2fcbda74df5a31708822aa5 //bxh 的HDOT //空
sid: 8, addr: 0x9aaa9ed5eed70494df66e0540d0bc5512c3938c6 //gHLTC //空
sid: 9, addr: 0x36768a55a260e561291b2fc14f67ddee86fc6fa1 //mdx HT-HUSD //空
sid: 10, addr: 0xf207e6d9f1e59cfaa8358e849cd6b7c8d323e025 //mdx HT-BTC //空
sid: 11, addr: 0xa8a8881edc4660b834c2b9e5add3cb46db1d65fb //mdx ETH-HUSD //空
sid: 12, addr: 0xb3cd38e467139721b319401e5bc06e3930b2216c //分配dev收益mdx
sid: 13, addr: 0xf1c11b4ac327d3e2da24114c183ec8a977ebcfe9 //coinwind HBTC //空
sid: 14, addr: 0xe0d179935161af517fbd5c57e993cce9e65c6137 //分配dev收益mdx //空
sid: 15, addr: 0x55297d6f8fb33beafe5880c3b59ba6cce7c8b13c //coinwind WHT
sid: 16, addr: 0x954e4a0eb2aabdc5a8140a162f6cab649f9fc0c6 //BOO HUSD
sid: 17, addr: 0xe4fdb79bae96a082a9d716b09f0fa8eedfa5ee32 // MDX HBTC-USDT 8 1.827616583664665540个LP/233065.623498707777205998池内总计
sid: 18, addr: 0x96164d9398408b5dc2cd6705ac7b98b231c26c22 //BOO HBTC //空
sid: 19, addr: 0x8a2fc39d821916149f4d7b722fec2be5d2abe825 //BOO ETH //空
sid: 20, addr: 0xc461dd52d605ab22d762d1a019da4342c9d8ad12 //back平台 ETH //空
sid: 21, addr: 0x54556e1aa1092bc614ad39f519fabce66e74b588 //back平台 HBTC //空
sid: 22, addr: 0x5948ad44812e02868aa5c9f018943d35670eaf8f //BOO USDT //空
sid: 23, addr: 0xdccf2926ee39de136ee31b805a5bd7f3d8c58119 //BACK WHT //空
sid: 24, addr: 0x22a411ff3ad2d25fe53ee0143cc36b13f8a9a6cc //分配dev收益mdx //空
sid: 25, addr: 0x8e5dc937013f67e4e506fe2e3d0774b66147404a //Belt.fi ETH
sid: 26, addr: 0x1fb01f73e3bdfd805bf8c8f39e4885d08369906d //Belt.fi HBTC
sid: 27, addr: 0xd16a55ba2b84419c83a671d8992db92d2910acd6 //gHLTC
sid: 28, addr: 0xf49a3ea13728e46204900c87390a45365c465ab6 //mdx HBTC-ETH
sid: 29, addr: 0xd3fcca8a912cd975ae85c57c903f1856a147b2b3 //mdx HETH-USDT 9 0
sid: 30, addr: 0xc1bd204bc1d0c1bf239aaf103b9a407f60056fbf //Belt.fi WHT 有
sid: 31, addr: 0xe2992d7d4f905294639ebe3fd916a2ed4de28a24 //MDX 过度
sid: 32, addr: 0x0ebfe4706f08aea1130e7e7239e06edd45b491d3 //MDX 过度
sid: 33, addr: 0xf02bd1bb3958de33818490f81f3f95a0157dc2cb
sid: 34, addr: 0xecf8b5fd8c1b82f8de88fb061641ad9c291fb885 //FILDA MDX
sid: 35, addr: 0xb4680dbb6a283065d4f45f4c5ab54138182da0ca //DEMETER HDOT (8185 dmHDOT 304dmt)
sid: 36, addr: 0x4d1fc3664298dc1f0ceae114f16a92ddcb659181 // 管理员调用的dmt
sid: 37, addr: 0x8f89467cdfdd96ce9e35aac6825c1ffadd4a684a //借贷平台 20,774.53639379 dmUSDT
sid: 38, addr: 0xd77adc07294df8b0d1a95571889a649c12c744b0 //dmMDX 1,051,236.85964317 dmMDX 11,707.917522113808967497 DMT
sid: 39, addr: 0x0298c2b32eae4da002a15f36fdf7615bea3da047 //HUSD
关于审计
审计方 | 结果 |
---|---|
成都链安 | 只审计了EDC代币的合约 |
CERTIK | 只了LP池内相关的2个合约 |
慢雾科技 | 审计打不开 |
另外:审计报告内的github链接打不开
https://github.com/Wendy-Earndefi/vault
区块链上的随机数生成
区块链中的随机数
在权益证明(PoS)区块链中,随机性对于验证人职责的公平且不可预测分配很重要。
计算机并不擅长随机数,因为它们是确定性设备(相同的输入始终会产生相同的输出)。通常大家在计算机上(例如在游戏应用程序中)所说的 “随机数” 实际上是伪随机的。也就是说,它们依赖于用户或其他类型的 Oracle(预言机)提供的足够随机的种子,例如气象站的大气噪声、你的心律,甚至是熔岩灯,它都可以从中产生一系列看似随机的数字。但是给定相同的种子,将始终生成相同的序列。
然而,这些输入将根据时间和空间而变化,而且不可能将相同的结果输入到全球特定区块链的所有节点中。如果节点获得不同的输入并用它来出块,则会发生分叉。显然,现实世界的无序状态不适合用作区块链随机性的种子。
目前区块链生产环境中使用的随机数方案主要有2种:
- RANDAO
- VRF
polkadot、filecoin、Algonand 采用的是VRF方案。
ETH2.0 采用RANDAO + VDF 方案。
VRF
可验证随机数(Verifiable Random Function)
VRF具有:
- 唯一性(对于同一消息,多次签名均为唯一相同结果的基于公钥签名机制)
- 抗碰撞性
- 伪随机性
VRF的算法主要包含VRFkeygen
, VRFeval
, VRFverify
.
采用VRF生成随机数
可验证随机函数(VRF)是一种数学运算,需要一些输入并产生一个随机数以及该提交者生成该随机数的真实性证明。任何挑战者都可以验证该证明,以确保随机数生成有效。
Polkadot 中使用的 VRF 与 Ouroboros Praos 中使用的 VRF 大致相同。Ouroboros 的随机性对于出块来说是安全的,并且对于 BABE 也运行得很好。它们的不同之处在于,Polkadot 的 VRF 不依赖于中央时钟(问题变成了 “谁控制中央时钟?”),而是取决于它自己的过去结果来确定现在和将来的结果,并且它使用slot
号(slot number
)作为时钟仿真器来估计时间。
具体操作如下:
slot
为6秒时长内的离散时间单位,每个slot
中可包含一个区块,也可不包含。多个slot
可组成epochs
,在波卡中,2400个slot
组成1个epoch
,也就是说每个epoch
时长为4小时。
可验证的随机函数是一个函数,在伪代码中,可以这样表示:
(RESULT, PROOF) = VRF(SECRET, INPUT)
也就是说,对于一些SECRET
,一些可以是公开的INPUT
,结果是一个元组RESULT
和PROOF
,在这里PROOF
可以通过外部观察来验证RESULT
的合法性。
换句话说,进行VRF Roll
会产生一个随机数和一个证明表示您获得了该随机数。并不仅仅是选中了你。
在每个slot
,每个验证者会掷骰子,以下作为VRF函数的输入:
- SECRET:为掷骰子产生结果的指定特殊的key(验证者的私钥)。
- INPUT:如果链中存在的
epoch
少于2个,则它是来自创世块的特定值。如果大于2个,则是过去2个epoch
中所有 VRF RESULT 的hash
。INPUT内还包含slot
的编号。
VRF的输出为:
- RESULT:产生的随机值。将该值与协议中设置的
THRESHOLD
比对,若该值低于THRESHOLD
,则摇出该值的验证者为该slot
的合格的区块生成候选人。该验证者创建并向网络提交区块,区块中需附带RESULT
和PROOF
值。 - PROOF:为该随机值的证明,证明生成该随机值的过程是正确的。
一旦验证者执行了 VRF,RESULT
就会与协议中定义的THRESHOLD
值进行比较。如果RESULT
小于THRESHOLD
,则验证器是该slot
的有效块提议者候选者。否则,验证器将跳过该槽。
钓鱼人(fisherman)- 监视网络的收集人和验证人错误行为的节点,将验证中继链区块。由于非法投掷将产生非法区块,并且由于钓鱼人将在验证人产生的每个区块中访问 RESULT 和 PROOF,因此他们很容易自动报告作弊的验证人。
总结一下:在 VRF 下,每个验证人都会为自己掷出一个数字,并根据THRESHOLD
对其进行检查,如果随机掷出的骰子低于该THRESHOLD
,则会生成一个区块。观察网络并报告不良行为的钓鱼人事后会验证这些投掷的有效性,并向系统报告任何作弊行为(例如,有人尽管掷出的数量超过THRESHOLD
,但仍然假装成出块者)。
精明的读者会注意到,由于这种工作方式,某些slot
可能没有验证人作为出块候选者,因为所有验证人候选者的得分都太高而错过了THRESHOLD
。我们阐明了如何解决此问题,并确保与 Wiki 页面的共识部分 的 Polkadot 出块时间保持几乎一致。
RANDAO
RANDAO是利用经济模式(奖励跟处罚)的方式,促使在公共场域中能产生随机变量
原理很简单,想参加的人把拿钱来抵押,需要产生随机数的人要付钱。所以参加者就可以从中分润,当然不守规矩抵押的钱也就会被没收,利用奖励跟处罚的方式迫使大家都守规矩。详细步骤如下:
首先,会有个收集seed的时间,例如6个block的时间。接着,想参与的人,投入某个数量的ETH到RANDAO这个smart contract(作质押),然后附上secret(某个只有你知道的值s,然后作sha3)。
-
等收集时间结束,就是验证时间。此阶段所有参与着需要把s传入smart contract做验证,smart contract会把s作sha3,去验证是不是跟第一阶段传进来的一致。最终会把验证过的s当作seed去产生随机数。
-
最后,就是产生随机数,然后把随机数传给之前有请求过的contract。然后归还质押的ETH跟利润分给参与者。
此外有几个附加条件:
-
第一阶段若收集到数笔一样的secret,只接受第一笔;
-
第一阶段会规定基本人数,若结束后未到达人数门坎,则此次的产生就失败;
-
若第二阶段需提供s;
- 若未提供,则质押的ETH会被没收;
- 若此阶段有一个以上参与着未提供s,则此次产生失败,并且把没收的 ETH分给有提供s的参与者。且退还请求者所支付的ETH;
RANDAO会在内建在Beacon chain的逻辑中,而不是一个独立的smart contract,但RANDAO有个缺点,就是最后一位可以预测/操纵结果。如下图,因为最后一位可以知道前面的值,所以在最后可以决定要出值或是不出,因此可以操纵结果。(目前epoch是64个slot,而每个slot是6秒,所以epoch约是6.4 minutes)
对于这一问题,以太坊 2.0 将通过 VDF (可验证延迟函数)来解决!
RANDAO + VDF
VDF把RANDAO产生出来的随机数当种子去产生随机数,而且计算时间要够长
VDF 全称为 Verifiable Delay Function (可验证延迟函数)。
其言外之意就是此类行数需要花很长时间来进行计算。
比如,对数字 X 来说,一种 VDF 可以是 X 的连续 6 次平方:
((((((X^2)^2)^2)^2)^2)^2)^2
假如 X=5,那么最终结果就是:
((((((5^2)^2)^2)^2)^2)^2)^2 =(((((25^2)^2)^2)^2)^2)^2 =((((625^2)^2)^2)^2)^2 =(((390625^2)^2)^2)^2 =((152587890625^2)^2)^2 =(23283064365386962890625^2)^2 =542101086242752217003726400434970855712890625^2 =293873587705571876992184134305561419454666389193021880377187926569604314863681793212890625
随着计算的继续,计算的结果将越来越大。一个复杂的 VDF 将需要花费很长的时间才能计算出来,因为对于任何计算机来说,其计算过程都是非常复杂的。这个例子来源于一个真实的 VDF,直到现在它还是在 MIT 的一个时间胶囊密码学谜题的一部分,该 VDF 用了 80 万亿次平方。
那么这有什么意义呢?
首先,在计算最终数字时的延迟(delay)是可以 验证的,我们知道哪些计算机操作是达到结果所必需的,并且能够以合理的精确度确定机器达到结果所需要的时间。
其次,如果要计算出第三级结果,那计算机就 必须 先计算出第一级和第二级的结果 —— 我们无法在多台计算机上并行地进行此计算,因为每个新的输入都依赖于之前的输出,而且每个输出都需要预先确定的计算时间。
如果现在我们用 RANDAO 中的随机数来代替上方 VDF 例子中的数字 X,且如果函数的指数不是 6 而是好几千,并且函数不是使用平方(^2)而是更复杂的函数,那么我们将得到一个完全不同的函数,这个函数会将 RANDAO 的结果转变成另一个完全不一样的随机数,而且要计算出这个结果将需要花上一段时间,不管你拥有多少台电脑。
以太坊随机数生成机制:RANDAO 与 VDF 的完美结合
通过引入这种延迟,并使计算的时间要长于验证者可以通过影响某个随机数而获得利益的时间,我们就可以消除最后一级的随机性偏差 —— 即消除单个验证者可以对 RANDAO 结果产生的最后一点操控。
在以太坊 2.0 阶段,这个 VDF 被定义为 102 分钟时长 —— 超过了一个半小时。当前以太坊基金会正与 Filecoin 等区块链项目合作,资助开发一种针对此计算优化的开源 ASIC —— 这是一种专门用来进行这种计算的微型计算机。该机器将由爱好者、加密货币项目和其他区块链平台甚至验证者来运行,它具有一个小优势,可以第一时间响应 VDF 检查,而且不需要比一般的微型计算机全节点更高的电力成本。
这样一种高度专业化的机器确保了任何其他试图对 RANDAO 重获最后一点影响的人,都必须开发出比当前的 ASIC 效率高 100 倍的机器。开发这种设备将耗资巨大,除非真有某种一本万利的用途;而如果能开发出来的话,几乎可以完全摧毁以太坊。
在以太坊 2.0 阶段,每 6.4 分钟称为一个时间段(epoch)。每经过一个 epoch, RAODAO 就揭晓一次,同时这也意味着每过一个 epoch 我们就得运行一个新的 VDF。每个 VDF 的周期是 102.4 分钟,因此总会有 16 个 VDF 同时运行。VDF 得到的结果即作为随机性种子,用来选定下一组验证者,保证公平性。
。。未完留坑
引用和参考文献
Casper FFG paper总结
介绍
权益证明设计有两个主要的思想流派:
- 基于链的权益证明chain-based proof of stake,模仿PoW机制,并以一系列区块为特征,并通过伪随机赋予利益相关者创建新区块的权利来模拟挖掘。以Peercoin,Blackcoin,和Iddo Bentov为代表。
- 基于拜占庭容错(BFT)的权益证明Byzantine fault tolerant based proof of stake。对拜占庭容错有了三十多年的研究。例如:实用拜占庭容错(PBFT)。 BFT算法通常具有经过验证的数学特性。例如,通常可以在数学上证明只要2/3个协议参与者诚实地遵循协议,那么,无论网络延迟如何,算法都能保证无法出现冲突区块。
我们的工作
Casper the Friendly Finality Gadget(Caper FFG)是一个顶层的提议机制用来解决谁提议区块的问题。Casper负责最终确定这些块,主要是选择一个代表分类帐规范交易的唯一链。 Casper提供安全性,但是活力取决于所选择的提议机制。 也就是说,如果攻击者完全控制提议机制,Casper可以防止最终确定两个冲突的检查点,但是攻击者可以阻止Casper完成任何未来的检查点。
Casper引入了BFT算法不必要支持的几个新功能:
- 问责制:如果validator(验证者)违反规则,我们可以检测到违规并知道哪个validator违反了规则。问责制允许我们惩罚恶意validator,解决困扰基于链的PoS的“无利害关系”(Nothing at Stake)问题。违反规则的处罚是验证人的全部存款。这种最大惩罚是防止违反协议。因为权益证明的安全性是基于罚款的大小,其可以设定为大大超过挖矿报酬的收益,所以股权证明提供了比工作证明更严格的安全激励。
- 动态验证者:我们引入了一种安全的方法使validator set(验证者集合)随时间进行变化。
- 防御性:我们引入了针对长程攻击的防御以及超过1/3的validator掉线的攻击,代价是非常弱的权衡同步性假设。
- 模块化叠加:Casper的设计成叠加层的模式使其更容易实现对现有PoW链的升级。
对此理解
- 问责制:质押32BETH,成为验证节点。验证节点通过下注提议来获取奖励。对于作恶节点进行罚款。
- 动态验证者机制:信标链每个epoch重新选举委员会。
长程攻击 long range attack
工作量证明的弱主观性
- 在工作量证明区块链中我们基于这样一个假设,除非遭遇了 51% 攻击,不可能有其它从创世区块开始并与当前主链存在潜在竞争关系的分支链。而对于一个企图篡位的分支链,要达到当前主链那样的长度,必然耗费巨量的算力。所以对采取工作量证明协议的区块链,最长链原则已经足够解决弱主观性问题了。
无利害关系 nothing at stake
- “无利害关系”问题指的是,权益证明机制中的矿工最佳的策略是在所有的分叉链上进行挖矿,因为签名的制造非常的便宜。validator仅仅是从交易池中取交易,打包到区块中,然后广播出去,仅此而已。所以说,几乎不消耗算力资源来创建一条从创世区块开始的长分支链的能力。而在工作量证明机制上需要付出算力成本,所以不存在此问题。所以Casper需要引入保证金机制来解决经典权益证明协议中做坏事的代价很低的问题。
任何加入到权益证明区块链的新节点都会接收到多条分支链,其中很大一部分长度也相同。由于无利害关系和弱主观性 ,仅仅依凭最长链原则不足以判断哪一条是主链。长程攻击恰恰利用了权益证明协议区块链的这两个特点。
Casper协议
在以太坊中,最初的提议机制是现有的PoW,使Casper的第一个版本成为混合PoW / PoS系统。 在未来版本中,PoW提议机制将被更有效的方式替代。 例如,我们可以想象将区块提议转换为某种PoS循环区块签名方案。
在Casper的这个简单版本中,我们假设有一组固定的validator和一个提议机制(例如,我们熟悉的PoW机制),它产生现有块的子块,形成一个不断增长的区块树。 根据比特币白皮书,树的根通常被称为“创世区块”。
在正常情况下,我们期望提议机制通常在链表中一个接一个地提出区块(即,每个“父”块恰好有一个“子”块)。 但是在网络延迟或故意攻击的情况下,提议机制将不可避免地偶尔产生同一父块的多个子块。 Casper的工作是从每个父块中选择一个子块,从而从区块树中选择一条规范链。
为了提高效率,Casper只考虑checkpoint tree(检查点子树)(图1a),而不是处理完整的区块树。 创世区块是一个检查点,区块树中高度(或块号)为100的倍数的每个区块也是一个检查点。 区块高度为100 * k的区块的“检查点高度”仅为k; 等价地,检查点c的高度h©是检查点链中区块的数量,从c沿着父链一直延伸到根(图1b)。
每个验证人都有押金; 当验证人加入时,其存款是存入的货币数量。 加入后,每个验证人的存款都会随着奖励和罚款而上升和下降。 权益证明的安全性来自存款的大小,而不是验证人的数量,因此对于本文的其余部分,当我们说“2/3的validator”时,我们指的是存款加权分数; 也就是说,一组验证人,其总存款大小占整个validator set的总存款大小的2/3。
validator广播信息需要包含(表1)中四条数据的投票消息数据以及一条签名数据:两个检查点s和t以及它们的高度h(s)和h(t)。 我们要求s是检查点树中t的祖先,否则投票被视为无效。 如果validator : ν的公钥不在validator set中,则该投票被视为无效。以及连同validator的签名信息,我们将以⟨ν, s, t, h(s), h(t)⟩
的形式投票。
**理解:**Casper FFG的这种投票消息,很巧妙地把二步融合到一个步骤里了,本质上它还是跟pBFT, Tendermint里的二阶段投票等价,pre-prepare->prepare, pre-vote -> pre-commit。
我们定义以下术语:
- supermajority link 是一对有序的checkpoint(a,b),也写成
a→b
。至少有2/3的validator(存款额度的2/3)发布了对源a和目标b的投票。 supermajority link可以跳过checkpoint,即h(b) > h(a) + 1
也是允许的。图1c显示了红色的三个不同的绝对多数链路:r→b1
,b1→b2
和b2→b3
。 - 当且仅当两个checkpoint a和b是不同分支中的节点时,它们被称为conflicting,即,它们都不是另一个的祖先或后代。
- 如果checkpoint c是根,或者存在supermajority link c’→c,其中c’是justified checkpoint,则checkpoint c被称为justified checkpoint。 图1c显示了justified checkpoint。
- 如果checkpoint c被证明是合理化的并且存在supermajority linkc→c’,其中c’是c的直接子项,则称其为finalized checkpoint。 等价地,checkpointc是最终确定的checkpoint当且仅当:checkpointc是合理化的,z存在supermajority linkc→c’,checkpointc和c’no conflicting,并且
h(c') = h(c) + 1
。
惩罚
为了防止validator在运行的过程中作恶,Casper制定了一套惩罚机制如下:对于相同的validator,发布了两个不同的投票vote=<v , s1 , t1 , h(s1) , h(t1)> 和vote=<v , s2 , t2 , h(s2) , h(t2)> ,如果存在以下两种情况之一则罚抵押的stake。
-
h(t1) = h(t2):对于同一个目标高度,不能发起两个不同的投票。
-
h(s1)< h(s2) < h(t2) < h(t1):两个投票的投票范围不能存在一个包含另一个。
我们来看一下在合法的交易的情况下,justified 和 finalized 是如何保证 checkpoint 的安全性的。当一个 checkpoint : c 成为了 justified,说明有超过2/3的 validator 支持 c 之前所有的 checkpoint,配合着第一个惩罚条件,在h©有超过2/3的validator唯一支持 checkpoint c。对于 finalized 的checkpoint : f,他会有一个从f出发,连接到f的子节点的 supermajority link。配合第二个惩罚条件,当一个 checkpoint f 成为了 finalized,说明全网超过2/3的 validator 不能发出跨越f的投票,这2/3的 validator 只能对f之后的 checkpoint 进行投票。如图2-2所示:对于任意一个 validator,在投出了 vote1之后就无法投 vote2,因此 f 之前的 checkpoint 将不会被改变,因为任意对于f之前 checkpoint 的投票都无法获得超过2/3的投票。
Casper最值得注意的特性是,如果没有≥1/ 3的 validator 违反两个Casper戒律或惩罚条件之一,就不可能最终确定两个相互冲突的检查点(图2-1)。
如果 validator 违反了任何惩罚条件,则违规的证据可以作为交易包含在区块链中,此时 validator 的全部存款将被拿走作为给证据交易提交者的小“发现者费用”。 在目前的以太坊中,停止执行惩罚条件需要对以太坊的工作量证明区块提议者进行成功的51%攻击。
证明安全和合理的活性
下面我们证明Casper的两个基本属性:可问责的安全性和合理的活性。可说明的安全性意味着两个相互冲突的检查点不能同时最终确定,除非≥1/ 3的 validator 违反惩罚条件(意味着至少有三分之一的存款丢失)。 合理的活性意味着,无论以前发生过什么事件(例如,惩罚事件,延迟区块,审查攻击等),如果≥2/ 3的 validator 遵循协议,那么总是可以在没有任何 validator 违反惩罚条件的情况下完成新的检查点。
假设按权重计2/3的 validator 没有违反惩罚条件,我们有以下属性:
- 如果s1→t1和s2→t2是不同的绝对多数链路,那么h(t1) != h(t2)。
- 如果s1→t1和s2→t2是不同的绝对多数链接,则不等式h(s1) < h(s2) < h(t2) < h(t1)不能成立。
从这两个属性中,我们可以立即发现,对于任何高度n:
- 最多存在一个绝对多数链接: s→t,其中h(t)= n。
- 最多存在一个高度为n的合理检查点。
有了这四个属性,我们转向主要定理。
定理1(问责安全)(Accountable Safety): 两个冲突的检查点a[m]和b[n]不能同时被最终确定。
证明:设a[m](有正当直接孩子a[m + 1])和b[n](有正当直接孩子b [n + 1])是不同的最终检查点,如图3所示。现在假设a[m]和 b[n]冲突,并且不失一般性h(a[m]) < h(b[n])(如果h(a[m]) = h(b[n]),则很明显1/3 验证者违反了条件I)。 设r→b [1]→b [2]→···→b [n]为检查点链,使得存在绝对多数链路r→b [1] ,.。 。,b [i]→b [i + 1] ,.。。 ,b [n]→b [n + 1]。 我们知道没有h(b[i])等于h(a [m])或h(a[m + 1]),因为这违反了属性(iv)。 令j为最小整数,使得h(b[j]) > h(a [m + 1]); 然后h(b[j -1]) < h(a[m])。 然而,这意味着存在从具有小于h(a[m])的纪元数的检查点到具有大于h(a[m + 1])的纪元数的检查点的超大多数链接,这与从a[m]到a[m + 1]的绝对多数链不兼容。
定理2(合理的活性)(Plausible Liveness): 如果存在扩展最终链的子项,则可以始终添加绝对多数链接以生成新的最终检查点。
Casper的分叉选择规则
Casper比标准PoW设计更复杂。因此,必须调整分叉选择。我们修改后的分叉选择规则应该被所有用户,验证器,甚至底层块提议机制所遵循。如果用户,验证者或块提议者改为遵循“始终在最长链上建立”的标准PoW分叉选择规则,则存在Casper被“卡住”的病态场景,并且在最长链上建立的任何区块都不能最终确定(甚至是合理的),除非有一些验证者无私地牺牲他们的存款。为了避免这种情况,我们引入一种新颖的,正确实施的(correct by construction)分叉选择规则:遵循包含最高高度的合理检查点的链。这个分叉选择规则是正确的,因为它遵循合理的活动证明(定理2),它精确地说明总是可以在具有最大高度的合理检查点顶部完成新的检查点。此分叉选择规则将在第3节和第4节中进行调整。
启用动态验证人集合
验证器集需要能够更改。新的验证器必须能够加入,现有的验证器必须能够离开。为了实现这一点,我们定义了一个块的朝代。区块b的朝代是从根到区块b的父节点链中的最终检查点的数量。当一个可能的验证者的存款消息被包含在具有朝代d的块中时,验证者ν将加入朝代d + 2的验证者集合。我们称d + 2这个验证者的起始朝代DS(ν)。
要离开验证者集合,验证者必须发送“提款”消息。如果验证者ν的提款消息包含在具有朝代d的块中,则类似地将验证者将离开具有王朝d + 2的区块的验证者集合;我们称d + 2为验证者的结束朝代,DE(ν)。如果尚未包括提款消息,则DE(ν)=∞。一旦验证者ν离开验证者集合,验证者的公钥将被禁止重新加入验证者集合。这消除了处理单个身份的多个开始/结束朝代的需要。
在结束朝代开始时,验证人的存款被锁定很长一段时间,称为退出延迟(认为是“四个月的区块”),然后才能取消存款。如果在撤回延迟期间,验证人违反任何戒条,则押金将被没收处罚。
我们定义了两个函数,为任何给定的朝代d生成两个验证器子集,前向验证者集合和后向验证者集合。它们被定义为,
注意这意味着朝代d的前向验证者集合是朝代d + 1的后验证者集合
请注意,为了使链能够“知道”它自己当前的朝代,我们需要稍微限制我们对“最终确定”的定义:之前,如果检查点c是合理的并且从c到任何其直接子检查点存在绝大多数的链,则检查点c是最终确定的。现在,最终确定还有一个附加条件 – 只有当绝大多数链接c→c’的投票以及递归证明c的所有绝对多数链接都被包含在c’的子代之前的区块链中时,才能最终确定,(在区块号h(c’)* 100之前)。为了支持动态验证者集合,我们重新定义了绝对多数的链接和最终确定,如下所示:
- 一对有序的检查点(s,t),其中t在朝代d中,如果朝代d中至少有2/3的前向验证者已经公布了投票s→t,并且朝代d中至少有2/3的后向验证者已经公布了投票s→t,则表示具有绝对多数的链。
- 原先,如果c是合理的,并且c→c’中存在绝对多数链接,其中c’是c的子节点,则检查点c被认为最终确定的。我们添加了这样的条件:只有当绝大多数链接c→c’的投票以及证明c合理的绝对多数链被包含在c’的区块链中,并且在c’的子块之前-在区块号h(c’)* 100之前。
前后验证器组通常会大部分重叠;但是如果两个验证者结合基本上不同,这种“缝合”机制可防止在最终检查点的两个孙子具有不同朝代的情况下的安全失败,因为证据包含在一个链中但不包括在另一个链中。有关此示例,请参见图4。
阻止攻击
有两种众所周知的针对权益证明系统的攻击:长程攻击和灾难性崩溃。 我们依次讨论每个问题。
长程攻击
验证者结束朝代之后的退出延迟引入了验证者与客户之间的同步性假设。 一旦验证者联盟撤回了他们的存款,如果该联盟过去很久以前有超过2/3的存款,他们可以利用他们的历史绝对多数来完成相互冲突的检查点的最终确定,而不必担心被没收处罚(因为他们已经撤回了他们的钱)。 这称为长程攻击,见图5。
简单来说,分叉选择规则阻止长程攻击永远不会恢复最终确定的块,并期望每个客户端能以某个常规频率“登录”并获得链的完整最新视图(例如,每1-2个月一次)。 一个最终确定更老区块的“长程修订版”的分叉将被忽略,因为所有客户端都已经看到在该高度的最终块,并拒绝反转它。
(忽略了证明部分的翻译)
灾难性崩溃
假设大于1/3的验证者同时崩溃 - 即,由于网络分区,计算机故障或验证者本身是恶意的,它们不再连接到网络。 直观地说,从这一点来看,不能创建绝对多数的链接,因此无法最终确定未来的检查点。
我们可以通过建立“不活动泄漏”来从这种情况进行恢复,这种泄漏会慢慢消耗任何不给检查点投票的验证人的存款,直到最终其存款规模减少到足以使投票的验证人为绝对多数。 最简单的公式就像“在每个时代,存款大小为D的验证人未能投票,它失去D * p(0 < p < 1)”,但对长时间未完成最终确定的区块使用增加泄漏率的公式以更快地解决灾难性崩溃,可能是最佳的。
这种泄漏的ether可以被销毁或在ω天后返回验证者。 泄漏资产是否应该被烧毁或返还以及不活动泄漏的确切公式超出了本文的范围,因为这些是经济激励的问题,而不是拜占庭式的容错。
不活动泄漏引入了两个冲突的检查点被最终确定而没有任何验证者被削减的可能性(如图6所示),验证者只在两个检查点中的一个上损失金钱。 假设验证者被分成两个子集,子集V[A]对链A投票和子集V[B]对链B投票。在链A上,V[B]的存款将泄漏,反之亦然,导致每个子集在其各自的链上具有绝对多数, 允许最终确定两个冲突的检查点,而不会明确地削减任何验证者(但由于泄漏,每个子集将在两个链中的一个链上丢失大部分存款)。 如果发生这种情况,那么每个验证者应该只是支持它首先看到的最终检查点。
从这些各种攻击中恢复的确切算法仍然是一个悬而未决的问题。 目前,我们假设验证者可以检测到明显的不良行为(例如,不包括证据)并手动创建“少数软分叉”。 这种少数派分支本身可以被视为区块链,与市场中的多数链竞争,如果多数链真正通过串联恶意攻击者来操作,那么我们可以假设市场将偏向少数派。
结论
我们介绍了Casper,这是一种来自拜占庭容错文献的新颖的权益证明系统。 Casper包括:两个削减条件,一个受[11]启发的正确构造的分叉选择规则,以及动态验证者结合。 最后,我们介绍了Casper的扩展(不反转最终检查点和不活动泄漏)来防御两种常见的攻击。
Casper仍然不完美。 例如,完全受损的区块提议机制将阻止Casper完成确认新块。 Casper是对几乎所有PoW链的基于PoS的严格安全性改进。 Casper没有完全解决的问题,特别是与51%攻击相关的问题,仍然可以使用用户激活的软叉进行纠正。 未来的发展无疑将提高Casper的安全性并减少对用户激活的软叉的需求。
未来的工作。 当前的Casper系统建立在PoW区块提案机制之上。 我们希望将区块提案机制转换为权益证明(PoS)。 即使验证者集合的权重随奖励和处罚而变化,我们也希望证明其可靠的安全性和合理的活力。 未来工作的另一个问题是对分叉选择规则的正式规范,考虑到对权益证明的常见攻击。 未来的工作报告将解释和分析Casper的经济激励及其后果。 与阻止攻击者的这种自动化策略相关的特定经济问题证明了不同客户端之间的不一致程度与攻击者所产生的成本之间的比率的上限。
引用和参考文献
Casper the Friendly Finality Gadget [Latest commit 61ae146 on 29 Oct 2017]
【文章翻译】Casper FFG基础
在确定性系统实现随机数-Randao机制
问题
如何在区块链系统中使用随机数
最简单的实现是使用当前状态信息处理后作为随机数的seed使用。例如:在solidity中使用blockhash、时间戳等信息处理后作为seed。
但是矿工有能力操纵这些区块链数据,因此能够间接影响随机数。若随机数包含区块链数据,会使矿工有能力构建对其有利的随机数。
规则
一枚任何人都能参与,大家一起生成随机数的DAO(去中心化自治组织)!首先,需要在区块链中创建一个RANDAO合约,定义参与规则。生成随机数的基本过程可以分为三个阶段:
第一阶段:收集有效sha3(s)
想要参与生成随机数的人需要在指定时间段内(例如,6个区块期间,约72s)向合约C发送m个ETH做抵押并附上sha3(s)的结果,s为参与者自选的秘密数字。
第二阶段:收集有效s
第一阶段后,成功提交sha3(s)的人要在第一阶段指定时间内向合约C发送一笔带有秘密数字s的交易。合约C会对s运行sha3运算并将结果与先前提交的数据进行比较,检查s是否有效。有效s将被保存到种子集合中,最终生成随机数。
第三阶段:计算随机数、退还押金及奖金
-
成功收集全部秘密数字后,合约C根据函数f(s1,s2,…,sn)计算出随机数,计算结果会被写入C的存储并发送到之前请求随机数的其他合约。
-
合约C在第一阶段将押金返还给参与者,并将利润分成相等份作为额外奖励发给全部参与者。收益指消耗随机数的其他合约所支付的费用。
附加规则
为确保RNG不受操纵,同时出于安全和效率考量,合约C有以下附加规则:
-
第一阶段中,超过两个相同sha3(s)按顺序提交时,接受第一个。
-
第一阶段中,设参与人数最低门槛,该时间段内未能收集足够sha3时,该区块高度的RNG失败。
-
参与者提交sha3(s)被合约C接受时,须在第二阶段中披露该s。
- 参与者未能在第二阶段披露s时,第一阶段发送的m个ETH被没收且不提供任何回报。
- 第二阶段有一个或多个s未披露时,此块高度的RNG失败。没收的ETH被均分成等份发予在第二阶段中披露s的其他参与者。其他合约支付的费用予以退还。
drand 分布式随机信标守护进程
ETH2.0 Specs
Casper共识机制
Casper 实现有助于改变以太坊生态系统。其中两个项目的实现分别是 Friendly Finality Gadget (Casper FFG) 和 Correct-by-Construction(Casper CBC)。
Casper FFG 是由以太坊创始人 Vitalik Buterin创立的。Casper FFG 是 Casper 的最初版本,是 PoW/PoS的混合版。然而,由 Vlad Zamfir 创立的 Casper CBC 已经制定了被广泛认为是一种卓越的协议。
ETH2.0 最终采用Vitalik 的 Casper FFG共识协议。
ETH2.0信标链(Beacon chain) 采用Casper共识机制,Casper属于权益证明制(PoS)范畴,除了继承PoS机制低能耗、防51%攻击更安全的优势外,还在现有PoS机制上增加经济惩罚机制,解决PoS机制本身存在的“无利害攻击”问题。
“无利害攻击”是PoS系统的一个经典攻击问题。PoS系统节点持有权益证明(即按币的数量)就可以参与挖矿,权益占比越大负责出块的概率也越大,由于PoS不需要额外付出挖矿的算力成本,挖矿边际成本为0,区块链网络产生分叉的可能性更高。PoS验证者可以选择同时在两条链上进行“投票”,不论最后哪条链成为“最长链”胜出,验证节点都可以获得区块奖励。这和PoW链不同,如图4所示,PoW链不会发生这种现象,因为算力只有一份,而Token同时存在在两条链上。随着时间推移,这种无成本为多条链出块的投票行为会助长更多的网络安全攻击行为,损伤区块链网络本身的安全性。
Casper通过设置惩罚经济模型来解决PoS的无成本攻击问题,确保网络可以正常运转。它的主要机制包括:
-
验证节点需要预先在链上抵押锁定一定数量的以太坊(BETH)作为保证金。
-
验证节点每一轮需要对区块进行“下注”,验证节点需要评估其他节点会“下注”哪个块胜出,同时也“下注”这个块。如果支持正确的链就可以拿回保证金外加区块奖励及区块内的交易Gas费;如果下注没有迅速达成一致,就只能拿回保证金。如果验证节点明显地改变下注,进行“投机下注”,比如先投某个块有很高概率胜出,再投另外一个块,将被信标链严惩, 最坏结果就是没收全部保证金,并从验证节点委员会中剔除,同时验证节点也可以监督其他节点的投注并举报此类投机行为。由于可能造成的经济损失,验证节点会选择对自己最有利的结果进行投注,当全网验证节点数量足够多时,投注机制确保最后的结果分布趋向于收敛,即大多数验证节点都选择的某个高概率胜出的区块作为最终一致性结果。
-
Casper验证节点委员会随机选举验证节点,每隔一个周期进行一次轮换选举,随机指派验证节点负责指定分片内的校验区块出块,确保分片内的验证节点不形成“合谋”,降低网络安全性。
Casper PoS的基本思路是正向奖励诚信、负向惩罚作恶,所以对于验证节点设置非常多的惩罚规则来防止分叉,对于不尽责的验证节点,也存在惩罚保证金的风险。成为Casper 验证节点需要在ETH PoW主链上注册的验证管理器合约上至少质押32个BETH才能运行信标链节点客户端软件。
考虑前期ETH矿工从PoW节点转为PoS验证节点参与度可能低于预期,Vitalik主导了Casper FFG这个PoW/PoS混合挖矿方案,在现有以太坊PoW协议上叠加一层PoS协议,在PoS协议正式稳定前维持PoW挖矿不变,但每通过PoW挖矿产生50个区块触发一次PoS节点区块验证,这样做方便让社区一部分用户可以先参与新PoS链的测试,直到社区顺利过渡到纯PoS主网。
Casper 还有一个纯PoS的竞争版本实现Casper CBC, 由Vlad Zamfir主导,和FFG采用的混合共识不同,CBC从一开始就完全定义成PoS链,理论创新较多,实际部署难度高于FFG,可能ETH2.0不会直接采用。CBC和FFG的目标是一致的,即提高整个区块链系统共识的安全性。技术思想方面,CBC更倾向通过新的共识改进BFT(拜占庭容错协议)不超过三分之一作恶节点数量的限制,容错率降到1/4。Vitalik Butterin主导的FFG更倾向如何将现有的BFT1/3容错协议更好地运用在信标链上。目前来看,Vitalik主导的FFG路线获得了胜出。
总结: merge阶段,ETH1.0 POW出块模式不变,在此基础上包装一层casper进行区块的二次确认,然后通过crosslink提交摘要信息至信标链。
分片技术
分片技术(sharding)其实来源于一种传统概念扩容技术,它指将数据库分成若干片段,以增加处理效率。在区块链中,它指将区块链网络分成多个碎片,每个碎片中包含一定的节点,由这些节点分别处理每个碎片中的事务,以提高区块链整体的性能。
因此,在ETH2.0升级中,以太坊将通过分片技术提高系统并发交易处理吞吐量水平。它将网络划分为64个分片(旧方案中曾预计采用1024个分片),每个分片处理网络中不同的交易,并处于独立的账户空间。
每个分片内的交易结果将由信标链网络上的验证节点委员会指定的验证节点负责验证,验证节点不需要验证分片外的交易,分片每隔一段时间(即时隙Slot,12s)由验证节点产生一个校对区块, 校对区块包含分片内的状态和交易内容,提供可验证信息(如Merkle根)。这也意味着,每过12秒整个网络中就产生64个分片链区块,再加1个信标链区块。
随后,通过交联(crosslink)过程,信标链可以引用不同分片链的内容和状态,获得该分片网络的真实性证明,而不需要去验证分片内每一笔交易,最终信标链验证委员会通过Casper机制对信标链内所有分片链进行校对,随后区块结果获得最终确定性,分片也将通过交联获得信标链对引用的分片区块完成最终确认的状态结果。
ETH2.0将网络分成两部分,左边L1代表信标链网络,右边L2代表分片网络,分片之间空间各自独立,不同的分片由验证节点组成的验证委员会维护,相当于也是多条独立的区块链。信标链通过交联(crosslink)收集分片内的交易真实性证明摘要(Merkle根),ETH2.0分片技术第一阶段就是建立信标链-分片的锚定模型,分片内交易结果的确认要等待信标链的区块结果确认,分片内不支持状态存储。
总结一下,ETH2.0分片技术的阶段1属于链上数据分片范畴,即只是将验证节点随机划分到不同的网络,处理不同的交易数据,分片未涉及处理状态的问题,ETH通过信标链这个核心中枢来负责最终交易状态的共识验证并在区块确认后在各个分片进行同步,信标链同时协调跨分片的状态通信,信标链基于Casper 共识来实现区块的更新, 通过交联技术实现信标链和分片的状态通信。
EVM 2.0虚拟机eWASM
ETH2.0的阶段1“分片链”仍不支持智能合约,但是随着ETH2.0新虚拟机eWASM的推出,这一情况将被改变。新虚拟机推出后,分片链将可以执行智能合约,每个分片链管理一个eWASM,包括账户、合约代码、状态、收据、其他抽象等等,这些ETH1.0拥有的概念将迁移至分片链。这将发生在信标链和分片网络稳定运行之后,分片内增加对智能合约的支持会增加状态存储的开销,以及分片之间跨分片(状态)通信的复杂性。eWASM将应对分片内运行智能合约的挑战,这是可预见的趋势,分片内支持状态操作才能明显提高ETH扩容后的交易性能。
eWASM是针对以太坊EVM推出的WebAssembly子集,EVM最初设计过程强调简单和安全,以保证智能合约运行结果的无歧义和正确性,所以EVM不支持数据类型方面精确度不确定的浮点计算。同时EVM只支持处理通用区块32位8字节数据,不支持64位。EVM本身支持的操作码也较少,只能支持有限的几种编程语言进行智能合约开发比如Solidity/Vyper,并不支持C/C++/Rust硬件及系统级别语言开发,Solidity本身语言在内存泄露、执行速度方面存在很多改进的地方。
WebAssembly(简称WASM)的设计目标是性能和效率,是为Web开发构建的一个高效计算引擎(Web虚拟机),体系架构设计接近传统计算机,支持接近硬件水平的指令,性能非常好,其支持计算机CPU RSIC指令集使得Web应用程序能利用本地计算机的硬件功能提高代码的执行速度,所以兼容多种编程语言,软件代码更加易于阅读和调试,包括Google、Microsoft等传统互联网公司以及Polkadot、EOS、Cardano等知名区块链项目都在陆续采用WASM。
eWASM是以太坊上的WASM, 向后兼容目前的EVM,不支持浮点数操作,其相比EVM最明显的优势是代码执行的速度和效率上的大幅提升,执行速度提升意味着区块每秒可以处理的交易数量TPS(吞吐量)提升,eWASM也支持更多的编程语言种类进行智能合约开发,这些编程语言相比Solidity具有更广泛的社区开发支持资源,包括编译工具、开发人员。
eWASM还有一个“优化”的地方是用eWASM智能合约替代预编译,预编译是EVM字节码的特殊位,通常代表某些通用的特定操作比如签名和哈希计算,EVM评估预编码字节代替评估合约地址内的整段代码,可以减少执行代码的Gas费用,如果不通过预编译,EVM将评估智能合约地址整段代码,效率非常低,同时很多复杂的密码计算将导致很高的Gas成本,有可能超过区块的限制而导致交易无法处理。
但是预编译存在一个缺点就是要如果加入新的操作字节码进行预编译,通常需要网络发生硬分叉,硬分叉因为有争议而较难实现。eWASM可以消除大多数当前的预编译,并用eWASM合约替换,这些智能合约可以通过重写和重新部署,而无需使用硬分叉,来增加更多新的通用操作,这使得eWASM比EVM更具优势。
智能合约如何跨分片调用
分片链A智能合约内抛出链B的调用消息,经过信标链传播在链B完成调用。
此部分调用是异步的过程。
参考链接
Rust笔记(十) 泛型、trait与生命周期
泛型
泛型函数
// 只展示语法形式,add函数编译会报错
fn add<T>(a: T, b: T) -> T
{
a + b
}
结构体泛型
struct Point<T, U> {
a: T,
b: T,
c: U,
}
impl<T, U> Point<T, U> {
fn new(a: T, b: T, c: U) -> Point<T, U> {
Point { a, b, c }
}
}
// 泛型偏特化 只有在具体类型才有new2方法
impl Point<i32, i64> {
fn new2(a: i32, b: i32, c: i64) -> Point<i32, i64> {
Point { a, b, c }
}
}
枚举泛型
enum Some<T> {
A(T),
None,
}
trait
定义
pub trait A {
fn string(&self) -> String {
String::from("impl")
}
}
实现
// 无法为外部类型来实现外部的trait
struct ImplA {}
struct ImplB {}
impl A for ImplA {}
impl A for ImplB {
//重写
fn string(&self) -> String {
String::from("implB")
}
}
trait 约束
// Trait 约束
fn s0(a: impl A, b: impl A) {}
// 要求参数同时实现A和Display的trait
fn s1(a: impl A + std::fmt::Display) {}
// trait bound(trait 与泛型绑定)
fn s2<T: A + std::fmt::Display>(a: T, b: T) {}
// where 子句 trait 约束
fn s3<T, U>(a: T, b: U)
where
T: A + std::fmt::Display,
U: A + std::fmt::Display,
{
// sss
}
返回值为 trait
// 返回只能为同一种类型 trait
fn s4(flag: bool) -> impl A {
// if flag {
ImplB {}
// } else {
// ImplA {} // error
// }
}
泛型 + trait
// where 子句对T进行trait约束
// 两个相同类型相加,要求T实现了`std::ops::Add`的trait
fn add<T>(a: T, b: T) -> T
where
T: std::ops::Add + std::ops::Add<Output = T>,
{
a + b
}
// 两个不同的类型相加,前提可转换,即实现了std::convert::From<U>或std::convert::Into<T>的trait
// 例如: add2(1, 2.0)
fn add2<T, U>(a: T, b: U) -> T
where
T: std::ops::Add + std::ops::Add<Output = T> + std::convert::From<U>,
{
a + T::from(b)
}
fn add3<T, U>(a: T, b: U) -> T
where
T: std::ops::Add + std::ops::Add<Output = T>,
U: std::convert::Into<T>,
{
a + U::into(b)
}
Rust笔记(九) 错误处理
不可恢复错误: panic
panic!("str");
可恢复错误: Result<T,E>
// 定义在标准库
// enum Result<T,E> {
// Ok(T),
// Err(E),
// }
let f = match std::fs::File::open("a.txt") {
Ok(file) => file,
Err(error) => match error.kind() {
std::io::ErrorKind::NotFound => match std::fs::File::create("a.txt") {
Ok(file) => file,
Err(error) => panic!("{:?}", error),
},
s => panic!("{:?}", s),
},
};
// 闭包简写
let f = std::fs::File::open("a.txt").unwrap_or_else(|error| {
if error.kind() == std::io::ErrorKind::NotFound {
std::fs::File::create("a.txt").unwrap_or_else(|error| panic!("{:?}", error))
} else {
panic!("{:?}", error)
}
});
// unwrap()
let f = std::fs::File::open("a.txt").unwrap();
// 如果能打开则返回Ok(x)内的值,如果出错则调用panic
// expect() 与unwrap() 一样但可以附加错误信息
let f = std::fs::File::open("a.txt").expect("无法打开");
传播错误
返回值使用Result<T,E>
来传播错误
// ? 只能用返回值类型Result的函数
// ? 如果执行失败则return Error
use std::io::Read;
fn read_string_from_file() -> Result<String, std::io::Error> {
let mut s = String::new();
std::fs::File::open("a.txt")?.read_to_string(&mut s)?;
Ok(s)
}
Rust笔记(八) 通用集合
Vector
let v: Vec<i32> = Vec::new();
// 用vec! 宏带值初始化
let mut v1 = vec![1, 2, 3, 4];
// 添加
v1.push(5);
// 读取
let c = &v1[1]; // 下标越界即panic
// get方法返回Option<T>
match v1.get(1) {
Some(x) => println!("{}", x),
None => println!("None"),
}
// enum + vector 存储多个类型的值
enum Cell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
Cell::Int(3),
Cell::Float(2.0),
Cell::Text(String::from("str")),
];
String
let mut s = String::new();
let s1 = String::from("ssss");
let s2 = "ssss".to_string();
let s3 = &s1[..];
// push_str
s.push_str(&s3);
s.push('l');
let s4 = s + &s1; // 使用s的所有权
// println!("{}", s); //error
// format
println!("{}", format!("{}-{}-{}", s1, s2, s4));
// string index
for i in s4.bytes() {
println!("{}", i);
}
for i in s4.chars() {
println!("{}", i);
}
HashMap
// 引入Package
use std::collections::HashMap;
let mut table: HashMap<i32, f32> = HashMap::new();
// insert
table.insert(23, 3.0);
// collect
let key = vec![10, 20];
let value = vec![1.0, 2.0];
let mut table2: HashMap<_, _> = key.iter().zip(value.iter()).collect();
// 所有权
// 实现了Copy 就赋值,没实现则移交所有权
// 访问
if let Some(x) = table2.get(&10) {
println!("v:{}", x);
} else {
println!("none");
}
// 遍历
for (k, v) in &table2 {
println!("{}:{}", k, v);
}
// not exist insert
let a = table2.entry(&10).or_insert(&3.0);
println!("{:?}", table2.entry(&10)); // OccupiedEntry
println!("{:?}", table2.entry(&30)); // VacantEntry