比特币中的交易可谓是比特币的最核心部分。比特币由交易产生,而区块就是用来存储交易的。所以,交易是比特币存在的载体,同时也是比特币中最复杂的部分。交易的运作层层相扣,各个部分缺一不可,十分严密,由此体现出了中本聪高超的设计技巧。接下来将会花费多个章节逐步介绍bitcoin中的交易

比特币或者类似的分布式系统在设计的时候会有一个和普通设计中有极大区别的地方:

分布式中的每个节点既是 client 也是 server。

所以在分布式系统的设计中,使用类来描述对象的时候,有时就要分清哪些情况下这个类是作为client使用的,哪些时候是作为server收到client的这个类使用。因为client和server运行的是同一套代码,而在实际运作过程中如果按照C/S的这种模型(其实不应该这样看待,应该转换自己的思维变为设计p2p节点的思维)来看待,就会看到C产出的 Tx/Block 和 S 收到的 Tx/Block。所以这时候就需要分清类中的属性哪些是属于client/server都等价对待的属性,哪些属性是分情况使用的。否则在看源码的过程中就很容易迷失自己的定位。

本章介绍交易在bitcoin源码当中的总体概况

本文只介绍 bitcoin 的 交易 在源码中是由哪几个类组合得到的,做个总体的介绍。而对于交易的原理,我们放在下一篇文章做详细介绍。本文会提前出现一些bitcoin的概念,但是目前并不需要知道指代什么,只需要先明白有这样的东西就足够了。

如上图的类图所示,这个UML图中包含了bitcoin交易相关的所有关键类。

CTransaction

在图中,最为核心的类的是 CTransaction 这个类就是我们常说的bitcoin的 “交易” (一般称为 Tx, 后文也会沿用这种说法)

其实对于这个 Tx 类,这里只是一个壳,本身这个类并没有什么作用。这个类起作用的是

vector<CTxIn> vin;
vector<CTxOut> vout;

这两个关键成员变量。这两个成员变量分别代表着比特币交易的 “收入” 与 “支出”。比特币的交易并不是记录账户形的数据变化(比如我们采用银行的模型来描述 A 向 B 转账100元,那么银行在记录这个转账的过程中会出现3个记录,这三个记录连成了一个 Transaction (事务)过程:A 的账户减少100元,记录的id为 tid1,B的账户加上100元,记录的id为 tid2,一笔转账记录记录了tid1向tid2转账了100元,成为A账户减少与B账户增加的“关系连接”。),而是日志形:比特币的Tx 只记录A 向 B 转账的这个“关系连接”,这条日志记录只包含了 A 向 B 转账了 100 元这条信息。而这里的 in 就是记录着 从 ‘’谁“ 来(目前先简单的这样看,实际完全不止这样,后文会慢慢重新解释), out 就是转给了谁,而转账了多少钱是包含在 out 中的。在中本聪的命名风格是使用一个前缀代表这个属性的类型,如果是flag还会加上一个f。所以这里的 vin/vout 就是指代in 和 out 都是 vector 类型,所以这里我们可以看到,一个 Tx 的 in/out 是可以有多个的。在后文中,我们称呼 in 为 TxIn,out 为 TxOut (注意这里把 in out 比作两个人是完全不恰当的,之后会重新描述)

而这两个类的另外两个属性

int nVersion;
int nTimeLock;

前者显然是用来控制版本的(这个涉及到另一个区块链系统的核心缺陷问题 -- 分叉 , 本系列可能暂时不会对这方面做出分析)

而后者在 bitcoin v0.1 源码中并没有起到什么作用。但是这个属性在今后的 bitcoin 版本中提供了一个转账过程中能够约定时间的能力,因为这个版本不涉及就不进行分析描述了(从这里也可以看出中本聪的前瞻性)

CTxIn / CTxOut

从这步起,我们直接抛弃 ”两个人之间进行交易“这样的概念,直接认为在比特币的交易系统中是不具备”所有人“这样概念(这样肯定很奇怪因为都没有所有人了比特币还有什么意义,但之后会解释),而只是把 ”交易“ 看作 ”比特币流“ 的中转的中转节点,就像水流分叉合并的那些节点一样:

典型的 bitcoin 交易链:(from Developer Guide - Bitcoin)

水流流量分叉图:

而每个交易就是一个中转(分叉)节点,而每个交易的 in / out 就是 这个中转(分叉)节点的流入和流出。

bitcoin 有一个相当相当重要的规定就是 每个 Tx 的 所有 In 进入了货币流必须在这个交易中全部流出去(流出去不代表成为其他Tx的In,而是必须要成为一个 TxOut。)

