def script_to_address(self, script): """Return the address for a given output script, which will be p2sh-p2wpkh for the segwit (currently). The underlying witness is however invisible at this layer; so it's just a p2sh address. """ return btc.script_to_address(script, get_p2sh_vbyte())
def on_tx_confirmed(self, cjorder, confirmations, txid): to_announce = [] for i, out in enumerate(cjorder.tx['outs']): addr = btc.script_to_address(out['script'], get_p2pk_vbyte()) if addr == cjorder.change_addr: neworder = { 'oid': self.get_next_oid(), 'ordertype': 'absorder', 'minsize': 12000, 'maxsize': out['value'], 'txfee': 10000, 'cjfee': 100000, 'utxo': txid + ':' + str(i) } to_announce.append(neworder) if addr == cjorder.cj_addr: neworder = { 'oid': self.get_next_oid(), 'ordertype': 'absorder', 'minsize': 12000, 'maxsize': out['value'], 'txfee': 10000, 'cjfee': 100000, 'utxo': txid + ':' + str(i) } to_announce.append(neworder) return [], to_announce
def __init__(self, blockchaininterface, txd, unconfirmfun, confirmfun): threading.Thread.__init__(self) self.daemon = True self.blockchaininterface = blockchaininterface self.unconfirmfun = unconfirmfun self.confirmfun = confirmfun self.tx_output_set = set([(sv['script'], sv['value']) for sv in txd['outs']]) self.output_addresses = [btc.script_to_address(scrval[0], get_p2pk_vbyte()) for scrval in self.tx_output_set] log.debug('txoutset=' + pprint.pformat(self.tx_output_set)) log.debug('outaddrs=' + ','.join(self.output_addresses))
def add_new_utxos(self, tx, txid): added_utxos = {} for index, outs in enumerate(tx['outs']): addr = btc.script_to_address(outs['script'], self.get_vbyte()) if addr not in self.addr_cache: continue addrdict = {'address': addr, 'value': outs['value']} utxo = txid + ':' + str(index) added_utxos[utxo] = addrdict self.unspent[utxo] = addrdict log.debug('added utxos, wallet now is \n' + pprint.pformat(self.get_utxos_by_mixdepth())) return added_utxos
def add_new_utxos(self, tx, txid): added_utxos = {} for index, outs in enumerate(tx['outs']): addr = btc.script_to_address(outs['script'], get_p2pk_vbyte()) if addr not in self.addr_cache: continue addrdict = {'address': addr, 'value': outs['value']} utxo = txid + ':' + str(index) added_utxos[utxo] = addrdict self.unspent[utxo] = addrdict log.debug('added utxos, wallet now is \n' + pprint.pformat( self.get_utxos_by_mixdepth())) return added_utxos
def __init__(self, blockr_domain, txd, unconfirmfun, confirmfun, timeoutfun): threading.Thread.__init__(self, name='BlockrNotifyThread') self.daemon = True self.blockr_domain = blockr_domain self.unconfirmfun = unconfirmfun self.confirmfun = confirmfun self.timeoutfun = timeoutfun self.tx_output_set = set([(sv['script'], sv['value']) for sv in txd['outs']]) self.output_addresses = [ btc.script_to_address(scrval[0], get_p2pk_vbyte()) for scrval in self.tx_output_set] log.debug('txoutset=' + pprint.pformat(self.tx_output_set)) log.debug('outaddrs=' + ','.join(self.output_addresses))
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 verify_unsigned_tx(self, txd): tx_utxo_set = set(ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) for ins in txd['ins']) # complete authentication: check the tx input uses the authing pubkey input_utxo_data = jm_single().bc_interface.query_utxo_set( list(tx_utxo_set)) if None in input_utxo_data: return False, 'some utxos already spent or not confirmed yet' input_addresses = [u['address'] for u in input_utxo_data] if btc.pubtoaddr( self.i_utxo_pubkey, get_p2pk_vbyte()) not in input_addresses: return False, "authenticating bitcoin address is not contained" my_utxo_set = set(self.utxos.keys()) if not tx_utxo_set.issuperset(my_utxo_set): return False, 'my utxos are not contained' my_total_in = sum([va['value'] for va in self.utxos.values()]) self.real_cjfee = calc_cj_fee( self.ordertype, self.cjfee, self.cj_amount) expected_change_value = ( my_total_in - self.cj_amount - self.txfee + self.real_cjfee) log.debug('potentially earned = {}'.format( self.real_cjfee - self.txfee)) log.debug('mycjaddr, mychange = {}, {}'.format( self.cj_addr, self.change_addr)) times_seen_cj_addr = 0 times_seen_change_addr = 0 for outs in txd['outs']: addr = btc.script_to_address(outs['script'], get_p2pk_vbyte()) if addr == self.cj_addr: times_seen_cj_addr += 1 if outs['value'] != self.cj_amount: return False, 'Wrong cj_amount. I expect ' + str( self.cj_amount) if addr == self.change_addr: times_seen_change_addr += 1 if outs['value'] != expected_change_value: return False, 'wrong change, i expect ' + str( expected_change_value) if times_seen_cj_addr != 1 or times_seen_change_addr != 1: fmt = ('cj or change addr not in tx ' 'outputs once, #cjaddr={}, #chaddr={}').format return False, (fmt(times_seen_cj_addr, times_seen_change_addr)) return True, None
def verify_unsigned_tx(self, txd): tx_utxo_set = set(ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) for ins in txd['ins']) # complete authentication: check the tx input uses the authing pubkey input_utxo_data = jm_single().bc_interface.query_utxo_set( list(tx_utxo_set)) if None in input_utxo_data: return False, 'some utxos already spent or not confirmed yet' input_addresses = [u['address'] for u in input_utxo_data] if btc.pubtoaddr(self.i_utxo_pubkey, get_p2pk_vbyte()) not in input_addresses: return False, "authenticating bitcoin address is not contained" my_utxo_set = set(self.utxos.keys()) if not tx_utxo_set.issuperset(my_utxo_set): return False, 'my utxos are not contained' my_total_in = sum([va['value'] for va in self.utxos.values()]) self.real_cjfee = calc_cj_fee(self.ordertype, self.cjfee, self.cj_amount) expected_change_value = (my_total_in - self.cj_amount - self.txfee + self.real_cjfee) log.debug('potentially earned = {}'.format(self.real_cjfee - self.txfee)) log.debug('mycjaddr, mychange = {}, {}'.format(self.cj_addr, self.change_addr)) times_seen_cj_addr = 0 times_seen_change_addr = 0 for outs in txd['outs']: addr = btc.script_to_address(outs['script'], get_p2pk_vbyte()) if addr == self.cj_addr: times_seen_cj_addr += 1 if outs['value'] != self.cj_amount: return False, 'Wrong cj_amount. I expect ' + str( self.cj_amount) if addr == self.change_addr: times_seen_change_addr += 1 if outs['value'] != expected_change_value: return False, 'wrong change, i expect ' + str( expected_change_value) if times_seen_cj_addr != 1 or times_seen_change_addr != 1: fmt = ('cj or change addr not in tx ' 'outputs once, #cjaddr={}, #chaddr={}').format return False, (fmt(times_seen_cj_addr, times_seen_change_addr)) return True, None
def verify_unsigned_tx(self, txd, offerinfo): tx_utxo_set = set(ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) for ins in txd['ins']) utxos = offerinfo["utxos"] cjaddr = offerinfo["cjaddr"] changeaddr = offerinfo["changeaddr"] amount = offerinfo["amount"] cjfee = offerinfo["offer"]["cjfee"] txfee = offerinfo["offer"]["txfee"] ordertype = offerinfo["offer"]["ordertype"] my_utxo_set = set(utxos.keys()) if not tx_utxo_set.issuperset(my_utxo_set): return (False, 'my utxos are not contained') my_total_in = sum([va['value'] for va in utxos.values()]) real_cjfee = calc_cj_fee(ordertype, cjfee, amount) expected_change_value = (my_total_in - amount - txfee + real_cjfee) jlog.info('potentially earned = {}'.format(real_cjfee - txfee)) jlog.info('mycjaddr, mychange = {}, {}'.format(cjaddr, changeaddr)) times_seen_cj_addr = 0 times_seen_change_addr = 0 for outs in txd['outs']: addr = btc.script_to_address(outs['script'], get_p2sh_vbyte()) if addr == cjaddr: times_seen_cj_addr += 1 if outs['value'] != amount: return (False, 'Wrong cj_amount. I expect ' + str(amount)) if addr == changeaddr: times_seen_change_addr += 1 if outs['value'] != expected_change_value: return (False, 'wrong change, i expect ' + str( expected_change_value)) if times_seen_cj_addr != 1 or times_seen_change_addr != 1: fmt = ('cj or change addr not in tx ' 'outputs once, #cjaddr={}, #chaddr={}').format return (False, (fmt(times_seen_cj_addr, times_seen_change_addr))) return (True, None)
def on_tx_confirmed(self, cjorder, confirmations, txid): to_announce = [] for i, out in enumerate(cjorder.tx['outs']): addr = btc.script_to_address(out['script'], get_p2pk_vbyte()) if addr == cjorder.change_addr: neworder = {'oid': self.get_next_oid(), 'ordertype': 'absorder', 'minsize': 12000, 'maxsize': out['value'], 'txfee': 10000, 'cjfee': 100000, 'utxo': txid + ':' + str(i)} to_announce.append(neworder) if addr == cjorder.cj_addr: neworder = {'oid': self.get_next_oid(), 'ordertype': 'absorder', 'minsize': 12000, 'maxsize': out['value'], 'txfee': 10000, 'cjfee': 100000, 'utxo': txid + ':' + str(i)} to_announce.append(neworder) return [], to_announce
def add_tx_notify(self, txd, unconfirmfun, confirmfun, notifyaddr, timeoutfun=None): if not self.notifythread: self.notifythread = BitcoinCoreNotifyThread(self) self.notifythread.start() one_addr_imported = False for outs in txd['outs']: addr = btc.script_to_address(outs['script'], get_p2pk_vbyte()) if self.rpc('getaccount', [addr]) != '': one_addr_imported = True break if not one_addr_imported: self.rpc('importaddress', [notifyaddr, 'joinmarket-notify', False]) tx_output_set = set([(sv['script'], sv['value']) for sv in txd['outs']]) self.txnotify_fun.append((tx_output_set, unconfirmfun, confirmfun, timeoutfun, False)) #create unconfirm timeout here, create confirm timeout in the other thread if timeoutfun: threading.Timer(jm_single().config.getint('TIMEOUT', 'unconfirm_timeout_sec'), bitcoincore_timeout_callback, args=(False, tx_output_set, self.txnotify_fun, timeoutfun) ).start()
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)
def script_to_address(self, script): """Return the address for a given output script, which will be p2pkh for the default Wallet object, and reading the correct network byte from the config. """ return btc.script_to_address(script, get_p2pk_vbyte())