def push(self): tx = btc.serialize(self.latest_tx) log.debug('\n' + tx) self.txid = btc.txhash(tx) log.debug('txid = ' + self.txid) tx_broadcast = jm_single().config.get('POLICY', 'tx_broadcast') if tx_broadcast == 'self': pushed = jm_single().bc_interface.pushtx(tx) elif tx_broadcast in ['random-peer', 'not-self']: n = len(self.active_orders) if tx_broadcast == 'random-peer': i = random.randrange(n + 1) else: i = random.randrange(n) if i == n: pushed = jm_single().bc_interface.pushtx(tx) else: self.msgchan.push_tx(self.active_orders.keys()[i], tx) pushed = True elif tx_broadcast == 'random-maker': crow = self.db.execute( 'SELECT DISTINCT counterparty FROM orderbook ORDER BY ' + 'RANDOM() LIMIT 1;' ).fetchone() counterparty = crow['counterparty'] log.debug('pushing tx to ' + counterparty) self.msgchan.push_tx(counterparty, tx) pushed = True if not pushed: log.debug('unable to pushtx') return pushed
def on_push_tx(self, nick, txhex): log.debug('received txhex from ' + nick + ' to push\n' + txhex) pushed = jm_single().bc_interface.pushtx(txhex) if pushed: log.debug('pushed tx ' + btc.txhash(txhex)) else: log.debug('failed to push tx sent by taker') self.msgchan.send_error(nick, 'Unable to push tx')
def outputs_watcher(self, wallet_name, notifyaddr, tx_output_set, unconfirmfun, confirmfun, timeoutfun): """Given a key for the watcher loop (notifyaddr), a wallet name (label), a set of outputs, and unconfirm, confirm and timeout callbacks, check to see if a transaction matching that output set has appeared in the wallet. Call the callbacks and update the watcher loop state. End the loop when the confirmation has been seen (no spent monitoring here). """ wl = self.tx_watcher_loops[notifyaddr] txlist = self.rpc("listtransactions", ["*", 100, 0, True]) for tx in txlist[::-1]: #changed syntax in 0.14.0; allow both syntaxes try: res = self.rpc("gettransaction", [tx["txid"], True]) except: try: res = self.rpc("gettransaction", [tx["txid"], 1]) except JsonRpcError as e: #This should never happen (gettransaction is a wallet rpc). log.info("Failed any gettransaction call") res = None except Exception as e: log.info(str(e)) if not res: continue if "confirmations" not in res: log.debug("Malformed gettx result: " + str(res)) return txd = self.get_deser_from_gettransaction(res) if txd is None: continue txos = set([(sv['script'], sv['value']) for sv in txd['outs']]) if not txos == tx_output_set: continue #Here we have found a matching transaction in the wallet. real_txid = btc.txhash(btc.serialize(txd)) if not wl[1] and res["confirmations"] == 0: log.debug("Tx: " + str(real_txid) + " seen on network.") unconfirmfun(txd, real_txid) wl[1] = True return if not wl[2] and res["confirmations"] > 0: log.debug("Tx: " + str(real_txid) + " has " + str(res["confirmations"]) + " confirmations.") confirmfun(txd, real_txid, res["confirmations"]) wl[2] = True wl[0].stop() return if res["confirmations"] < 0: log.debug("Tx: " + str(real_txid) + " has a conflict. Abandoning.") wl[0].stop() return
def push(self): tx = btc.serialize(self.latest_tx) jlog.debug('\n' + tx) self.txid = btc.txhash(tx) jlog.debug('txid = ' + self.txid) pushed = jm_single().bc_interface.pushtx(tx) if not pushed: self.on_finished_callback(False, fromtx=True) else: jm_single().bc_interface.add_tx_notify(self.latest_tx, self.unconfirm_callback, self.confirm_callback, self.my_cj_addr)
def tx_watcher(self, txd, unconfirmfun, confirmfun, spentfun, c, n): """Called at a polling interval, checks if the given deserialized transaction (which must be fully signed) is (a) broadcast, (b) confirmed and (c) spent from. (c, n ignored in electrum version, just supports registering first confirmation). TODO: There is no handling of conflicts here. """ txid = btc.txhash(btc.serialize(txd)) wl = self.tx_watcher_loops[txid] #first check if in mempool (unconfirmed) #choose an output address for the query. Filter out #p2pkh addresses, assume p2sh (thus would fail to find tx on #some nonstandard script type) addr = None for i in range(len(txd['outs'])): if not btc.is_p2pkh_script(txd['outs'][i]['script']): addr = btc.script_to_address(txd['outs'][i]['script'], get_p2sh_vbyte()) break if not addr: log.error("Failed to find any p2sh output, cannot be a standard " "joinmarket transaction, fatal error!") reactor.stop() return unconftxs_res = self.get_from_electrum( 'blockchain.address.get_mempool', addr, blocking=True).get('result') unconftxs = [str(t['tx_hash']) for t in unconftxs_res] if not wl[1] and txid in unconftxs: print("Tx: " + str(txid) + " seen on network.") unconfirmfun(txd, txid) wl[1] = True return conftx = self.get_from_electrum('blockchain.address.listunspent', addr, blocking=True).get('result') conftxs = [str(t['tx_hash']) for t in conftx] if not wl[2] and len(conftxs) and txid in conftxs: print("Tx: " + str(txid) + " is confirmed.") confirmfun(txd, txid, 1) wl[2] = True #Note we do not stop the monitoring loop when #confirmations occur, since we are also monitoring for spending. return if not spentfun or wl[3]: return
def push(self): tx = btc.serialize(self.latest_tx) jlog.debug('\n' + tx) self.txid = btc.txhash(tx) jlog.info('txid = ' + self.txid) #If we are sending to a bech32 address, in case of sweep, will #need to use that bech32 for address import, which requires #converting to script (Core does not allow import of bech32) if self.my_cj_addr.lower()[:2] in ['bc', 'tb']: notify_addr = btc.address_to_script(self.my_cj_addr) else: notify_addr = self.my_cj_addr #add the txnotify callbacks *before* pushing in case the #walletnotify is triggered before the notify callbacks are set up; #this does leave a dangling notify callback if the push fails, but #that doesn't cause problems. jm_single().bc_interface.add_tx_notify(self.latest_tx, self.unconfirm_callback, self.confirm_callback, notify_addr, vb=get_p2sh_vbyte()) tx_broadcast = jm_single().config.get('POLICY', 'tx_broadcast') nick_to_use = None if tx_broadcast == 'self': pushed = jm_single().bc_interface.pushtx(tx) elif tx_broadcast in ['random-peer', 'not-self']: n = len(self.maker_utxo_data) if tx_broadcast == 'random-peer': i = random.randrange(n + 1) else: i = random.randrange(n) if i == n: pushed = jm_single().bc_interface.pushtx(tx) else: nick_to_use = self.maker_utxo_data.keys()[i] pushed = True else: jlog.info("Only self, random-peer and not-self broadcast " "methods supported. Reverting to self-broadcast.") pushed = jm_single().bc_interface.pushtx(tx) if not pushed: self.on_finished_callback(False, fromtx=True) else: if nick_to_use: return (nick_to_use, tx)
def tx_watcher(self, txd, unconfirmfun, confirmfun, spentfun, c, n): """Called at a polling interval, checks if the given deserialized transaction (which must be fully signed) is (a) broadcast, (b) confirmed and (c) spent from at index n, and notifies confirmation if number of confs = c. TODO: Deal with conflicts correctly. Here just abandons monitoring. """ txid = btc.txhash(btc.serialize(txd)) wl = self.tx_watcher_loops[txid] try: res = self.rpc('gettransaction', [txid, True]) except JsonRpcError as e: return if not res: return if "confirmations" not in res: log.debug("Malformed gettx result: " + str(res)) return if not wl[1] and res["confirmations"] == 0: log.debug("Tx: " + str(txid) + " seen on network.") unconfirmfun(txd, txid) wl[1] = True return if not wl[2] and res["confirmations"] > 0: log.debug("Tx: " + str(txid) + " has " + str(res["confirmations"]) + " confirmations.") confirmfun(txd, txid, res["confirmations"]) if c <= res["confirmations"]: wl[2] = True #Note we do not stop the monitoring loop when #confirmations occur, since we are also monitoring for spending. return if res["confirmations"] < 0: log.debug("Tx: " + str(txid) + " has a conflict. Abandoning.") wl[0].stop() return if not spentfun or wl[3]: return #To trigger the spent callback, we check if this utxo outpoint appears in #listunspent output with 0 or more confirmations. Note that this requires #we have added the destination address to the watch-only wallet, otherwise #that outpoint will not be returned by listunspent. res2 = self.rpc('listunspent', [0, 999999]) if not res2: return txunspent = False for r in res2: if "txid" not in r: continue if txid == r["txid"] and n == r["vout"]: txunspent = True break if not txunspent: #We need to find the transaction which spent this one; #assuming the address was added to the wallet, then this #transaction must be in the recent list retrieved via listunspent. #For each one, use gettransaction to check its inputs. #This is a bit expensive, but should only occur once. txlist = self.rpc("listtransactions", ["*", 1000, 0, True]) for tx in txlist[::-1]: #changed syntax in 0.14.0; allow both syntaxes try: res = self.rpc("gettransaction", [tx["txid"], True]) except: try: res = self.rpc("gettransaction", [tx["txid"], 1]) except: #This should never happen (gettransaction is a wallet rpc). log.info("Failed any gettransaction call") res = None if not res: continue deser = self.get_deser_from_gettransaction(res) if deser is None: continue for vin in deser["ins"]: if not "outpoint" in vin: #coinbases continue if vin["outpoint"]["hash"] == txid and vin["outpoint"][ "index"] == n: #recover the deserialized form of the spending transaction. log.info("We found a spending transaction: " + \ btc.txhash(binascii.unhexlify(res["hex"]))) res2 = self.rpc("gettransaction", [tx["txid"], True]) spending_deser = self.get_deser_from_gettransaction( res2) if not spending_deser: log.info( "ERROR: could not deserialize spending tx.") #Should never happen, it's a parsing bug. #No point continuing to monitor, we just hope we #can extract the secret by scanning blocks. wl[3] = True return spentfun(spending_deser, vin["outpoint"]["hash"]) wl[3] = True return
def add_tx_notify(self, txd, unconfirmfun, confirmfun, notifyaddr, wallet_name=None, timeoutfun=None, spentfun=None, txid_flag=True, n=0, c=1, vb=None): """Given a deserialized transaction txd, callback functions for broadcast and confirmation of the transaction, an address to import, and a callback function for timeout, set up a polling loop to check for events on the transaction. Also optionally set to trigger "confirmed" callback on number of confirmations c. Also checks for spending (if spentfun is not None) of the outpoint n. If txid_flag is True, we create a watcher loop on the txid (hence only really usable in a segwit context, and only on fully formed transactions), else we create a watcher loop on the output set of the transaction (taken from the outs field of the txd). """ if not vb: vb = get_p2pk_vbyte() if isinstance(self, BitcoinCoreInterface) or isinstance( self, RegtestBitcoinCoreInterface): #This code ensures that a walletnotify is triggered, by #ensuring that at least one of the output addresses is #imported into the wallet (note the sweep special case, where #none of the output addresses belong to me). one_addr_imported = False for outs in txd['outs']: addr = btc.script_to_address(outs['script'], vb) if self.rpc('getaccount', [addr]) != '': one_addr_imported = True break if not one_addr_imported: self.rpc('importaddress', [notifyaddr, 'joinmarket-notify', False]) #Warning! In case of txid_flag false, this is *not* a valid txid, #but only a hash of an incomplete transaction serialization. txid = btc.txhash(btc.serialize(txd)) if not txid_flag: tx_output_set = set([(sv['script'], sv['value']) for sv in txd['outs']]) loop = task.LoopingCall(self.outputs_watcher, wallet_name, notifyaddr, tx_output_set, unconfirmfun, confirmfun, timeoutfun) log.debug("Created watcher loop for address: " + notifyaddr) loopkey = notifyaddr else: loop = task.LoopingCall(self.tx_watcher, txd, unconfirmfun, confirmfun, spentfun, c, n) log.debug("Created watcher loop for txid: " + txid) loopkey = txid self.tx_watcher_loops[loopkey] = [loop, False, False, False] #Hardcoded polling interval, but in any case it can be very short. loop.start(5.0) #Give up on un-broadcast transactions and broadcast but not confirmed #transactions as per settings in the config. reactor.callLater( float(jm_single().config.get("TIMEOUT", "unconfirm_timeout_sec")), self.tx_network_timeout, loopkey) confirm_timeout_sec = int(jm_single().config.get( "TIMEOUT", "confirm_timeout_hours")) * 3600 reactor.callLater(confirm_timeout_sec, self.tx_timeout, txd, loopkey, timeoutfun)