bitcoin transaction 比特币 交易

一说起交易的概念,可能大家都明白。比如这么热的天,胖兔出门散步渴了,一看微信里零钱余额还有8块,便买了瓶3块钱的饮料,扫码付账3块,零钱还剩下5块。OK,这就是日常再普遍不过的一次交易。按照普通的程序设计思路,可以为每个人的钱包余额定义一个变量,每次交易花费之后,便从余额中减去交易费用。瞧,是不是很简单?

但是在比特币的世界里,交易跟咱们平常耳熟能详的交易过程却不一样。你的比特币钱包里,并没有一个代表余额的数字,那有什么呢?是一个或者一堆叫做UTXO(Unspent Transaction Output)的东东,直译叫做未花费的交易输出。什么鬼?名字就很别扭对不对?其实你可以把它理解成会员卡,每张会员卡里都有一个数字代表比特币数量,但跟现实生活中不一样的是,每张会员卡都只能使用一次!你不能像在超市刷卡那样,原来卡里有100,刷掉60,卡里还剩下40,而是不管卡里有多少,只要拿出来刷(Spend)了,那对不起,这张会员卡就变成已花费状态,然后从此消失。那有人说,要找零怎么办?其实很简单,再返一张新的会员卡给自己,卡里还有40(先不考虑交易费用),这张新卡自然也是未花费状态,继续存入你的钱包,OK,交易就这样完成了!

下图是采用UTXO方式交易的示意图。

bitcoin transaction 比特币 交易

图2-1 UTXO交易示意图

上图中,交易C的两个输入,分别来自交易A和交易B的输出,在交易C中付给的120接着又在交易D中作为输入使用。在ABCD这4个交易都完成以后,余下可用的UTXO只有30、80、40这3个了。

有人可能会想,干嘛要这样设计呀?多麻烦!俗话说存在即真理,比特币既然这么设计,必有它的考虑。

第一,利于并行。我的钱包里可能有很多张UTXO,那么我可以用不同的UTXO同时去买不同的东西,这个时候比特币网络就可以并行处理这些不同的交易,要知道比特币网络中,每个交易从发生到确认一般需要6个区块,也就是一个小时,甚至有的交易还可能会失败,这对互不干扰的并行处理来说不算什么,换成串行处理的余额式钱包那就很麻烦了。

第二,可回溯。采取UTXO的方式,每一次交易的输入,都来自之前某个交易的输出,并可以一直追溯到源头。换句话说,对每个比特币来说,可以从它的产生开始,一直查询到相关的每一次交易,任何人想伪造、篡改都是办不到的!

第三,避免数据膨胀。采用账户余额的方式,数据库的体积将会随着账户数量的增长而迅速增加,而且账户数据不会被删除。而采用UTXO的方式,不会出现数据迅速增长的情况。

当然,UTXO确实也有它不便的地方,比如要想查每个账户的余额,必须得回溯查询一大堆交易信息,对时间和资源来说都是一种浪费,因此后来的以太坊对此进行了针对性改进。当然这是后话了,将来有机会再探讨。

bitcoin transaction 比特币 交易

图2-2 UTXO数量变化趋势图

上图是blockchain公布的比特币UTXO数量变化情况。可以看到,最近一年中,UTXO数量最多的时候是在2018年1月10日左右,峰值达到6771万。本文写作时,UTXO数量已经下降到5575万左右,可见UTXO方式能够有效地防止用户数据膨胀。

OK,原理交待完毕。下面再看相关源码。


跟交易相关的基础数据,都定义在src/primitives/transaction.h里,主要的一共有4个类,由简到繁分别是COutPoint,CTxIn、CTxOut、CTransaction。下面是它们简单的结构关系图:

bitcoin transaction 比特币 交易

图2-2 交易基础类结构

COutPoint

前面已经介绍过,交易的每一笔输入,其实都是来自于之前某个交易的输出。COutPoint就是用来定位之前这一笔交易的。它有两个数据成员:

hash就是之前那一笔交易的hash值,可以据此来寻找到完整的交易信息。

