Пример #1
0
 def handleSolve(self, payload):
     addrStr = payload["addr"]
     proof = ByteArray(payload["proof"])
     redemptionAddrStr = payload["redemptionAddr"]
     addr = decodeAddress(addrStr, self.netParams)
     if not isinstance(addr, AddressScriptHash):
         return makeErr("unsupported address type "+str(type(addr)))
     redemptionAddr = decodeAddress(redemptionAddrStr, self.netParams)
     return self.submitProof(addr, proof, redemptionAddr)
Пример #2
0
    def validate(self, addr):
        """
        Validate performs some checks that the PurchaseInfo provided by the
        stake pool API is valid for this given voting address. DecredError is
        raised on failure to validate.

        Args:
            addr (string): The base58-encoded pubkey address that the wallet
                uses to vote.
        """
        pi = self.purchaseInfo
        redeemScript = pi.script
        scriptAddr = addrlib.AddressScriptHash.fromScript(
            redeemScript, self.netParams)
        if scriptAddr.string() != pi.ticketAddress:
            raise DecredError("ticket address mismatch. %s != %s" %
                              (pi.ticketAddress, scriptAddr.string()))
        # extract addresses
        scriptType, addrs, numSigs = txscript.extractPkScriptAddrs(
            0, redeemScript, self.netParams)
        if numSigs != 1:
            raise DecredError("expected 2 required signatures, found 2")
        found = False
        signAddr = addrlib.decodeAddress(addr, self.netParams)
        for addr in addrs:
            if addr.string() == signAddr.string():
                found = True
                break
        if not found:
            raise DecredError("signing pubkey not found in redeem script")
Пример #3
0
 def processTransactionIO(self, addr, ch, tx):
     self.processTransactionInputs(ch, tx)
     pkScript = txscript.payToAddrScript(decodeAddress(addr, self.netParams))
     found = 0
     for vout, txOut in enumerate(tx.txOut):
         if txOut.pkScript == pkScript:
             funds = FundingOutput(addr, tx.cachedHash(), vout, txOut.value, int(time.time()))
             if funds.id in ch.funds:
                 continue
             found += 1
             ch.addFunds(funds)
             self.db(self.insertFunds_, funds)
     log.info(f"found {found} outputs that pay to challenge {addr}")
