class TestTransactionSigning(AsyncioTestCase): async def asyncSetUp(self): self.ledger = Ledger({ 'db': Database(':memory:'), 'headers': Headers(':memory:') }) await self.ledger.db.open() async def asyncTearDown(self): await self.ledger.db.close() async def test_sign(self): account = Account.from_dict( self.ledger, Wallet(), { "seed": "carbon smart garage balance margin twelve chest sword toas" "t envelope bottom stomach absent" }) await account.ensure_address_gap() address1, address2 = await account.receiving.get_addresses(limit=2) pubkey_hash1 = self.ledger.address_to_hash160(address1) pubkey_hash2 = self.ledger.address_to_hash160(address2) tx = Transaction() \ .add_inputs([Input.spend(get_output(int(2*COIN), pubkey_hash1))]) \ .add_outputs([Output.pay_pubkey_hash(int(1.9*COIN), pubkey_hash2)]) await tx.sign([account]) self.assertEqual( hexlify(tx.inputs[0].script.values['signature']), b'304402200dafa26ad7cf38c5a971c8a25ce7d85a076235f146126762296b1223c42ae21e022020ef9eeb8' b'398327891008c5c0be4357683f12cb22346691ff23914f457bf679601')
async def create_tx_from_nothing(self, my_account, height): to_address = await my_account.receiving.get_or_create_usable_address() to_hash = Ledger.address_to_hash160(to_address) tx = Transaction(height=height, is_verified=True) \ .add_inputs([self.txi(self.txo(1, sha256(str(height).encode())))]) \ .add_outputs([self.txo(1, to_hash)]) await self.ledger.db.insert_transaction(tx) await self.ledger.db.save_transaction_io(tx, to_address, to_hash, '') return tx
async def create_tx_from_txo(self, txo, to_account, height): from_hash = txo.script.values['pubkey_hash'] from_address = self.ledger.hash160_to_address(from_hash) to_address = await to_account.receiving.get_or_create_usable_address() to_hash = Ledger.address_to_hash160(to_address) tx = Transaction(height=height, is_verified=True) \ .add_inputs([self.txi(txo)]) \ .add_outputs([self.txo(1, to_hash)]) await self.ledger.db.insert_transaction(tx) await self.ledger.db.save_transaction_io(tx, from_address, from_hash, '') await self.ledger.db.save_transaction_io(tx, to_address, to_hash, '') return tx
class TransactionIOBalancing(AsyncioTestCase): async def asyncSetUp(self): self.ledger = Ledger({ 'db': Database(':memory:'), 'headers': Headers(':memory:') }) await self.ledger.db.open() self.account = Account.from_dict( self.ledger, Wallet(), { "seed": "carbon smart garage balance margin twelve chest sword " "toast envelope bottom stomach absent" }) addresses = await self.account.ensure_address_gap() self.pubkey_hash = [ self.ledger.address_to_hash160(a) for a in addresses ] self.hash_cycler = cycle(self.pubkey_hash) async def asyncTearDown(self): await self.ledger.db.close() def txo(self, amount, address=None): return get_output(int(amount * COIN), address or next(self.hash_cycler)) def txi(self, txo): return Input.spend(txo) def tx(self, inputs, outputs): return Transaction.create(inputs, outputs, [self.account], self.account) async def create_utxos(self, amounts): utxos = [self.txo(amount) for amount in amounts] self.funding_tx = Transaction(is_verified=True) \ .add_inputs([self.txi(self.txo(sum(amounts)+0.1))]) \ .add_outputs(utxos) await self.ledger.db.insert_transaction(self.funding_tx) for utxo in utxos: await self.ledger.db.save_transaction_io( self.funding_tx, self.ledger.hash160_to_address( utxo.script.values['pubkey_hash']), utxo.script.values['pubkey_hash'], '') return utxos @staticmethod def inputs(tx): return [round(i.amount / COIN, 2) for i in tx.inputs] @staticmethod def outputs(tx): return [round(o.amount / COIN, 2) for o in tx.outputs] async def test_basic_use_cases(self): self.ledger.fee_per_byte = int(.01 * CENT) # available UTXOs for filling missing inputs utxos = await self.create_utxos([1, 1, 3, 5, 10]) # pay 3 coins (3.02 w/ fees) tx = await self.tx( [], # inputs [self.txo(3)] # outputs ) # best UTXO match is 5 (as UTXO 3 will be short 0.02 to cover fees) self.assertListEqual(self.inputs(tx), [5]) # a change of 1.98 is added to reach balance self.assertListEqual(self.outputs(tx), [3, 1.98]) await self.ledger.release_outputs(utxos) # pay 2.98 coins (3.00 w/ fees) tx = await self.tx( [], # inputs [self.txo(2.98)] # outputs ) # best UTXO match is 3 and no change is needed self.assertListEqual(self.inputs(tx), [3]) self.assertListEqual(self.outputs(tx), [2.98]) await self.ledger.release_outputs(utxos) # supplied input and output, but input is not enough to cover output tx = await self.tx( [self.txi(self.txo(10))], # inputs [self.txo(11)] # outputs ) # additional input is chosen (UTXO 3) self.assertListEqual([10, 3], self.inputs(tx)) # change is now needed to consume extra input self.assertListEqual([11, 1.96], self.outputs(tx)) await self.ledger.release_outputs(utxos) # liquidating a UTXO tx = await self.tx( [self.txi(self.txo(10))], # inputs [] # outputs ) self.assertListEqual([10], self.inputs(tx)) # missing change added to consume the amount self.assertListEqual([9.98], self.outputs(tx)) await self.ledger.release_outputs(utxos) # liquidating at a loss, requires adding extra inputs tx = await self.tx( [self.txi(self.txo(0.01))], # inputs [] # outputs ) # UTXO 1 is added to cover some of the fee self.assertListEqual([0.01, 1], self.inputs(tx)) # change is now needed to consume extra input self.assertListEqual([0.97], self.outputs(tx))