n是该笔交易输出中的序号。从前面的例子我们可以知道,每一笔交易可以有多个输出,比如我拿100BTC(即100个比特币)作为输入,要转给别人60BTC,就需要产生两个输出:一个是别人的钱包里多出一个60BTC,另一人是自己的钱包里多出一个40BTC(不考虑交易费用)。之后,我再去用这个40BTC,那么回溯就会找到这个100=60+40的交易,hash就是这个交易的hash值,n就指向第2个输出。

CTxIn

CTxIn就是交易输入。主要包含以下几个数据成员:

prevout是COutPoint类型,指向的就是相关联的上一个交易,该交易的输出就是本次交易的输入。

scriptSig是CScript类型,指的是解锁脚本。通俗的理解就是,如何证明这个UTXO是你的,并且能够作为本次交易的输入?要靠这个脚本来提供证明。关于脚本,将来再进行详细解析。

nSequence字面意义是序号,这个数据最初的设计目的,是希望矿工在打包交易数据的时候,优先选择序列号更高的,后来发现它其实没什么用,就在BIP 68(比特币改进提议,第68号)中,将它用于nLockTime(相对锁定时间)功能,这个值默认是0xffffffff,32位整数的最大值,这个时候nLockTime是不起作用直接忽略的。

scriptWitness也是CScript类型,用于隔离见证功能(BIP 148),这是很重要的一次改进。不过暂且我们也不去理会,留待未来再详细解析。

CTxOut

CTxOut是交易输出。主要就是两个数据成员:

nValue的类型是CAmount,指的是这笔输出的金额。

scriptPubKey是CScript类型,它用脚本的方式,明确了谁可以收到这笔输出,也就是这笔输出归谁所有。从变量名称可以看出来,脚本中包含了接收者的公钥,只有接收者本人用自己的私钥才能成功匹配,成为合法拥有者。

CTransaction

终于轮到CTransaction交易了,它需要关注的数据成员主要有:

vin是vector<CTxIn>类型,表示输入集合。一个交易不光可以有多个输出,也可以有多个输入。比如我要付100BTC出去,但我只有40BTC、60BTC两个UTXO怎么办?很简单,就把这两个都作为输入就行了。

vout是vector<CTxOut>类型,表示输出集合。这个应该不用解释了。

nVersion是交易版本号。

nLockTime是之前刚刚介绍的锁定时间,它指的是交易什么时候有效。允许取值有三种情况:如果值为0,代表交易立即生效;如果值小于50亿,那么指代的是区块高度,表示区块链达到一定高度(区块总数达到一定数量)之后,交易才有效;如果值大于等于50亿,则表示Unix时间戳,用1970年1月1日以来的经过的秒数,指的是交易生效时间,必须达到这个时间点之后交易才算有效。

两个私有数据成员,hash和m_witness_hash,是用来计算哈希值的,它们只用于内存中,一般情况下只计算一次。说到这里,需要说明一下的是,刚刚介绍CTransaction这6个数据成员都是带有const修饰符的,为什么呢?这是因为每个交易的数据只要一有变化,整个哈希值就要重新计算,为了避免无意中的改动,干脆把所有数据成员声明为const,这样就不会出问题了。

那么问题来了,如果交易数据确实要改动怎么办?问得好,继续往下看,源码里还声明了一个CMutableTransaction,顾名思义就是可变的交易,它不是一个类,而是一个结构,跟CTransaction一样,拥有vin、vout、nVersion、nLockTime四个主要数据,但它的所有数据都是可变的。然后CTransaction类能够直接从CMutableTransaction结构来进行构造,这样就解决了CTransaction数据修改的问题。

最后还声明了一个CTransactionRef类型,它等同于std::shared_ptr<const CTransaction>,可以理解为指向交易数据的一个指针。这个类型在后面用得很多,需要关注一下。

OK,交易的基本数据结构就这几个了。那么这些交易又是怎么被打包的呢?它们在被打包之前又是怎么个状态呢?下一节继续探究比特币交易池。


本文原创作者胖兔(魏兆华),首次发表于简书,欢迎转载,请注明出处。



作者:魏兆华
链接:https://www.jianshu.com/p/a85f41490467
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

上一篇:windows 安装Bitcoin Core使用


下一篇:287-BitCoin全攻略三_创建区块链