北京大学肖臻老师《区块链技术与应用》公开课-ETH

ETH部分

北京大学肖臻老师《区块链技术与应用》公开课笔记-BTC

14-ETH-以太坊概述


  • 改进

    • 提高性能:以太坊出块时间十几秒,为此设计了GHOST机制

    • mining puzzle:

      • 对内存要求很高:ASIC resistance
      • proof of work -> proof of state(2.0)
    • 对智能合约的支持:smart contract

      • 比特币:去中心化的货币
      • 以太坊:增加了去中心化合约的支持
      • 从技术手段做到不可能违约
  1. 数据结构和数学机制
  2. 挖矿算法:POW, POS
  3. 智能合约

15-ETH-账户


账户模式

  • 比特币(与人们交易习惯不符)

    • 基于交易的账本,存在double spending attack
    • 收到的币,必须一次全花出去,需要换到新的地址,否则就变旷工费了
  • 以太坊

    • 基于账户的模型,存在replay attack

      • 防范:记录发布交易次数nonce
    • 不用说明币的来源

    • 账户类型

      • 外部账户 external owned account

        公私钥控制

      • 合约账户 smart contract account

        代码、状态。合约账户不能调用外部账户

        以太坊的账户希望保持账户稳定


16-ETH-数据结构


目标:

实现账户地址到账户状态的映射
a d d r e s s → s t a t e address \to state addressstate
​ add:160bits 表示为40个16进制数

​ 状态:外部账户和合约账户的状态

  • 实现方法

    • 哈希表:直接在哈希表中查询余额(不行)

      • 如何证明余额?

        得把哈希表内容组合成merkle tree,新交易产生时,又得改变整个merkle tree,比特币基于交易,每个块最多4000个,所以可以。但以太坊基于账户

    • Merkle tree(不行)

      • 问题

        Merkle tree没有一个快速查找更新的方法

        不同排序组建的树不一样,根哈希值也不一样,怎么办?

        • 比特币不用排序:

          最后获得记账权的那个节点算

        • 以太坊

          不能不排序,因为账户是不可能一改就发,是存在本地的

          排序,sorted merkle tree?生成新账户,排序会被打乱。插入代价太大。

数据结构(MPT)

长度都是40,分支最大为16

tree:字典树

  • 特点:
  1. trie中每个节点的分支数目取决于Key值中每个元素的取值范围
  2. trie查找效率取决于key的长度。
  3. 理论上哈希会出现碰撞,而trie上面不会发生碰撞。不同地址肯定就不会碰撞
  4. 给定输入,无论如何顺序插入,构造的trie都是一样的。
  5. 更新操作的局部性好
  • 缺点

    过于稀疏

Patricia trie(Patricia tree)

  • 特点

    把冗余的几个单词和在一起,如果重新插入了一个新单词,原来的可能要拓展

MPT

Merkle Patricia tree

  • 与PT的区别

    把普通指针换成了哈希指针,最后就会有一个根哈希值

Modified MPT

以太坊采用的数据结构
在这里插入图片描述

每次产生一个新的区块,新建一个MPT,但新MPT只会新增修改内容,其余部分以及原来内容都保留,然后引用未修改部分。

  • 为什么要保留历史状态?

    不破坏哈希指针

    防止分叉导致回滚时,无法退回到上一状态

在这里插入图片描述


Block Header

  • 状态树:root
  • 交易树:TxHash
  • 收据树:ReceiptHash

在这里插入图片描述

Gaslimit:区块中所有交易能够消耗的汽油上限

类比比特币对一个区块大小1M的限制


区块结构

在这里插入图片描述

最后说明
状态树中保存Key-value对,key就是地址,而value状态通过RLP(Recursive Length Prefix,一种进行序列化的方法)编码序列号之后再进行存储。


