def sync_unspent(self):
        st = time.time()
        # block height needs to be real time for addition to our utxos:
        current_blockheight = self.bci.rpc("getblockcount", [])
        wallet_name = self.get_wallet_name()
        self.reset_utxos()

        listunspent_args = []
        if 'listunspent_args' in jm_single().config.options('POLICY'):
            listunspent_args = ast.literal_eval(jm_single().config.get(
                'POLICY', 'listunspent_args'))

        unspent_list = self.bci.rpc('listunspent', listunspent_args)
        # filter on label, but note (a) in certain circumstances (in-
        # wallet transfer) it is possible for the utxo to be labeled
        # with the external label, and (b) the wallet will know if it
        # belongs or not anyway (is_known_addr):
        our_unspent_list = [
            x for x in unspent_list
            if (self.bci.is_address_labeled(x, wallet_name)
                or self.bci.is_address_labeled(x, self.EXTERNAL_WALLET_LABEL))
        ]
        for u in our_unspent_list:
            if not self.is_known_addr(u['address']):
                continue
            self._add_unspent_utxo(u, current_blockheight)
        et = time.time()
        jlog.debug('bitcoind sync_unspent took ' + str((et - st)) + 'sec')
Example #2
0
    def sync_unspent(self, wallet):
        from jmclient.wallet import BitcoinCoreWallet

        if isinstance(wallet, BitcoinCoreWallet):
            return
        st = time.time()
        wallet_name = self.get_wallet_name(wallet)
        wallet.unspent = {}

        listunspent_args = []
        if 'listunspent_args' in jm_single().config.options('POLICY'):
            listunspent_args = ast.literal_eval(jm_single().config.get(
                'POLICY', 'listunspent_args'))

        unspent_list = self.rpc('listunspent', listunspent_args)
        for u in unspent_list:
            if 'account' not in u:
                continue
            if u['account'] != wallet_name:
                continue
            if u['address'] not in wallet.addr_cache:
                continue
            wallet.unspent[u['txid'] + ':' + str(u['vout'])] = {
                'address': u['address'],
                'value': int(Decimal(str(u['amount'])) * Decimal('1e8'))
            }
        et = time.time()
        log.debug('bitcoind sync_unspent took ' + str((et - st)) + 'sec')
        self.wallet_synced = True
Example #3
0
    def on_auth_received(self, nick, offer, commitment, cr, amount, kphex):
        """Receives data on proposed transaction offer from daemon, verifies
        commitment, returns necessary data to send ioauth message (utxos etc)
        """
        #deserialize the commitment revelation
        cr_dict = PoDLE.deserialize_revelation(cr)
        #check the validity of the proof of discrete log equivalence
        tries = jm_single().config.getint("POLICY", "taker_utxo_retries")

        def reject(msg):
            jlog.info("Counterparty commitment not accepted, reason: " + msg)
            return (False, )

        if not verify_podle(str(cr_dict['P']),
                            str(cr_dict['P2']),
                            str(cr_dict['sig']),
                            str(cr_dict['e']),
                            str(commitment),
                            index_range=range(tries)):
            reason = "verify_podle failed"
            return reject(reason)
        #finally, check that the proffered utxo is real, old enough, large enough,
        #and corresponds to the pubkey
        res = jm_single().bc_interface.query_utxo_set([cr_dict['utxo']],
                                                      includeconf=True)
        if len(res) != 1 or not res[0]:
            reason = "authorizing utxo is not valid"
            return reject(reason)
        age = jm_single().config.getint("POLICY", "taker_utxo_age")
        if res[0]['confirms'] < age:
            reason = "commitment utxo not old enough: " + str(
                res[0]['confirms'])
            return reject(reason)
        reqd_amt = int(
            amount *
            jm_single().config.getint("POLICY", "taker_utxo_amtpercent") /
            100.0)
        if res[0]['value'] < reqd_amt:
            reason = "commitment utxo too small: " + str(res[0]['value'])
            return reject(reason)
        if res[0]['address'] != self.wallet.pubkey_to_address(cr_dict['P']):
            reason = "Invalid podle pubkey: " + str(cr_dict['P'])
            return reject(reason)

        # authorisation of taker passed
        #Find utxos for the transaction now:
        utxos, cj_addr, change_addr = self.oid_to_order(offer, amount)
        if not utxos:
            #could not find funds
            return (False, )
        self.wallet.update_cache_index()
        # Construct data for auth request back to taker.
        # Need to choose an input utxo pubkey to sign with
        # (no longer using the coinjoin pubkey from 0.2.0)
        # Just choose the first utxo in self.utxos and retrieve key from wallet.
        auth_address = utxos[utxos.keys()[0]]['address']
        auth_key = self.wallet.get_key_from_addr(auth_address)
        auth_pub = btc.privtopub(auth_key)
        btc_sig = btc.ecdsa_sign(kphex, auth_key)
        return (True, utxos, auth_pub, cj_addr, change_addr, btc_sig)
