def next_tx(self): """Process the next transaction in this wallet. Returns: `TransactionType`: Whether the tx was a `SEND` or `RECEIVE`.""" if self.tx_index == len(self.txs): raise StopIteration txn = self.txs[self.tx_index] self.tx_index += 1 txid = txn['txid'] if txn['type'] == 'received': amt = float_to_satoshis(txn['amount']) utxos = self.get_utxos(txid, amt) self.utxos.extend(utxos) return TransactionType.RECEIVE elif txn['type'] == 'sent': #remove inputs from utxos set inputs = self.get_inputs(txid) for tx_input in inputs: dprint(("Attempting to delete this utxo from set due to send: " "%s") % str(tx_input)) try: self.utxos.remove(tx_input) except ValueError: print( ("WARNING: Missing input %s from wallet %s in tx %s. " "This indicates a bug in this program, incomplete " "clustering analysis, or a multi-party transaction " "such as a CoinJoin. This input will be ignored.") % (str(tx_input), self.wallet_label, txid)) continue dprint("Deleted this utxo from set due to send: %s" % str(tx_input)) #add change to utxo set, if any spend_amts = [] for spend_output in txn['outputs']: assert spend_output['wallet_id'] != self.wallet_label spend_amt = float_to_satoshis(spend_output['amount']) spend_amts.append(spend_amt) change_utxos = self.get_utxos_mismatch_amt(txid, spend_amts) dprint("Adding these change utxos due to send: %s" % str(change_utxos)) self.utxos.extend(change_utxos) return TransactionType.SEND else: raise TypeError
def next_tx(self): """Process the next transaction in this wallet. Returns: `TransactionType`: Whether the tx was a `SEND` or `RECEIVE`.""" if self.tx_index == len(self.txs): raise StopIteration txn = self.txs[self.tx_index] self.tx_index += 1 txid = txn['txid'] if txn['type'] == 'received': amt = float_to_satoshis(txn['amount']) utxos = self.get_utxos(txid, amt) self.utxos.extend(utxos) return TransactionType.RECEIVE elif txn['type'] == 'sent': #remove inputs from utxos set inputs = self.get_inputs(txid) for tx_input in inputs: dprint(("Attempting to delete this utxo from set due to send: " "%s") % str(tx_input)) try: self.utxos.remove(tx_input) except ValueError: print(("WARNING: Missing input %s from wallet %s in tx %s. " "This indicates a bug in this program, incomplete " "clustering analysis, or a multi-party transaction " "such as a CoinJoin. This input will be ignored.") % (str(tx_input), self.wallet_label, txid)) continue dprint("Deleted this utxo from set due to send: %s" % str(tx_input)) #add change to utxo set, if any spend_amts = [] for spend_output in txn['outputs']: assert spend_output['wallet_id'] != self.wallet_label spend_amt = float_to_satoshis(spend_output['amount']) spend_amts.append(spend_amt) change_utxos = self.get_utxos_mismatch_amt(txid, spend_amts) dprint("Adding these change utxos due to send: %s" % str(change_utxos)) self.utxos.extend(change_utxos) return TransactionType.SEND else: raise TypeError
def get_current_desired_spend(self): """Based on the current sending transaction, get the intended spend amt. Returns: int: The total desired spend implied by this sending transaction. """ current_tx = self.txs[self.tx_index] if current_tx['type'] != 'sent': dprint(str(current_tx)) raise CurrentTxNotSendError desired_spend = 0 for output in current_tx['outputs']: desired_spend += float_to_satoshis(output['amount']) return desired_spend
def get_utxos(self, txid, output_amt): """Find outputs for specified tx sent to this wallet. WalletExplorer.com's /wallet endpoint indicates a total amount sent to the wallet but not specifically which output(s). We'll first try to use bitcoind's RPC interface to match the amount to a unique transaction output. If that can't be done, we'll follow up with a call to the /tx API endpoint and get a complete list of outputs sent to the wallet. Args: txid (str): Transaction hash. output_amt (int): The amount of the output we want to find in integer satoshi terms. In the event of duplicate values in a transaction's outputs, the first one is used. Returns: List[utxos], with each utxo as a tuple of (txid, output_index, amt_satoshis). """ dprint("Looking for output amt %s in %s" % (str(output_amt), txid)) assert type(txid) in (str, unicode) assert isinstance(output_amt, int) or isinstance(output_amt, long) tx_rpc_json = self.conn.get_decoded_tx(txid) utxo = None for i, vout in enumerate(tx_rpc_json['vout']): if float_to_satoshis(vout['value']) == output_amt: if utxo is None: utxo = (txid, i, output_amt) else: #resolve ambiguity as to which output is correct utxos = http.get_outputs_sent_to_wallet(txid, self.wallet_label) return utxos if utxo is None: #there may be multiple outputs that add up the specified amount utxos = http.get_outputs_sent_to_wallet(txid, self.wallet_label) utxo_sum = 0 for utxo in utxos: utxo_sum += utxo[2] if utxo_sum != output_amt: raise OutputNotFoundError else: return utxos else: return [utxo]
def get_utxos(self, txid, output_amt): """Find outputs for specified tx sent to this wallet. WalletExplorer.com's /wallet endpoint indicates a total amount sent to the wallet but not specifically which output(s). We'll first try to use bitcoind's RPC interface to match the amount to a unique transaction output. If that can't be done, we'll follow up with a call to the /tx API endpoint and get a complete list of outputs sent to the wallet. Args: txid (str): Transaction hash. output_amt (int): The amount of the output we want to find in integer satoshi terms. In the event of duplicate values in a transaction's outputs, the first one is used. Returns: List[utxos], with each utxo as a tuple of (txid, output_index, amt_satoshis). """ dprint("Looking for output amt %s in %s" % (str(output_amt), txid)) assert type(txid) in (str, unicode) assert isinstance(output_amt, int) or isinstance(output_amt, long) tx_rpc_json = self.conn.get_decoded_tx(txid) utxo = None for i, vout in enumerate(tx_rpc_json['vout']): if float_to_satoshis(vout['value']) == output_amt: if utxo is None: utxo = (txid, i, output_amt) else: #resolve ambiguity as to which output is correct utxos = http.get_outputs_sent_to_wallet( txid, self.wallet_label) return utxos if utxo is None: #there may be multiple outputs that add up the specified amount utxos = http.get_outputs_sent_to_wallet(txid, self.wallet_label) utxo_sum = 0 for utxo in utxos: utxo_sum += utxo[2] if utxo_sum != output_amt: raise OutputNotFoundError else: return utxos else: return [utxo]
def get_inputs(self, txid): """Get a list of inputs for the specified transaction. Returns: List of utxos as tuples of (txid, output_index, amt_in_satoshis). """ tx_rpc_json = self.conn.get_decoded_tx(txid) inputs = [] for vin in tx_rpc_json['vin']: input_prev_txid = vin['txid'] input_prev_index = vin['vout'] input_rpc_json = self.conn.get_decoded_tx(input_prev_txid) input_amt = input_rpc_json['vout'][input_prev_index]['value'] input_satoshis = float_to_satoshis(input_amt) utxo = (input_prev_txid,input_prev_index, input_satoshis) inputs.append(utxo) return inputs
def get_inputs(self, txid): """Get a list of inputs for the specified transaction. Returns: List of utxos as tuples of (txid, output_index, amt_in_satoshis). """ tx_rpc_json = self.conn.get_decoded_tx(txid) inputs = [] for vin in tx_rpc_json['vin']: input_prev_txid = vin['txid'] input_prev_index = vin['vout'] input_rpc_json = self.conn.get_decoded_tx(input_prev_txid) input_amt = input_rpc_json['vout'][input_prev_index]['value'] input_satoshis = float_to_satoshis(input_amt) utxo = (input_prev_txid, input_prev_index, input_satoshis) inputs.append(utxo) return inputs
def get_outputs_sent_to_wallet(txid, wallet_label): """Find all outputs in tx sent to specified wallet using remote API. Returns: List[utxos], with each tuple as (txid, output_index, amt_satoshis) """ tx_json = get_tx_data_json(txid) utxos = [] for i, output in enumerate(tx_json['out']): receiver_wallet = None amt_in_satoshis = float_to_satoshis(output['amount']) if 'label' in output: receiver_wallet = output['label'] else: receiver_wallet = output['wallet_id'] if receiver_wallet == wallet_label: utxo = (txid, i, amt_in_satoshis) utxos.append(utxo) return utxos
def get_utxos_mismatch_amt(self, txid, amt_list): """Return all utxos that don't match the output amounts specified. Args: txid (str): transaction hash amt_list (List[int]): List of amounts we want to omit, each expressed in integer satoshi terms. Returns: List of utxos as tuples of (txid, output_index, amt_in_satoshis). """ assert type(txid) in (str, unicode) assert isinstance(amt_list, list) tx_rpc_json = self.conn.get_decoded_tx(txid) utxos = [] for i, vout in enumerate(tx_rpc_json['vout']): output_val = float_to_satoshis(vout['value']) if output_val not in amt_list: utxo = (txid, i, output_val) utxos.append(utxo) return utxos