BTC 学习笔记连载(11)——比特币的实现 & UTXO

欢迎交流:twitter.com/songoku_web3

转载请注明出处~

前面几篇文章已经讲到了比特币就是一个去中心化的分布式账本,全网每个节点基于算力投票对区块达成共识。这个分布式账本的数据结构就是一个区块链,本质上区块链记录的是历史的交易记录,但所有用户最关心的是自己的账户的比特币余额。

我们说比特币是:Transaction Base Ledger

本质上比特币就是一个交易驱动的状态机!

什么是状态机?

什么是状态?

我们以前玩游戏经常会听到一句:信春哥,满血复活!这里的满血就是一种状态,当然半血、空血都是对应游戏角色里的状态。

是状态就可能会变化,比如中了boss技能你的血条会掉,吃药包血条会恢复,奶妈也可以给你加血,这些事件都能触发你的状态变化,血条状态值为0时就Game over了。

什么是状态机?

你可以理解这个驱动着血条状态变化的程序或数学模型,就是一个状态机!

我们说的状态机严格定义叫有限状态机(FSM Finite-state machine),又称有限状态自动机(FSA Finite-state automaton),简称状态机,是表示有限的状态及驱动这些状态转化的数学计算模型。

状态机有几个关键概念:状态(State)、转移(Transition)、事件(Event)、动作(Action)。

  • 状态(State)
    将一个系统离散化,可以得到很多种状态,当然这些状态是有限的。
    eg:地铁的门禁闸机可以分为开启状态、关闭状态;台灯可以分为关闭、柔光、强光等不同的几个状态;
    从这里我们可以看出,一个状态机至少包含2种状态。

  • 转移(Transition)
    当接收一个输入执行了某些动作后,从一种状态转换到另一种状态的过程就是转移(Transition),自然任何一个状态机都需要先定义状态机的转移流程。

  • 事件(Event)
    事件也叫转移条件(Transition condition),在某一状态下只有达到了转移条件,才会按照状态机的转移流程,执行相应的动作转移到相应的状态。
    eg:以电风扇为例,按下1档键就是一个事件,这个时间会触发转换动作将风扇的状态转移到1档,这时候你就能享受到1档风带来的温暖了。按2档、按关闭按键时同理。

  • 动作(Action)
    在状态机的运转过程中会伴随多种动作。
    eg:以多档台灯为例,进入动作在台灯被开启时进行,转移动作在进行档位转移时进行,退出动作在关闭台灯时进行。

上面的概念中事件和动作理解起来有点绕,猛地一看感觉是一回事,其实拆开来看就容易理解了:事件触发了动作,改变了状态!

比特币的实现

开篇提到了,本质上比特币是一个由交易驱动的状态机!

这里边我们来拆解一下:

  • 事件:
    签名交易 eg:A → B 0.5 BTC 假设原本状态为:A 1.5 BTC、B 2.3 BTC

  • 动作:
    扣减 A 0.5 BTC
    增加 B 0.5 BTC

  • 转移:
    A 1.5 BTC→ A 1 BTC
    B 2.3 BTC → B 2.8 BTC

  • 状态:
    A 1 BTC、B 2.8 BTC (这里先不考虑Fee)

发生了一笔A转账给B的交易,最终改变了A、B的余额状态,整个比特币系统干的就是这个事情!

在这个过程中我们最关心的是什么?

最终状态!

这个最终状态其实可以等同于比特币系统的当前状态,显性地看就是每个比特币账户对应的余额状态。我有10个比特币,只有我签名的交易才能改变它的状态,否则它就躺在链上谁都不可能拿得走。

千千万万个比特币账户的余额状态都是这样转换的,每当一个获得记账权的区块合法上链之后,该区块的所有交易就触发了一系列的账户状态转变。