17-ETH-交易树和收据树


  • 目的

    有利于快速查询执行结果

  • 结构

    交易数和收据树都是MPT

  • 与状态树区别

    都是只记录这个区块内的交易和收据,而状态树是全部的,不同区块会共享节点

  • 查询

    过去10天所有众筹、发行新币时间

    • bloom filter

      • 支持比较高效的查找

        给大的集合计算出一个摘要,比如给每个元素取哈希值,然后做成一个向量。要查询是否在里面,给元素取哈希,看向量里有没有值就可以了,这样在里面绝对不会查不到。但不在里面有可能因为哈希碰撞而误报。

      • 删除元素

        无法删除,万一有哈希碰撞,就多删了别人的了

    先查每个区块的块头里面的bloom filter里面有没有,然后再看每个交易里面的bloom filter里面有没有。

  • 运行过程:交易驱动的状态机(transaction-driven state machine

    确定性的状态转移
    在这里插入图片描述

18-ETH-GHOST协议


出块时间10几秒,出块时间过高带来了一些问题。

  • centralization bias

    虽然挖出块的概率一样,但大算力矿池成为最长合法链的概率更高

  • uncle block

    每个区块可以维持两个uncle区块,获得7/8的出块奖励,自己获得1/32奖励。

    不用检查叔叔区块交易的合法性,因为不会执行。只差是否符合挖矿难度要求。

  • 实际GHOST

    解决系统临时性分叉

    真叔叔7/8,每上一代叔叔少1/8,最小2/8。要求7代以内有共同祖先。但不管哪一代,自己都是1/32。

    但只能是分叉后的第一个区块,否则分叉攻击的风险会大幅下降

  • 奖励

    • block reward

      3个ETH。

    • gas fee


19-ETH-挖矿算法


  • ASIC RESISTANCE

    算力强但访问内存性能差距不大,因此常用的方法为Memory Hard Mining Puzzle,即增加对内存访问的需求

莱特币挖矿算法基本思想

  1. 设置一个很大的数组,按照顺序填充伪随机数。

    因为哈希函数的输出我们并不能提前预料,所以看上去就像是一大堆随机的数据,因此称其为“伪随机数”。

    Seed为种子节点,通过Seed进行一些运算获得第一个数,之后每个数字都是通过前一个位置的值取哈希得到的。
    可以看到,这样的数组中取值存在前后依赖关系

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zhP2KNcK-1623678078250)(Untitled.assets/20200228171425644.png)]

  2. 在需要求解Puzzle的时候,按照伪随机顺序,从数组中读取一些数,每次读取位置与前一个数相关。例如:第一次,从A位置读取其中数据,根据A中数据计算获得下一次读取位置B;第二次,从B位置读取其中数据,根据B中数据计算获得下一次读取位置C;

    在这里插入图片描述


以太坊

  • 数据集

    以太坊中,设计了两个数据集,一大一小。

    小的为16MB的cache,大的数据集为1G的dataset(DAG)。其关系为,1G的数据集是通过16MB数据集生成而来的。

    • 为何设计一大一小?

      轻节点保存16MB的Cache进行验证即可,而矿工为了挖矿更快,减少重复计算则需要存储1GB大小的大数据集。

      在这里插入图片描述

  • 生成

    • 小的数据集

      和莱特币一样,也是从前往后取哈希

    • 大的数据集

      每个位置的元素可以独立生成,方便轻节点验证

      大的数组中每个元素都是从小数组中按照伪随机顺序读取一些元素,方法同莱特币中相同。如第一次读取A位置数据,对当前哈希值更新迭代算出下一次读取位置B,再进行哈希值更新迭代计算出C位置元素。如此来回迭代读取256次,最终算出一个数作为DAG中第一个元素,如此类推,DAG中每个元素生成方式都依次类推。
      在这里插入图片描述

  • 挖矿过程

    根据区块block header和其中的Nonce值计算一个初始哈希,根据其映射到某个初始位置A,读取A位置的数及其相邻的后一个位置A’上的数,根据该两个数进行运算,算得下一个位置B,读取B和B’位置上的数,依次类推,迭代读取64次,共读取128个数。

    最后,计算出一个哈希值与挖矿难度目标阈值比较,若不符合就重新更换Nonce,重复以上操作直到最终计算哈希值符合难度要求或当前区块已经被挖出。

    在这里插入图片描述

以太坊伪代码

  1. 生成cache

在这里插入图片描述

  1. 通过cache生成1G的大数据集

    基本思想,按照伪随机顺序读取cache中256个数

在这里插入图片描述

在这里插入图片描述

  1. 矿工挖矿,轻节点验证

    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

20-ETH-难度调整


前文中介绍了比特币难度调整是每隔2016个区块调整难度,从而达到维持出块时间10min的目标。而以太坊则与之不同,每个区块都有可能会进行难度调整。以太坊难度调整较为复杂,存在多个版本,网络上存在诸多不一致,这里遵循以代码逻辑为准的原则,从代码中查看以太坊难度调整算法。