举例来说:如果A 转账 100 给 B,但是现在A能控制的Out 有2个,一个是Out1是60,一个是Out2是50,那么A一检查自己的Out就会发现,60和50都不够100,那么就只能把 Out1 和 Out2 都作为当前要生产的 Tx 的 In。但是这种情况下,所有In的和就大于要支出的 100了。那么如果不付交易费的话,除去转账给 B 的 100 所对应的当前Tx的 Out,那么还会多出10。在bitcoin中就强行规定,这多出的10也要创建一个 Out 来锁住这10 块,以规定每笔交易的 In 和 Out 的总数都要相同。那么因为这 10 相当于我们通俗意义上的“找零”,所以这个 10 块的 Out 的锁当然就是 A 自己可以控制的锁,相当于这个Out指向了自己。

所以这样我们可以看到,一个交易只含有一个输入和一个输出,那么这个交易并不是看作一个人转账到了另一个人身上,而是把比特币看作像流水一样的货币流,从某个地方流入到了这个交易的输入,由从这个交易的输出流到另一个地方去。那么接下来的问题就显而易见了--如何控制货币流的流动?答案你就是 CTxIn 和 CTxOut 的属性。

我们来看下这两个类有些什么属性:

class CTxIn{
public:
COutPoint prevout;
CScript scriptSig;
unsigned int nSequence;
};
class CTxOut{
public:
int64 nValue;
CScript scriptPubKey;
};

对于 CTxIn:

COutPoint 这个类如其名,就是起到 Point 的作用,但是它的命名是 OutPoint, 最初接触的时肯定会感到迷惑。但这个名字确实起得十分正确:TxIn 虽然按照之前的分析可知它是 Tx 的 流入,而流入 Tx 必定来自于 另一个 Tx。 TxIn 只是 Tx 的一个属性,描述了 本 Tx 的 ”流入“ 的情况,但它本身也是个壳,而从哪个Tx流入的信息就是由 COutPoint 所记录。所以对于本 Tx 来说,TxIn 所含有的 ”从哪流入的“ 那个(上一个) Tx 对于本 Tx 来说 就是上一个 Tx 的 Out 的指向。而本 Tx 是不能持有上一个 Tx 的 out 的,所以就使用了 Point 指针来记录。

nSequence 在 v0.1 中没有起到什么作用,也不会用来作校验,但是这个字段今后被作为了其他用途,而且成为了bitcoin的一个软分叉的最佳例子。

对于 CTxOut:

value 就是记录着”从这个出口会流出多少“的信息。简单的来说就是可以理解为通俗意义上的转账了。但是我们这里还是强调,首先理解bitcoin先抛开 支付交易 等概念,而是把 bitcoin 看成流动的水,而这里的 value 就是记录从这里会流出多少 bitcoin 的意思。显然一个 Tx 的所有 TxOut 的 value 的和 应该等于 所有 TxIn 流入的总和 (不考虑手续费,弱考虑手续费就是小于等于),否则这笔交易就应该认为是非法的(不能凭空多出钱来)。

scriptSig / scriptPubkey

那么没有介绍的 scriptSig 和 scriptPubkey 就是控制 ”凭什么从这里流出“ 的机制。这块绝对是 中本聪 创立 比特币的 又一大惊为天人的发明,这两个属性就是 导致今后著名的”智能合约“的雏形。今后将会花费详细的一篇文章仔细介绍。在这里我们先简单的介绍如下:

在刚才的讨论中我们知道,比特币流从一个交易流动到了另一个交易,像这样一直传递下去。但是这样显然是不行的,因为没有人宣告这个”流“的归属。换句话说,我们在日常中使用100块钱进行交易,核心是因为这100块的纸币从一个人的手上流动到了另个一个人的手上。但是当你持有了这100块的纸币时,你就确认了这100块纸币流的归属。

但是在比特币的体系中,请你直接抛弃这种思想,而是使用另一种方式来思考,而这种方式当你换个角度看的时候它就和你交易100块的纸币是相同的。

