def test_no_combine_with_fee(self): """ Verify that unused unspents do not increase fee. """ unspents_single = [Unspent(5000, 0, '', '', 0)] unspents_original = [Unspent(5000, 0, '', '', 0), Unspent(5000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 1000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=1, leftover=RETURN_ADDRESS, combine=False, message=None ) unspents_single, outputs_single = sanitize_tx_data( unspents_single, outputs_original, fee=1, leftover=RETURN_ADDRESS, combine=False, message=None ) assert unspents == [Unspent(5000, 0, '', '', 0)] assert unspents_single == [Unspent(5000, 0, '', '', 0)] assert len(outputs) == 2 assert len(outputs_single) == 2 assert outputs[1][0] == RETURN_ADDRESS assert outputs_single[1][0] == RETURN_ADDRESS assert outputs[1][1] == outputs_single[1][1]
def test_no_combine_insufficient_funds(self): unspents_original = [Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 2500, 'satoshi')] with pytest.raises(InsufficientFunds): sanitize_tx_data( unspents_original, outputs_original, fee=50, leftover=RETURN_ADDRESS, combine=False, message=None )
def test_long_pushdata(self): BYTES = len(b'hello').to_bytes( 1, byteorder='little') + b'hello' # 6 bytes each * 40 = 240 bytes with pytest.raises(ValueError): sanitize_tx_data(UNSPENTS, [(out[0], out[1], 'satoshi') for out in OUTPUTS], 0, RETURN_ADDRESS, message=BYTES * 40, custom_pushdata=True)
def test_combine_insufficient_funds(self): unspents_original = [ Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0) ] outputs_original = [('test', 2500, 'satoshi')] with pytest.raises(InsufficientFunds): sanitize_tx_data(unspents_original, outputs_original, fee=50, leftover=RETURN_ADDRESS, combine=True, message=None)
def test_fee_applied(self): unspents_original = [ Unspent(1000, 0, "", "", 0), Unspent(1000, 0, "", "", 0) ] outputs_original = [(BITCOIN_CASHADDRESS_COMPRESSED, 2000, "satoshi")] with pytest.raises(InsufficientFunds): sanitize_tx_data( unspents_original, outputs_original, fee=1, leftover=RETURN_ADDRESS, combine=True, message=None, )
def prepare_transaction(cls, address, outputs, compressed=True, fee=None, leftover=None, combine=True, message=None, unspents=None): """Prepares a P2PKH transaction for offline signing. :param address: The address the funds will be sent from. :type address: ``str`` :param outputs: A sequence of outputs you wish to send in the form ``(destination, amount, currency)``. The amount can be either an int, float, or string as long as it is a valid input to ``decimal.Decimal``. The currency must be :ref:`supported <supported currencies>`. :type outputs: ``list`` of ``tuple`` :param compressed: Whether or not the ``address`` corresponds to a compressed public key. This influences the fee. :type compressed: ``bool`` :param fee: The number of satoshi per byte to pay to miners. By default Bitcash will poll `<https://bitcoincashfees.earn.com>`_ and use a fee that will allow your transaction to be confirmed as soon as possible. :type fee: ``int`` :param leftover: The destination that will receive any change from the transaction. By default Bitcash will send any change to the same address you sent from. :type leftover: ``str`` :param combine: Whether or not Bitcash should use all available UTXOs to make future transactions smaller and therefore reduce fees. By default Bitcash will consolidate UTXOs. :type combine: ``bool`` :param message: A message to include in the transaction. This will be stored in the blockchain forever. Due to size limits, each message will be stored in chunks of 40 bytes. :type message: ``str`` :param unspents: The UTXOs to use as the inputs. By default Bitcash will communicate with the blockchain itself. :type unspents: ``list`` of :class:`~bitcash.network.meta.Unspent` :returns: JSON storing data required to create an offline transaction. :rtype: ``str`` """ unspents, outputs = sanitize_tx_data( unspents or NetworkAPI.get_unspent_testnet(address), outputs, fee or get_fee(), leftover or address, combine=combine, message=message, compressed=compressed) data = { 'unspents': [unspent.to_dict() for unspent in unspents], 'outputs': outputs } return json.dumps(data, separators=(',', ':'))
def test_long_message(self): amount = b'\x00\x00\x00\x00\x00\x00\x00\x00' _, outputs = sanitize_tx_data(UNSPENTS, [(out[0], out[1], 'satoshi') for out in OUTPUTS], 0, RETURN_ADDRESS, message='hello' * 50) assert construct_output_block(outputs).count(amount) == 2
def create_transaction( self, outputs, fee=None, leftover=None, combine=True, message=None, unspents=None, custom_pushdata=False, ): # pragma: no cover """Creates a signed P2PKH transaction. :param outputs: A sequence of outputs you wish to send in the form ``(destination, amount, currency)``. The amount can be either an int, float, or string as long as it is a valid input to ``decimal.Decimal``. The currency must be :ref:`supported <supported currencies>`. :type outputs: ``list`` of ``tuple`` :param fee: The number of satoshi per byte to pay to miners. By default Bitcash will poll `<https://bitcoincashfees.earn.com>`_ and use a fee that will allow your transaction to be confirmed as soon as possible. :type fee: ``int`` :param leftover: The destination that will receive any change from the transaction. By default Bitcash will send any change to the same address you sent from. :type leftover: ``str`` :param combine: Whether or not Bitcash should use all available UTXOs to make future transactions smaller and therefore reduce fees. By default Bitcash will consolidate UTXOs. :type combine: ``bool`` :param message: A message to include in the transaction. This will be stored in the blockchain forever. Due to size limits, each message will be stored in chunks of 220 bytes. :type message: ``str`` :param unspents: The UTXOs to use as the inputs. By default Bitcash will communicate with the blockchain itself. :type unspents: ``list`` of :class:`~bitcash.network.meta.Unspent` :returns: The signed transaction as hex. :rtype: ``str`` """ unspents, outputs = sanitize_tx_data( unspents or self.unspents, outputs, fee or DEFAULT_FEE, leftover or self.address, combine=combine, message=message, compressed=self.is_compressed(), custom_pushdata=custom_pushdata, ) return create_p2pkh_transaction(self, unspents, outputs, custom_pushdata=custom_pushdata)
def test_zero_remaining(self): unspents_original = [Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 2000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=True, message=None ) assert unspents == unspents_original assert outputs == [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 2000)]
def test_message(self): unspents_original = [Unspent(10000, 0, '', '', 0), Unspent(10000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 1000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=5, leftover=RETURN_ADDRESS, combine=True, message='hello' ) assert len(outputs) == 3 assert outputs[2][0] == b'hello' assert outputs[2][1] == 0
def test_no_combine_remaining(self): unspents_original = [Unspent(7000, 0, '', '', 0), Unspent(3000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 2000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=False, message=None ) assert unspents == [Unspent(3000, 0, '', '', 0)] assert len(outputs) == 2 assert outputs[1][0] == RETURN_ADDRESS assert outputs[1][1] == 1000
def test_combine_remaining(self): unspents_original = [ Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0) ] outputs_original = [('test', 500, 'satoshi')] unspents, outputs = sanitize_tx_data(unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=True, message=None) assert unspents == unspents_original assert len(outputs) == 2 assert outputs[1][0] == RETURN_ADDRESS assert outputs[1][1] == 1500
def test_combine_remaining(self): unspents_original = [ Unspent(1000, 0, "", "", 0), Unspent(1000, 0, "", "", 0) ] outputs_original = [(BITCOIN_CASHADDRESS_COMPRESSED, 500, "satoshi")] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=True, message=None, ) assert unspents == unspents_original assert len(outputs) == 2 assert outputs[1][0] == RETURN_ADDRESS assert outputs[1][1] == 1500
def test_message_pushdata(self): unspents_original = [ Unspent(10000, 0, '', '', 0), Unspent(10000, 0, '', '', 0) ] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 1000, 'satoshi')] BYTES = len(b'hello').to_bytes(1, byteorder='little') + b'hello' unspents, outputs = sanitize_tx_data(unspents_original, outputs_original, fee=5, leftover=RETURN_ADDRESS, combine=True, message=BYTES, custom_pushdata=True) assert len(outputs) == 3 assert outputs[2][0] == b'\x05' + b'hello' assert outputs[2][1] == 0
def test_message_pushdata(self): unspents_original = [ Unspent(10000, 0, "", "", 0), Unspent(10000, 0, "", "", 0) ] outputs_original = [(BITCOIN_CASHADDRESS_COMPRESSED, 1000, "satoshi")] BYTES = len(b"hello").to_bytes(1, byteorder="little") + b"hello" unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=5, leftover=RETURN_ADDRESS, combine=True, message=BYTES, custom_pushdata=True, ) assert len(outputs) == 3 assert outputs[2][0] == b"\x05" + b"hello" assert outputs[2][1] == 0
def test_no_combine_remaining_small_inputs(self): unspents_original = [ Unspent(1500, 0, "", "", 0), Unspent(1600, 0, "", "", 0), Unspent(1700, 0, "", "", 0), ] outputs_original = [(BITCOIN_CASHADDRESS_COMPRESSED, 2000, "satoshi")] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=False, message=None, ) print(unspents) assert unspents == [ Unspent(1500, 0, "", "", 0), Unspent(1600, 0, "", "", 0) ] assert len(outputs) == 2 assert outputs[1][0] == RETURN_ADDRESS assert outputs[1][1] == 1100
def test_no_input(self): with pytest.raises(ValueError): sanitize_tx_data([], [], 70, '')