以太坊难度调整

在这里插入图片描述

在这里插入图片描述

出块间隔除9,然后向下取整

难度炸弹

从旁观者角度来看,挖矿消耗了大量电力、资金等,如果转入放弃挖矿,必然是一件好事。但从矿工的角度,花费了很大精力投入成本购买设备,突然被告知“不挖矿了”,这必然是一件很难接受的事情。而以太坊本身为一个分布式系统,其转入POS必须经过系统中大多数矿工认可才行,如果届时矿工联合起来转入POS,那么这一设计初衷就成了一江流水。
因此,以太坊在设计之初便添加了难度炸弹,迫使矿工转入POS。那么 如何促使矿工自愿升级软件,而非坚持POS呢?

在这里插入图片描述

在上面难度炸弹的公式中,有人应该注意到了第二项中的fake block number,该数仅仅为对当前区块编号减去了三百万,也就是相当于将区块编号回退了三百万个。那么,在前三百万个区块的时候,这个fake block number就是负数吗?
答案是否定的。实际上,在以太坊最初的设计中,并没有第二个公式。也就是说,最初就是简单地直接用区块编号除以100000。而在转入权益证明时间节点一再推迟后,以太坊系统采取了将区块编号回退三百万个区块的方法来降低挖矿难度,当然,为了保持公平,也将出块奖励从5个以太币减少到了3个以太币,这也是fake block number这一项出现的原因。

以太坊发展

在这里插入图片描述

具体代码实现

  1. 难度计算公式
    bigTime为当前区块时间戳,bigParentTime为当前区块的父区块时间戳。

在这里插入图片描述

  1. 基础部分计算

    在这里插入图片描述

  2. 难度炸弹计算

    在这里插入图片描述

    为什么不是减去3000000,而是2999999?
    因为这里判断的父区块号,而公式中是根据当前区块来算的。


21-ETH-权益证明


  • 思考:显而易见,“挖矿”过程消耗了大量的电力资源,这些能耗是必须的吗?

    矿工挖矿是为了取得出块奖励,获取收益。而系统给予出块奖励的目的是激励矿工参与区块链系统维护,进行记账,而挖矿本质上是看矿工投入资金来决定的(投入资金买设备->设备决定算力->算力比例决定收益)。
    那么,为什么不直接拼“钱”呢?现状是用钱购买矿机维护系统稳定,为什么不大家都将钱投入到系统开发和维护中,而根据投入钱的多少来进行收益分配呢?

    这就是权益证明的基本思想。

  • POW劣势

    区块链的安全性由外部决定,不是闭环的。虽然市值很高,但相比世界经济总量很小,外部势力可以购买设备攻击该币种。

    POS,只能大量买入加密货币,这样会导致币价大涨,而且币被攻破了,他自己的资金也损失了。

  • POS劣势:双边下注

    如下图所示,区块链系统产生了分叉,存在两个区块A和B竞争主链时,采用权益证明的方法就是所有持币者对这两个区块投入币进行投票,从而决定哪一个区块成为最长合法链上的区块。假如有一个人,在A和B同时进行了下注。最终A区块胜出,那么他能够获得A区块相应收益,而在B区块进行投票放入的“筹码”也会被退还,这也就导致其每次都能获得收益。
    由于一个人可以拥有多个账户,所以我们无法强迫一个人一次只能投向一个区块。而越有钱的人,通过“双边下注”得到的收益也就越多
    在这里插入图片描述

以太坊拟采用的权益证明

以太坊中,准备采用的权益证明协议为Casper the Friendly Finality Gadget(FFG),该协议在过渡阶段是要和POW结合使用的。

  • Casper协议引入一个概念:Validator(验证者)

    一个用户想要成为Validator,需要上交一笔“保证金”,这笔保证金会被系统锁定。Validator的职责是推动系统达成共识,投票决定哪一条链成为最长合法链,投票权重取决于保证金数目。

    实际中,采用两次投票的方式:预投票和Commit 投票,规定每次投票结果都要获得2/3以上的验证者同意。在实际中,针对其进行了一些修改,两次投票在实际中只需要一次即可。

    实际中100个epoch分成了两个50个epcoh,每次只投一轮票,两个都要有2/3通过。

    矿工挖矿会获得出块奖励,而验证者也会得到相应奖励。当然,为了防止验证者的不良行为,规定其被发现时要受到处罚。例如某个验证者“行政不作为”,不参与投票导致系统迟迟无法达成共识,这时扣掉部门保证金;如果某个验证者“乱作为”,给两边都进行投票,被发现后没收全部保证金。没收的保证金被销毁,从而减少系统中货币总量。验证者存在“任期”,在任期结束后,进入“等待期”,在此期间等待其他节点检举揭发是否存在不良行为,若通过等待期,则可以取回保证金并获得一定投票奖励。