本质上全网所有账户的最终状态,就是由历史上已发生的所有交易Transaction促成的:

  • 我们知道账户的当前状态(余额

  • 我们也知道这个状态的由来(交易记录

那上面举例的转账动作,是直接在A账户扣0.5个BTC、在B账户增加0.5个BTC吗?

显然不是,那是怎么搞的呢?

比特币的精髓——UTXO

开篇提到了比特币是:Transaction Base Ledger

它不是根据某个账户里的余额来进行消费的,整个比特币系统中没人任何信息显性地表达某个账户余额,这跟后来的以太坊 Ethereum 是有本质区别的,以太坊是:Account Base Ledger,这个以后再展开。

账户都没有余额的概念,那我们平常在钱包里看到的余额是什么呢?

在《BTC 学习笔记连载(8)——BTC的共识协议》中我们讲过,任何一笔Transaction都需要说明币的来源,也就是当你要签名消费1个BTC的时候,需要在之前的某笔或多笔交易中有至少1个BTC被转给你,且这1个BTC还没有被消费掉。这就意味着每次签名一笔交易,都要去查询之前的区块,找到这个币的来源。

这合理吗?
比如某笔交易0.3个BTC是在Block #10000转给你的;
另外0.8个BTC在Block #1000000转给你的;

难道要从当前区块向前查询到Block #1000000,再查询到Block #10000?

这效率得多低啊!显然不可能这么搞。

中本聪巧妙地通过一个简单地数据结构UTXO解决了这个问题。

什么是UTXO?

UTXO(Unspent Transaction Output),即未花费的交易产出,这个数据结构中存储着所有尚未被消费的交易产出,比特币系统中所有的全节点都要维护这个数据结构。

这里额外强调一点,比特币的增发其实只有一个途径,就是每个区块的第一笔铸币交易,除此之外的任何交易都只是将某个账户下的BTC转给另一个账户而以,整个系统不会凭空产生比特币。

比特币的每一笔交易都可能有多个输入和多个输出:

如图展示了在一笔交易Transaction 1001中,A 给 B和C分别转账了1、1.2个BTC。

这笔交易使用了2条UTXO:

  • Transaction 120 产生的 M → A 1 BTC

  • Transaction 155 产生的 N → A 1.5 BTC

同时新生成3条新的UTXO:

  • Transaction 1001 产生的 A → B 1 BTC

  • Transaction 1001 产生的 A → C 1.2 BTC

  • Transaction 1001 产生的 A → A’ 0.3 BTC

每一笔交易都可能会消耗多条UTXO,并且产生多条新的UTXO,所以任何一条UTXO都需要表明是来至于哪笔交易的第几个输出:

K-value的形式记录的UTXO
K-value的形式记录的UTXO

这样当地址2的用户想要签名一笔交易时,在UTXO一查就知道是否有足够的比特币。钱包应用是轻节点,只会存储跟钱包相关地址的所有UTXO,把这些UTXO求和自然就能在钱包上看到余额了。

整个比特币网络那么多全节点,他们是怎么更新及维护自己的UTXO呢?交易在不同的节点广播往往会产生一些差异,UTXO要怎么达成一致的呢?有的节点包含某条UTXO,另外的节点暂时没包含,那意味着交易被广播时可能在某些节点被判定为合法交易,而在其它一些节点被判定为非法交易。

这个问题很多同学也表示过疑惑,其实比特币的整个实现主要有4个环节,环环相扣又互相影响,最终UTXO巧妙地在全网节点之间达成一致:

  1. 签名交易并广播

  2. 验证交易
    合法交易放入Unconfirm Transaction Pool

  3. 矿工打包交易Mining

  4. 更新UTXO
    依据Confirm的Block更新UTXO状态
    包括更新Unconfirm Transaction Pool

签名交易并广播

  • 如图节点11签名一笔交易 Tx 1001 并进行广播

  • 比特币的交易由2部分构成:
    交易的输入Input
    交易的输出Output

  • 这里的Input即为该笔交易所使用的UTXO
    即发起交易者私钥-地址对应的UTXO记录
    只有合法的UTXO会被其它节点验证通过

验证交易

  • 当节点13收到这笔广播的交易时要验证交易的合法性
    一笔交易的输出是不需要验证的,想转账给谁就转给谁;
    需要验证Input,即交易所使用的那条UTXO;
    验证UTXO本质上是在验证对应的之前那笔交易的输出;

  • 主要基于比特币的脚本进行验证
    比特币的脚本语言是一种基于栈的语言,这里主要依赖锁定脚本、解锁脚本来验证比特币的交易合法性;
    锁定脚本是用来放置在输出上的消费条件,它制定了以后花费这笔输出时所需要满足的条件,直接点讲本笔交易锁定脚本要干得事情,就是锁定新产生的这条UTXO;
    解锁脚本自然就是解锁本笔交易要花费的这条UTXO;

  • 再简单点讲就是将之前那笔交易的输出和本笔交易的输入放在一起执行,如果执行通过就是合法的,否则视为非法交易丢弃;

  • 节点13会将合法的交易继续广播给其它相邻节点

  • 同时将该笔合法的交易放入Unconfirm Transaction Pool

注意:即便这笔交易验证合法了,也不会立刻更新UTXO。

为什么?有兴趣的同学可以思考一下先。

既然讲到了比特币脚本,我们就深入展开多说两句,不知道你听到这种说法的时候会不会惊掉下巴:

真正掌握比特币的是锁定脚本,而不是私钥。

我们知道每个UTXO都包含锁定脚本,锁定脚本定义了该UTXO的花费条件,问题就在这个花费条件上:

  • 花费条件是由交易发送方定义的

  • 通常UTXO的花费条件都是接收地址对应的私钥解锁
    这也是钱包构造交易时默认的花费条件

  • 但也可以被定义为其它的花费条件,也就是上面讲的状态机中的事件定义
    eg:由别的私钥或附带其它条件才能解锁
    这个附带条件可以是到达某个时间或某个区块高度才能花费该UTXO

比特币的锁定脚本、解锁脚本正是比特币可编程货币的体现,也就是比特币其实已经自带了简单的智能合约。但解锁脚本的花费条件不会进行合法检验,任何全节点都不会否定这个花费条件,即便这个条件是100年后才能花费它。

是不是感觉有点坑?

一般用户即钱包都不会去额外设置这个花费条件,我们也从来没有遇到这种情况发生,但是怎么防范它发生呢?怎么知道对方给你的转账有没有做手脚呢?

老实讲我也没研究过,有兴趣的研究明白告诉我一下吧!

矿工打包交易Mining

  • 旷工在Unconfirm Transaction Pool里选取交易打包区块
    在收到新的合法Block之前,这个Pool里的交易将一直被视为有效,所以直接选取进行打包即可

  • 在Mining过程中Pool也会随时有新的合法交易被添加进来
    旷工在任意时刻都可以重新打包区块再进行挖矿

  • 打包好的区块包含Block header、Block body2部分
    Block header 包含Pre hash,它代表你要跟随哪个区块进行挖矿

  • 比特币协议遵循最长合法链原则
    即所有合法的节点都会跟随最长合法链后面进行挖矿
    这也就在全网达成了对账本的共识,大家都会认可并对最长的那条合法链达成一致

如图,某旷工紧随这区块MM进行mining,在这个过程中收到了另一个区块NN的消息,验证NN为合法区块之后,旷工会将Pre hash设置为NN区块重新打包区块Mining,因为此时此刻NN区块才是最长合法链。

全网诚实节点都会切换到NN区块之后,如果你不切换就会白白浪费算力。

在比特币的共识协议中,区块大小被限制为1M,一笔交易差不多250字节左右,所以1个区块大概最多能容纳4000笔交易:

1024 * 1024 / 250 ≈ 4000

比特币共识协议规定平均每10分钟出一个块,这样就决定了每秒钟最多支持7笔交易:

4000 /(6*10)≈ 7

其实绝大多数历史区块都没有容纳4000笔交易这么多,很多区块甚至只打包了几百笔交易,但是全网转账的需求是巨大的,Unconfirm Transaction Pool 里会有越来越多的交易等待被打包上链,而且这些交易并非基于排队机制,矿工就会先打包那些Transaction fee多的交易,这样就导致众多的交易躺在Pool里无法得到及时处理。

这就是比特币网络的拥堵问题!

那把区块大小改大一点不久解决了吗?

或者把出块时间改短一点行不行?

要不留个作业思考一下吧,后面讲比特币网络时我会详细讲。

更新UTXO

很多了解过一点比特币原理的同学都知道,收到比特币需要经过6个区块确认,6个区块确认之后收到的BTC就稳了。

为什么?

什么叫区块确认?

为什么是6个区块确认?

我们通过某笔交易来看一下:

  • Block # 158976 区块通过Mining获得了记账权
    该区块随即被全网节点接受并成为合法区块

  • Block # 158976区块有一笔交易Tx 1000
    这笔交易是 A→B 0.1 BTC

  • Block # 158976区块就是第1个Confirm

  • 紧接着 Block # 158977、Block # 158978 被挖出
    Block # 158977 为Tx 1000的第2个Confirm
    Block # 158978 为Tx 1000的第3个Confirm

  • Block # 158979 还没有被挖出来
    全网所有全节点都在打包自己的“Block # 158979”Mining
    这个区块还没有获得记账权,自然无法算作一个Confirm

就这样持续下去,这笔交易会有越来越多的Confirm,后面的所有Block都认同Block # 158976 是一个合法的区块,都认同交易Tx 1000是一笔合法的交易。

那么这笔交易产生的UTXO就是一条有效的UTXO!

由这条UTXO驱动的余额状态变化就就是有效的!

这是比特币的共识协议决定的,一个区块几百、成千笔的交易,就在这个区块被Confirm的那一刻上链成为有效的交易,也就在这一刻改变了N多个由这些交易驱动的账户状态。

所以,什么时候全网的节点需要更新UTXO集合?

3秒钟思考一下

……

……

……

就是一个Block获得记账权成为最长合法链的时候!

  • 如图当节点B收到1个节点A广播过来的新Block #159878

  • 节点B首先需要检测这个Block是不是一个合法的区块
    包括区块的挖矿难度、PreHash、随机数Nonce等
    当然还包括Block body里包含的每一笔交易,这就是根据UTXO来检测的

  • 如果这个区块是一个合法的区块,节点B会更新自己的区块链账本

  • 然后根据这个区块的交易更新自己的UTXO集合
    将那些使用掉的UTXO删除
    讲那些新生成的UTXO加入到集合中

  • 然后更新Unconfirm Transaction Pool里交易
    将那些无效的交易剔除掉,由于新区块上链导致UTXO集合变化,所以必然导致Pool里的一些交易变为无效
    同学们思考一下,为什么?(不理解的话欢迎Twitter交流)

  • 然后重新打包交易Mining

  • 最后将这个合法的区块继续广播出去

全网所有诚实节点都按这个共识进行账本的更新、进行Transaction Pool的更新、进行UTXO集合的更新,就一定能够很容易达成共识、保持一致。

不是说需要6个区块Confirm吗?不需要等待6个Confirm再更新UTXO吗?

当然不行!说的不是一个事。

为什么?

留个作业,同学们思考一下吧。

那6个Confirm的说法是指啥?

5个不行吗?

6个就一定稳了吗?

我这么问,当然就不是!

比特币的记账权是基于算力的,POW公链天生就是弱终局性!

什么是弱终局性?

以太坊转POS后采用的是类PBFT的模式,2轮验证确定终局,一旦区块被确定其内容就不能再被修改。比特币可不是这样的,POW是基于算力说话的,只要你的算力在全网占绝对优势,理论上你可以在任何高度分叉一条链,当分叉的目的是篡改某笔交易的时候,我们通常称之为Forking attack!

如图,某算力想要篡改Tx 11(A→B 100 BTC)这笔交易:

  • 紧随之前区块进行分叉,插入一笔交易Tx 22
    A→A’ 100 BTC,将原本转给B的BTC转给自己
    可能是1个小时前转给B时,换取了B的一袋金条

  • 这条分叉链最终因为算力优势而胜出成为最长合法链
    全网诚实的节点都会将自己的算力从第1条链转向第2条链

  • 如果第2条链持续出块没有被超过
    第1条链Tx 11所在区块及后续所有区块交易都将作废
    这些交易触发的所有状态转换都将无效

理论上这是可以办到的!

但是很少有人这么干,基本没有节点有这样的算力优势,即便矿池有这样的算力优势也不会这么干,没有人会冒着损失巨大资产的风险去摧毁用户对整个比特币网络的共识及信心,我能想到的只有国家机器会有这个动机,但是在巨大的投入成本面前我想他们会冷静的,一旦偷鸡不成会成为历史的笑柄。

总之正常人都不会去干这种吃力不讨好的事。

那为什么是6个Confirm呢?

这是基于经验的,比特币历史上从来没有任何一笔交易在6个Confirm之后还能被分叉的,6个区块确认后一般就没有什么问题了,考虑比特币网络延迟等因素,全网任何节点还”认为”6个之前的Block是最长合法区块的概率几乎为0。

所以6个出块之后也就是差不多1个小时左右就稳了!

其实5个也行,甚至4个,只是6个更保险一点。

但一定要理解它的弱终局性及背后的本质。

总结一下

还记得开篇那句话吧?

比特币是一个交易驱动的状态机!

区块链记录的就是历史交易记录,每次最新区块上链就是在改变全网账户最终状态。

每当一个新区块获得记账权上链时,区块中包含的交易就会驱动UTXO更新,从而驱动全网账户的余额状态改变!

除了对应的私钥,任何其它力量都无法改变这个状态!

每个诚实全节点都保有一份区块链完整交易记录,就是我们通常说的账本,这个账本在全网所有诚实节点之间都将趋于一致。

比特币是人类历史上第一次,通过技术手段实现了私有财产神圣不可侵犯!

伟大的中本聪,颁10个诺贝尔奖都不过分!

Subscribe to All in Web3
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.