【web3】02. 区块链原理介绍

780 阅读15分钟

1. 区块链基本原理

所谓区块链,实际上就是一种数据结构,是一种链式结构(可以理解为具有指向前面节点指针的链表),每个节点表示一个区块,区块实际上是用来存储数据的数据包,里面存储交易信息等数据。

graph RL
区块n --> ... --> 区块2 --> 区块1 --> 区块0:创世区块

1.1. 区块存在于哪里?

【web3】01. 学习环境搭建及测试中讲到,以以太坊为例,我们可以使用 Geth 启动一个以太坊的节点,实际上全球有千千万万个节点,这些节点可能会存储部分区块,也可能会存储全量区块信息,所以区块存储于这些节点上。

我们知道,区块链是没有主服务器的,或者说千千万万个节点组成了主服务器(分布式服务器),没有任何一台服务器可以直接操控数据,而是要大量的服务器一起合作操控数据,这也是区块链最主要的特性。

区块里面存储的信息主要包括区块头和区块体,区块头包含区块的元数据信息,比如当前区块的 hash,上一个区块的指针等;区块体中包含区块的交易信息。

1.2. 什么是交易?

所谓交易,实际上就是区块链上的数据发生变更的过程,比如由账户 A 转账 1 个以太币给账户 B,那么则称为发生了一次交易。当然交易不限于转账,还有比如一些其他的 token 转换等,比如我们常说的 NFT 的交易、智能合约的执行。

1.3. 区块的本质

区块实际上就是将一个个的交易打包成数据包。打包过程是按照分布式一致性算法再各个节点之间达成共识,认定某个客户端打包的区块是合法的过程,具体打包的过程以及计算和共识算法将在下面介绍。

1.4. 账户

所谓账户,实际上就是一个公钥/私钥对,我们知道,区块链主要依赖于非对称加密算法,非对称加密算法中,公钥是可以公开的,而私钥是不能公开的,公钥加密的数据只能通过私钥解密,从而实现了安全性。

而区块链中的账户,以以太坊为例,账户地址就是你的公钥通过 hash 得到的值,私钥只能自己持有,不能透露给其他任何一个人,否则别人拿到你的私钥就可以对你的账户进行任何操作。

如果你自己不主动透露私钥,那么别人在持有你公钥地址的情况下,在当前计算机算力的情况下,想枚举你的私钥几乎是一件不可能完成的事情(当然如果后续算力有突破,比如量子计算机有重大突破,也许能够猜出来你的私钥)。

以太坊中的账户分为「外部账户」与「合约账户」,这两种账户都会存储一些 token 数据(比如你的以太坊余额、你的 NFT 等),一般外部账户是你用于存储资产的账户,合约账户是用来执行只智能合约的账户,合约账户会关联智能合约代码,可以执行智能合约,其他的这两种账户区别不大。

1.5. 挖矿

挖矿就是将大家的交易打包成区块的过程,挖矿首先有矿工,矿工有一个账户,矿工将最新的交易信息进行收集和打包,并通过计算最终证明自己打包的区块合法,并得到其他节点认可,这个时候这个区块就会被纳入整个区块链的链中,成为最新的一个链,而矿工打包区块后也可以获得一定的 token 收益。

1.6. 再回顾一下

假设你有两个账户 A 和 B,你的朋友有两个账户 C 和 D,余额如下:

  • A 有 5 eth,B 有 5 eth
  • C 有 1 eth,D 有 1 eth

那么,发出以下交易:账户 A 向账户 C 发送 1 eth,账户 B 向账户 D 发送 1 eth。这时候会有矿工将这两笔交易进行计算,打包到一个(或者多个)区块中,打包完成得到确认后,相当于这两笔交易得到了确认,这个时候 C 和 D 账户分别会多出来 1 eth 的余额,而 A 和 B 账户则会少 1 eth 余额。

在实际交易过程中,需要支付一定的费用(gas),所以 A 和 B 账户会减少的余额会大于 1 eth。

完成交易后,你们的账户余额和交易信息将永久存储于区块中,并可以通过各种手段查询。


2. 区块存储了什么信息?

本节有些名词不熟悉也没关系,后面会解释。

2.1. 模拟一次交易

我们尝试使用环境搭建中使用的 Ganache 来生成一个区块。首先打开 Ganache,可以看到 Ganache 上显示的比如我的 RPC SERVER 为 http:127.0.0.1:7545 ,那么我们打开 Postman 来使用 json rpc 进行一次交易:

POST / HTTP/1.1
Host: 127.0.0.1:7545
Content-Type: application/json
Content-Length: 341

{
    "jsonrpc": "2.0",
    "method": "eth_sendTransaction",
    "params": [
        {
            "from": "0xDb09923Aa60F7bbB4Ce7850aBdF40b3A6b938cdF",
            "to": "0x83284b0daade164a6107afEF990D45B0B2A7C716",
            "gas": "0x76c0",
            "value": "0xDE0B6B3A7640000",
            "data": ""
        }
    ],
    "id": 1
}