Пример #4
0
    def purchaseTickets(self, keysource, utxosource, req):
        """
        Based on dcrwallet (*Wallet).purchaseTickets.
        purchaseTickets indicates to the wallet that a ticket should be
        purchased using any currently available funds. Also, when the spend
        limit in the request is greater than or equal to 0, tickets that cost
        more than that limit will return an error that not enough funds are
        available.

        Args:
            keysource account.KeySource: a source for private keys.
            utxosource func(int, filterFunc) -> (list(UTXO), bool): a source for
                UTXOs. The filterFunc is an optional function to filter UTXOs,
                and is of the form func(UTXO) -> bool.
            req account.TicketRequest: the ticket data.

        Returns:
            (splitTx, tickets) tuple: first element is the split transaction.
                Second is a list of generated tickets.
            splitSpent list(msgtx.TxOut): the outputs spent for the split
                transaction.
            internalOutputs list(msgtx.TxOut): new outputs that fund internal
                addresses.

        """
        self.updateTip()
        # account minConf is zero for regular outputs for now. Need to make that
        # adjustable.
        # if req.minConf < 0:
        #     raise DecredError("negative minconf")

        # Need a positive or zero expiry that is higher than the next block to
        # generate.
        if req.expiry < 0:
            raise DecredError("negative expiry")

        # Perform a sanity check on expiry.
        if req.expiry <= self.tipHeight + 1 and req.expiry > 0:
            raise DecredError("expiry height must be above next block height")

        # Fetch a new address for creating a split transaction. Then,
        # make a split transaction that contains exact outputs for use
        # in ticket generation. Cache its hash to use below when
        # generating a ticket. The account balance is checked first
        # in case there is not enough money to generate the split
        # even without fees.
        # TODO (copied from dcrwallet) This can still sometimes fail if the
        # split amount required plus fees for the split is larger than the
        # balance we have, wasting an address. In the future,
        # address this better and prevent address burning.

        # Calculate the current ticket price.
        ticketPrice = self.stakeDiff()

        # Ensure the ticket price does not exceed the spend limit if set.
        if req.spendLimit > 0 and ticketPrice > req.spendLimit:
            raise DecredError("ticket price %f above spend limit %f" %
                              (ticketPrice, req.spendLimit))

        # Check that pool fees is zero, which will result in invalid zero-valued
        # outputs.
        if req.poolFees == 0:
            raise DecredError("no pool fee specified")

        stakeSubmissionPkScriptSize = 0

        # Check the pool address from the request.
        if not req.poolAddress:
            raise DecredError(
                "no pool address specified. solo voting not supported")

        poolAddress = addrlib.decodeAddress(req.poolAddress, self.netParams)

        # Check the passed address from the request.
        if not req.votingAddress:
            raise DecredError(
                "voting address not set in purchaseTickets request")

        # decode the string addresses. This is the P2SH multi-sig script
        # address, not the wallets voting address, which is only one of the two
        # pubkeys included in the redeem P2SH script.
        votingAddress = addrlib.decodeAddress(req.votingAddress,
                                              self.netParams)

        # The stake submission pkScript is tagged by an OP_SSTX.
        if isinstance(votingAddress, addrlib.AddressScriptHash):
            stakeSubmissionPkScriptSize = txscript.P2SHPkScriptSize + 1
        elif (isinstance(votingAddress, addrlib.AddressPubKeyHash)
              and votingAddress.sigType == crypto.STEcdsaSecp256k1):
            stakeSubmissionPkScriptSize = txscript.P2PKHPkScriptSize + 1
        else:
            raise DecredError("unsupported voting address type %s" %
                              votingAddress.__class__.__name__)

        ticketFeeIncrement = req.ticketFee
        if ticketFeeIncrement == 0:
            ticketFeeIncrement = self.relayFee()

        # Make sure that we have enough funds. Calculate different
        # ticket required amounts depending on whether or not a
        # pool output is needed. If the ticket fee increment is
        # unset in the request, use the global ticket fee increment.

        # A pool ticket has:
        #   - two inputs redeeming a P2PKH for the worst case size
        #   - a P2PKH or P2SH stake submission output
        #   - two ticket commitment outputs
        #   - two OP_SSTXCHANGE tagged P2PKH or P2SH change outputs
        #
        #   NB: The wallet currently only supports P2PKH change addresses.
        #   The network supports both P2PKH and P2SH change addresses however.
        inSizes = [
            txscript.RedeemP2PKHSigScriptSize,
            txscript.RedeemP2PKHSigScriptSize
        ]
        outSizes = [
            stakeSubmissionPkScriptSize,
            txscript.TicketCommitmentScriptSize,
            txscript.TicketCommitmentScriptSize,
            txscript.P2PKHPkScriptSize + 1,
            txscript.P2PKHPkScriptSize + 1,
        ]
        estSize = txscript.estimateSerializeSizeFromScriptSizes(
            inSizes, outSizes, 0)

        ticketFee = txscript.calcMinRequiredTxRelayFee(ticketFeeIncrement,
                                                       estSize)
        neededPerTicket = ticketFee + ticketPrice

        # If we need to calculate the amount for a pool fee percentage,
        # do so now.
        poolFeeAmt = txscript.stakePoolTicketFee(
            ticketPrice,
            ticketFee,
            self.tipHeight,
            req.poolFees,
            self.subsidyCache,
            self.netParams,
        )

        # Fetch the single use split address to break tickets into, to
        # immediately be consumed as tickets.
        splitTxAddr = keysource.internal()

        # TODO: Don't reuse addresses
        # TODO: Consider wrapping. see dcrwallet implementation.
        splitPkScript = txscript.makePayToAddrScript(splitTxAddr,
                                                     self.netParams)

        # Create the split transaction by using txToOutputs. This varies
        # based upon whether or not the user is using a stake pool or not.
        # For the default stake pool implementation, the user pays out the
        # first ticket commitment of a smaller amount to the pool, while
        # paying themselves with the larger ticket commitment.
        splitOuts = []
        for i in range(req.count):
            userAmt = neededPerTicket - poolFeeAmt
            poolAmt = poolFeeAmt

            # Pool amount.
            splitOuts.append(
                msgtx.TxOut(
                    value=poolAmt,
                    pkScript=splitPkScript,
                ))

            # User amount.
            splitOuts.append(
                msgtx.TxOut(
                    value=userAmt,
                    pkScript=splitPkScript,
                ))

        txFeeIncrement = req.txFee
        if txFeeIncrement == 0:
            txFeeIncrement = self.relayFee()

        # Send the split transaction.
        # sendOutputs takes the fee rate in atoms/byte
        splitTx, splitSpent, internalOutputs = self.sendOutputs(
            splitOuts, keysource, utxosource, int(txFeeIncrement / 1000))

        # Generate the tickets individually.
        tickets = []

        for i in range(req.count):
            # Generate the extended outpoints that we need to use for ticket
            # inputs. There are two inputs for pool tickets corresponding to the
            # fees and the user subsidy, while user-handled tickets have only one
            # input.
            poolIdx = i * 2
            poolTxOut = splitTx.txOut[poolIdx]
            userIdx = i * 2 + 1
            txOut = splitTx.txOut[userIdx]

            eopPool = txscript.ExtendedOutPoint(
                op=msgtx.OutPoint(
                    txHash=splitTx.cachedHash(),
                    idx=poolIdx,
                    tree=wire.TxTreeRegular,
                ),
                amt=poolTxOut.value,
                pkScript=poolTxOut.pkScript,
            )
            eop = txscript.ExtendedOutPoint(
                op=msgtx.OutPoint(
                    txHash=splitTx.cachedHash(),
                    idx=userIdx,
                    tree=wire.TxTreeRegular,
                ),
                amt=txOut.value,
                pkScript=txOut.pkScript,
            )

            addrSubsidy = addrlib.decodeAddress(keysource.internal(),
                                                self.netParams)

            # Generate the ticket msgTx and sign it.
            ticket = txscript.makeTicket(
                self.netParams,
                eopPool,
                eop,
                votingAddress,
                addrSubsidy,
                ticketPrice,
                poolAddress,
            )
            forSigning = []
            eopPoolCredit = txscript.Credit(
                op=eopPool.op,
                blockMeta=None,
                amount=eopPool.amt,
                pkScript=eopPool.pkScript,
                received=int(time.time()),
                fromCoinBase=False,
            )
            forSigning.append(eopPoolCredit)
            eopCredit = txscript.Credit(
                op=eop.op,
                blockMeta=None,
                amount=eop.amt,
                pkScript=eop.pkScript,
                received=int(time.time()),
                fromCoinBase=False,
            )
            forSigning.append(eopCredit)

            # Set the expiry.
            ticket.expiry = req.expiry

            txscript.signP2PKHMsgTx(ticket, forSigning, keysource,
                                    self.netParams)

            # dcrwallet actually runs the pk scripts through the script
            # Engine as another validation step. Engine not implemented in
            # Python yet.
            # validateMsgTx(op, ticket, creditScripts(forSigning))

            # For now, don't allow any high fees (> 1000x default). Could later
            # be a property of the account.
            if txscript.paysHighFees(eop.amt, ticket):
                raise DecredError("high fees detected")

            self.broadcast(ticket.txHex())
            tickets.append(ticket)
            log.info("published ticket %s" % ticket.txid())

            # Add a UTXO to the internal outputs list.
            txOut = ticket.txOut[0]
            internalOutputs.append(
                account.UTXO(
                    address=votingAddress.string(),
                    txHash=ticket.cachedHash(),
                    vout=0,  # sstx is output 0
                    ts=int(time.time()),
                    scriptPubKey=txOut.pkScript,
                    satoshis=txOut.value,
                ))
        return (splitTx, tickets), splitSpent, internalOutputs