def calculate_fidelity_bond_values(fidelity_bonds_info):
    if len(fidelity_bonds_info) == 0:
        return {}
    interest_rate = get_interest_rate()
    blocks = jm_single().bc_interface.get_current_block_height()
    mediantime = jm_single().bc_interface.get_best_block_median_time()

    validated_bonds = {}
    for bond_data in fidelity_bonds_info:
        try:
            fb_proof = FidelityBondProof.parse_and_verify_proof_msg(
                bond_data["counterparty"], bond_data["takernick"],
                bond_data["proof"])
        except ValueError:
            continue
        if fb_proof.utxo in validated_bonds:
            continue
        utxo_data = FidelityBondMixin.get_validated_timelocked_fidelity_bond_utxo(
            fb_proof.utxo, fb_proof.utxo_pub, fb_proof.locktime,
            fb_proof.cert_expiry, blocks)
        if utxo_data is not None:
            validated_bonds[fb_proof.utxo] = (fb_proof, utxo_data)

    fidelity_bond_values = {
        bond_data.maker_nick:
        FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
            utxo_data["value"],
            jm_single().bc_interface.get_block_time(
                jm_single().bc_interface.get_block_hash(blocks -
                                                        utxo_data["confirms"] +
                                                        1)),
            bond_data.locktime, mediantime, interest_rate)
        for bond_data, utxo_data in validated_bonds.values()
    }
    return fidelity_bond_values
Example #5
0
 def listunspent(self, minconf=None):
     listunspent_args = []
     if 'listunspent_args' in jm_single().config.options('POLICY'):
         listunspent_args = ast.literal_eval(jm_single().config.get(
             'POLICY', 'listunspent_args'))
     if minconf is not None:
         listunspent_args[0] = minconf
     return self._rpc('listunspent', listunspent_args)
 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 sync_unspent(self):
        st = time.time()
        # block height needs to be real time for addition to our utxos:
        current_blockheight = self.bci.get_current_block_height()
        if not current_blockheight:
            # this failure will shut down the application elsewhere, here
            # just give up:
            return
        wallet_name = self.get_wallet_name()
        self.reset_utxos()

        listunspent_args = []
        if 'listunspent_args' in jm_single().config.options('POLICY'):
            listunspent_args = ast.literal_eval(jm_single().config.get(
                'POLICY', 'listunspent_args'))

        unspent_list = self.bci.rpc('listunspent', listunspent_args)
        # filter on label, but note (a) in certain circumstances (in-
        # wallet transfer) it is possible for the utxo to be labeled
        # with the external label, and (b) the wallet will know if it
        # belongs or not anyway (is_known_addr):
        our_unspent_list = [
            x for x in unspent_list
            if (self.bci.is_address_labeled(x, wallet_name)
                or self.bci.is_address_labeled(x, self.EXTERNAL_WALLET_LABEL))
        ]
        for utxo in our_unspent_list:
            if not self.is_known_addr(utxo['address']):
                continue
            # The result of bitcoin core's listunspent RPC call does not have
            # a "height" field, only "confirmations".
            # But the result of scantxoutset used in no-history sync does
            # have "height".
            if "height" in utxo:
                height = utxo["height"]
            else:
                height = None
                # wallet's utxo database needs to store an absolute rather
                # than relative height measure:
                confs = int(utxo['confirmations'])
                if confs < 0:
                    jlog.warning("Utxo not added, has a conflict: " +
                                 str(utxo))
                    continue
                if confs >= 1:
                    height = current_blockheight - confs + 1
            self._add_unspent_txo(utxo, height)
        et = time.time()
        jlog.debug('bitcoind sync_unspent took ' + str((et - st)) + 'sec')
