def web_agent(self, pool=None, socks_endpoint=None): """ :param socks_endpoint: If ``None`` (the default), a suitable SOCKS port is chosen from our config (or added). If supplied, should be a Deferred which fires an IStreamClientEndpoint (e.g. the return-value from :meth:`txtorcon.TorConfig.socks_endpoint`) or an immediate IStreamClientEndpoint You probably don't need to mess with this. :param pool: passed on to the Agent (as ``pool=``) """ # local import since not all platforms have this from txtorcon import web if socks_endpoint is None: socks_endpoint = _create_socks_endpoint(self._reactor, self._protocol) if not isinstance(socks_endpoint, Deferred): if not IStreamClientEndpoint.providedBy(socks_endpoint): raise ValueError( "'socks_endpoint' should be a Deferred or an IStreamClient" "Endpoint (got '{}')".format(type(socks_endpoint)) ) return web.tor_agent( self._reactor, socks_endpoint, pool=pool, )
def web_agent(self, pool=None, socks_endpoint=None): """ :param socks_endpoint: If ``None`` (the default), a suitable SOCKS port is chosen from our config (or added). If supplied, should be a Deferred which fires an IStreamClientEndpoint (e.g. the return-value from :meth:`txtorcon.TorConfig.socks_endpoint`) or an immediate IStreamClientEndpoint You probably don't need to mess with this. :param pool: passed on to the Agent (as ``pool=``) """ # local import since not all platforms have this from txtorcon import web if socks_endpoint is None: socks_endpoint = _create_socks_endpoint(self._reactor, self._protocol) if not isinstance(socks_endpoint, Deferred): if not IStreamClientEndpoint.providedBy(socks_endpoint): raise ValueError( "'socks_endpoint' should be a Deferred or an IStreamClient" "Endpoint (got '{}')".format(type(socks_endpoint))) return web.tor_agent( self._reactor, socks_endpoint, pool=pool, )
def _get_agent(self, request, timeout): bind_address = request.meta.get('bindaddress') or self._bindAddress proxy = os.environ.get("SOCKS_PROXY", request.meta.get('proxy')) _proxy_protocol, _proxy_hostport, proxy_host, proxy_port, _proxy_params = _parse( proxy) proxy_endpoint = TCP4ClientEndpoint(reactor, proxy_host, proxy_port, timeout=timeout, bindAddress=bind_address) agent = txtorcon_web.tor_agent(reactor, socks_endpoint=proxy_endpoint) return agent
def web_agent(self, reactor, socks_endpoint, pool=None): """ :param socks_endpoint: create one with :meth:`txtorcon.TorConfig.create_socks_endpoint`. Can be a Deferred. :param pool: passed on to the Agent (as ``pool=``) """ # local import because there isn't Agent stuff on some # platforms we support, so this will only error if you try # this on the wrong platform (pypy [??] and old-twisted) from txtorcon import web return web.tor_agent( reactor, socks_endpoint, circuit=self, pool=pool, )
def test_agent_with_circuit(self): reactor = Mock() circuit = Mock() socks_ep = Mock() proto = Mock() gold = object() proto.request = Mock(return_value=defer.succeed(gold)) def getConnection(key, endpoint): self.assertTrue(isinstance(endpoint, TorCircuitEndpoint)) target = endpoint._target_endpoint self.assertTrue(target._tls) self.assertEqual(target._host, u'meejah.ca') self.assertEqual(target._port, 443) return defer.succeed(proto) pool = Mock() pool.getConnection = getConnection agent = yield tor_agent(reactor, socks_ep, circuit=circuit, pool=pool) # apart from the getConnection asserts... res = yield agent.request(b'GET', b'https://meejah.ca') self.assertIs(res, gold)
def send_payjoin(manager, accept_callback=None, info_callback=None, tls_whitelist=None): """ Given a JMPayjoinManager object `manager`, initialised with the payment request data from the server, use its wallet_service to construct a payment transaction, with coins sourced from mixdepth `mixdepth`, then wait for the server response, parse the PSBT, perform checks and complete sign. The info and accept callbacks are to ask the user to confirm the creation of the original payment transaction (None defaults to terminal/CLI processing), and are as defined in `taker_utils.direct_send`. If `tls_whitelist` is a list of bytestrings, they are treated as hostnames for which tls certificate verification is ignored. Obviously this is ONLY for testing. Returns: (True, None) in case of payment setup successful (response will be delivered asynchronously) - the `manager` object can be inspected for more detail. (False, errormsg) in case of failure. """ # wallet should already be synced before calling here; # we can create a standard payment, but have it returned as a PSBT. assert isinstance(manager, JMPayjoinManager) assert manager.wallet_service.synced payment_psbt = direct_send(manager.wallet_service, manager.amount, manager.mixdepth, str(manager.destination), accept_callback=accept_callback, info_callback=info_callback, with_final_psbt=True) if not payment_psbt: return (False, "could not create non-payjoin payment") manager.set_payment_tx_and_psbt(payment_psbt) # add delayed call to broadcast this after 1 minute reactor.callLater(60, fallback_nonpayjoin_broadcast, manager, b"timeout") # Now we send the request to the server, with the encoded # payment PSBT # First we create a twisted web Agent object: # TODO genericize/move out/use library function: def is_hs_uri(s): x = urlparse.urlparse(s) if x.hostname.endswith(".onion"): return (x.scheme, x.hostname, x.port) return False tor_url_data = is_hs_uri(manager.server) if tor_url_data: # note the return value is currently unused here socks5_host = jm_single().config.get("PAYJOIN", "onion_socks5_host") socks5_port = int(jm_single().config.get("PAYJOIN", "onion_socks5_port")) # note: SSL not supported at the moment: torEndpoint = TCP4ClientEndpoint(reactor, socks5_host, socks5_port) agent = tor_agent(reactor, torEndpoint) else: if not tls_whitelist: agent = Agent(reactor) else: agent = Agent( reactor, contextFactory=WhitelistContextFactory(tls_whitelist)) body = BytesProducer(payment_psbt.to_base64().encode("utf-8")) #Set the query parameters for the request: # construct the URI from the given parameters pj_version = jm_single().config.getint("PAYJOIN", "payjoin_version") params = {"v": pj_version} disable_output_substitution = "false" if manager.disable_output_substitution: disable_output_substitution = "true" else: if jm_single().config.getint("PAYJOIN", "disable_output_substitution") == 1: disable_output_substitution = "true" params["disableoutputsubstitution"] = disable_output_substitution # to determine the additionalfeeoutputindex in cases where we have # change and we are allowing fee bump, we examine the initial tx: if manager.change_out: params["additionalfeeoutputindex"] = manager.change_out_index params["maxadditionalfeecontribution"] = \ get_max_additional_fee_contribution(manager) min_fee_rate = float(jm_single().config.get("PAYJOIN", "min_fee_rate")) params["minfeerate"] = min_fee_rate destination_url = manager.server.encode("utf-8") url_parts = list(urlparse.urlparse(destination_url)) url_parts[4] = urlencode(params).encode("utf-8") destination_url = urlparse.urlunparse(url_parts) # TODO what to use as user agent? d = agent.request(b"POST", destination_url, Headers({ "User-Agent": ["Twisted Web Client Example"], "Content-Type": ["text/plain"] }), bodyProducer=body) d.addCallback(receive_payjoin_proposal_from_server, manager) # note that the errback (here "noResponse") is *not* triggered # by a server rejection (which is accompanied by a non-200 # status code returned), but by failure to communicate. def noResponse(failure): failure.trap(ResponseFailed, ConnectionRefusedError, HostUnreachableError) log.error(failure.value) fallback_nonpayjoin_broadcast(manager, b"connection refused") d.addErrback(noResponse) return (True, None)
def get_tor_agent(socks5_host, socks5_port): torEndpoint = TCP4ClientEndpoint(reactor, socks5_host, socks5_port) return tor_agent(reactor, torEndpoint)
def test_agent_no_socks(self): reactor = Mock() with self.assertRaises(Exception) as ctx: yield tor_agent(reactor, None) self.assertTrue('Must provide socks_endpoint' in str(ctx.exception))
def test_agent(self): reactor = Mock() socks_ep = Mock() yield tor_agent(reactor, socks_ep)