
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形式的交易。想深入更多比特币交易的细节,请参考精通比特币第六章。
Leave Tinychain源码阅读笔记3-验证交易 to:
Read more #cn posts
Best Posts From derekray
We have not curated any of derekray's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.