22-ETH-智能合约


智能合约:运行在区块链系统上的一段代码,代码逻辑定义了合约内容。智能合约编写代码为Solidity,其语法与JavaScript很接近。

先执行合约还是先挖矿:先执行,不执行怎么得到块头里的根哈希值。

没有挖到矿:无任何收益,但不验证本地三颗树无法更新,以后无法挖矿,


  • 智能合约的账户保存了合约当前的运行状态:

    • balance:当前余额
    • nonce:交易次数
    • code:合约代码
    • storage:存储,数据结构为一棵MPT
  • 下图显示了智能合约的代码结构。

    • contarct 类似于类

    • event

      • 新出的最高价
      • winner 和他的出价
    • mapping:哈希表

      solidity哈希表不支持遍历,用bider数组来记录

      bidders.push(bidder)

      bidders.length

    • 构造函数:constructor

    • Payable:以太坊中规定,如果一个函数可以接收外部转账,则必须标记为payable。

    • fallback函数

      • 匿名函数,无参无返回值
      • 调用情况:缺省的调用fallback函数
        • 向一个合约地址转账而不加任何data
        • 被调用的函数不存在
      • 如果转账金额不是0,同样需要声明payable,否则会抛出异常
  • 调用智能合约

    转账是发起了对合约的调用,具体调用哪一个函数是在data域说明的。如下图中的TXData

    调用方式:

    • 外部账户发起

    • 或者一个合约调用另一个合约

      • 直接调用

        如果A发生错误,会导致发起A的合约一起回滚

        根据A的地址实例化A,然后根据实例化A的对象调用A的方法

      • 使用address类型的call函数

        如果被调用合约发生异常,会返回false,但发起调用的合约还可以继续执行

      • 代理调用delegatecall()

        与call()相同,只是不能使用.value()

        不需要切换到被调用合约的环境中执行。

  • 智能合约的创建和运行

    外部账户发起转账交易到0×0地址,转账金额为0,但是要支付汽油费,合约的代码放在data域里面

    合约代码写完后要编译成bytecode,运行在EVM(Ethereum Virtual Machine)上

    • EVM:寻址空间256位
  • 汽油费(gas fee)

    • 原因:智能合约是个Turing-complete Programming Model,避免其出现死循环

    • 执行合约中的指令要收取汽油费,由发起交易的人支付

      一次性扣完汽油费,多退,不够的话就回滚

      • AccountNonce:交易序号,防止重放攻击
      • Gaslimit:愿意支付最大汽油量
      • Price:单位汽油价格
      • Recipient:收款人地址
      • Amount:转账金额
      • Payload:data域,用于存放调用哪个函数及其参数取值
  • 错误处理:

    原子化处理,一个交易要么完全执行,要么完全不执行。包括转账处理以及对智能合约的调用

    • 出现错误情况
      • 汽油费不够,已经消耗的汽油费不退
      • assert语句:判断内部条件
      • require语句:判断外部条件
      • revert():终止运行并自动回滚
    • solidity无try cathch结构
    • 嵌套调用情况:取决于调用方式
      • 直接调用:连锁式回滚
      • call():不会连锁回滚
    • 转账就有可能调用函数:fallback()
  • 收据:Receipt数据结构

    其中status域表示交易执行情况

  • 智能合约可以获得的信息

    不可以获得系统信息,因为每个全节点的系统都不一样

    注意:下面有的是成员变量有的是成员函数。对于成员变量,括号里的内容表示的是该变量的类型

    • 区块信息

      • block.blockhash(uint blocknumber) returns (bytes32)

        给定区块的哈希(仅对最近的256个区块有效而不包括当前区块)

      • block.coinbase(address):挖出当前区块的矿工地址

      • block.difficulty(uint):当前区块难度

      • block.gaslimit(uint):当前区块gas限额

      • block.number(uint):当前区块号

      • block.timestep(uint):自unix epoch起始当前区块以秒计的时间戳

    • 调用信息

      • msg.data(bytes):完整的calldata
      • msg.gas(uint):剩余gas
      • msg.sender(address):消息发送者(当前调用)
      • msg.sig(bytes4):calldata的前4字节(也就是函数标识符)
      • msg.value(uint):随消息发送的wei的数量
      • now(uint):目前区块时间戳
      • tx.gasprice(uint):交易的gas价格
      • tx.origin(address):交易发起者(完全的调用链)
    • 地址类型

      • <address>.balance(uint256)

        以wei为单位的address地址余额

      • <address>.transfer(uint256 amount)

        向address地址类型发送数量为amount的wei,失败时抛出异常,发送2300gas的矿工费,不可调节

      • <address>.send(uint256 amount) returns (bool)

        向地址类型数量为amount的wei,失败时返回false,发送2300 gas的矿工费用,不可调节

      • <address>.call(...) returns (bool)

        发出底层call,失败时返回false,发送所有可用gas,不可调节

      • <address>.callcode(...) returns (bool)

        发出底层CALLCODE,失败时返回false,发送所有可用gas,不可调节

      • <address>.delegatecall(...) returns (bool)

        发出底层DELEGATECALL,失败时返回false,发送所有可用gas,不可调节

  • 三种发送ETH的方式

    前两者是专门转账的函数,而且由于汽油费固定,别人难以再反过来调用。

    • <adddress>.transfer(uint256 amounnt):会产生连锁回滚
    • <adddress>.send(uint256 amount) returns (bool):不会连锁回滚
    • <adddress>.call.value(uint256 amount)()
  • Q&A

    • 智能合约支不支持多线程处理?

      不支持。以太坊是交易驱动的状态机,每个状态必须完全确定,使得每个全节点运算节点都一样

      多线程如果多个核对内存访问的顺序不同,结果可能不一样。

      产生真正随机数也不支持。


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

