def txWithTxid(txid): txInOne = msgtx.TxIn(msgtx.OutPoint(ByteArray("ff"), 0, 0), valueIn=1) txInTwo = msgtx.TxIn(None, valueIn=3) txOutOne = msgtx.TxOut(pkScript=stakeSubmission, value=3) txOutTwo = msgtx.TxOut() txOutThree = msgtx.TxOut() txOutFour = msgtx.TxOut() txOutFive = msgtx.TxOut() txsIn = [txInOne, txInTwo] txsOut = [txOutOne, txOutTwo, txOutThree, txOutFour, txOutFive] return msgtx.MsgTx(reversed(ByteArray(txid)), None, None, txsIn, txsOut, None, None)
def test_tx_from_hex(self, prepareLogger): tx = msgtx.MsgTx( cachedHash=None, serType=0, version=123, txIn=[ msgtx.TxIn( previousOutPoint=msgtx.OutPoint(txHash=newHash(), idx=5, tree=1), sequence=msgtx.MaxTxInSequenceNum, valueIn=500, blockHeight=111, blockIndex=12, signatureScript=newHash(), ), ], txOut=[ msgtx.TxOut(value=321, pkScript=newHash(), version=0), msgtx.TxOut(value=654, pkScript=newHash(), version=0), msgtx.TxOut(value=987, pkScript=newHash(), version=0), ], lockTime=int(time.time()), expiry=int(time.time()) + 86400, ) b = tx.serialize() reTx = msgtx.MsgTx.unblob(b) assert tx.serType == reTx.serType assert tx.version == reTx.version assert tx.lockTime == reTx.lockTime assert tx.expiry == reTx.expiry for i, txIn in enumerate(tx.txIn): reTxIn = reTx.txIn[i] assert txIn.previousOutPoint.hash == reTxIn.previousOutPoint.hash assert txIn.previousOutPoint.index == reTxIn.previousOutPoint.index assert txIn.previousOutPoint.tree == reTxIn.previousOutPoint.tree assert txIn.sequence == reTxIn.sequence assert txIn.valueIn == reTxIn.valueIn assert txIn.blockHeight == reTxIn.blockHeight assert txIn.blockIndex == reTxIn.blockIndex assert txIn.signatureScript == reTxIn.signatureScript for i, txOut in enumerate(tx.txOut): reTxOut = reTx.txOut[i] assert txOut.value == reTxOut.value assert txOut.version == reTxOut.version assert txOut.pkScript == reTxOut.pkScript
def utxosource(amt, filter): nextVal = 10 total = 0 utxos = [] while total < amt: atoms = int(nextVal * 1e8) privKey = Curve.generateKey() pkHash = crypto.hash160( privKey.pub.serializeCompressed().b) addr = addrlib.AddressPubKeyHash(pkHash, testnet) addrs.append(addr) addrString = addr.string() keys[addrString] = privKey pkScript = txscript.makePayToAddrScript( addrString, testnet) txHash = rando.newHash() txid = reversed(txHash).hex() utxos.append( account.UTXO( address=addrString, txHash=txHash, vout=0, ts=int(time.time()), scriptPubKey=pkScript, satoshis=atoms, )) tx = msgtx.MsgTx.new() tx.addTxOut(msgtx.TxOut(value=atoms, pkScript=pkScript)) txs[txid] = tx total += atoms nextVal *= 2 return utxos, True
def submitProof(self, addr, proof, redemptionAddr): addrStr = addr.string() signingKeyB, doubleHashB = self.db(self.selectKeyByProof_, addrStr, proof.b) signingKey = crypto.privKeyFromBytes(ByteArray(bytes(signingKeyB))) # Prepare the redemption script redeemScript = ByteArray(opcode.OP_SHA256) redeemScript += txscript.addData(bytes(doubleHashB)) redeemScript += opcode.OP_EQUALVERIFY redeemScript += txscript.addData(signingKey.pub.serializeCompressed()) redeemScript += opcode.OP_CHECKSIG # Collect all outputs for the address. We could do this from the cache, # but we'll generate a new call to dcrdata instead to make sure we have # the freshest data. utxos = self.blockchain.UTXOs([addrStr]) if len(utxos) == 0: return dict( error="challenge is either unfunded or has already been redeemed", code=1, ) rewardTx = msgtx.MsgTx.new() reward = 0 for utxo in utxos: reward += utxo.satoshis prevOut = msgtx.OutPoint(utxo.txHash, utxo.vout, msgtx.TxTreeRegular) rewardTx.addTxIn(msgtx.TxIn(prevOut, valueIn=utxo.satoshis)) # sigScript = txscript.addData(answerHash) + txscript.addData(script) # rewardTx.addTxIn(msgtx.TxIn(prevOut, signatureScript=sigScript)) # Add the reward output with zero value for now. txout = msgtx.TxOut(pkScript=txscript.payToAddrScript(redemptionAddr)) rewardTx.addTxOut(txout) # Get the serialized size of the transaction. Since there are no signatures # involved, the size is known exactly. Use the size to calculate transaction # fees. # 1 + 73 = signature # 1 + 32 = answer hash # 1 + 70 = redeem script sigScriptSize = 1 + 73 + 1 + 32 + 1 + 70 maxSize = rewardTx.serializeSize() + len(utxos)*sigScriptSize fees = feeRate * maxSize if reward <= fees: return makeErr(f"reward, {reward}, must cover fees {fees}, tx size = {maxSize} bytes, fee rate = {feeRate} atoms/byte") netReward = reward - fees # Set the value on the reward output. txout.value = netReward for idx, txIn in enumerate(rewardTx.txIn): sig = txscript.rawTxInSignature(rewardTx, idx, redeemScript, txscript.SigHashAll, signingKey.key) sigScript = txscript.addData(sig) + txscript.addData(DummyHash) + txscript.addData(redeemScript) txIn.signatureScript = sigScript return { "txHex": rewardTx.serialize().hex(), }
def test_checkoutput(): # Amount is zero. tx = msgtx.TxOut() with pytest.raises(DecredError): dcrdata.checkOutput(tx, 0) # Amount is negative. tx = msgtx.TxOut(-1) with pytest.raises(DecredError): dcrdata.checkOutput(tx, 0) # Amount is too large. tx = msgtx.TxOut(txscript.MaxAmount + 1) with pytest.raises(DecredError): dcrdata.checkOutput(tx, 0) # Tx is dust output. script = ByteArray([opcode.OP_RETURN, opcode.OP_NOP]) tx = msgtx.TxOut(value=1, pkScript=script) with pytest.raises(DecredError): dcrdata.checkOutput(tx, 0)
def test_ticketFromTx(self): stakeSubmission = ByteArray(opcode.OP_SSTX) stakeSubmission += opcode.OP_HASH160 stakeSubmission += opcode.OP_DATA_20 stakeSubmission += 1 << (8 * 19) stakeSubmission += opcode.OP_EQUAL tx = msgtx.MsgTx.new() tx.txOut = [msgtx.TxOut(pkScript=stakeSubmission, value=3)] ticket = account.UTXO.ticketFromTx(tx, nets.testnet) assert ticket.tinfo.status == "mempool" tinfo = account.TicketInfo("no_status", None, 0, 0) ticket = account.UTXO.ticketFromTx(tx, nets.testnet, None, tinfo) assert ticket.tinfo.status == "unconfirmed"
def test_tx_hash(self, prepareLogger): """ test_tx_hash tests the ability to generate the hash of a transaction accurately. """ # Hash of first transaction from block 113875. wantHash = reversed( ByteArray( "4538fc1618badd058ee88fd020984451024858796be0a1ed111877f887e1bd53" )) msgTx = msgtx.MsgTx.new() txIn = msgtx.TxIn( previousOutPoint=msgtx.OutPoint( txHash=None, idx=0xFFFFFFFF, tree=msgtx.TxTreeRegular, ), sequence=0xFFFFFFFF, valueIn=5000000000, blockHeight=0x3F3F3F3F, blockIndex=0x2E2E2E2E, signatureScript=ByteArray( [0x04, 0x31, 0xDC, 0x00, 0x1B, 0x01, 0x62]), ) # fmt: off txOut = msgtx.TxOut( value=5000000000, version=0xF0F0, pkScript=ByteArray([ 0x41, # OP_DATA_65 0X04, 0XD6, 0X4B, 0XDF, 0XD0, 0X9E, 0XB1, 0XC5, 0XFE, 0X29, 0X5A, 0XBD, 0XEB, 0X1D, 0XCA, 0X42, 0X81, 0XBE, 0X98, 0X8E, 0X2D, 0XA0, 0XB6, 0XC1, 0XC6, 0XA5, 0X9D, 0XC2, 0X26, 0XC2, 0X86, 0X24, 0XE1, 0X81, 0X75, 0XE8, 0X51, 0XC9, 0X6B, 0X97, 0X3D, 0X81, 0XB0, 0X1C, 0XC3, 0X1F, 0X04, 0X78, 0X34, 0XBC, 0X06, 0XD6, 0XD6, 0XED, 0XF6, 0X20, 0XD1, 0X84, 0X24, 0X1A, 0X6A, 0XED, 0X8B, 0X63, 0xA6, # 65-byte signature 0xAC, # OP_CHECKSIG ]), ) # fmt: on msgTx.addTxIn(txIn) msgTx.addTxOut(txOut) msgTx.lockTime = 0 msgTx.expiry = 0 # Check that this is the very first tx in the chain. assert msgTx.looksLikeCoinbase() # Ensure the hash produced is expected. assert msgTx.hash() == wantHash
def multiTx(): """ multiTx is a MsgTx with an input and output and used in various tests. """ return msgtx.MsgTx( cachedHash=None, serType=wire.TxSerializeFull, version=1, txIn=[ msgtx.TxIn( previousOutPoint=msgtx.OutPoint( txHash=None, idx=0xFFFFFFFF, tree=0, ), sequence=0xFFFFFFFF, valueIn=0x1212121212121212, blockHeight=0x15151515, blockIndex=0x34343434, signatureScript=ByteArray( [0x04, 0x31, 0xDC, 0x00, 0x1B, 0x01, 0x62]), ), ], txOut=[ msgtx.TxOut( value=0x12A05F200, version=0xABAB, pkScript=ByteArray([ 0x41, # OP_DATA_65 0X04, 0XD6, 0X4B, 0XDF, 0XD0, 0X9E, 0XB1, 0XC5, 0XFE, 0X29, 0X5A, 0XBD, 0XEB, 0X1D, 0XCA, 0X42, 0X81, 0XBE, 0X98, 0X8E, 0X2D, 0XA0, 0XB6, 0XC1, 0XC6, 0XA5, 0X9D, 0XC2, 0X26, 0XC2, 0X86, 0X24, 0XE1, 0X81, 0X75, 0XE8, 0X51, 0XC9, 0X6B, 0X97, 0X3D, 0X81, 0XB0, 0X1C, 0XC3, 0X1F, 0X04, 0X78, 0X34, 0XBC, 0X06, 0XD6, 0XD6, 0XED, 0XF6, 0X20, 0XD1, 0X84, 0X24, 0X1A, 0X6A, 0XED, 0X8B, 0X63, 0xA6, # 65-byte signature 0xAC, # OP_CHECKSIG ]), ), msgtx.TxOut( value=0x5F5E100, version=0xBCBC, pkScript=ByteArray([ 0x41, # OP_DATA_65 0X04, 0XD6, 0X4B, 0XDF, 0XD0, 0X9E, 0XB1, 0XC5, 0XFE, 0X29, 0X5A, 0XBD, 0XEB, 0X1D, 0XCA, 0X42, 0X81, 0XBE, 0X98, 0X8E, 0X2D, 0XA0, 0XB6, 0XC1, 0XC6, 0XA5, 0X9D, 0XC2, 0X26, 0XC2, 0X86, 0X24, 0XE1, 0X81, 0X75, 0XE8, 0X51, 0XC9, 0X6B, 0X97, 0X3D, 0X81, 0XB0, 0X1C, 0XC3, 0X1F, 0X04, 0X78, 0X34, 0XBC, 0X06, 0XD6, 0XD6, 0XED, 0XF6, 0X20, 0XD1, 0X84, 0X24, 0X1A, 0X6A, 0XED, 0X8B, 0X63, 0xA6, # 65-byte signature 0xAC, # OP_CHECKSIG ]), ), ], lockTime=0, expiry=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
# challenge address. if hash2x != doubleHash: print("'{}' is the wrong answer.".format(answer)) continue print("\nCorrect answer!") # Build the transaction. rewardTx = msgtx.MsgTx.new() for utxo in utxos: prevOut = msgtx.OutPoint(reversed(ByteArray(utxo["txid"])), int(utxo["vout"]), msgtx.TxTreeRegular) rewardTx.addTxIn(msgtx.TxIn(prevOut, valueIn=utxo["satoshis"])) # Add the reward output with zero value for now. txout = msgtx.TxOut(pkScript=txscript.payToAddrScript(rewardAddr)) rewardTx.addTxOut(txout) # Get the serialized size of the transaction. Since there are no signatures # involved, the size is known exactly. Use the size to calculate transaction # fees. # 1 + 73 = signature # 1 + 32 = answer hash # 1 + 70 = redeem script sigScriptSize = 1 + 73 + 1 + 32 + 1 + 70 maxSize = rewardTx.serializeSize() + len(utxos) * sigScriptSize fees = feeRate * maxSize if reward <= fees: raise AssertionError(f"reward must be > fees") netReward = reward - fees # Set the value on the reward output.