Пример #5
0
def test_addresses():
    addrPKH = addrlib.AddressPubKeyHash
    addrSH = addrlib.AddressScriptHash.fromScript
    addrSHH = addrlib.AddressScriptHash
    addrPK = addrlib.AddressSecpPubKey
    """
    name (str): A name for the test.
    addr (str): The expected Address.string().
    saddr (str): The expected Address.scriptAddress() after decoding.
    encoded (str): The expected Address.address().
    valid (bool): False if the make func is expected to raise an error.
    scriptAddress (ByteArray): The expected Address.scriptAddress(), but for
        the address made with make.
    make (func -> str): A function to create a new Address.
    netParams (module): The network parameters.
    skipComp (bool): Skip string comparison of decoded address. For
        AddressSecpPubKey, the encoded pubkey is unrecoverable from the
        AddressSecpPubKey.address() string.
    """
    tests = [
        # Positive P2PKH tests.
        dict(
            name="mainnet p2pkh",
            addr="DsUZxxoHJSty8DCfwfartwTYbuhmVct7tJu",
            encoded="DsUZxxoHJSty8DCfwfartwTYbuhmVct7tJu",
            valid=True,
            scriptAddress=ByteArray(
                "2789d58cfa0957d206f025c2af056fc8a77cebb0"),
            make=lambda: addrPKH(
                ByteArray("2789d58cfa0957d206f025c2af056fc8a77cebb0"),
                mainnet,
                crypto.STEcdsaSecp256k1,
            ),
            netParams=mainnet,
        ),
        dict(
            name="mainnet p2pkh 2",
            addr="DsU7xcg53nxaKLLcAUSKyRndjG78Z2VZnX9",
            encoded="DsU7xcg53nxaKLLcAUSKyRndjG78Z2VZnX9",
            valid=True,
            scriptAddress=ByteArray(
                "229ebac30efd6a69eec9c1a48e048b7c975c25f2"),
            make=lambda: addrPKH(
                ByteArray("229ebac30efd6a69eec9c1a48e048b7c975c25f2"),
                mainnet,
                crypto.STEcdsaSecp256k1,
            ),
            netParams=mainnet,
        ),
        dict(
            name="testnet p2pkh",
            addr="Tso2MVTUeVrjHTBFedFhiyM7yVTbieqp91h",
            encoded="Tso2MVTUeVrjHTBFedFhiyM7yVTbieqp91h",
            valid=True,
            scriptAddress=ByteArray(
                "f15da1cb8d1bcb162c6ab446c95757a6e791c916"),
            make=lambda: addrPKH(
                ByteArray("f15da1cb8d1bcb162c6ab446c95757a6e791c916"),
                testnet,
                crypto.STEcdsaSecp256k1,
            ),
            netParams=testnet,
        ),
        # Negative P2PKH tests.
        dict(
            name="p2pkh wrong hash length",
            addr="",
            valid=False,
            make=lambda: addrPKH(
                ByteArray("000ef030107fd26e0b6bf40512bca2ceb1dd80adaa"),
                mainnet,
                crypto.STEcdsaSecp256k1,
            ),
            netParams=mainnet,
        ),
        dict(
            name="p2pkh bad checksum",
            addr="TsmWaPM77WSyA3aiQ2Q1KnwGDVWvEkhip23",
            valid=False,
            netParams=testnet,
        ),
        # Positive P2SH tests.
        dict(
            # Taken from transactions:
            # output:
            #   3c9018e8d5615c306d72397f8f5eef44308c98fb576a88e030c25456b4f3a7ac
            # input:
            #   837dea37ddc8b1e3ce646f1a656e79bbd8cc7f558ac56a169626d649ebe2a3ba.
            name="mainnet p2sh",
            addr="DcuQKx8BES9wU7C6Q5VmLBjw436r27hayjS",
            encoded="DcuQKx8BES9wU7C6Q5VmLBjw436r27hayjS",
            valid=True,
            scriptAddress=ByteArray(
                "f0b4e85100aee1a996f22915eb3c3f764d53779a"),
            make=lambda: addrSH(
                ByteArray("512103aa43f0a6c15730d886cc1f0342046d2"
                          "0175483d90d7ccb657f90c489111d794c51ae"),
                mainnet,
            ),
            netParams=mainnet,
        ),
        dict(
            # Taken from transactions:
            # output:
            #   b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d
            # input: (not yet redeemed at time test was written)
            name="mainnet p2sh 2",
            addr="DcqgK4N4Ccucu2Sq4VDAdu4wH4LASLhzLVp",
            encoded="DcqgK4N4Ccucu2Sq4VDAdu4wH4LASLhzLVp",
            valid=True,
            scriptAddress=ByteArray(
                "c7da5095683436f4435fc4e7163dcafda1a2d007"),
            make=lambda: addrSHH(
                ByteArray("c7da5095683436f4435fc4e7163dcafda1a2d007"),
                mainnet,
            ),
            netParams=mainnet,
        ),
        dict(
            # Taken from bitcoind base58_keys_valid.
            name="testnet p2sh",
            addr="TccWLgcquqvwrfBocq5mcK5kBiyw8MvyvCi",
            encoded="TccWLgcquqvwrfBocq5mcK5kBiyw8MvyvCi",
            valid=True,
            scriptAddress=ByteArray(
                "36c1ca10a8a6a4b5d4204ac970853979903aa284"),
            make=lambda: addrSHH(
                ByteArray("36c1ca10a8a6a4b5d4204ac970853979903aa284"),
                testnet,
            ),
            netParams=testnet,
        ),
        # Negative P2SH tests.
        dict(
            name="p2sh wrong hash length",
            addr="",
            valid=False,
            make=lambda: addrSHH(
                ByteArray("00f815b036d9bbbce5e9f2a00abd1bf3dc91e95510"),
                mainnet,
            ),
            netParams=mainnet,
        ),
        # Positive P2PK tests.
        dict(
            name="mainnet p2pk compressed (0x02)",
            addr="DsT4FDqBKYG1Xr8aGrT1rKP3kiv6TZ5K5th",
            encoded="DsT4FDqBKYG1Xr8aGrT1rKP3kiv6TZ5K5th",
            valid=True,
            scriptAddress=ByteArray(
                "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
            ),
            make=lambda: addrPK(
                ByteArray(
                    "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
                ),
                mainnet,
            ),
            netParams=mainnet,
            skipComp=True,
        ),
        dict(
            name="mainnet p2pk compressed (0x03)",
            addr="DsfiE2y23CGwKNxSGjbfPGeEW4xw1tamZdc",
            encoded="DsfiE2y23CGwKNxSGjbfPGeEW4xw1tamZdc",
            valid=True,
            scriptAddress=ByteArray(
                "03e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de"
            ),
            make=lambda: addrPK(
                ByteArray(
                    "03e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de"
                ),
                mainnet,
            ),
            netParams=mainnet,
            skipComp=True,
        ),
        dict(
            name="mainnet p2pk uncompressed (0x04)",
            addr="DkM3EyZ546GghVSkvzb6J47PvGDyntqiDtFgipQhNj78Xm2mUYRpf",
            encoded="DsfFjaADsV8c5oHWx85ZqfxCZy74K8RFuhK",
            valid=True,
            saddr=
            "0264c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f0",
            scriptAddress=ByteArray(
                "0464c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f"
                "0b87ca4279b565d2130ce59f75bfbb2b88da794143d7cfd3e80808a1fa3203904"
            ),
            make=lambda: addrPK(
                ByteArray(
                    "0464c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f"
                    "0b87ca4279b565d2130ce59f75bfbb2b88da794143d7cfd3e80808a1fa3203904"
                ),
                mainnet,
            ),
            netParams=mainnet,
        ),
        dict(
            name="testnet p2pk compressed (0x02)",
            addr="Tso9sQD3ALqRsmEkAm7KvPrkGbeG2Vun7Kv",
            encoded="Tso9sQD3ALqRsmEkAm7KvPrkGbeG2Vun7Kv",
            valid=True,
            scriptAddress=ByteArray(
                "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e"
            ),
            make=lambda: addrPK(
                ByteArray(
                    "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e"
                ),
                testnet,
            ),
            netParams=testnet,
            skipComp=True,
        ),
        dict(
            name="testnet p2pk compressed (0x03)",
            addr="TsWZ1EzypJfMwBKAEDYKuyHRGctqGAxMje2",
            encoded="TsWZ1EzypJfMwBKAEDYKuyHRGctqGAxMje2",
            valid=True,
            scriptAddress=ByteArray(
                "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5"
            ),
            make=lambda: addrPK(
                ByteArray(
                    "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5"
                ),
                testnet,
            ),
            netParams=testnet,
            skipComp=True,
        ),
        dict(
            name="testnet p2pk uncompressed (0x04)",
            addr="TkKmMiY5iDh4U3KkSopYgkU1AzhAcQZiSoVhYhFymZHGMi9LM9Fdt",
            encoded="Tso9sQD3ALqRsmEkAm7KvPrkGbeG2Vun7Kv",
            valid=True,
            saddr=
            "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
            scriptAddress=ByteArray(
                "046a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06"
                "ed548c8c16fb5eb9007cb94220b3bb89491d5a1fd2d77867fca64217acecf2244"
            ),
            make=lambda: addrPK(
                ByteArray(
                    "046a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06"
                    "ed548c8c16fb5eb9007cb94220b3bb89491d5a1fd2d77867fca64217acecf2244"
                ),
                testnet,
            ),
            netParams=testnet,
        ),
        # Negative P2PK tests.
        dict(
            name="mainnet p2pk hybrid (0x06)",
            addr="",
            valid=False,
            make=lambda: addrPK(
                ByteArray(
                    "0664c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f"
                    "0b87ca4279b565d2130ce59f75bfbb2b88da794143d7cfd3e80808a1fa3203904"
                ),
                mainnet,
            ),
            netParams=mainnet,
        ),
        dict(
            name="mainnet p2pk hybrid (0x07)",
            addr="",
            valid=False,
            make=lambda: addrPK(
                ByteArray(
                    "07348d8aeb4253ca52456fe5da94ab1263bfee16bb8192497f666389ca964f847"
                    "98375129d7958843b14258b905dc94faed324dd8a9d67ffac8cc0a85be84bac5d"
                ),
                mainnet,
            ),
            netParams=mainnet,
        ),
        dict(
            name="testnet p2pk hybrid (0x06)",
            addr="",
            valid=False,
            make=lambda: addrPK(
                ByteArray(
                    "066a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06"
                    "ed548c8c16fb5eb9007cb94220b3bb89491d5a1fd2d77867fca64217acecf2244"
                ),
                testnet,
            ),
            netParams=testnet,
        ),
        dict(
            name="testnet p2pk hybrid (0x07)",
            addr="",
            valid=False,
            make=lambda: addrPK(
                ByteArray(
                    "07edd40747de905a9becb14987a1a26c1adbd617c45e1583c142a635bfda9493d"
                    "fa1c6d36735974965fe7b861e7f6fcc087dc7fe47380fa8bde0d9c322d53c0e89"
                ),
                testnet,
            ),
            netParams=testnet,
        ),
    ]

    for test in tests:
        # Decode addr and compare error against valid.
        err = None
        name = test.get("name")
        try:
            decoded = addrlib.decodeAddress(test["addr"], test["netParams"])
        except DecredError as e:
            err = e
        assert (err is None) == test["valid"], f"{name} error: {err}"

        if err is None:
            # Ensure the stringer returns the same address as the original.
            assert test["addr"] == decoded.string(), name

            # Encode again and compare against the original.
            encoded = decoded.address()
            assert test["encoded"] == encoded

            # Perform type-specific calculations.
            if isinstance(decoded, addrlib.AddressPubKeyHash):
                d = ByteArray(b58decode(encoded))
                saddr = d[2:2 + crypto.RIPEMD160_SIZE]

            elif isinstance(decoded, addrlib.AddressScriptHash):
                d = ByteArray(b58decode(encoded))
                saddr = d[2:2 + crypto.RIPEMD160_SIZE]

            elif isinstance(decoded, addrlib.AddressSecpPubKey):
                # Ignore the error here since the script
                # address is checked below.
                try:
                    saddr = ByteArray(decoded.string())
                except ValueError:
                    saddr = test["saddr"]

            else:
                raise AssertionError(
                    f"Decoded address is of unknown type {type(decoded)}")

            # Check script address, as well as the Hash160 method for P2PKH and
            # P2SH addresses.
            assert saddr == decoded.scriptAddress(), name

            if isinstance(decoded, addrlib.AddressPubKeyHash):
                assert decoded.pkHash == saddr

            if isinstance(decoded, addrlib.AddressScriptHash):
                assert decoded.hash160() == saddr

        make = test.get("make")
        if not test["valid"]:
            # If address is invalid, but a creation function exists,
            # verify that it raises a DecredError.
            if make:
                try:
                    make()
                    raise AssertionError(
                        "invalid tests should raise exception")
                except DecredError:
                    pass
            continue

        # Valid test, compare address created with f against expected result.
        addr = make()
        assert addr != object()
        if not test.get("skipComp"):
            assert decoded == addr, name
            assert decoded == addr.string(), name
            assert addr.string() == decoded, name
        assert addr.scriptAddress() == test["scriptAddress"], name

        # Test blobbing
        b = addrlib.Address.blob(addr)
        reAddr = addrlib.Address.unblob(b)
        assert addr == reAddr
