Tinychain源码阅读笔记3-验证交易
Tinychain源码阅读笔记3-交易有效性
def validate_txn(txn: Transaction,
as_coinbase: bool = False,
siblings_in_block: Iterable[Transaction] = None,
allow_utxo_from_mempool: bool = True,
) -> Transaction:
"""
Validate a single transaction. Used in various contexts, so the
parameters facilitate different uses.
"""
txn.validate_basics(as_coinbase=as_coinbase)
available_to_spend = 0
for i, txin in enumerate(txn.txins):
utxo = utxo_set.get(txin.to_spend)
if siblings_in_block:
utxo = utxo or find_utxo_in_list(txin, siblings_in_block)
if allow_utxo_from_mempool:
utxo = utxo or find_utxo_in_mempool(txin)
if not utxo:
raise TxnValidationError(
f'Could find no UTXO for TxIn[{i}] -- orphaning txn',
to_orphan=txn)
if utxo.is_coinbase and \
(get_current_height() - utxo.height) < \
Params.COINBASE_MATURITY:
raise TxnValidationError(f'Coinbase UTXO not ready for spend')
try:
validate_signature_for_spend(txin, utxo, txn)
except TxUnlockError:
raise TxnValidationError(f'{txin} is not a valid spend of {utxo}')
available_to_spend += utxo.value
if available_to_spend < sum(o.value for o in txn.txouts):
raise TxnValidationError('Spend value is more than available')
return txn
传递的参数是
validate_txn(txn, siblings_in_block=block.txns[1:],
allow_utxo_from_mempool=False)
首先遍历txn中的所有txins,也就是交易输入,然后utxo = utxo_set.get(txin.to_spend)
看看txin
和utxo_set
的结构
class TxIn(NamedTuple):
"""Inputs to a Transaction."""
# A reference to the output we're spending. This is None for coinbase
# transactions.
to_spend: Union[OutPoint, None]
# The (signature, pubkey) pair which unlocks the TxOut for spending.
unlock_sig: bytes
unlock_pk: bytes
# A sender-defined sequence number which allows us replacement of the txn
# if desired.
sequence: int
utxo_set: Mapping[OutPoint, UnspentTxOut] = {}
OutPoint = NamedTuple('OutPoint', [('txid', str), ('txout_idx', int)])
utxo_set
是OutPoint
为key,UnspentTxOut
为value的键值对集合,OutPoint
是txid
和txout_idx
组成的NamedTuple
。也就是通过交易哈希值和交易输出的序号,得到utxo
。
utxo_set
又是如何被赋值的呢?在后面我们会看到add_to_utxo
这个函数
def add_to_utxo(txout, tx, idx, is_coinbase, height):
utxo = UnspentTxOut(
*txout,
txid=tx.id, txout_idx=idx, is_coinbase=is_coinbase, height=height)
logger.info(f'adding tx outpoint {utxo.outpoint} to utxo_set')
utxo_set[utxo.outpoint] = utxo
add_to_utxo
接受交易输出、交易、交易输出的序列号、是否为coinbase交易的bool值以及交易高度这些参数,然后根据这些参数生成了一个UnspentTxOut
对象
class UnspentTxOut(NamedTuple):
value: int
to_address: str
# The ID of the transaction this output belongs to.
txid: str
txout_idx: int
# Did this TxOut from from a coinbase transaction?
is_coinbase: bool
# The blockchain height this TxOut was included in the chain.
height: int
@property
def outpoint(self): return OutPoint(self.txid, self.txout_idx)
除了那些参数外,UnspentTxOut
自带outpoint
这个属性,它返回一个OutPoint
。所以在add_to_utxo
中,utxo_set[utxo.outpoint] = utxo
。utxo_set
就设置好了,utxo_set
实际上就是所有未被花费的交易输出。
siblings_in_block
是block.txn[1:],
if siblings_in_block:
utxo = utxo or find_utxo_in_list(txin, siblings_in_block)
find_utxo_in_list
函数
def find_utxo_in_list(txin, txns) -> UnspentTxOut:
txid, txout_idx = txin.to_spend
try:
txout = [t for t in txns if t.id == txid][0].txouts[txout_idx]
except Exception:
return None
return UnspentTxOut(
*txout, txid=txid, is_coinbase=False, height=-1, txout_idx=txout_idx)
这个函数作用是从同一个block
的txn
中找所需要的utxo
,为什么要这样做呢,因为有可能一个人短时间内连续发起两笔交易,交易还未确认,第二笔交易引用了第一笔交易输出的utxo
,为了验证这样的情况,就进行了这样的检查。
最后,需要对交易签名进行验证,看validate_signature_for_spend
函数
def validate_signature_for_spend(txin, utxo: UnspentTxOut, txn):
pubkey_as_addr = pubkey_to_address(txin.unlock_pk)
verifying_key = ecdsa.VerifyingKey.from_string(
txin.unlock_pk, curve=ecdsa.SECP256k1)
if pubkey_as_addr != utxo.to_address:
raise TxUnlockError("Pubkey doesn't match")
try:
spend_msg = build_spend_message(
txin.to_spend, txin.unlock_pk, txin.sequence, txn.txouts)
verifying_key.verify(txin.unlock_sig, spend_msg)
except Exception:
logger.exception('Key verification failed')
raise TxUnlockError("Signature doesn't match")
return True
首先将txin
中的unlock_pk
转化为比特币地址,与所引用的utxo
中的address
比较,如果这一步都错了,即所提供的公钥不匹配,那肯定验证失败。然后生成了一个由txin.to_spend
,txin.unlock_pk
,txin.sequence
,txn.txouts
生成的消息spend_msg
,
最后将签名txin.unlock_sig
, 消息spend_msg
传递给函数verifying_key.verify
进行验证,这样就在不揭露私钥的情况下验证交易。值得一提的是,tinychain
并没有提供解锁脚本和锁定脚本来验证交易,因为tinychain
只提供P2PKH
形式的交易。想深入更多比特币交易的细节,请参考精通比特币第六章。