区块链概述
前言
Web3(第三代互联网)是基于区块链技术的去中心化互联网形态,旨在重构现有互联网的中心化权力结构,赋予用户对数据、资产及身份的全新掌控能力。其本质是通过区块链技术,实现无需信任第三方中介的价值交换与协作体系.
区块链基本概念
我们一般意识形态中的链是铁链,有铁铸成,一环扣一环.区块链也可以这么理解,只不过他不是有铁铸成,而是由一定的数据结构连接而成,呈现链型结构,这种结构就是链表.区块抽象到计算机语言就是一个对象,一个结构体.
class Block{//区块号private long number;//前一个区块的哈希值private String preHash;//资深的哈希值private String hash;//携带的数据private String value;//创建的时间戳private long time;
}
当链表中每个数据个体是上述区块的时候就构成了一条区块链.
在上面的例子中,我们用来存储打包到区块中的数据变量只有一个value, 如果把value换成一个数组或者更多的变量,这个区块就会变得更复杂,他的功能也会跟着变得更多.
此外,链中的区块被规定是唯一的,即相同的区块号的区块不能以同一个身份在一条链中出现两次,如果出现了,那么链会将其纠正过来.
以上是区块链的节点图,可以说每个节点中都维护着一条区域链,他们达成共识,相互同步数据.
根据节点部署的情况,区块链的链分类通常有三类:
a.公有链的维护节点比较多,节点网络对所有人开放,任何人都可以进行特定的数据访问.目前被广泛接受,认可,有价值的“代币”(token)几乎都是基于公有链的.
b.私有链是变相个人或者某个组织的.
c.联盟链是多个组织团队的节点联合在一起维护的,对组织开放.
共识的作用
如上文所说:每个节点都拥有自己存储数据的地方,节点之间虽然会相互通信,但又彼此不依赖,互不信任.
在这种情况下,各个节点如何保证在互相通信的过程中维护数据的一致性,从而使链上相同区块号的区块只有一个呢?此时就诞生了区块链技术占中的另一个知识点:共识,又称共识机制.
所谓共识,通俗来讲,就是我们大家对某种事物的理解达成一致的意思,比如日常快回讨论问题,又比如判断一个动物是不是猫,我们肉眼看了后觉得像猫,那么我们就认为他是猫,这就是是共识,共识就是一种规则.
对于区域链,我把其比喻为“矿”.产生新区块的行为则可比喻为“挖矿”. 实现“挖矿”行为的代码模块,则可称为“矿工”.
以开会为例子,类比区块链的共识方式
a.参会的人=挖矿的旷工
b.开会=共识算法
c.解决讨论的问题=让自己的账本跟其他节点保持一致
如下图所示, 节点矿工通过某种共识方式算法来解决该节点的账本与其他节点的账本保持一致,以维护同一条区块链.
目前在区域链中,常见的共识算法有如下几种:
- Pow,代表者是比特币(BTC),区块链1.0
- Pos,代表者是以太坊(ETH),区域链2.0
- DPos,代表者是柚子(EOS),区域链3.0
- PBFT拜占庭容错,联盟链中常用.
PoW算法
PoW(Proof of Work)是一种通过计算能力竞争获取记账权的共识算法,矿工需解决复杂数学问题(如哈希碰撞)以生成新区块,其他节点验证后达成全网共识.
- 能源消耗大:POW算法需要大量的计算能力来完成工作量证明,这导致了巨大的能源消耗。
- 硬件要求高:为了进行挖矿,需要专门的硬件设备,这对于一般用户来说不太友好。
- 可能存在51%攻击:如果某个实体掌握了网络51%以上的算力,就有可能对网络进行攻击。
Pos算法
PoS(Proof of Stake, 股权证明) . 股份制的意思,谁的股份多,谁的话语权就大,这和现实生活中股份制公司的股东差不多。 但是,在区块链的应用中,我们不可能真实地给链中的节点分配股份,取而代之的是另外一些东西一一例如代币,让这些东西来充当股份,再将这些东西分配给链中的各节点。
例如,假设某条非虚拟货币相关的与实体业结合的公有链-一汽车链,我们可以把每车主所拥有的车辆数目和他的车价值多少钱来分配股份 (比如规定一个公式:车数X车价值=股份的多少)。
可见,在 PoS 中,股份只是一个衡量话语权的概念。我们可以在自己的 PoS 应用中进行更复杂的实现,比如使用多个变量参与到股份值的计算中。PoS 共识算法以拥有某样东西的数量来衡量话语权的多少,只要节点拥有这类东西,哪怕有一个,也是有话语权的,即使这种话语权很小。
Pos 是由点点币(PPCoin)首先应用的. 项目方通过代币公开销售(如ICO、IEO)向投资者分配初始代币,早期参与者以法币或加密货币换取代币所有权.
在创世区块内写明了股权分配比例
在PoS(权益证明)机制中,铸块(区块生成)通过验证者质押代币的权益权重分配出块权,权益权重由质押数量与时间加权确定(如币龄=数量×质押时长), 被选中则为质押者. 没被选中则通过验证者的方式参与到区块链网络维护中.
PoS链参考传统金融分润机制,将交易费按比例分配给验证者、质押者及治理基金:
- 验证者:主导区块生成,通常收取手续费的主要部分(如70%-80%), 当验证者成功生成区块时,其累计的币龄会被清空,并基于清空的币龄按预设利率分配利息;
- 质押者:通过委托质押共享收益,按质押比例分润剩余手续费(如15%-20%)4;
- 协议金库:预留手续费用于生态治理或安全维护(如5%-10%)
即要做交易的节点 通过转让、交易的方式获取“代币”,再通过 “交易费”逐渐分散到用户手钱包地址中去里.
虽然解决了Pow的问题,但也有一些引入了一缺点:
- 纯PoS机制的加密货币,只能通过IPO的方式发行,这就导致“少数人” 获得大量成本极低的加密货币,在利益面前,很难保证他们不会大量抛售。
- 拥有代市数量大的节点获得记账权的概率会更大,使得网络共识受少数富裕账户支配,而头去公正性
DPos算法
Pow 和 Pos 虽然都能在一定程度上有效地解决记账行为的一致性共识问题,也各有各的像点,但是现有的比特币 Pow 机制纯粹依赖算力,导致专业从事挖矿的矿工群体似予已和比特币区完全分隔,基些矿池的巨大算力俨然成为另一个中心,这与比特币的去中心化思想相冲突、机制虽然考虑到了 Pow 的不足,但依据 IPO 的方式发行代币数量,导致少部分账户代市量自权力也很大,有可能支配记账权。
DPoS(Delegated Proof of Stake,股权授权证明机制)是一种基于选举机制的区块链共识算法.DPoS 引入了“见证者节点”这个概念。见证者节点可以生成区块。DPoS 的选举方式如下:每一个持有股份的节点都可以投票选举见证者节点,得到总同意数中的前 N 位候选者可以当选为见证者节点。这个值需满足:至少一半的参与投票者相信N经充分地去中心化(至少有一半参与投票的持股节点数认为,当达到了 N 位见证者的时候,这区块链已经充分地去中心化了),且最好是奇数。
- 这里有权限生成块的是见证者节点,而不是持股节点。
- N 是见证者队列的名额限制
- 见证者节点的排序是根据一定算法进行的。
见证者节点的候选名单每个维护周期更新一次,见证者节点被选出之后,会进行随机排引每个见证者节点按顺序有一定的权限时间生成区块,若见证人在给定的时间片不能生成区块,区生成权限将交给下一个时间片对应的见证人。
- 能耗更低。DPoS 机制将节点数量进一步减少到 N 个,在保证网络安全的前提下,整个络的能耗进一步降低,网络运行成本最低。
- 更加去中心化,选举的 N 值算法充分体现去中心化。
- 避免了 PoS 的少部分账户代币量巨大导致权力太大的问题,话语权在被选举出的 N个点中。
- 更快的确认速度,由见证者节点进行确认,而不是所有的持股节点。
链的分叉
共识虽然让区块链中的各个节点为一定程度上保持一致,但是也会不可避免产生的问题,即链的分叉.
- 区块链中的每一个区块在节点中生成后都会通过P2P网络广播到其他节点中去
- 广播后的区块在到达其他节点后,被广播节点在广播给它的一批又一批的区块和它自己所产生的区块中做出抉择,即选出一个获胜的区块
- 然而,共识算法仍然无法保证不出现确认冲突的问题
例如:比特币中的 Pow 共识算法依赖谁算出合法哈希且谁算得快来抉择最终选谁。
事实上,即使我们产生区块的时间戳精确到毫秒级,依然会出现同时算出哈希值的多个节点(至少有两个节点),即节点 A 和节点 B 同时算出了合法的哈希值,产生了区块 1,广播出去了。
- 节点 C 也陆续收到了节点 A 和节点 B 的区块 1,但是节点C首先收到的是节点 A 的区块 1,此时虽然节点 B 的区块 1也合法,但是也不采纳了。
- 同时,节点D也收到了节点A 和节点 B 的区块 1,但是节点 D先收到节点B 的区块 1
为什么?因为节点D 的网络路由距离节点 B 的网络近,离节点 A 的网络远,那么节点 D 就会先采纳节点 B 的区块1而不采纳节点 A 的区块 1.此时,链就分叉了.
链的分叉主要有以下2种:
- 软分叉。一旦出现,最后的结果是能掰正的,专业的说法是:旧节点能够认可新节点产生的区块,称为软分叉。
- 由网络距离引起的分叉, 可根据 “最长链机制”(或者其它选择机制) 来自我纠正
- 因共识规则改变,旧节点为能够识别新节点为产生的区块,但旧区块不能被新节点为接受,解决法必须依赖人力升级节点到统一版本
- 硬分叉。一旦出现,最后的结果是一分为二,专业的说法是:旧节点无法认可新节点产
生的区块,称为硬分叉。PS: 新旧规则互不兼容
比特币UTXO模型
UTXO(Unspent Transaction Output,未花费的交易输出),是一种交易数据的存储模型.目前比特币所采用的就是它.
UTXO 比较接近我们生活中钱财交易的记账模式,每一条符合 UTXO 模型的交易记录都拥有如下特点:
- 每笔交易拥有输入部分 (Input) 和输出部分 (Output)。
- 输出能够从 Unspend 状态转为 Spend 状态,这个过程称为“被使用”。Spend 状态的输出会成为另外一条符合 UTXO 模型交易的输入部分。
- 输出部分包含:
- 已被花费的输出,即已经当作了后面交易的输入,此时的 Spend 字段的值为 true
- 没被花费的输出,即还没被作为后面交易的输入,此时的 Spend 字段的值为 false
ps:
- Spend 字段的存储, 在节点可采用UTXO数据库,做加速处理,而不用能历史区块去推理
- UTXO模型的输入,必须是达成共识后的输出区块
- A的 100 个 BTC 不会凭空产生,它也是由其他输入赋予的,例如比特币挖矿所得。
- 在交易 2中,其输入部分为交易1的输出,此时交易1的输出变为 Spend 状态.交易1中的输出不再是 UTXO交易,因为它已经作为了交易 2 的输入。
- 交易2 使用输入的 100 个 BTC,分别输出给 B和 A。B获得 10个 BTC,A 进行自己的找零操作,给了B的10个 BTC 后,自己剩下 90 个。
比特币最初的代币产生是从挖矿中获取的,后续的代币因为不断地被交易而被分配到各个址中去,根据 UTXO 的模型,可以知道:
- 每一笔交易的输出最终都能追寻一个一开始的输入。
- 交易的最初输入都来源于挖矿的收益地址,这个地址我们一般称为 CoinBase.
- 当我们需要计算某个地中的余额时,需要遍历整个网络中所有与该地址相关的区块内的 UTXO,汇总后便是它的余额(性能问题)
ps:钱包地址,可以是用户线下生成“公钥哈希的编码形式”标识, 再把标识做为交易中的目标地址就行
以太坊的基本概念
以太坊其实就是区块链的一种应用,是一条公链,包含但不限于“区块链”所具有的技术特点。
- 区块链是一个整体的名词,我们可以根据区块链技术开发出很多公链或者私链,再给这些链个名称,例如使用“以太坊”这个名称
- 公链就是最多节点所共同维护的链
- 私链是节点比较少的链,一般是一个节点,任何人都在自己的计算机上进行私链的部署,只需要下载一份公链的代码,在自己的本地机器上跑起来
以太坊被公认代表了区块链的2.0版本: 技术模块方面比比特币公链多出智能合约功能,其共识机制正在从 PoW向Pos过渡,但是直到现在,以太坊最新版本的共识机依然是 Pow.
以上是以太坊的架构图, 应用通过 Web3.js 或其他版本的以太坊接口访问代码,来访问以太坊的 RPC 接口获数据。接口分为与智能合约相关和与区块相关,共两个部分。
最下面一层是实现智能合约和区块链相关引用的技术框架或者中间件:
- Whisper 是 P2P 通信模块中的协议,节点间的点对点通信消息都经过它转发,所转发经过加密传输
- Swarm 是以太坊实现的类似于 IPFS 的分布式文件存储系统
- Crypto 是以太坊的加密模块,内部包含 SHA3、SECP256k1 等加密算法。
- RLP 是以太坊所使用的一种数据编码方式,包含数据的序列化与反序列化。
- …等等
什么是DApp
DApp的英文全称是 “Decentralized Application”, 即分布式,去中心化应用程序.
以下是以太坊DApp的常见交互步骤
- 开发者将编写好的智能合约部署到链上
- 开发者采用计算语言(例如Java 或者 Go)来编写对应于当前所发布的智能合约的访问业务接口
- 用户通过访问接口(页面/客户端) 调用链上的合约,得到输出的数据
账户模型
以太坊区块 Header 结构体中 Root变量的真实含义是,以太坊区块账户 MPT树根节点的哈希值
区块账户MPT树中每个叶子节点的Key是以太坊钱包的地址值,叶子节点的 Value对应的是以太坊的状态对象stateObject。而状态对象stateObject中又含有账户Account 对象,Account对象代表了以太坊账户的核心属性,包括:
- Nonce:记录该账户已发送的交易数量,用于防止重放攻击
- Balance:存储账户当前的以太币余额的内存地址
- Root:当前MPT树根节点hash
- CodeHash:合约账户的代码哈希值(如果是外部账户则为空)
ps: 以太坊交易打包严格按照Nonce的顺序来, 假如提交的Nonce是55,当前值为53, 则会等待54的交易才进行打包.
打包完的交易在新区块中,共识新区块头中的Acount会更新Nonce值为55
以太坊Ghost协议
首先,比特币公链是根据最长链规则来解决区块链分叉问题的,但并不是所有的解决分叉问题都是使用最长链规则,以太坊就不是。以太坊解决区块链分叉问题目前使用的是Ghost协议,而Ghost 协议的真实作用是链选择。不同于比特币的最长链规则,以太坊在选择最长链时不以哪条链区块连续最长是将分叉区块也考虑了进去,选择出一条包含了分叉区块在内区块数目最多.
智能合约
智能合约是DApp的核心组件,通过代码自动执行预设规则,其运行在区块链节点EVM上确保去中心化. 以太坊也提供了传统的 RESTIAPI的调用方式,让我们可以调用智能合约的函数下面我们来理清一些关系:
- 智能合约→被部署到以太坊节点上一调用时被以太坊虚拟机编译并加载
- 以太坊节点一被部署在不同的服务器上一节点们共同维护以太坊公链。
- 调用者→调用以太坊节点的接口-访问某个智能合约一获得结果
日前以太坊智能合约的编程语言是 Solidity,采用 Solidity 语言就可以编写出各种各样的智能合约,然后将其部署到以太坊上.以下合约实现了最简单的加密货币形式。该合约仅允许其创建者创建新硬币(可能有不同的发行方案)。任何人都可以互相发送硬币,而无需使用用户名和密码进行注册,您所需要的只是一个以太坊密钥对。
// SPDX-License-Identifier: GPL-3.0 告诉您源代码是在 GPL 3.0 版下获得许可的
pragma solidity ^0.8.4; //下源代码是为 Solidity 版本 0.8.4 或更高版本的语言编写的contract Coin {// 声明了一个地址类型的状态变量。该address类型是一个 160 位的值,不允许任何算术运算// 关键字public自动生成一个函数,允许您从合约外部访问状态变量的当前值。如果没有这个关键字,其他合约就无法访问该变量。address public minter;// 关键字 public 修饰变量 对 minter 自动生成的函数如下// view关键字用于修饰函数,表示该函数只读取区块链数据而不修改状态; 函数调用时无需支付Gas// external关键字用于修饰函数,表示该函数只能从合约外部调用// public关键字用于修饰函数,表示该函数合约内外部调用, 但使用的memory处理参数, 外部调用比external费Gasfunction minter() external view returns (address) { return minter; }// 创建了一个公共状态变量,但它是一个更复杂的数据类型,可以看作是虚拟初始化的哈希表mapping (address => uint) public balances;// 关键字 public 修饰变量 对 balances 自动生成的函数更复杂function balances(address account) external view returns (uint) {return balances[account];}// 声明了一个“事件”,用于函数send的最后一行触发, 通知// 以太坊客户端(例如 Web 应用程序)可以在无需太多成本的情况下侦听在区块链上发出的这些事件。一旦它发出,侦听器就会收到from, to 和 amount参数,这使得跟踪事务成为可能。event Sent(address from, address to, uint amount);// 构造函数是一个特殊的函数,在合约创建过程中执行,之后不能调用constructor() {// msg变量(连同tx和block)是一个 特殊的全局变量,包含允许访问区块链的属性。msg.sender始终是当前(外部)函数调用的来源地址// 结合 构造函数的特殊性,minter值就为 创建合约的人的地址minter = msg.sender;}// mint函数将一定数量的新创建的硬币发送到另一个地址。function mint(address receiver, uint amount) public {// require函数调用定义了在未满足时还原所有更改的条件。// balances 与 minter 在函数中修改的将做为交易进行提交到新区块中, 从而改变stateObject// require 相当于回滚操作// 确保只有合约的创建者可以调用mint require(msg.sender == minter);// 创建者给receiver铸造// 一般来说,创建者可以铸造任意数量的代币,但在某些时候,这会导致一种称为“溢出”的现象, 即超出uint的上限balances[receiver] += amount;}// 定义一种错误,您向调用者提供有关条件或操作失败原因的更多信息// 错误与revert 语句一起使用 。revert语句无条件中止并恢复与require函数类似的所有更改,但它还允许您提供错误的名称和将提供给调用者(并最终提供给前端应用程序或块资源管理器)的附加数据,以便故障可以更容易地被调试或响应。error InsufficientBalance(uint requested, uint available);// 任何人(已经拥有其中一些硬币)都可以使用send功能将硬币发送给其他任何人function send(address receiver, uint amount) public {// 如果发件人没有足够的硬币发送,则if条件评估为真。因此,revert操作将导致操作失败,同时使用InsufficientBalance错误向发送者提供错误详细信息。if (amount > balances[msg.sender])revert InsufficientBalance({requested: amount,available: balances[msg.sender]});// balances 在函数中修改的将做为交易进行提交到新区块中, 从而改变stateObjectbalances[msg.sender] -= amount;balances[receiver] += amount;// 触发事件emit Sent(msg.sender, receiver, amount);}
}
要监听Sent事件,您可以使用以下 JavaScript 代码,该代码使用web3.js创建Coin合约对象
Coin.Sent().watch({}, '', function(error, result) {if (!error) {console.log("Coin transfer: " + result.args.amount +" coins were sent from " + result.args.from +" to " + result.args.to + ".");console.log("Balances now:\n" +"Sender: " + Coin.balances.call(result.args.from) +"Receiver: " + Coin.balances.call(result.args.to));}
})
以太坊虚拟机具有三个可以存储数据的区域:存储(Storage)、内存(memory)和堆栈(calldata)。
gas费用是: calldata < memory < Storage
Storage 是智能合约在区块链上的永久存储区域,所有状态变量(如 uint number)和映射(mapping)一旦写入,会永久存储在合约对应的区块链账本中,即使节点崩溃或重启也不会丢失,数据不可篡改.通过 EVM 的 SSTORE 指令写入的 Storage 数据会被记录在区块链的交易日志中,成为全网共识的一部分,具有不可篡改性
合约的标准
我们知道,动物是生物的一个种类,在动物的大范围下又分人类、猫类、鱼类等。类似以太坊智能合约也是一个对合约的统称,在此合约下,又有特别针对一类合约的标准,例如代币合约标准–ERC20 代币标准、ERC721标准等;
- ERC20 :同质化代币(FT)标准,如虚拟货币、质押代币等
- ERC721:非同质化代币(NFT)标准,如数字艺术品、游戏资产.每个代币具有唯一tokenId
- 其它
以ERC20 为例子
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;contract PaiXToken {// 代币名称string public name = "PaiX Token";// 代币符号string public symbol = "PAIX";// 代币小数位数uint8 public decimals = 6;// 代币总供应量,初始值在构造函数中设置uint256 public totalSupply;// 记录每个地址持有的代币数量mapping(address => uint256) public balanceOf;// 记录授权关系,即地址A允许地址B使用的代币数量mapping(address => mapping(address => uint256)) public allowance;// 合约所有者地址,拥有铸造代币的权限address public owner;// 当代币转移时触发,记录发送方、接收方和金额event Transfer(address indexed from, address indexed to, uint256 value);// 当授权发生变化时触发,记录授权方、被授权方和金额event Approval(address indexed owner,address indexed spender,uint256 value);// 当新代币被铸造时触发,记录接收方和金额event Mint(address indexed to, uint256 value);// 限制只有合约所有者才能调用被修饰的函数modifier onlyOwner() {require(msg.sender == owner, "Only owner can call this function");_;}constructor(uint256 _initialSupply) {// 设置合约部署者为所有者owner = msg.sender;// 计算初始总供应量(考虑小数位数)totalSupply = _initialSupply;// 将所有初始代币分配给合约部署者balanceOf[msg.sender] = totalSupply;// 发出代币创建的转账事件(从零地址转到部署者)emit Transfer(address(0), msg.sender, totalSupply);}function transfer(address _to,uint256 _value) public returns (bool success) {// 检查发送者余额是否足够require(balanceOf[msg.sender] >= _value, "Insufficient balance");// 检查接收地址是否有效(不是零地址)require(_to != address(0), "Invalid address");// 从发送者余额中减去转账金额balanceOf[msg.sender] -= _value;// 向接收者余额中添加转账金额balanceOf[_to] += _value;// 发出转账事件emit Transfer(msg.sender, _to, _value);return true;}function approve(address _spender,uint256 _value) public returns (bool success) {// 设置调用者允许指定地址使用的代币数量allowance[msg.sender][_spender] = _value;// 发出授权事件emit Approval(msg.sender, _spender, _value);return true;}function transferFrom(address _from,address _to,uint256 _value) public returns (bool success) {// 检查源地址余额是否足够require(balanceOf[_from] >= _value, "Insufficient balance");// 检查调用者是否有足够的授权额度require(allowance[_from][msg.sender] >= _value,"Insufficient allowance");// 检查接收地址是否有效require(_to != address(0), "Invalid address");// 从源地址余额中减去转账金额balanceOf[_from] -= _value;// 向接收地址余额中添加转账金额balanceOf[_to] += _value;// 减少调用者的授权额度allowance[_from][msg.sender] -= _value;// 发出转账事件emit Transfer(_from, _to, _value);return true;}function mint(address _to,uint256 _value) public onlyOwner returns (bool success) {require(_to != address(0), "Invalid address");// 增加总供应量totalSupply += _value;// 增加接收地址的余额balanceOf[_to] += _value;// 发出铸造事件emit Mint(_to, _value);// 发出从零地址到接收地址的转账事件(表示新代币的创建)emit Transfer(address(0), _to, _value);return true;}function burn(uint256 _value) public returns (bool success) {// 检查调用者余额是否足够require(balanceOf[msg.sender] >= _value, "Insufficient balance");// 减少调用者的余额balanceOf[msg.sender] -= _value;// 减少总供应量totalSupply -= _value;// 发出从调用者到零地址的转账事件(表示代币的销毁)emit Transfer(msg.sender, address(0), _value);return true;}
}
合约开发
- Solidity部份: 使用hardhat进行合约的开发, 部署.OpenZeppelin提供标准的合约框架
- 客户端部分: 使用web3j… 有JAVA,JS, GO
- 钱包部分:打开谷歌浏览器,在应用商店搜索MetaMask…
- 链部署: Geth + Prysm(共识)
主要参考
《区块链Dapp开发》
《开发者实战指南:从零搭建一个以太坊测试节点 (Geth + Prysm)》
《solidity 中文教程》
《hardhat开发者文档中文版》
《OpenZeppelin》