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_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_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_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 = [('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 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 get_unspent(cls, address): endpoint = cls.UNSPENT_API offset = 0 utxos_per_page = 1000 payload = {'active': address, 'offset': str(offset), 'limit': str(utxos_per_page)} r = requests.get(endpoint, params=payload, timeout=DEFAULT_TIMEOUT) if r.status_code == 500: # pragma: no cover return [] elif r.status_code != 200: # pragma: no cover raise ConnectionError unspents = [ Unspent(tx['value'], tx['confirmations'], tx['script'], tx['tx_hash_big_endian'], tx['tx_output_n']) for tx in r.json()['unspent_outputs'] ] #! BlockchainAPI only supports up to 1000 UTXOs. #! Raises an exception for addresses that may contain more UTXOs. if len(unspents) == 1000: raise ExcessiveAddress return unspents[::-1]
def get_unspent(cls, address): # Get current block height: r_block = requests.get(cls.MAIN_ENDPOINT + 'blocks/tip/height', timeout=DEFAULT_TIMEOUT) if r_block.status_code != 200: # pragma: no cover raise ConnectionError block_height = int(r_block.text) r = requests.get(cls.MAIN_UNSPENT_API.format(address), timeout=DEFAULT_TIMEOUT) #! BlockstreamAPI blocks addresses with "too many" UTXOs. if r.status_code == 400 and r.text == "Too many history entries": raise ExcessiveAddress elif r.status_code != 200: # pragma: no cover raise ConnectionError script_pubkey = bytes_to_hex(address_to_scriptpubkey(address)) return sorted( [ Unspent( tx["value"], block_height - tx["status"]["block_height"] + 1 if tx["status"]["confirmed"] else 0, script_pubkey, tx["txid"], tx["vout"], ) for tx in r.json() ], key=lambda u: u.confirmations, )
def test_init(self): unspent = Unspent(10000, 7, 'script', 'txid', 0) assert unspent.amount == 10000 assert unspent.confirmations == 7 assert unspent.script == 'script' assert unspent.txid == 'txid' assert unspent.txindex == 0
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 test_repr(self): unspent = Unspent(10000, 7, 'script', 'txid', 0) assert repr(unspent) == ("Unspent(amount=10000, confirmations=7, " "script='script', txid='txid', txindex=0, " "segwit=False, " "sequence=4294967295)")
def get_unspent_testnet(cls, address): # Get current block height: r_block = requests.get(cls.TEST_ENDPOINT + 'blocks/tip/height', timeout=DEFAULT_TIMEOUT) if r_block.status_code != 200: # pragma: no cover raise ConnectionError block_height = int(r_block.text) r = requests.get(cls.TEST_UNSPENT_API.format(address), timeout=DEFAULT_TIMEOUT) if r.status_code == 400: # pragma: no cover return [] elif r.status_code != 200: # pragma: no cover raise ConnectionError script_pubkey = bytes_to_hex(address_to_scriptpubkey(address)) return [ Unspent( tx["value"], block_height - tx["status"]["block_height"] + 1 if tx["status"]["confirmed"] else 0, script_pubkey, tx["txid"], tx["vout"], ) for tx in r.json() ]
def get_unspent_testnet(cls, address): endpoint = cls.TEST_UNSPENT_API + "&limit=100" unspents = [] r = requests.get(endpoint.format(address), timeout=DEFAULT_TIMEOUT) if r.status_code != 200: # pragma: no cover raise ConnectionError response = r.json() while len(response) > 0: unspents.extend( Unspent( currency_to_satoshi(tx['value'], 'satoshi'), tx['confirmations'], tx['script'], tx['mintTxid'], tx['mintIndex'], ) for tx in response ) response = requests.get( endpoint.format(address) + "&since={}".format(response[-1]['_id']), timeout=DEFAULT_TIMEOUT ).json() return unspents
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 mock_create_transaction(destination, destsamnts, key, fee=5): data = { 'address': 'mnQCmXfohuwBKjXtSKoHbxcZmQmveCdXtk', 'txid': 'dbbfbf43d47b12913dff041e2770eec236b0aec7ea6df071f4f7a1fe0b1c6526', 'vout': 1, 'scriptPubKey': '76a9144b820d01817f597d072a333776f4d672499ed57388ac', 'amount': 4.59801327, 'satoshis': 459801327, 'confirmations': 0, 'ts': 1531728453 } v = int(float(data['amount']) * (10**8)) unspents = [ Unspent(v, data['confirmations'], data['scriptPubKey'], data['txid'], data['vout']) ] outputs = [] for x in destination: amnt = 500 amount = trans_btc.round_sig((amnt / 1000), 4) outputs.append((x, amnt, 'mbtc')) destsamnts.append((x, amount)) tx = key.create_transaction(outputs, fee=fee, unspents=unspents) return tx
def get_unspent_testnet(cls, address): r = requests.get(cls.TEST_UNSPENT_API + address, timeout=DEFAULT_TIMEOUT) return [ Unspent(currency_to_satoshi(tx['amount'], 'btc'), tx['confirmations'], tx['script'], tx['tx'], tx['n']) for tx in r.json()['data']['unspent'] ]
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 get_unspent_testnet(cls, address): r = requests.get(cls.TEST_UNSPENT_API.format(address), timeout=DEFAULT_TIMEOUT) return [ Unspent(currency_to_satoshi(tx['amount'], 'btc'), tx['confirmations'], tx['scriptPubKey'], tx['txid'], tx['vout']) for tx in r.json() ]
def get_unspent_testnet(cls, address): r = requests.get(cls.TEST_UNSPENT_API.format(address) + '?limit=1000', timeout=DEFAULT_TIMEOUT) return [ Unspent(currency_to_satoshi(tx['value'], 'btc'), tx['confirmations'], tx['script_pub_key']['hex'], tx['txid'], tx['n']) for tx in r.json()['unspent'] ]
def get_unspent_testnet(cls, address): r = requests.get(cls.TEST_UNSPENT_API + address, timeout=DEFAULT_TIMEOUT) if r.status_code != 200: # pragma: no cover raise ConnectionError return [ Unspent(currency_to_satoshi(tx['amount'], 'btc'), tx['confirmations'], tx['script'], tx['tx'], tx['n']) for tx in r.json()['data']['unspent'] ]
def get_unspent_testnet(cls, address): txs_per_page = 1000 payload = {'limit': str(txs_per_page)} r = requests.get(cls.TEST_UNSPENT_API.format(address), params=payload, timeout=DEFAULT_TIMEOUT) if r.status_code != 200: # pragma: no cover raise ConnectionError response = r.json() unspents = [] next_link = None if 'unspent' in response: unspents.extend( Unspent( currency_to_satoshi(tx['value'], 'btc'), tx['confirmations'], tx['script_pub_key']['hex'], tx['txid'], tx['n'], ) for tx in response['unspent'] ) next_link = response['paging']['next_link'] while next_link: r = requests.get(next_link, params=payload, timeout=DEFAULT_TIMEOUT) if r.status_code != 200: # pragma: no cover raise ConnectionError response = r.json() unspents.extend( Unspent( currency_to_satoshi(tx['value'], 'btc'), tx['confirmations'], tx['script_pub_key']['hex'], tx['txid'], tx['n'], ) for tx in response['unspent'] ) next_link = response['paging']['next_link'] return unspents
def get_unspent(cls, address): r = requests.get(cls.UNSPENT_API + address, timeout=DEFAULT_TIMEOUT) if r.status_code == 500: return [] return [ Unspent(tx['value'], tx['confirmations'], tx['script'], tx['tx_hash_big_endian'], tx['tx_output_n']) for tx in r.json()['unspent_outputs'] ][::-1]
def get_unspent(self, address): r = self.listunspent(0, 9999999, [address]) return [ Unspent( currency_to_satoshi(tx["amount"], "btc"), tx["confirmations"], tx["scriptPubKey"], tx["txid"], tx["vout"], ) for tx in r ]
def get_unspent(cls, address): r = requests.get(cls.MAIN_UNSPENT_API.format(address), timeout=DEFAULT_TIMEOUT) if r.status_code != 200: # pragma: no cover raise ConnectionError return [ Unspent(currency_to_satoshi(tx['amount'], 'btc'), tx['confirmations'], tx['scriptPubKey'], tx['txid'], tx['vout']) for tx in r.json() ]
def get_unspent(cls, address): r = requests.get(cls.MAIN_UNSPENT_API.format(address) + '?limit=1000', timeout=DEFAULT_TIMEOUT) if r.status_code != 200: # pragma: no cover raise ConnectionError return [ Unspent(currency_to_satoshi(tx['value'], 'btc'), tx['confirmations'], tx['script_pub_key']['hex'], tx['txid'], tx['n']) for tx in r.json()['unspent'] ]
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_dont_overwrite_relative_locktime(self): # opting in for RBF shouldn't overwrite a relative locktime unspents = [] for unspent in UNSPENTS_RBF: locked_unspent = Unspent.from_dict(unspent.to_dict()) locked_unspent.sequence = 50000 locked_unspent.opt_in_for_RBF() unspents.append(locked_unspent) rbf_tx = create_new_transaction(PrivateKey(), unspents, OUTPUTS_RBF) START = 8 # exclude transaction version END = len( rbf_tx) - 12 # exlude locktime and 2x one byte of empty witness assert rbf_tx[START:END] != FINAL_TX_RBF[START:END]
def get_unspent_testnet(cls, address): r = requests.get(cls.TEST_UNSPENT_API.format(address), timeout=DEFAULT_TIMEOUT) if r.status_code != 200: # pragma: no cover raise ConnectionError return [ Unspent( currency_to_satoshi(tx["amount"], "btc"), tx["confirmations"], tx["scriptPubKey"], tx["txid"], tx["vout"], ) for tx in r.json() ]
def test_opt_in_for_RBF(self): # test based on tx 4162a41175658e76ae5d22f02739932c9997caaeaeaa1e1db30f352f926aa97a, mined in block 640001 unspents = [] for unspent in UNSPENTS_RBF: replaceable_unspent = Unspent.from_dict(unspent.to_dict()) replaceable_unspent.opt_in_for_RBF() unspents.append(replaceable_unspent) rbf_tx = create_new_transaction(PrivateKey(), unspents, OUTPUTS_RBF) START = 8 # exclude transaction version END = len( rbf_tx) - 12 # exlude locktime and 2x one byte of empty witness assert rbf_tx[START:END] == FINAL_TX_RBF[START:END]