Пример #6
0
    def test_main(self, monkeypatch):
        """
        Test account functionality.
        """
        # Set up globals for test.
        monkeypatch.setattr(account, "DefaultGapLimit", 2)

        db = KeyValueDatabase(":memory:").child("tmp")
        acct = self.newAccount(db)
        acct.unlock(self.cryptoKey)
        acct.generateGapAddresses()
        for _ in range(20):
            acct.nextExternalAddress()
        satoshis = int(round(5 * 1e8))
        txHash = rando.newHash()
        txid = reversed(txHash).hex()
        vout = 2
        address = acct.nextExternalAddress()

        utxo = account.UTXO(
            address=address,
            txHash=txHash,
            vout=vout,
            scriptPubKey=ByteArray(0),
            satoshis=satoshis,
            maturity=1,
        )

        # A helper function to get the current utxo count.
        utxocount = lambda: len(list(acct.utxoscan()))

        # Add the utxo
        acct.addUTXO(utxo)
        # Check the count and balance categories.
        assert utxocount() == 1
        assert acct.calcBalance(1).total == satoshis
        assert acct.calcBalance(1).available == satoshis
        assert acct.calcBalance(0).available == 0
        # Helper functions.
        assert acct.caresAboutTxid(txid)
        assert not acct.caresAboutTxid("")
        acct.addUTXO(utxo)
        assert utxocount() == 1
        acct.spendUTXO(utxo)
        assert utxocount() == 0

        b = acct.serialize()
        reAcct = account.Account.unblob(b.b)

        assert acct.pubKeyEncrypted == reAcct.pubKeyEncrypted
        assert acct.privKeyEncrypted == reAcct.privKeyEncrypted
        assert acct.name == reAcct.name
        assert acct.coinID == reAcct.coinID
        assert acct.netID == reAcct.netID
        assert acct.gapLimit == reAcct.gapLimit
        assert acct.cursorExt == reAcct.cursorExt
        assert acct.cursorInt == reAcct.cursorInt
        assert acct.gapLimit == reAcct.gapLimit

        # Create a faux blockchain for the account.
        class Blockchain:
            txidsForAddr = lambda addr: []
            UTXOs = lambda addrs: []
            tipHeight = 5

        acct.blockchain = Blockchain

        class Signals:
            b = None

            @classmethod
            def balance(cls, b):
                cls.b = b

        acct.signals = Signals

        # Add a txid for this first address
        txid = rando.newHash().hex()
        zerothAddr = acct.externalAddresses[0]
        acct.addTxid(zerothAddr, txid)
        # Add a voting service provider
        vspKey = rando.newKey().hex()
        ticketAddr = "ticketAddr"
        pi = PurchaseInfo("addr", 1.0, ByteArray(b"scripthashscript"),
                          ticketAddr, 1, 0, 2)
        vsp = VotingServiceProvider("https://myvsp.com", vspKey,
                                    nets.mainnet.Name, pi)
        with pytest.raises(DecredError):
            acct.setPool(None)
        acct.setPool(vsp)
        acct.setNewPool(vsp)
        # Add UTXOs
        utxos = [account.UTXO.parse(u) for u in self.dcrdataUTXOs]
        acct.resolveUTXOs(utxos)
        ticketStats = acct.ticketStats()
        assert ticketStats.count == 0
        assert ticketStats.value == 0

        # Check that the addresses txid and the vsp load from the database.
        acct.txs.clear()
        acct.stakePools.clear()
        acct.utxos.clear()
        assert acct.stakePool() is None
        acct.load(db, Blockchain, Signals)
        assert zerothAddr in acct.txs
        assert len(acct.txs[zerothAddr]) == 1
        assert acct.txs[zerothAddr][0] == txid
        assert len(acct.stakePools) == 1
        assert acct.stakePool().apiKey == vspKey
        assert acct.calcBalance().available == self.utxoTotal
        assert len(acct.getUTXOs(self.utxoTotal)[0]) == 2
        assert len(acct.getUTXOs(self.utxoTotal, lambda u: False)[0]) == 0
        assert acct.lastSeen(acct.externalAddresses, -1) == 0
        branch, idx = acct.branchAndIndex(zerothAddr)
        assert branch == account.EXTERNAL_BRANCH
        assert idx == 0
        branch, idx = acct.branchAndIndex(acct.internalAddresses[0])
        assert branch == account.INTERNAL_BRANCH
        assert idx == 0
        checkKey = acct.privKey.child(account.EXTERNAL_BRANCH).child(0).key
        with pytest.raises(DecredError):
            acct.privKeyForAddress("")
        assert acct.privKeyForAddress(zerothAddr).key == checkKey
        ticketAddrs = acct.addTicketAddresses([])
        assert len(ticketAddrs) == 1
        assert ticketAddrs[0] == ticketAddr
        assert acct.hasPool()

        # Exercise setNode
        acct.setNode(1)
        assert acct.node == 1

        # Add a coinbase transaction output to the account.
        coinbase = newCoinbaseTx()
        cbUTXO = account.UTXO("addr",
                              ByteArray(b"id"),
                              1,
                              height=5,
                              satoshis=int(1e8))
        coinbase.addTxOut(msgtx.TxOut(int(1e8)))
        coinbaseID = coinbase.id()
        cbUTXO.txid = coinbaseID
        acct.addUTXO(cbUTXO)
        acct.addMempoolTx(coinbase)
        assert coinbaseID in acct.mempool
        # Send a block signal and have the transaction confirmed.
        sig = {"message": {"block": {"Tx": [{"TxID": coinbaseID}]}}}
        acct.blockchain.tipHeight = 5
        acct.blockchain.tx = lambda *a: coinbase
        acct.blockSignal(sig)
        assert coinbaseID not in acct.mempool
        assert cbUTXO.key() in acct.utxos
        maturity = 5 + nets.mainnet.CoinbaseMaturity
        assert acct.utxos[cbUTXO.key()].maturity == maturity
        assert acct.calcBalance(
            maturity).available == self.utxoTotal + cbUTXO.satoshis
        assert acct.calcBalance(0).available == self.utxoTotal
        acct.spendUTXOs([cbUTXO])
        assert acct.calcBalance(maturity).available == self.utxoTotal

        # make a ticket and a vote
        ticket = account.UTXO.parse(dcrdataUTXO)
        utxo.setTicketInfo(dcrdataTinfo)
        utxo.scriptPubKey = ticketScript
        utxo.parseScriptClass()
        acct.addUTXO(ticket)
        expVal = acct.calcBalance().available - ticket.satoshis
        voteTx = msgtx.MsgTx.new()
        voteTx.addTxIn(
            msgtx.TxIn(
                msgtx.OutPoint(reversed(ByteArray(ticket.txid)), ticket.vout,
                               0)))
        acct.blockchain.tx = lambda *a: voteTx
        acct.addressSignal(ticketAddr, "somehash")
        assert Signals.b.available == expVal

        # Detect a new utxo for the account.
        newVal = int(5e8)
        expVal = acct.calcBalance().available + newVal
        addr = acct.externalAddresses[1]
        a = addrlib.decodeAddress(addr, nets.mainnet)
        script = txscript.payToAddrScript(a)
        newTx = msgtx.MsgTx.new()
        op = msgtx.TxOut(newVal, script)
        newTx.addTxOut(op)
        acct.blockchain.tx = lambda *a: newTx
        utxo = account.UTXO(addr, ByteArray(b"txid"), 0, satoshis=newVal)
        acct.blockchain.txVout = lambda *a: utxo
        acct.addressSignal(addr, "somehash")
        assert Signals.b.available == expVal

        # test syncing. add a single output for external address #2
        acct.stakePool().getPurchaseInfo = lambda: None
        acct.stakePool().authorize = lambda a: True
        newVal = int(3e8)
        addr = acct.externalAddresses[2]
        a = addrlib.decodeAddress(addr, nets.mainnet)
        script = txscript.payToAddrScript(a)
        newTx = msgtx.MsgTx.new()
        op = msgtx.TxOut(newVal, script)
        newTx.addTxOut(op)
        utxo = account.UTXO(addr, ByteArray(b"txid"), 0, satoshis=newVal)

        def t4a(*a):
            acct.blockchain.txidsForAddr = lambda *a: []
            return [newTx.id()]

        acct.blockchain.txidsForAddr = t4a

        def utxos4a(*a):
            acct.blockchain.UTXOs = lambda *a: []
            return [utxo]

        acct.blockchain.UTXOs = utxos4a
        acct.blockchain.subscribeAddresses = lambda addrs, receiver: None
        acct.blockchain.subscribeBlocks = lambda a: None
        acct.sync()
        assert Signals.b.available == newVal

        # spend the utxo by sending it. Reusing newTx for convenience.
        changeVal = int(5e7)
        addr = acct.internalAddresses[0]
        change = account.UTXO(addr,
                              ByteArray(b"newtxid"),
                              0,
                              satoshis=changeVal)
        acct.blockchain.sendToAddress = lambda *a: (newTx, [utxo], [change])
        acct.sendToAddress(1, "recipient")
        assert Signals.b.available == changeVal

        # purchase some tickets. Reusing newTx for convenience again.
        ticket = account.UTXO.parse(dcrdataUTXO)
        ticket.setTicketInfo(dcrdataTinfo)
        ticket.scriptPubKey = ticketScript
        ticket.parseScriptClass()
        acct.blockchain.purchaseTickets = lambda *a: ([newTx, []], [],
                                                      [ticket])
        acct.signals.spentTickets = lambda: True
        acct.purchaseTickets(1, 1)
        assert Signals.b.total == changeVal + ticket.satoshis

        # revoke the ticket
        ticketTx = msgtx.MsgTx.new()
        op = msgtx.TxOut(ticket.satoshis, ticket.scriptPubKey)
        ticketTx.addTxOut(op)
        ticket.tinfo.status = "missed"
        redeemHash = addrlib.AddressScriptHash(
            txscript.extractStakeScriptHash(ticketScript, opcode.OP_SSTX),
            nets.mainnet,
        )
        acct.stakePool().purchaseInfo.ticketAddress = redeemHash.string()
        revoked = False

        def rev(*a):
            nonlocal revoked
            revoked = True

        acct.blockchain.tx = lambda *a: ticketTx
        acct.blockchain.revokeTicket = rev
        acct.revokeTickets()
        assert revoked
Пример #7
0
    print("\nFunding found at {}:{}".format(utxo["txid"], utxo["vout"]))
    reward += utxo.get("satoshis")

print(f"Network = {net.Name}")
print(f"Total challenge funding is {reward/1e8:8f} DCR")

# Collect an address to send the funds to.

recipient = input(f"\nEnter a {net.Name} address to receive the reward.\n")
# Reject identical challenge and reward addresses as user error.
if challengeAddr == recipient:
    exit("Challenge address cannot be the same as reward address")

while True:
    try:
        rewardAddr = decodeAddress(recipient, net)
        break
    except:
        recipient = input(
            f"Invalid address. Enter an address for {net.Name}.\n")

while True:
    answer = input("\nWhat is your answer?\n").strip()
    # Get the double hash, build the redeem script, and check if it hashes
    # correctly.
    answerHash = hash256(answer.encode("utf-8"))
    hash2x = hash256(answerHash)
    # Prepare the script and compare its hash to the hash encoded in the
    # challenge address.
    if hash2x != doubleHash:
        print("'{}' is the wrong answer.".format(answer))