引言:
交易是比特币的核心,区块链唯一的目的就是安全可靠的存储交易数据,使得交易数据在创建之后不可更改。由于实现真正的交易比较复杂,我们将交易分为2部分来实现。本文将大体实现交易机制,交易的具体细节实现,容后再述。
什么是交易?
一般的web应用需要创建如下数据表用于实现支付逻辑:用户账户、交易。用户账户表用于存储用户信息和余额,交易信息表用于存储钱从一个账户转账到另一个账户。然而在比特币交易的实现方式完全不同:
1.没有账户的概念
2.没有余额的概念
3.没有地址
4.没有金币
5.没有发送方和接收方
由于区块链是一个公开透明的数据库,并不希望存储用户的钱包等敏感信息。金币不在账户中保存,钱不再从一个地址转到另一个地址,没有账户保存账户余额,区块链仅保存交易信息。那交易又包含什么信息呢?
比特币交易
一个交易由输入输出构成:
一笔新的交易的输入指向之前一笔旧的交易的输出(矿工费除外,即每个区块的第一笔交易)。交易输出是真正保存金币的地方。
需要注意的是:
1.存在未被输入引用的输出,即未被花费的金币
2.在一笔交易中,输入可能来源于多比旧的交易的输出
3.一笔输入必须引用一个交易的输出
在本文中,我们将继续使用钱、金币、花费、发送、账户等字眼。但是在比特币中并不存在这种概念,交易仅仅将价值由脚本锁定,该脚本指定只有锁定这笔交易的用户可以解锁使用该笔交易的输出。
交易输出
实际上,比特币使用交易输出存储金币(Value),存储的意思是使用一个脚本对交易输出加锁。在比特币中,该脚本定义了输出加锁解锁的逻辑。这种脚本语言非常底层(为了避免黑客攻击或者误用)。由于我们尚未实现地址的概念,因此这里不再赘述该脚本的加解锁。我们可以直接定义加解密使用设定的字符串。
需要注意的是一笔交易中的输出是一个整体,不可分割,即每个交易输出的Value都不可分割。
交易输入
交易输入指向以前交易的输出,因此交易输入中的TxId为指向交易的Id,Vout为指向交易的交易输出的索引,ScriptKey为交易输出提供数据。如果数据正确,交易输出可以解锁,即该笔交易输出的Value可以用于生成新的交易输出。这种机制保证了,一个用户只能使用自己加锁的金币。
先有鸡还是先有蛋?
在比特币中,交易输入来源于交易输出的逻辑类似鸡蛋问题。在比特币中,先有蛋而后有鸡。交易输入来源于交易输出,交易输出为交易输入提供来源。
当一个矿工在挖掘一个区块的时候,他会在区块的交易前加一笔coinbase交易,该交易输入不需要指向之前的交易输出,这笔交易是对矿工挖出区块的奖励。
一笔coinbase交易只有一个交易输入,该交易输入的TxId为空,Vout为-1,同时CoinBase交易不使用ScriptKey,保存特定字符串。
区块链保存交易数据
相应的创世区块和区块生成需要修改:
ProofOfWork需要做相应的修改:
在这里,我们仍然使用哈希来代表每个区块交易,其算法如下图:
我们简单的将所有交易的id进行join然后计算出其哈希,作为挖矿的数据准备。在比特币中,采用merlerk tree对交易进行处理。容后再述。
未花费交易输出
为了实现交易,我们需要找到某个用户所有的未花费交易输出(Unspent Transaction Outpus,UTXO)。未花费意味着,该交易输出未被新的交易输入引用。对应第一张图中的:
1.Tx0 Output1
2.Tx1 Output0
3.Tx3 Output0
4.Tx4 Output0
当然,当我们查看余额时,我们只需要对应地址可以解锁的输出。在交易输入输出中,我们定义了简单的加锁逻辑。
在交易中,为了找到UTXO,我们可先查找有交易输出未被引用的交易,其实现如下:
在以上代码中,我们将被交易输入引用的交易存储在spentTXO中,循环每个交易查看,交易输出,若满足交易输出未被引用则将其append到unspentTX中。
在上述代码中,我们找到了存在未被引用的output的交易,且存在output可以被指定address解锁的交易序列。接下来,我们从中找出,address锁定的output,即UTXOs:
若想查看address对余额,我们只需要将UTXO中的Value相加即可,具体实现如下:
转账
为了将coin从一个address转入另一个address,我们需要实现一笔交易(非coinBase)。首先我们需要从未花费交易中找到足够的余额和对应tx,作为新的一笔交易的输入引用。
用以上交易,作为新的交易的输入,并生成新的交易输出,建立交易。
最后,我们将一笔笔交易加入区块,并计入区块链。