上述的 http 请求表示从 from 地址发送 1 个以太币到 to 地址,点击发送后即可看到两个地址之间以太币的变化。

完成以后,点击 Ganache 上面的 Blocks 即可看到区块信息,我们点击一个有交易信息的区块,比如:

区块信息

可以看到,区块信息里面包含区块的基本信息和交易信息(TX),区块基本信息里面包含区块的 Hash、Gas 信息等,交易信息里面包括该区块内的各个交易(实际上真正的区块里面会包含很多个交易),可以点进去下面的交易信息详细查看交易。

随后,我们还可以使用 json rpc 发送请求查询区块的详细信息:

POST / HTTP/1.1
Host: 127.0.0.1:7545
Content-Type: application/json
Content-Length: 187

{
    "jsonrpc": "2.0",
    "method": "eth_getBlockByHash",
    "params": [
        "0x0ffc4887ac419b47ec031a8631eb7a3ed2158dfdfeffa5d48ac3fcb94afd5c33",
        true
    ],
    "id": 1
}

params 里面的 hash 即为区块的 hash 值,上述请求得到如下返回结果:

{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        "number": "0x4b",
        "hash": "0x0ffc4887ac419b47ec031a8631eb7a3ed2158dfdfeffa5d48ac3fcb94afd5c33",
        "parentHash": "0x5b588b349f5e7e1b6ec07b7d37ff21c54a0bdbf143cbff8984f9c30d8833565b",
        "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "nonce": "0x0000000000000000",
        "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
        "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        "transactionsRoot": "0x6f7cedbfc41e2c8a80b0890da3b7edf2aeb7a0ccf053ab0a2783d516fe14086e",
        "stateRoot": "0xed75eeb47b729ef9c8f5859e05feaf7a0aa31afa76bebe0e802875ebf3448d3a",
        "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
        "miner": "0x0000000000000000000000000000000000000000",
        "difficulty": "0x0",
        "totalDifficulty": "0x0",
        "extraData": "0x",
        "size": "0x3e8",
        "gasLimit": "0x6691b7",
        "gasUsed": "0x5208",
        "timestamp": "0x63b27639",
        "transactions": [
            {
                "hash": "0xb021466ffc375326a37a72b08410b1450ee49730fcca05c436337917228fd94e",
                "nonce": "0x49",
                "blockHash": "0x0ffc4887ac419b47ec031a8631eb7a3ed2158dfdfeffa5d48ac3fcb94afd5c33",
                "blockNumber": "0x4b",
                "transactionIndex": "0x0",
                "from": "0xdb09923aa60f7bbb4ce7850abdf40b3a6b938cdf",
                "to": "0x83284b0daade164a6107afef990d45b0b2a7c716",
                "value": "0xde0b6b3a7640000",
                "gas": "0x76c0",
                "gasPrice": "0x7d0",
                "input": "0x",
                "v": "0x26",
                "r": "0x8ec51b193f3850856a94e384abed34e191788a7498f0a58430795f2dfe2808f1",
                "s": "0x6a441df88c856e56b7b5320394cef261cc38e16ea78ee7c4b95db964938df2d5"
            }
        ],
        "uncles": []
    }
}

可以看到,实际上,每个区块包含区块头和区块体,区块头中包含区块的基本信息,而区块体中包含区块的交易信息(transactions)。线上的实时区块信息也可以在线查看,比如这个区块:etherscan.io/block/16317…

我们挨个解释返回值信息。

2.2. 区块头信息

  • number: 区块号,比如我的区块编号是 75,就是 16 进制的 0x4b;
  • hash:当前区块的 hash 值;
  • parentHash:父区块的 hash 值,比如这里表示的是区块编号为 74 的区块 hash 值,还记得前面说的区块是个链式结构吗?parentHash 就是指向前面区块的指针;
  • mixHash:用于工作量证明机制的 hash 值(答案),与 nonce 一起用于工作量证明,后面工作量证明机制会讲到,此处由于是 Ganache 模拟的,所以该 hash 为 0;
  • nonce:与 mixHash 一起用于工作量证明;
  • sha3Uncles:32 字节的叔块 sha3 的 hash 值,叔块的概念下面会介绍;
  • logsBloom:交易的日志 Bloom 过滤器,可以用于快速过滤交易日志(暂时不用关心);
  • transactionsRoot:可以参考这里讲的比较详细,总体来说,是区块中交易默克尔树的根 hash 值,用于证明区块中交易完整性;
  • stateRoot:表示执行完该区块内所有交易后,整个区块链的状态 hash,暂时先不用关心,想了解可以参考这里
  • receiptsRoot:表示交易回执的 hash,暂时先不用关心;
  • miner:挖出该区块的矿工地址(或者叫 Coinbase);
  • difficulty:表示区块挖矿的难度,可以简单理解为计算区块是一个根据问题猜答案的过程,difficulty 是「问题」,而 mixHash 和 nonce 是「答案」。需要注意,这里是区块的 nonce,交易中也存在一个 nonce,与此处不一样;
  • totalDifficulty:表示从创世区块开始,到该区块截止的总体难度之和;
  • extraData:区块附加数据,一般由矿工设置;
  • size:区块大小;
  • gasLimit:gas 就是执行交易、合约等所需的费用,gasLimit 就是该区块内所有交易所能花费的 gas 上限(多余的未使用完的 gas 会退回);
  • gasUsed:区块内所有交易已经使用了的 gas 费用; timeStamp:区块的时间戳。

