在比特币交易中有一个非常重要的概念UTXO(Unspent Transaction Output),也就是说比特币用UTXO取代了传统的账号系统。这句话如何理解呢,我们做个对比就知道了。假设A,B2位矿工分别挖到区块,获得coinbase奖励25btc,然后均转给C,C又转40个BTC给D。那么传统的账号系统,如下图:
UTXO的流通方式如下:
做过数据运维的朋友可能会说,UTXO不就是数据库里的日志表嘛?此话不假。我们知道银行或是其他的类似系统,不仅要对数据结果做落地入库,还要对交易记录做严格的管理,一旦发现数据异常,就要通过交易记录逐笔分析。所以严格意义上的讲,数据结果表是冗余的,因为结果是可以通过过程演算出来的,而且记录更容易查到那些作弊的人。当然记录推算结果也是要付出代价的,比如要消耗大量的计算,同时对记录的完整性有非常高的要求,不能有任何一条记录出错,否则全盘出错。比特币经过去中心化的分布式存储,以及共识机制的改良,把UTXO的思想发挥到了极致。
我们再来看一下master bitcoin的书中对UTXO的描述:
比特币交易的基本单位是未经使用的一个交易输出,简称UTXO。UTXO是不能再分割、被所有者锁住或记录于区块链中的并被整个网络识别成货币单位的一定量的比特币货币。比特币网络监测着以百万为单位的所有可用的(未花费的)UTXO。当一个用户接收比特币时,金额被当作UTXO记录到区块链里。这样,一个用户的比特币会被当作UTXO分散到数百个交易和数百个区块中。实际上,并不存在储存比特币地址或账户余额的地点,只有被所有者锁住的、分散的UTXO。“一个用户的比特币余额”,这个概念是一个通过比特币钱包应用创建的派生之物。比特币钱包通过扫描区块链并聚合所有属于该用户的UTXO来计算该用户的余额。
下面我们来分析UTXO中的TX,TX就是transaction的缩写。CTransaction有两个重要的成员变量std::vector
(上图有个笔误,CTxOut中的scriptPubKey应该是锁定脚本)
对应的数据库序列化如下:
普通交易输入(CTxIn)
普通交易输出(CTxOut)
交易(CTransaction)
创世coinbase
CTxIn类代码
/** An input of a transaction. It contains the location of the previous
* transaction's output that it claims and a signature that matches the
* output's public key.
*/
class CTxIn
{
public:
COutPoint prevout; //上一笔交易输出位置(通过hash定位到交易,通过索引定位到vout)
CScript scriptSig; //解锁脚本
unsigned int nSequence; //序列号
CTxIn()
{
nSequence = std::numeric_limits::max();
}
explicit CTxIn(COutPoint prevoutIn, CScript scriptSigIn=CScript(), unsigned int nSequenceIn=std::numeric_limits::max())
{
prevout = prevoutIn;
scriptSig = scriptSigIn;
nSequence = nSequenceIn;
}
CTxIn(uint256 hashPrevTx, unsigned int nOut, CScript scriptSigIn=CScript(), unsigned int nSequenceIn=std::numeric_limits::max())
{
prevout = COutPoint(hashPrevTx, nOut);
scriptSig = scriptSigIn;
nSequence = nSequenceIn;
}
IMPLEMENT_SERIALIZE
(
READWRITE(prevout);
READWRITE(scriptSig);
READWRITE(nSequence);
)
bool IsFinal() const
{
return (nSequence == std::numeric_limits::max());
}
friend bool operator==(const CTxIn& a, const CTxIn& b)
{
return (a.prevout == b.prevout &&
a.scriptSig == b.scriptSig &&
a.nSequence == b.nSequence);
}
friend bool operator!=(const CTxIn& a, const CTxIn& b)
{
return !(a == b);
}
std::string ToString() const
{
std::string str;
str += "CTxIn(";
str += prevout.ToString();
if (prevout.IsNull())
str += strprintf(", coinbase %s", HexStr(scriptSig).c_str());
else
str += strprintf(", scriptSig=%s", scriptSig.ToString().substr(0,24).c_str());
if (nSequence != std::numeric_limits::max())
str += strprintf(", nSequence=%u", nSequence);
str += ")";
return str;
}
void print() const
{
printf("%s\n", ToString().c_str());
}
};
CTxOut类代码
/** An output of a transaction. It contains the public key that the next input
* must be able to sign with to claim it.
*/
class CTxOut
{
public:
int64 nValue; //输出金额
CScript scriptPubKey; //锁定脚本
CTxOut()
{
SetNull();
}
CTxOut(int64 nValueIn, CScript scriptPubKeyIn)
{
nValue = nValueIn;
scriptPubKey = scriptPubKeyIn;
}
IMPLEMENT_SERIALIZE
(
READWRITE(nValue);
READWRITE(scriptPubKey);
)
void SetNull()
{
nValue = -1;
scriptPubKey.clear();
}
bool IsNull() const
{
return (nValue == -1);
}
uint256 GetHash() const
{
return SerializeHash(*this);
}
friend bool operator==(const CTxOut& a, const CTxOut& b)
{
return (a.nValue == b.nValue &&
a.scriptPubKey == b.scriptPubKey);
}
friend bool operator!=(const CTxOut& a, const CTxOut& b)
{
return !(a == b);
}
bool IsDust() const;
std::string ToString() const
{
if (scriptPubKey.size() < 6)
return "CTxOut(error)";
return strprintf("CTxOut(nValue=%"PRI64d".%08"PRI64d", scriptPubKey=%s)", nValue / COIN, nValue % COIN, scriptPubKey.ToString().substr(0,30).c_str());
}
void print() const
{
printf("%s\n", ToString().c_str());
}
};
CTransaction类代码
/** The basic transaction that is broadcasted on the network and contained in
* blocks. A transaction can contain multiple inputs and outputs.
*/
/**
* 交易可以在公网中通过p2p进行广播,也可以被打包在区块中。
* 每一笔交易可以有多个输入和输出。
*/
class CTransaction
{
public:
static int64 nMinTxFee; //最小交易手续费
static int64 nMinRelayTxFee; //最小传播交易手续费
static const int CURRENT_VERSION=1; //当前版本号
int nVersion; //版本号
std::vector vin; //交易输入列表
std::vector vout; //交易输出列表
unsigned int nLockTime; //锁定时间
CTransaction()
{
SetNull();
}
IMPLEMENT_SERIALIZE
(
READWRITE(this->nVersion);
nVersion = this->nVersion;
READWRITE(vin);
READWRITE(vout);
READWRITE(nLockTime);
)
void SetNull()
{
nVersion = CTransaction::CURRENT_VERSION;
vin.clear();
vout.clear();
nLockTime = 0;
}
bool IsNull() const
{
return (vin.empty() && vout.empty());
}
uint256 GetHash() const
{
return SerializeHash(*this);
}
bool IsFinal(int nBlockHeight=0, int64 nBlockTime=0) const
{
// Time based nLockTime implemented in 0.1.6
if (nLockTime == 0)
return true;
if (nBlockHeight == 0)
nBlockHeight = nBestHeight;
if (nBlockTime == 0)
nBlockTime = GetAdjustedTime();
if ((int64)nLockTime < ((int64)nLockTime < LOCKTIME_THRESHOLD ? (int64)nBlockHeight : nBlockTime))
return true;
BOOST_FOREACH(const CTxIn& txin, vin)
if (!txin.IsFinal())
return false;
return true;
}
bool IsNewerThan(const CTransaction& old) const
{
if (vin.size() != old.vin.size())
return false;
for (unsigned int i = 0; i < vin.size(); i++)
if (vin[i].prevout != old.vin[i].prevout)
return false;
bool fNewer = false;
unsigned int nLowest = std::numeric_limits::max();
for (unsigned int i = 0; i < vin.size(); i++)
{
if (vin[i].nSequence != old.vin[i].nSequence)
{
if (vin[i].nSequence <= nLowest)
{
fNewer = false;
nLowest = vin[i].nSequence;
}
if (old.vin[i].nSequence < nLowest)
{
fNewer = true;
nLowest = old.vin[i].nSequence;
}
}
}
return fNewer;
}
bool IsCoinBase() const
{
return (vin.size() == 1 && vin[0].prevout.IsNull());
}
/** Check for standard transaction types
@return True if all outputs (scriptPubKeys) use only standard transaction forms
*/
/*
* 判断该交易是否合法,主要通过检查交易的极端的size,
* txin的scriptSig,以及txout的scriptPubKey
*/
bool IsStandard() const;
/** Check for standard transaction types
@param[in] mapInputs Map of previous transactions that have outputs we're spending
@return True if all inputs (scriptSigs) use only standard transaction forms
*/
/*
*检查交易输入的scriptSigs的合法性
*/
bool AreInputsStandard(CCoinsViewCache& mapInputs) const;
/** Count ECDSA signature operations the old-fashioned (pre-0.6) way
@return number of sigops this transaction's outputs will produce when spent
*/
unsigned int GetLegacySigOpCount() const;
/** Count ECDSA signature operations in pay-to-script-hash inputs.
@param[in] mapInputs Map of previous transactions that have outputs we're spending
@return maximum number of sigops required to validate this transaction's inputs
*/
unsigned int GetP2SHSigOpCount(CCoinsViewCache& mapInputs) const;
/** Amount of bitcoins spent by this transaction.
@return sum of all outputs (note: does not include fees)
*/
int64 GetValueOut() const
{
int64 nValueOut = 0;
BOOST_FOREACH(const CTxOut& txout, vout)
{
nValueOut += txout.nValue;
if (!MoneyRange(txout.nValue) || !MoneyRange(nValueOut))
throw std::runtime_error("CTransaction::GetValueOut() : value out of range");
}
return nValueOut;
}
/** Amount of bitcoins coming in to this transaction
Note that lightweight clients may not know anything besides the hash of previous transactions,
so may not be able to calculate this.
@param[in] mapInputs Map of previous transactions that have outputs we're spending
@return Sum of value of all inputs (scriptSigs)
*/
int64 GetValueIn(CCoinsViewCache& mapInputs) const;
static bool AllowFree(double dPriority)
{
// Large (in bytes) low-priority (new, small-coin) transactions
// need a fee.
return dPriority > COIN * 144 / 250;
}
int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=true, enum GetMinFee_mode mode=GMF_BLOCK) const;
friend bool operator==(const CTransaction& a, const CTransaction& b)
{
return (a.nVersion == b.nVersion &&
a.vin == b.vin &&
a.vout == b.vout &&
a.nLockTime == b.nLockTime);
}
friend bool operator!=(const CTransaction& a, const CTransaction& b)
{
return !(a == b);
}
std::string ToString() const
{
std::string str;
str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%"PRIszu", vout.size=%"PRIszu", nLockTime=%u)\n",
GetHash().ToString().c_str(),
nVersion,
vin.size(),
vout.size(),
nLockTime);
for (unsigned int i = 0; i < vin.size(); i++)
str += " " + vin[i].ToString() + "\n";
for (unsigned int i = 0; i < vout.size(); i++)
str += " " + vout[i].ToString() + "\n";
return str;
}
void print() const
{
printf("%s", ToString().c_str());
}
// Check whether all prevouts of this transaction are present in the UTXO set represented by view
// 判断该交易的所有的prevouts是否出现在视图的UTXO集合中
bool HaveInputs(CCoinsViewCache &view) const;
// Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
// This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it
// instead of being performed inline.
// 判断该交易的所有输入是否是合法(是否双花,scripts & sigs, 金额)
bool CheckInputs(CValidationState &state, CCoinsViewCache &view, bool fScriptChecks = true,
unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC,
std::vector *pvChecks = NULL) const;
// Apply the effects of this transaction on the UTXO set represented by view
// 更新该交易到视图的UTXO集合
void UpdateCoins(CValidationState &state, CCoinsViewCache &view, CTxUndo &txundo, int nHeight, const uint256 &txhash) const;
// Context-independent validity checks
// 上下文无关的日常校验
bool CheckTransaction(CValidationState &state) const;
// Try to accept this transaction into the memory pool
// 接受该交易,添加到交易池
bool AcceptToMemoryPool(CValidationState &state, bool fCheckInputs=true, bool fLimitFree = true, bool* pfMissingInputs=NULL);
protected:
static const CTxOut &GetOutputFor(const CTxIn& input, CCoinsViewCache& mapInputs);
};
关于锁定时间的解释如下:
交易的锁定时间
锁定时间定义了能被加到区块链里的最早的交易时间。在大多数交易里,它被设置成0,用来表示立即执行。如
果锁定时间不是0并且小于5亿,就被视为区块高度,意指在这个指定的区块高度之前,该交易没有被包含在区块
链里。如果锁定时间大于5亿,则它被当作是一个Unix纪元时间戳(从1970年1月1日以来的秒数),并且在这个
指定时点之前,该交易没有被包含在区块链里。锁定时间的使用相当于将一张纸质支票的生效时间予以后延。
在熟悉交易的数据结构后,我们再来看一下交易流程:
具体的步骤如下:
一、构造交易
钱包类CWallet的CreateTransaction函数创建交易。
主要分3步:
1、填充交易的输出交易(vout)
创建交易时,指定输出交易的信息,主要是输出的脚本(地址构造成CScript)、输出的币数量(nValue)。
2、填充交易的输出交易(vin)
先从钱包的交易信息中选择合适的比特币(SelectCoins函数),填充到交易的输入交易中。
3、签名(CTxIn.scriptSig)
对输入交易的scriptSig签名(SignSignature函数)。
由新的交易信息、私钥计算哈希值(SignatureHash函数),填充到输入交易的scriptSig中(Solver函数)。构造交易完毕,再提交交易,发送出去。
二、发送交易
当构造完交易,则提交交易(钱包类CWallet的CommitTransaction函数),发送出去。
提交交易时,先把交易添加到钱包中,然后标记旧的比特币为已经花费,再添加到交易内存池中,最后把交易传播下去。
传播交易时(RelayTransaction函数),由交易信息的哈希值构造CInv,类型为MSG_TX,添加到每个节点的发送清单(vInventoryToSend)中,发送消息(SendMessages)时把节点中的发送清单中的交易信息以”inv”命令发送出去。
三、接收交易
当节点接收到交易命令(”tx”)后,把交易信息添加到交易内存池中,且传播
下去,详见ProcessMessage函数。