def test_no_combine_mainnet_with_testnet(self): unspents = [Unspent(20000, 0, '', '', 0)] outputs = [(BITCOIN_ADDRESS, 500, 'satoshi'), (BITCOIN_ADDRESS_TEST, 500, 'satoshi')] with pytest.raises(ValueError): sanitize_tx_data( unspents, outputs, fee=50, leftover=RETURN_ADDRESS, # leftover is a testnet-address combine=False, message=None, version='main', ) with pytest.raises(ValueError): sanitize_tx_data( unspents, outputs, fee=50, leftover=BITCOIN_ADDRESS, # leftover is a mainnet-address combine=False, message=None, version='main', )
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 = [(RETURN_ADDRESS, 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_ADDRESS_TEST, 2500, 'satoshi')] with pytest.raises(InsufficientFunds): sanitize_tx_data( unspents_original, outputs_original, fee=50, leftover=RETURN_ADDRESS, combine=False, message=None )
def test_fee_applied(self): unspents_original = [Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0)] outputs_original = [(BITCOIN_ADDRESS, 2000, 'satoshi')] with pytest.raises(InsufficientFunds): sanitize_tx_data( unspents_original, outputs_original, fee=1, leftover=RETURN_ADDRESS, combine=True, message=None )
def test_outputs_pay2sh_testnet(self): amount = b'\x01\x00\x00\x00\x00\x00\x00\x00' _, outputs = sanitize_tx_data( UNSPENTS, [(BITCOIN_ADDRESS_TEST_PAY2SH, 1, 'satoshi')], 0, RETURN_ADDRESS, version='test' ) outs = construct_outputs(outputs) assert len(outs) == 2 and outs[0].amount == amount and outs[0].script_pubkey.hex() == 'a914' + PAY2SH_TEST_HASH.hex() + '87'
def test_no_combine_with_absolute_fee(self): # Based on simplifications branch_and_bound roughly reduces # to this formula with one input # amount + overhead*fee <= unspent - unspent.vsize*fee < amount + overhead*fee + input*fee + output*fee # amount + 40*fee <= unspent - 148*fee < amount + 40*fee + 182*fee # amount <= unspent - 188*fee < amount + 182*fee fee = 8000 unspents_original = [Unspent(2000000, 0, '', '', 0)] outputs_original = [(BITCOIN_ADDRESS_TEST, 100000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=fee, absolute_fee=True, leftover=RETURN_ADDRESS, combine=False, message=None, version='test', ) assert unspents == unspents_original assert len(outputs) == 2 assert sum(u.amount for u in unspents) - sum(o[1] for o in outputs) == fee
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 Bit will poll `<https://bitcoinfees.21.co>`_ 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 Bit will send any change to the same address you sent from. :type leftover: ``str`` :param combine: Whether or not Bit should use all available UTXOs to make future transactions smaller and therefore reduce fees. By default Bit 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 Bit will communicate with the blockchain itself. :type unspents: ``list`` of :class:`~bit.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_cached(), 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'*9, version='test' ) outs = construct_outputs(outputs) assert len(outs) == 5 and outs[3].amount == amount and outs[4].amount == amount
def test_outputs_pay2sh(self): amount = b'\x01\x00\x00\x00\x00\x00\x00\x00' _, outputs = sanitize_tx_data(UNSPENTS, [(BITCOIN_ADDRESS_PAY2SH, 1, 'satoshi')], 0, RETURN_ADDRESS_MAIN) outs = construct_outputs(outputs) assert len(outs) == 2 and outs[0].value == amount and outs[ 0].script.hex() == 'a914' + BITCOIN_HASH_PAY2SH + '87'
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' * 9) assert construct_output_block(outputs).count(amount) == 2
def test_outputs_pay2segwit_testnet(self): amount = b'\x01\x00\x00\x00\x00\x00\x00\x00' _, outputs = sanitize_tx_data( UNSPENTS, [(BITCOIN_SEGWIT_ADDRESS_TEST, 1, 'satoshi'), (BITCOIN_SEGWIT_ADDRESS_TEST_PAY2SH, 1, 'satoshi')], 0, RETURN_ADDRESS, version='test' ) outs = construct_outputs(outputs) assert len(outs) == 3 and outs[0].amount == amount and outs[0].script_pubkey.hex() == '0014' + BITCOIN_SEGWIT_HASH_TEST assert outs[1].amount == amount and outs[1].script_pubkey.hex() == '0020' + BITCOIN_SEGWIT_HASH_TEST_PAY2SH
def test_outputs_pay2segwit(self): amount = b'\x01\x00\x00\x00\x00\x00\x00\x00' _, outputs = sanitize_tx_data( UNSPENTS, [(BITCOIN_SEGWIT_ADDRESS, 1, 'satoshi'), (BITCOIN_SEGWIT_ADDRESS_PAY2SH, 1, 'satoshi')], 0, RETURN_ADDRESS_MAIN) outs = construct_outputs(outputs) assert len(outs) == 3 and outs[0].value == amount and outs[ 0].script.hex() == '0014' + BITCOIN_SEGWIT_HASH assert outs[1].value == amount and outs[1].script.hex( ) == '0020' + BITCOIN_SEGWIT_HASH_PAY2SH
def test_zero_remaining(self): unspents_original = [Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0)] outputs_original = [(BITCOIN_ADDRESS_TEST, 2000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=True, message=None, version='test' ) assert unspents == unspents_original assert outputs == [(BITCOIN_ADDRESS_TEST, 2000)]
def test_message(self): unspents_original = [Unspent(10000, 0, '', '', 0), Unspent(10000, 0, '', '', 0)] outputs_original = [(BITCOIN_ADDRESS_TEST, 1000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=5, leftover=RETURN_ADDRESS, combine=True, message='hello', version='test' ) 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_ADDRESS_TEST, 2000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=False, message=None, version='test' ) assert(len(unspents)) == 1 assert len(outputs) == 2 assert outputs[1][0] == RETURN_ADDRESS assert outputs[1][1] == unspents[0].amount - 2000
def test_no_combine_remaining(self): unspents_original = [Unspent(7000, 0, '', '', 0), Unspent(3000, 0, '', '', 0)] outputs_original = [('test', 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 create_transaction(self, outputs, fee=None, leftover=None, combine=True, message=None, unspents=None): """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 Bit will poll `<https://bitcoinfees.21.co>`_ 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 Bit will send any change to the same address you sent from. :type leftover: ``str`` :param combine: Whether or not Bit should use all available UTXOs to make future transactions smaller and therefore reduce fees. By default Bit 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 Bit will communicate with the testnet blockchain itself. :type unspents: ``list`` of :class:`~bit.network.meta.Unspent` :returns: The signed transaction as hex. :rtype: ``str`` """ unspents, outputs = sanitize_tx_data(unspents or self.unspents, outputs, fee or get_fee_cached(), leftover or self.address, combine=combine, message=message, compressed=self.is_compressed()) return create_p2pkh_transaction(self, unspents, outputs)
def test_combine_remaining(self): unspents_original = [ Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0) ] outputs_original = [(BITCOIN_ADDRESS, 500, 'satoshi')] unspents, outputs = sanitize_tx_data(unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS_MAIN, combine=True, message=None) assert unspents == unspents_original assert len(outputs) == 2 assert outputs[1][0] == RETURN_ADDRESS_MAIN assert outputs[1][1] == 1500
def test_no_combine_remaining_small_inputs(self): unspents_original = [ Unspent(1500, 0, '', '', 0), Unspent(1600, 0, '', '', 0), Unspent(1700, 0, '', '', 0) ] outputs_original = [(RETURN_ADDRESS_MAIN, 2000, 'satoshi')] unspents, outputs = sanitize_tx_data(unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS_MAIN, combine=False, message=None) assert unspents == [ Unspent(1500, 0, '', '', 0), Unspent(1600, 0, '', '', 0) ] assert len(outputs) == 2 assert outputs[1][0] == RETURN_ADDRESS_MAIN assert outputs[1][1] == 1100
def test_no_input(self): with pytest.raises(ValueError): sanitize_tx_data([], [], 70, '')