2.3. 交易信息

交易信息包含在区块的 transactions 中,里面是一个交易列表,列表中的每个交易包括,公链上交易举例 ,下面讲解的还是上述示例上的交易:

  • hash:交易的 hash 值,用来标识交易;
  • nonce:交易发起方的 nonce,交易发起方每进行一次交易,nonce 加 1,此处是为了防止双重花费攻击等问题;
  • blockHash:该交易所处的区块 hash 值,本区块的指针;
  • blockNumber:本区块的 index;
  • transactionIndex:该交易在所在区块中的索引;
  • from:交易发起账户;
  • to:交易接收方账户;
  • value:交易数量,比如此处表示 1 eth,即 10^18 wei;
  • gas:交易设置的花费;
  • gasPrice:gas 的价格,这个价格是浮动的,会动态调整,表示 1gas 能兑换多少 wei;
  • input:交易的输入信息,比如执行智能合约的方法名称、入参等信息;
  • v/r/s:交易签名相关信息,在交易发起时根据发起账户的私钥计算得出。

3. 区块打包流程

区块的打包,即「挖矿」。

在介绍挖矿之前,我们先来看一个区块链网络中各个节点的连接关系,比如如下所示(引用网络图片):

image.png

区块之间的各个节点是通过 p2p 通讯(Peer-to-Peer)连接的,每个节点可以与其他节点通讯,同时该 p2p 网络具备自动发现节点等各种机制(暂时不必太深究),以太坊是基于 Kademlia 算法实现的网络。

3.1. 流程预览

一次交易的打包,分为「发起」、「广播」、「打包执行」、「验证」四个步骤。

  • 发起:一个客户端发起一次交易请求,请求一般在比如钱包等客户端中发起,发起后将该请求发到某个以太坊节点(也可能是当前节点)。交易发起时一般要设置该交易的 from 和 to 地址、发起账户的 nonce 值、gasPrice等信息,还需要设置 input 即是否调用智能合约等信息,并通过交易发起账户的私钥计算出 v/r/s 签名(参考前面);
  • 广播:交易发起后,会将该交易广播至其他节点。节点接收到交易后,需要验证交易的签名合法性、nonce 合法性以及交易的金额、gas 等是否合法,验证合法后,将该交易加入当前节点的交易池(内存)中,如此,二级节点再继续向外广播交易...;
  • 打包执行:具有挖矿功能的节点,在攒一批交易后,开始打包下一个区块,生成区块数据。并在计算出来合法区块后,将区块广播到其他节点;
  • 验证:其他节点收到交易区块后,进行区块的验证工作,当大多数节点认为该区块是合法区块,则就会将该区块设置为下一个区块,并加入到自己的链中。

3.2. 区块合法性共识机制

以以太坊为例,一个区块的产生,需要两个步骤:1)节点在本地打包运算形成新的区块,即挖矿;2)向其他节点广播,其他节点投票某个区块为合法区块,并加入自己的链。

3.2.1. 挖矿算法 PoW:工作量证明

工作量证明,顾名思义就是哪个客户端的工作量最大,哪个客户端挖出合法区块的可能性就越高。简单来说,就是让客户端来计算一个 hash 值,使得这个 hash 值满足一定条件:

hash = SHA256(index + previousHash + timestamp + data + nonce)

其中,index 是当前区块的 index,previousHash 是前一个区块的 hash,timestamp 是当前区块创建的时间戳,data 是当前区块数据,nonce 是一个从 0 递增的数据。

挖矿的目标就是不断递增枚举 nonce 值,从而使得最终的 hash 经过运算后小于 difficulty(难度)。优先计算出来的符合条件的区块就有机会成为下一个区块。

思考题:PoW 为什么能防止篡改数据?

3.2.2. 挖矿算法 PoS:权益证明

2022 年,以太坊已经从 PoW 切换到了 PoS 证明,PoS 证明主要是看账户的币龄,币龄就是币数乘以持币的时间,比如你的账户有 1 个 eth,持有了 10 天,那么你的币龄就是 10。币龄越高越有机会获得记账权。