Example #8
0
 def inform_user_details(self):
     self.user_info("Your receiving address is: " + self.destination_addr)
     self.user_info("You will receive amount: " + str(
         self.receiving_amount) + " satoshis.")
     self.user_info("The sender also needs to know your ephemeral "
               "nickname: " + jm_single().nickname)
     self.user_info("This information has been stored in a file payjoin.txt;"
               " send it to your counterparty when you are ready.")
     with open("payjoin.txt", "w") as f:
         f.write("Payjoin transfer details:\n\n")
         f.write("Address: " + self.destination_addr + "\n")
         f.write("Amount (in sats): " + str(self.receiving_amount) + "\n")
         f.write("Receiver nick: " + jm_single().nickname + "\n")
     if not self.user_check("Enter 'y' to wait for the payment:"):
         sys.exit(EXIT_SUCCESS)
Example #9
0
    def push(self):
        jlog.debug('\n' + bintohex(self.latest_tx.serialize()))
        self.txid = bintohex(self.latest_tx.GetTxid()[::-1])
        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.CCoinAddress(self.my_cj_addr).to_scriptPubKey()
        else:
            notify_addr = self.my_cj_addr
        #add the callbacks *before* pushing to ensure triggering;
        #this does leave a dangling notify callback if the push fails, but
        #that doesn't cause problems.
        self.wallet_service.register_callbacks([self.unconfirm_callback],
                                               self.txid, "unconfirmed")
        self.wallet_service.register_callbacks([self.confirm_callback],
                                               self.txid, "confirmed")
        task.deferLater(
            reactor,
            float(jm_single().config.getint("TIMEOUT",
                                            "unconfirm_timeout_sec")),
            self.handle_unbroadcast_transaction, self.txid, self.latest_tx)

        tx_broadcast = jm_single().config.get('POLICY', 'tx_broadcast')
        nick_to_use = None
        if tx_broadcast == 'self':
            pushed = self.push_ourselves()
        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 = self.push_ourselves()
            else:
                nick_to_use = list(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 = self.push_ourselves()
        if not pushed:
            self.on_finished_callback(False, fromtx=True)
        else:
            if nick_to_use:
                return (nick_to_use, bintohex(self.latest_tx.serialize()))
Example #10
0
def estimate_tx_fee(ins, outs, txtype='p2pkh'):
    '''Returns an estimate of the number of satoshis required
    for a transaction with the given number of inputs and outputs,
    based on information from the blockchain interface.
    '''
    tx_estimated_bytes = btc.estimate_tx_size(ins, outs, txtype)
    log.debug("Estimated transaction size: " + str(tx_estimated_bytes))
    fee_per_kb = jm_single().bc_interface.estimate_fee_per_kb(
        jm_single().config.getint("POLICY", "tx_fees"))
    absurd_fee = jm_single().config.getint("POLICY", "absurd_fee_per_kb")
    if fee_per_kb > absurd_fee:
        #This error is considered critical; for safety reasons, shut down.
        raise ValueError("Estimated fee per kB greater than absurd value: " + \
                         str(absurd_fee) + ", quitting.")
    log.debug("got estimated tx bytes: " + str(tx_estimated_bytes))
    return int((tx_estimated_bytes * fee_per_kb) / Decimal(1000.0))
Example #11
0
    def on_sig(self, nick, sigb64):
        """Processes transaction signatures from counterparties.
        Returns True if all signatures received correctly, else
        returns False
        """
        if self.aborted:
            return False
        sig = base64.b64decode(sigb64).encode('hex')
        inserted_sig = False
        txhex = btc.serialize(self.latest_tx)

        # batch retrieval of utxo data
        utxo = {}
        ctr = 0
        for index, ins in enumerate(self.latest_tx['ins']):
            utxo_for_checking = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            #'deadbeef' markers mean our own input scripts are not ''
            if (ins['script'] != ''):
                continue
            utxo[ctr] = [index, utxo_for_checking]
            ctr += 1
        utxo_data = jm_single().bc_interface.query_utxo_set(
            [x[1] for x in utxo.values()])

        # insert signatures
        for i, u in utxo.iteritems():
            if utxo_data[i] is None:
                continue
            sig_good = btc.verify_tx_input(txhex, u[0], utxo_data[i]['script'],
                                           *btc.deserialize_script(sig))
            if sig_good:
                jlog.debug('found good sig at index=%d' % (u[0]))
                self.latest_tx['ins'][u[0]]['script'] = sig
                inserted_sig = True
                # check if maker has sent everything possible
                self.utxos[nick].remove(u[1])
                if len(self.utxos[nick]) == 0:
                    jlog.debug(('nick = {} sent all sigs, removing from '
                                'nonrespondant list').format(nick))
                    self.nonrespondants.remove(nick)
                break
        if not inserted_sig:
            jlog.debug('signature did not match anything in the tx')
            # TODO what if the signature doesnt match anything
            # nothing really to do except drop it, carry on and wonder why the
            # other guy sent a failed signature

        tx_signed = True
        for ins in self.latest_tx['ins']:
            if ins['script'] == '':
                tx_signed = False
        if not tx_signed:
            return False
        assert not len(self.nonrespondants)
        jlog.debug('all makers have sent their signatures')
        self.taker_info_callback("INFO", "Transaction is valid, signing..")
        jlog.debug("schedule item was: " +
                   str(self.schedule[self.schedule_index]))
        return self.self_sign_and_push()
Example #12
0
 def filter_orderbook(self, orderbook, sweep=False):
     if sweep:
         self.orderbook = orderbook  #offers choosing deferred to next step
     else:
         allowed_types = ["reloffer", "absoffer"] if jm_single().config.get(
             "POLICY",
             "segwit") == "false" else ["swreloffer", "swabsoffer"]
         self.orderbook, self.total_cj_fee = choose_orders(
             orderbook,
             self.cjamount,
             self.n_counterparties,
             self.order_chooser,
             self.ignored_makers,
             allowed_types=allowed_types)
         if self.orderbook is None:
             #Failure to get an orderbook means order selection failed
             #for some reason; no action is taken, we let the stallMonitor
             # + the finished callback decide whether to retry.
             return False
         if self.filter_orders_callback:
             accepted = self.filter_orders_callback(
                 [self.orderbook, self.total_cj_fee], self.cjamount)
             if accepted == "retry":
                 #Special condition if Taker is "determined to continue"
                 #(such as tumbler); even though these offers are rejected,
                 #we don't trigger the finished callback; see above note on
                 #`if self.orderbook is None`
                 return False
             if not accepted:
                 return False
     return True
Example #13
0
 def estimate_fee_per_kb(self, N):
     if not self.absurd_fees:
         return super(RegtestBitcoinCoreInterface,
                      self).estimate_fee_per_kb(N)
     else:
         return jm_single().config.getint("POLICY",
                                          "absurd_fee_per_kb") + 100
Example #14
0
    def __init__(self, wallet):
        # The two principal member variables
        # are the blockchaininterface instance,
        # which is currently global in JM but
        # could be more flexible in future, and
        # the JM wallet object.
        self.bci = jm_single().bc_interface
        # keep track of the quasi-real-time blockheight
        # (updated in main monitor loop)
        self.update_blockheight()
        self.wallet = wallet
        self.synced = False

        # Dicts of registered callbacks, by type
        # and then by txinfo, for events
        # on transactions.
        self.callbacks = {}
        self.callbacks["all"] = []
        self.callbacks["unconfirmed"] = {}
        self.callbacks["confirmed"] = {}

        self.restart_callback = None

        # transactions we are actively monitoring,
        # i.e. they are not new but we want to track:
        self.active_txids = []
        # to ensure transactions are only processed once:
        self.processed_txids = []

        self.set_autofreeze_warning_cb()
Example #15
0
    def check_for_reuse(self, added_utxos):
        """ (a) Check if addresses in new utxos are already in
        used address list, (b) record new addresses as now used
        (c) disable the new utxo if it returned as true for (a),
        and it passes the filter set in the configuration.
        """
        to_be_frozen = set()
        for au in added_utxos:
            if self.has_address_been_used(added_utxos[au]["address"]):
                to_be_frozen.add(au)
        # any utxos actually added must have their destination address
        # added to the used address list for this program run:
        for au in added_utxos.values():
            self.used_addresses.add(au["address"])

        # disable those that passed the first check, before the addition,
        # if they satisfy configured logic
        for utxo in to_be_frozen:
            freeze_threshold = jm_single().config.getint(
                "POLICY", "max_sats_freeze_reuse")
            if freeze_threshold == -1 or added_utxos[utxo][
                    "value"] <= freeze_threshold:
                # freezing of coins must be communicated to user:
                self.autofreeze_warning_cb(utxo)
                self.disable_utxo(*utxo)
Example #16
0
 def ensure_wallet_unlocked():
     wallet_info = jm_single().bc_interface.rpc('getwalletinfo', [])
     if 'unlocked_until' in wallet_info and wallet_info[
             'unlocked_until'] <= 0:
         while True:
             password = getpass('Enter passphrase to unlock wallet: ')
             if password == '':
                 raise RuntimeError('Aborting wallet unlock')
             try:
                 # TODO cleanly unlock wallet after use, not with arbitrary timeout
                 jm_single().bc_interface.rpc('walletpassphrase',
                                              [password, 10])
                 break
             except jm_single().JsonRpcError as exc:
                 if exc.code != -14:
                     raise exc
Example #17
0
 def handle_unbroadcast_transaction(self, txid, tx):
     """ The wallet service will handle dangling
     callbacks for transactions but we want to reattempt
     broadcast in case the cause of the problem is a
     counterparty who refused to broadcast it for us.
     """
     if not self.wallet_service.check_callback_called(
             self.txid, self.unconfirm_callback, "unconfirmed",
             "transaction with txid: " + str(self.txid) +
             " not broadcast."):
         # we now know the transaction was not pushed, so we reinstigate
         # the cancelledcallback with the same logic as explained
         # in Taker.push():
         self.wallet_service.register_callbacks([self.unconfirm_callback],
                                                txid, "unconfirmed")
         if jm_single().config.get('POLICY', 'tx_broadcast') == "not-self":
             warnmsg = ("You have chosen not to broadcast from your own "
                        "node. The transaction is NOT broadcast.")
             self.taker_info_callback("ABORT",
                                      warnmsg + "\nSee log for details.")
             # warning is arguably not correct but it will stand out more:
             jlog.warn(warnmsg)
             jlog.info(btc.human_readable_transaction(tx))
             return
         if not self.push_ourselves():
             jlog.error("Failed to broadcast transaction: ")
             jlog.info(btc.human_readable_transaction(tx))
Example #18
0
 def import_new_addresses(self, addr_list):
     # FIXME: same code as in taker.py
     bci = jm_single().bc_interface
     if not hasattr(bci, 'import_addresses'):
         return
     assert hasattr(bci, 'get_wallet_name')
     bci.import_addresses(addr_list, bci.get_wallet_name(self.wallet))
Example #19
0
 def __init__(self, fromaccount):
     super(BitcoinCoreWallet, self).__init__()
     if not isinstance(jm_single().bc_interface, BitcoinCoreInterface):
         raise RuntimeError('Bitcoin Core wallet can only be used when '
                            'blockchain interface is BitcoinCoreInterface')
     self.fromaccount = fromaccount
     self.max_mix_depth = 1
Example #20
0
 def try_to_create_my_orders(self):
     if not jm_single().bc_interface.wallet_synced:
         return
     self.offerlist = self.create_my_orders()
     self.sync_wait_loop.stop()
     if not self.offerlist:
         jlog.info("Failed to create offers, giving up.")
         sys.exit(0)
Example #21
0
 def filter_orderbook(self, orderbook, sweep=False):
     #If honesty filter is set, we immediately filter to only the prescribed
     #honest makers before continuing. In this case, the number of
     #counterparties should already match, and this has to be set by the
     #script instantiating the Taker.
     #Note: If one or more of the honest makers has dropped out in the meantime,
     #we will just have insufficient offers and it will fail in the usual way
     #for insufficient liquidity.
     if self.honest_only:
         orderbook = [
             o for o in orderbook if o['counterparty'] in self.honest_makers
         ]
     if sweep:
         self.orderbook = orderbook  #offers choosing deferred to next step
     else:
         if jm_single().config.get("POLICY", "segwit") == "false":
             allowed_types = ["reloffer", "absoffer"]
         elif jm_single().config.get("POLICY", "native") == "false":
             allowed_types = ["swreloffer", "swabsoffer"]
         else:
             allowed_types = ["sw0reloffer", "sw0absoffer"]
         self.orderbook, self.total_cj_fee = choose_orders(
             orderbook,
             self.cjamount,
             self.n_counterparties,
             self.order_chooser,
             self.ignored_makers,
             allowed_types=allowed_types,
             max_cj_fee=self.max_cj_fee)
         if self.orderbook is None:
             #Failure to get an orderbook means order selection failed
             #for some reason; no action is taken, we let the stallMonitor
             # + the finished callback decide whether to retry.
             return False
         if self.filter_orders_callback:
             accepted = self.filter_orders_callback(
                 [self.orderbook, self.total_cj_fee], self.cjamount)
             if accepted == "retry":
                 #Special condition if Taker is "determined to continue"
                 #(such as tumbler); even though these offers are rejected,
                 #we don't trigger the finished callback; see above note on
                 #`if self.orderbook is None`
                 return False
             if not accepted:
                 return False
     return True
Example #22
0
    def __init__(self, wallet):
        # The two principal member variables
        # are the blockchaininterface instance,
        # which is currently global in JM but
        # could be more flexible in future, and
        # the JM wallet object.
        self.bci = jm_single().bc_interface

        # main loop used to check for transactions, instantiated
        # after wallet is synced:
        self.monitor_loop = None
        self.wallet = wallet
        self.synced = False

        # used to flag RPC failure at construction of object:
        self.rpc_error = False

        # keep track of the quasi-real-time blockheight
        # (updated in main monitor loop)
        self.current_blockheight = None

        if self.bci is not None:
            if not self.update_blockheight():
                # this accounts for the unusual case
                # where the application started up with
                # a functioning blockchain interface, but
                # that bci is now failing when we are starting
                # the wallet service.
                jlog.error("Failure of RPC connection to Bitcoin Core in "
                           "wallet service startup. Application cannot "
                           "continue, shutting down.")
                self.rpc_error = ("Failure of RPC connection to Bitcoin "
                                  "Core in wallet service startup.")
                # no need to call stopService as it has not yet been started.
                stop_reactor()
        else:
            jlog.warning("No blockchain source available, " +
                         "wallet tools will not show correct balances.")

        # Dicts of registered callbacks, by type
        # and then by txinfo, for events
        # on transactions.
        self.callbacks = {
            "all": [],  # note: list, not dict
            "unconfirmed": {},
            "confirmed": {},
        }

        self.restart_callback = None

        # transactions we are actively monitoring,
        # i.e. they are not new but we want to track:
        self.active_txs = {}
        # to ensure transactions are only processed once:
        self.processed_txids = set()

        self.set_autofreeze_warning_cb()
    def sync_unspent(self, wallet):
        st = time.time()
        wallet_name = self.get_wallet_name(wallet)
        wallet.reset_utxos()

        listunspent_args = []
        if 'listunspent_args' in jm_single().config.options('POLICY'):
            listunspent_args = ast.literal_eval(jm_single().config.get(
                'POLICY', 'listunspent_args'))

        unspent_list = self.rpc('listunspent', listunspent_args)
        for u in unspent_list:
            if not wallet.is_known_addr(u['address']):
                continue
            self._add_unspent_utxo(wallet, u)
        et = time.time()
        log.debug('bitcoind sync_unspent took ' + str((et - st)) + 'sec')
        self.wallet_synced = True
    def _verify_ioauth_inputs(self, nick, utxo_list, auth_pub):
        utxo_data = jm_single().bc_interface.query_utxo_set(utxo_list)
        if None in utxo_data:
            raise IoauthInputVerificationError([
                "ERROR: outputs unconfirmed or already spent. utxo_data="
                f"{pprint.pformat(utxo_data)}",
                "Disregarding this counterparty."
            ])

        # Complete maker authorization:
        # Extract the address fields from the utxos
        # Construct the Bitcoin address for the auth_pub field
        # Ensure that at least one address from utxos corresponds.
        for inp in utxo_data:
            try:
                if self.wallet_service.pubkey_has_script(
                        auth_pub, inp['script']):
                    break
            except EngineError as e:
                pass
        else:
            raise IoauthInputVerificationError([
                f"ERROR maker's ({nick}) authorising pubkey is not included "
                "in the transaction!"
            ])

        total_input = sum([d['value'] for d in utxo_data])
        real_cjfee = calc_cj_fee(self.orderbook[nick]['ordertype'],
                                 self.orderbook[nick]['cjfee'], self.cjamount)
        change_amount = (total_input - self.cjamount -
                         self.orderbook[nick]['txfee'] + real_cjfee)

        # certain malicious and/or incompetent liquidity providers send
        # inputs totalling less than the coinjoin amount! this leads to
        # a change output of zero satoshis; this counterparty must be removed.
        if change_amount < jm_single().DUST_THRESHOLD:
            raise IoauthInputVerificationError([
                f"ERROR counterparty requires sub-dust change. nick={nick} "
                f"totalin={total_input:d} cjamount={self.cjamount:d} "
                f"change={change_amount:d}",
                f"Invalid change, too small, nick={nick}"
            ])
        return self._MakerTxData(nick, utxo_data, total_input, change_amount,
                                 real_cjfee)
Example #25
0
 def broadcast_fallback(self):
     self.user_info("Broadcasting non-coinjoin fallback transaction.")
     txid = btc.txhash(self.fallback_tx)
     success = jm_single().bc_interface.pushtx(self.fallback_tx)
     if not success:
         self.user_info("ERROR: the fallback transaction did not broadcast. "
                   "The payment has NOT been made.")
     else:
         self.user_info("Payment received successfully, but it was NOT a coinjoin.")
         self.user_info("Txid: " + txid)
     reactor.stop()
Example #26
0
 def push(self):
     tx = btc.serialize(self.latest_tx)
     jlog.debug('\n' + tx)
     self.txid = btc.txhash(tx)
     jlog.debug('txid = ' + self.txid)
     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:
         jm_single().bc_interface.add_tx_notify(self.latest_tx,
                                                self.unconfirm_callback,
                                                self.confirm_callback,
                                                self.my_cj_addr)
         if nick_to_use:
             return (nick_to_use, tx)
    def __init__(self,
                 wallet_service,
                 acceptance_callback=None,
                 info_callback=None):
        """
        Class to manage processing of SNICKER proposals and
        co-signs and broadcasts in case the application level
        configuration permits.

        `acceptance_callback`, if specified, must have arguments
        and return type as for the default_acceptance_callback
        in this class.
        """

        # This is a Joinmarket WalletService object.
        self.wallet_service = wallet_service

        # The simplest filter on accepting SNICKER joins:
        # that they pay a minimum of this value in satoshis,
        # which can be negative (e.g. to account for fees).
        self.income_threshold = jm_single().config.getint(
            "SNICKER", "lowest_net_gain")

        # The acceptance callback which defines if we accept
        # a valid proposal and sign it, or not.
        if acceptance_callback is None:
            self.acceptance_callback = self.default_acceptance_callback
        else:
            self.acceptance_callback = acceptance_callback

        # callback for information messages to UI
        if not info_callback:
            self.info_callback = self.default_info_callback
        else:
            self.info_callback = info_callback
        # A list of currently viable key candidates; these must
        # all be (pub)keys for which the privkey is accessible,
        # i.e. they must be in-wallet keys.
        # This list will be continuously updated by polling the
        # wallet.
        self.candidate_keys = []

        # A list of already processed proposals
        self.processed_proposals = []

        # maintain a list of all successfully broadcast
        # SNICKER transactions in the current run.
        self.successful_txs = []

        # the main monitoring loop that checks for proposals:
        self.proposal_poll_loop = None
Example #28
0
 def unconfirm_callback(self, txd, txid):
     if not self.tx_match(txd):
         return False
     jlog.info("Transaction seen on network, waiting for confirmation")
     #To allow client to mark transaction as "done" (e.g. by persisting state)
     self.on_finished_callback(True, fromtx="unconfirmed")
     self.waiting_for_conf = True
     confirm_timeout_sec = float(jm_single().config.get(
         "TIMEOUT", "confirm_timeout_hours")) * 3600
     task.deferLater(reactor, confirm_timeout_sec,
                     self.wallet_service.check_callback_called,
                     txid, self.confirm_callback, "confirmed",
                     "transaction with txid " + str(txid) + " not confirmed.")
     return True
Example #29
0
 def get_utxos_by_mixdepth(self):
     unspent_list = jm_single().bc_interface.rpc('listunspent', [])
     result = {0: {}}
     for u in unspent_list:
         if not u['spendable']:
             continue
         if self.fromaccount and (('account' not in u)
                                  or u['account'] != self.fromaccount):
             continue
         result[0][u['txid'] + ':' + str(u['vout'])] = {
             'address': u['address'],
             'value': int(Decimal(str(u['amount'])) * Decimal('1e8'))
         }
     return result
Example #30
0
 def try_to_create_my_orders(self):
     """Because wallet syncing is not synchronous(!),
     we cannot calculate our offers until we know the wallet
     contents, so poll until BlockchainInterface.wallet_synced
     is flagged as True. TODO: Use a deferred, probably.
     Note that create_my_orders() is defined by subclasses.
     """
     if not jm_single().bc_interface.wallet_synced:
         return
     self.offerlist = self.create_my_orders()
     self.sync_wait_loop.stop()
     if not self.offerlist:
         jlog.info("Failed to create offers, giving up.")
         sys.exit(0)