主页 > imtoken钱包下载苹果 > 比特币源码分析——交易

比特币源码分析——交易

imtoken钱包下载苹果 2023-01-17 02:38:50

交易是比特币的核心数据结构之一。交易的产生、共识、存储、传播都是建立在它之上的,所以我们从交易开始,然后扩展到其他部分。

我们可以使用 Bitcoin Core 的命令行界面(getrawtransaction 和 decodeawtransaction)来检索“原始”交易,对其进行解码,然后查看它包含的内容。

{
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid":"7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
      "vout": 0,
      "scriptSig": "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
      "sequence": 4294967295
    }
 ],
  "vout": [
    {
      "value": 0.01500000,
      "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
    },
    {
      "value": 0.08450000,
      "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
    }
  ]
}

可以发现事务中有两个关键成员变量vin和vout。

这两个变量分别代表比特币交易的“收入”和“支出”。比特币交易不以账户的形式记录数据变化(比如我们用银行模型来描述从 A 到 B 的 100 元转账,那么银行在记录这次转账的过程中会记录 3 条记录,而这些三条记录连接为A Transaction(交易)过程:A的账户减100元,记录id为tid1,B的账户加100元,记录id为tid2,一条转账记录记录tid1转账100元tid2,成为 A 账户减少与 B 账户增加之间的“关系”呈对数形:比特币的 Tx 只记录了 A 向 B 转账的“关系”。该日志记录仅包括A向B转账100元的信息。而这里的in是从'who'记录下来的(目前看的比较简单,其实不止这些,后面会重新解释),out是转给谁的,还有转出的金额包含在 out 中。在中本聪的命名风格中,使用前缀来表示这个属性的类型。如果它是一个标志,一个 f 将被添加。所以这里的vin/vout表示in和out都是向量类型,所以这里我们可以看到一个Tx可以有多个in/out。在下面的文字中,我们调用了TxIn和out TxOut(注意,将in out与两个人进行比较是完全不合适的,我们稍后会重新讨论。描述)而这里的in是从'who'记录下来的(目前看的比较简单,其实不止这些,后面会重新解释),out是转给谁的,还有转出的金额包含在 out 中。在中本聪的命名风格中,使用前缀来表示这个属性的类型。如果它是一个标志,一个 f 将被添加。所以这里的vin/vout表示in和out都是向量类型,所以这里我们可以看到一个Tx可以有多个in/out。在下面的文字中,我们调用了TxIn和out TxOut(注意,将in out与两个人进行比较是完全不合适的,我们稍后会重新讨论。描述)而这里的in是从'who'记录下来的(目前看的比较简单,其实不止这些,后面会重新解释),out是转给谁的,还有转出的金额包含在 out 中。在中本聪的命名风格中比特币的源代码在哪里,使用前缀来表示这个属性的类型。如果它是一个标志比特币的源代码在哪里,一个 f 将被添加。所以这里的vin/vout表示in和out都是向量类型,所以这里我们可以看到一个Tx可以有多个in/out。在下面的文字中,我们调用了TxIn和out TxOut(注意,将in out与两个人进行比较是完全不合适的,我们稍后会重新讨论。描述)out 是转给谁的,转出的金额包含在 out 中。在中本聪的命名风格中,使用前缀来表示这个属性的类型。如果它是一个标志,一个 f 将被添加。所以这里的vin/vout表示in和out都是向量类型,所以这里我们可以看到一个Tx可以有多个in/out。在下面的文字中,我们调用了TxIn和out TxOut(注意,将in out与两个人进行比较是完全不合适的,我们稍后会重新讨论。描述)out 是转给谁的,转出的金额包含在 out 中。在中本聪的命名风格中,使用前缀来表示这个属性的类型。如果它是一个标志,一个 f 将被添加。所以这里的vin/vout表示in和out都是向量类型,所以这里我们可以看到一个Tx可以有多个in/out。在下面的文字中,我们调用了TxIn和out TxOut(注意,将in out与两个人进行比较是完全不合适的,我们稍后会重新讨论。描述)

CTransaction类就是我们常说的比特币的“交易”(通常称为Tx,后面会用到)

Tx 类存储了以上两个关键变量

向量 vin;

向量 vout;

比特币的源代码在哪里

下面介绍Tx的源码:

/** The basic transaction that is broadcasted on the network and contained in blocks.  
 * A transaction can contain multiple inputs and outputs.
 * 下面就是在网络中广播然后被打包进区块的最基本的交易的结构,一个交易可能包含多个交易输入和输出。
 */
class CTransaction
{
public:
    // Default transaction version. 默认交易版本
    static const int32_t CURRENT_VERSION=2;
    // Changing the default transaction version requires a two step process: first
    // adapting relay policy by bumping MAX_STANDARD_VERSION, and then later date
    // bumping the default CURRENT_VERSION at which point both CURRENT_VERSION and
    // MAX_STANDARD_VERSION will be equal.
    static const int32_t MAX_STANDARD_VERSION=2;
    // The local variables are made const to prevent unintended modification
    // without updating the cached hash value. However, CTransaction is not
    // actually immutable; deserialization and assignment are implemented,
    // and bypass the constness. This is safe, as they update the entire
    // structure, including the hash.
    /** 下面这些变量都被定义为常量类型,从而避免无意识的修改了交易而没有更新缓存的hash值;
    * 但还是可以通过重新构造一个交易然后赋值给当前交易来进行修改,这样就更新了交易的所有内容
    */
    const int32_t nVersion;  // 版本
    const std::vector vin; // 交易输入
    const std::vector vout; // 交易输出
    const uint32_t nLockTime; // 锁定时间
private:
    /** Memory only. */
    const uint256 hash;
    uint256 ComputeHash() const;
public:
    /** Construct a CTransaction that qualifies as IsNull() */
    CTransaction();
    /** Convert a CMutableTransaction into a CTransaction. */
    CTransaction(const CMutableTransaction &tx);
    CTransaction(CMutableTransaction &&tx);
    template 
    inline void Serialize(Stream& s) const {
        SerializeTransaction(*this, s);
    }
    /** This deserializing constructor is provided instead of an Unserialize method.
     *  Unserialize is not possible, since it would require overwriting const fields. */
    template 
    CTransaction(deserialize_type, Stream& s) : CTransaction(CMutableTransaction(deserialize, s)) {}
    bool IsNull() const {
        return vin.empty() && vout.empty();
    }
    const uint256& GetHash() const {
        return hash;
    }
    // Compute a hash that includes both transaction and witness data
    uint256 GetWitnessHash() const;
    // Return sum of txouts.
    CAmount GetValueOut() const; // 返回交易输出金额之和
    // GetValueIn() is a method on CCoinsViewCache, because
    // inputs must be known to compute value in.
    /**
     * Get the total transaction size in bytes, including witness data.
     * "Total Size" defined in BIP141 and BIP144.
     * @return Total transaction size in bytes
     */
    unsigned int GetTotalSize() const; // 返回交易大小
    bool IsCoinBase() const  // 判断是否是coinbase交易
    {
        return (vin.size() == 1 && vin[0].prevout.IsNull());
    }
    friend bool operator==(const CTransaction& a, const CTransaction& b)
    {
        return a.hash == b.hash;
    }
    friend bool operator!=(const CTransaction& a, const CTransaction& b)
    {
        return a.hash != b.hash;
    }
    std::string ToString() const;
    bool HasWitness() const
    {
        for (size_t i = 0; i < vin.size(); i++) {
            if (!vin[i].scriptWitness.IsNull()) {
                return true;
            }
        }
        return false;
    }
};

从这一步,我们直接抛弃了“两个人之间的交易”的概念,直接认为比特币交易系统中没有“每个人”的概念(这一定很奇怪,因为没有比特币的所有者是什么意思币,但我稍后会解释),但只是将“交易”视为“比特币流”的中转节点,就像那些被水流叉合并的节点一样:

典型的比特币交易链:

比特币有个很重要的规定,就是每一个Tx的In全部进入币流,并且必须在本次交易中全部流出(流出并不意味着成为其他Tx的In,而是必须成为一个TxOut。)

例如:如果A给B转了100,但是现在A可以控制2个Out,一个是Out1是60,另一个是Out2是50,那么A检查自己的时候会发现60和50都不够Out 100,则只能使用 Out1 和 Out2 作为当前要生产的 Tx 的 In。但是在这种情况下,所有Ins的总和大于100要花费。那么,如果没有支付交易费用,不包括当前Tx对应的100转给B的Out,就会多出10。在比特币中,必须为额外的10个区块创建一个Out来锁定这些10 个区块,这样每个交易的 In 和 Out 的总数必须相同。那么因为这10个相当于我们通俗意义上的“变变”,所以Out的10个区块的锁当然是A可以控制的锁,

所以我们可以看到一笔交易只包含一个输入和一个输出,那么这个交易就不算是从一个人到另一个人的转账,而是像水一样的货币流动,从某个本地流入到这个的输入交易,并从这个交易的输出到另一个地方。那么接下来的问题就变得显而易见了——如何控制资金流向?答案是 CTxIn 和 CTxOut 的属性。

让我们看看这两个类

比特币的源代码在哪里

CTxIn:

class CTxIn{
public:
    COutPoint prevout;
    CScript scriptSig;
    unsigned int nSequence;
};  

Tx 流入的信息由 COutPoint 记录。

nSequence 在 v0.1 中没有任何作用,也不会用于验证,但该字段以后会用于其他用途,是比特币软分叉的最好例子。

COutPoint 的属性:

/** An outpoint - a combination of a transaction hash and an index n into its vout. 
* COutPoint主要用在交易的输入CTxIn中,用来确定当前输出的来源,
* 包括前一笔交易的hash,以及对应前一笔交易中的第几个输出的序列号。
*/
class COutPoint
{
public:
    uint256 hash; // 交易的哈希
    uint32_t n;  // 对应的序列号
    COutPoint() { SetNull(); }
    COutPoint(uint256 hashIn, uint32_t nIn) { hash = hashIn; n = nIn; }
    ADD_SERIALIZE_METHODS;  // 用来序列化数据结构,方便存储和传输
};

CTx输出:

class CTxOut{
public:
    int64 nValue;
    CScript scriptPubKey;
};

value 是记录“从这个出口流出多少”的信息。简单来说,可以理解为通俗意义上的转移。但我们这里还是强调,对比特币的第一个理解是抛开支付交易等概念,而是把比特币当成流水,这里的价值是记录有多少比特币会从这里流出。显然,一个Tx的所有TxOut的值之和应该等于所有TxIn流入的总和(不考虑手续费,手续费小于等于弱对价),否则应考虑交易非法(你不能凭空花更多的钱)。

下面将解释CTxIn.scriptSig、CTxOut.scriptPubKey