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)
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")
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}")
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
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
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
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))