Tinychain源码阅读笔记3-验证交易

in #cn5 years ago (edited)

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)看看txinutxo_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_setOutPoint为key,UnspentTxOut为value的键值对集合,OutPointtxidtxout_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] = utxoutxo_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)

这个函数作用是从同一个blocktxn中找所需要的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_spendtxin.unlock_pktxin.sequencetxn.txouts生成的消息spend_msg
最后将签名txin.unlock_sig, 消息spend_msg传递给函数verifying_key.verify进行验证,这样就在不揭露私钥的情况下验证交易。值得一提的是,tinychain并没有提供解锁脚本和锁定脚本来验证交易,因为tinychain只提供P2PKH形式的交易。想深入更多比特币交易的细节,请参考精通比特币第六章

Coin Marketplace

STEEM 0.29
TRX 0.12
JST 0.034
BTC 63950.69
ETH 3311.68
USDT 1.00
SBD 3.92