这种思想那就是当我们重新审视交易的时候,我们发现货币流是从一个(多个)交易流向一个(多个)交易的过程。那么如果我们有独特的手段能够控制它为什么能流动,比如说在流出去的时候我们采取一个手段给这个出口 out 上一个锁,而当你想控制这个从这个出口出去的流的时候你就创造一把能打开这个锁的钥匙,作为下一个交易的 in 。也就是说我们连续的看2个交易的中间部分:前一个交易的 out 和 后一个交易的 in。如果我们能把 前一个 out 加一个锁,然后规定后一个 in 要能成立的条件是 in 附带的 钥匙 能够打开out锁。(维护为什么能开锁这个过程是由矿工挖矿保证,现在只说交易不说区块,所以可以先理解为这是天然存在的规则。)那么因为上锁和开锁是“个人”行为,但一个交易的out被上了一把只有一个特别的人才能打开的锁,那么就像本应该从这个out流出去的货币被“锁在了这个out”里面(注:这个上锁的过程不需要这个特别的人参与,这个特别的人只需要提供一点信息代表这把锁只能他打开就行(就是指地址))。那么这种行为就等价于只有这个特别的人才能“控制”这个Tx的Out,也就是只有这个特别的人才能 “占有” Out 里面被锁住的钱。

虽然这个钱并不像现实生活中能够实实在在的把这100块钱拿到手上,而只能是通过 in/out 的锁控制 钱的流动。但是我们换个方向想,虽然我们只能提供这个“钥匙”,但是这个“钥匙和锁”能够控制 out 所含有的 货币流动,那么这个 in/out 的上锁开锁机制 是不是就相当于你占有了这笔钱?(因为虽然这个钱不是真正的在你手里(比如银行账户有个针对你的账户而bitcoin 系统没有),但是你可以控制一些 针对你的锁所锁定的钱的流动权利,那么就像水流的分叉点的出口只有你能开锁,虽然别人都看得到,但是因为别人不能开锁,那么别人也无可奈何,因为他们控制不了)。

所以我们可以看到,我们所谓的转账在bitcoin的系统当中,例如A转账100给B,那么就需要 B 向A 提供一些信息(bitcoin地址),这个信息不会暴露B的个人情况,但是可以表面B能够控制由这个信息产生的锁。随后 A 就可以创建一个交易,这个交易的out 就可以用B 提供的这个信息上了一把只有B能够控制的锁,然后这个交易的 in 就是 由 A 提供 A 能够控制的其他交易的Out 的 对应的钥匙。 如下图所示:

好了,之上面这么大段的陈述过后,我们终于可以提出,CTxIn 和 CTxOut 的属性 scriptSig 和 scriptPubkey 就是刚在我们讨论中的 钥匙 和 锁。 scriptSig 就是用于对应签名的钥匙,而 scriptPubkey就是 B 提供了地址而生成的锁。

而我们所说的实现钥匙和锁的功能,依靠的就是 这两个属性的类型 -> CScript

从命名上可以看出,中本聪在设计之初就认为具有这样功能的东西应该是像”脚本“一样可以被”执行“,熟悉计算机的人当看到script 的命名就可以想象到 这个机制 是可以 ”被编程的”。而在bitcoin 的系统中也确实如此,bitcoin 提供了一系列的操作指令,可以让使用者自行编程。而验证的过程实际上就是执行了脚本。在此先不做过多描述,之后会有文章详细描述bitcoin的script系统。

COutPoint

这个类含有两个属性

class COutPoint{
public:
uint256 hash;
unsigned int n;
};

按照上文的解释,我们可以得到这里的 hash 指代的就是 txin 所来自的那个Tx的 hash, 而n指代这个 in 是来自上一个交易的第 n 个 out, 如下图所示:

CInPoint

这个类在我们对 bitcoin 的讨论中不是很重要,这个类只出现在一个维护 COutPoint与CInPoint的 map 中。

所以我们认为 CInPoint 和 COutPoint 是 键值对应关系。当我们确认了一个 COutPoint 的时候,我们可以假装把这个COutPoint看作是上一个 Tx 的 Out, 那么 这个 map 对应的 CInPoint 意思就是指代为 上一个 Tx的Out 指向的 下一个

它持有的属性

class CInPoint{
public:
CTransaction* ptx;
unsigned int n;
};

CTransaction* 是一个 针对 COutPoint 这个Out 指向的 In 所在的那个交易。那么在COutPoint那个图的例子中就是指代当前的Tx 这个 Tx 的指针。而这里的 n 就是指代 这个 In 是当前的 Tx 的 第 n 个 In, 在上图中也同样是 0 (因为只有1个In)

CScript

CScript 实际上就是一个 vector<unsigned char> 也就是说 Script 实际上就是一串 Bytes 流。只不过这个字节流 是可以被解析为 <指令> 或者 <指令> <数据> 这样的一个一个元信息。而一个 Script 就是这些元信息组成的字节流。

所以 CScript 本身这个类 不重要,重要的是 Script 说代表的指令和数据,以及这些指令的组合关系以达到相应的效果。

它的验证需要一个 VM 来执行(脚本),而执行(解析)指令的方式和指令的含义与规则就是VM的规则与实现