第一版问题:transer报错的话,所有人的交易就全回滚,没有人能够收到钱。

比如返回给合约账户,但其没有定义fallback函数,就会回滚

在这里插入图片描述

问题:重入攻击

解决方法:先清零,后转账。你调用合约的时候,对面合约有可能返回来调用你的合约并修改状态

在这里插入图片描述

还有一种修改方法

在这里插入图片描述


img
在这里插入图片描述

在这里插入图片描述

和call()调用基本一致,区别在于其并不会切入被调用合约的上下文中。

在这里插入图片描述

23-ETH-TheDAO


比特币实现了去中心化的货币,以太坊实现了去中心化的合约

DAO: Decentralized Autonomous Organization 去中心化自治组织

  • The DAO:众筹投资资金

    钱的来源通过区块链众筹,以太币发给智能合约发回the dao代币,代币越多投票权重越大,有了收益也是根据代币进行收益分配

  • DAC:… Coporatioin

    出于盈利目的

  • 历史

    the DAO 2016年5月份开始众筹,一个月筹集了1.5亿美元以太币。只存活了3个月

    问题是如何取回以太币,通过拆分DAO,形成childDAO,要取回钱,只能用拆分的方法,比如拆分成只有自己一个人的账户,然后自己投给自己。没有withdraw方法。拆分成子基金后,相应的以太币会打到子基金里,要保存28天之后才能取出来。

    • 问题:split DAO实现:重入攻击

      先转账后清零,巨大的bug

      image-20210614171714442
    • 结果黑客重入攻击,两个月转走了5000万

      • 争论:code is law?

        一个the DAO占了10%,to big to fail

      • 如何补救?

        原则:只能回滚黑客交易,其他交易不能回滚。

        1. 锁定黑客账户

          • 升级ETH:凡是跟the DAO相关的账户,不能做任何交易(软分叉)

            bug:新增了一条语句,没有收汽油费,结果导致大量攻击,软分叉失败

        2. 清退

          通过软件升级的方法,把the DAO账户上的所有资金,强行转到一个新的智能合约上,新的智能合约只有一个功能:退钱。(硬分叉)

          用软件升级的方法,192万个区块,强行重新记账。

          争论十分激烈,最后投票!可以把手里的以太币发到智能合约里进行投票,最后大多数人支持硬分叉。

          有一些矿工依旧在旧链上挖,ETH,ETC。

          ETC: Ethereum Classic

          后来加了chain ID,就彻底分开了。

24-ETH-反思


智能合约的反思

  • 智能合约真的智能吗?

    • 有人称其应该为自动合约,可以视为代码合同

    • 不可篡改性其实是双刃剑,软件更新需要硬分叉来实现,需要向矿工说明理由,但一旦说明理由黑客就会在更改之前抢先发动攻击,冻结账户,终止交易十分困难

    • 无法阻止对有漏洞合约的调用,

  • 没有什么是绝对不可篡改的

  • 语言设计上

    • 重入攻击

      Solidity语言设计上是否有问题?我转账是调用你的函数,你的函数反过来可以调用我,这很反直觉

    • 图灵完备的表达程序是不是一个好问题?

      常用智能合约的功能出现模板,就像各种律师事务所-

  • 开源的好处?

    接受群众监督,增加公信力,开源的代码不容易出现安全漏洞,但为什么还是出现了?

    Many eyeball fallacy:实际上真正有时间和经历看源代码的人非常少。

  • 去中心化?

    以太坊并没有能力强制大家执行新结果,最终是矿工决定的。

    去中心化不是全自动化,不是说不能更改。现实世界根本没法分叉。存在分叉恰恰是民主的体现。

    • 去中心化和分布式不一定是等价的

      去中心化一定是分布式的,分布式的不一定是去中心化的

      状态机不是为了提高速度,而是为了容错,即使有一台出现故障,其他的也可以工作。智能合约是用来编写控制逻辑的

25-ETH-美链


  • ICO:Initial coin offering

    这些发行的代币没有自己的区块链,而是以智能合约的形式运行于以太坊的EVM平台,发行代币的智能合约对应的是以太坊状态树上的智能节点,相当于这个智能合约一共有多少个以太币。代币的转账、发行、销毁都是通过销毁智能合约的函数来进行的。

    每个代币可以制定自己的发行规则,比如一个ETH换100个代币。

    • EOS

      上线之前也是作为以太坊上代币的模型运行

  • 美链是一个部署在以太坊上的智能合约,有自几的代币BEC

运算溢出,导致amount很小,这样账户自己只减了很少的币,却给其他人转了很多币,相当于凭空产生了很多币

在这里插入图片描述

26-总结


  • 保险理赔

    时间最长的是人工审核,而区块链并没有什么好的优势

  • 防伪溯源

    记录源头,以及运输掉包很容易篡改。

  • 信任机制

    网上购物,对方不发货怎么办?中心化世界里可以建立线上和线下的信任关系。

    比特币是去中心化的,但商业模型可以不是去中心化的

  • 不可篡改性

    网上购物的例子

  • 法律保护

    缺乏监管,也意味着缺乏司法保护。

    法律保护和支付手段没有什么直接关系,比如欧洲就没有信用卡盗刷的法律措施,美国不通过州也不一样,所以他们跟技术是没有太大关系的。

    比特币不应用于和已有支付方式进行竞争。

  • 应该用于已有支付方式不是很好的领域,想要支付难度大很多。

    跨国转账,国外开会,请他做一些工作,支付是一个问题。

    缺乏全球流通的电子货币。

    下一代互联网是价值交换网络,使得价值交换与信息传播同等重要。

  • 评价效率的好坏,要在特定条件下看,以及跟其他的比较

    电报在清朝末年传入我国,但老师上大学的时候还在用。当时没有更高效的通讯方式。电报还是比较便宜。

  • 智能合约?

    程序化是个大趋势

    现实世界中的智能合约:ATM机。ATM机会不会出问题?会。智能合约的历史是比较短的,随着时间推移,技术的不断完善,会出现很多短板。

    不要以为去中心化可以解决所有问题。即使不出现黑客盗币,也不一定不会出现问题。

    比如高考:工农兵大学生可能更糟糕。民主可能会有很多弊病,不要认为任何问题都可以通过民主投票的方式解决。

参考博客

北京大学肖臻老师《区块链技术与应用》公开课系列笔记——目录导航页

猜你喜欢

转载自blog.csdn.net/qq_20493631/article/details/117913224