CTxIndex / CDiskTxPos

这两个类和bitcoin的协议也就没什么关系了,他们是用来Tx 在本地的存储与索引使用的。不过这里要注意,在bitcoin的源码中,CTxIndex 是很重要的一个类,它的存储,更新和删除控制这能否在本地存储中找到这个对应的 Tx 数据,以及标注这个Tx 是否被花费。

class CTxIndex{
public:
CDiskTxPos pos;
vector<CDiskTxPos> vSpent;
};
class CDiskTxPos{
public:
unsigned int nFile;
unsigned int nBlockPos;
unsigned int nTxPos;
};

在存储中,bitcoin 使用 Tx 的hash 为键,CTxIndex 为值进行存储。所以在拿到一个 CTransaction(或其子类)可以通过得到这个Tx的 hash 索引本地的存储得到这个Tx 所对应的 TxIndex。

而 TxIndex 的属性 vSpent 就是一个相当重要的属性,因为这关系到一个 Tx 的Out 是否是 UTXO(Unspent Transaction Output)。由前文的讨论可知,那么一个 UTXO 就是一个被上锁了但是没有被开锁过的 Out。而这个TxIndex 的 vSpent 是一个 vector ,它和当前 Tx 的 vout 相对应。

这里我们要强调,Tx 的产生和 确认 不是同一个决定的,是之前所讨论的 Client 和 Server。产生Tx 的称为 client ,接受确认这个Tx 合法的是 Server, Client 和 Server 存储的CTxIndex 是不会进行传输的!所以CTxIndex 在 C/S 上是分别生成的。那么我们使用 CTxIndex 的 vSpent 来标识这个 Out 是不是一个 UTXO 就相当重要了。因为 C / S 分别的存储都是根据自己的历史生成的,所以如果 Client 要欺骗别人, 是不能在 别人自己的验证中通过的。

举例来说就是 A 产生了Tx 并告诉别人来确认这个 Tx 是合法的,但是 A 使用的一个 in 来自的一个 Out 是已经被花费过的,比如我们假设这个Out所在的Tx叫做 Tx_prev,这个Out是第3个Out,但是A不管,仍然使用了这个被花费过的Out。那么当别人收到这个Tx进行验证的时候,他们就检查自己Tx_prev所对应的自己的本地存储的 Tx_index_prev ,然后一检查 vSpent[3] 是否是null, 如果是null 那么就是合法的,如果不是Null,那么就代表这个Out已经被花费过了。可见这里的验证是和A的本地存储无关的,A不可能修改自己的本地存储来欺骗别人。因为传输的内容只有 Tx, 而 TxIndex 是各个节点根据收到的Tx或block 自己生成的。所以节点们一检查发现 vSpent[3] 不是个 null, 那么就会认为 A的那个 Tx 是非法的。

而 CDiskTxPos 是代表这这个 Tx 在本地存储的位置。 在bitcoin 源码中,Tx 的存储是紧密的排列在文件当中的,而找到这个Tx就是首先找到存储的文件,再找到这个Tx在这个文件中的偏移。所以 nFile和 nTxPos 就分别代表着是哪一个文件和在文件中的偏移位置。

nBlockPos 代表这个 Tx 在 Block 中的位置。

CMerkleTx

这个类是 Tx 的子类,这个类使用来在Block 中相关处理的时候用的。CMerkleTx 是矿工(前文指代的server)所保存Tx时相关的类

它在原本的Tx的基础上添加了

class CMerkleTx : public CTransaction{
public:
uint256 hashBlock;
vector<uint256> vMerkleBranch;
int nIndex;
};

3个属性,hashBlock代表着当前的 Tx 所在的Block 的hash(作为索引),vMerkleBranch是该Tx 在 merkle tree 中 所配对的所有hash值(这个配对的hash值在以后的文章会解释),这里是用来验证Tx 在block中的附加信息。index代表着该Tx在block中的位置。

CWalletTx

这个类是 CMerkleTx 的子类,实际上就是我们产生Tx以及和wallet相关的Tx。这里我们着重介绍Tx,和wallet的信息以及产生的过程就暂时不先在这里介绍。

结尾

以上就是对bitcoin中 Tx 相关的类的介绍。只能先说明 bitcoin 实现 Tx 的过程中使用了哪些类及类中的属性可能起到的作用。在下一篇文章中我将会介绍 Tx 的运作原理。而之后关于 Tx 的文章就是根据原理看源码是如何处理的。

bitcoin源码解析 - 交易 Transcation (一)的更多相关文章

  1. bitcoin 源码解析 - 交易 Transaction(三) - Script

    bitcoin 源码解析 - 交易 Transaction(三) - Script 之前的章节已经比较粗略的解释了在Transaction体系当中的整体运作原理.接下来的章节会对这个体系进行分解,比较 ...

  2. bitcoin 源码解析 - 交易 Transaction

    bitcoin 源码解析 - 交易 Transaction(三) - Script 之前的章节已经比较粗略的解释了在Transaction体系当中的整体运作原理.接下来的章节会对这个体系进行分解,比较 ...

  3. bitcoin 源码解析 - 交易 Transaction(二) - 原理篇

    这篇文章我断断续续写了呃···· 应该快三个星期了? 所以前后的风格可能差别相当大.真是十分的怠惰啊··· 最近实在是不够努力.用python重写bitcoin的项目也卡在网络编程部分(这方面真是我的 ...

  4. 以太坊go-ethereum签名部分源码解析

    以太坊go-ethereum签名部分源码解析 golang标准库里的crypto/ecdsa椭圆曲线加密算法所提供的函数有: ecdsa.PublicKey结构体通过持有一个elliptic,Curv ...

  5. IPerf——网络测试工具介绍与源码解析(1)

    IPerf是一个开源的测试网络宽带并能统计并报告延迟抖动.数据包丢失率信息的控制台命令程序,通过参数选项可以方便地看出,通过设置不同的选项值对网络带宽的影响,对于学习网络编程还是有一定的借鉴意义,至少 ...

  6. 【JUC源码解析】Exchanger

    简介 Exchanger,并发工具类,用于线程间的数据交换. 使用 两个线程,两个缓冲区,一个线程往一个缓冲区里面填数据,另一个线程从另一个缓冲区里面取数据.当填数据的线程将缓冲区填满时,或者取数据的 ...

  7. Fabric1.4源码解析:链码实例化过程

    之前说完了链码的安装过程,接下来说一下链码的实例化过程好了,再然后是链码的调用过程.其实这几个过程内容已经很相似了,都是涉及到Proposal,不过整体流程还是要说一下的. 同样,切入点仍然是fabr ...

  8. Fabric1.4源码解析:客户端安装链码

          看了看客户端安装链码的部分,感觉还是比较简单的,所以在这里记录一下.       还是先给出安装链码所使用的命令好了,这里就使用官方的安装链码的一个例子: #-n 指定mycc是由用户定义 ...

  9. Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战

    Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台-  什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ...

随机推荐

  1. vue 环境的搭建及初始化项目

    其实超级简单,虽然网上很多,但是我顺便记录下相当于做笔记吧 1nodejs 的安装, 在node官网下载,点击安装,安装的时候最好选择路径在d盘 2设置环境变量 我的电脑-->属性-->系 ...

  2. Python 会是我们的未来吗?

    Python 热度激增 根据 Stack Overflow 的一项调查显示,Python 不仅在专业领域的使用率得到增长,在普通开发上的使用率也有所提升,有 40% 的受访者表示他们现在正在使用 Py ...

  3. (网页)the server responded with a status of 403 (Forbidden)

    403跨域问题.看一下要访问的url.

  4. 12-openldap使用AD密码

    阅读视图 本文严重参考 Openldap 整合windows AD认证 本文其他参考 OpenLDAP使用AD密码 Configuring OpenLDAP pass-through authenti ...

  5. NFS服务搭建与配置

    启动NFS SERVER之前,首先要启动RPC服务(CentOS5.8下为portmap服务,CentOS6.6下为rpcbind服务,下同),否则NFS SERVER就无法向RPC服务注册了.另外, ...

  6. IO流_SequenceInputStream(序列流)

    SequenceInputStream(序列流):就是将多个流合成一个有序的流 需求:将三个文件中的数据合并到一个文件中 import java.io.FileInputStream; import ...

  7. File类_常见的方法(获取目录中指定规则的内容)

    首先定义过滤器 import java.io.File; import java.io.FilenameFilter; public class FileByJava implements Filen ...

  8. cf C. Finite or not? 数论

    You are given several queries. Each query consists of three integers pp, qq and bb. You need to answ ...

  9. C# 响应微信发送的Token验证,文字、图文自动回复、请求客服对话.....

    代码如下,有需要的可以参考: using System; using System.Collections.Generic; using System.Linq; using System.Web; ...

  10. 协程与concurent.furtrue实现线程池与进程池

    1concurent.furtrue实现线程池与进程池 2协程 1concurent.furtrue实现线程池与进程池 实现进程池 #进程池 from concurrent.futures impor ...