Exemplo n.º 1
0
    def postRequest(self,
                    body,
                    server,
                    success_callback,
                    url=None,
                    params=None,
                    headers=None):
        """ Pass body of post request as string, will be encoded here.
	"""
        agent, destination_url = self.getAgentDestination(server,
                                                          params=params)
        if url:
            destination_url = destination_url + url
        body = BytesProducer(body.encode("utf-8"))
        headers = self.getDefaultHeaders() if not headers else headers
        d = agent.request(b"POST", destination_url, headers, bodyProducer=body)
        d.addCallback(success_callback, server)

        # 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, ConnectionLost)
            log.msg(failure.value)
            self.callRemote(BIP78SenderReceiveError,
                            errormsg="failure to connect",
                            errorcode=10000)

        d.addErrback(noResponse)
 def test_listutxos_and_freeze(self):
     self.daemon.auth_disabled = True
     agent = get_nontor_agent()
     pre_addr = self.get_route_root()
     pre_addr += "/wallet/"
     pre_addr += self.daemon.wallet_name
     addr = pre_addr + "/utxos"
     addr = addr.encode()
     yield self.do_request(agent, b"GET", addr, None,
                           self.process_listutxos_response)
     # Test of freezing is currently very primitive: we only
     # check that the action was accepted; a full test would
     # involve checking that spending the coin works or doesn't
     # work, as expected.
     addr = pre_addr + "/freeze"
     addr = addr.encode()
     utxostr = self.mixdepth1_utxos[0]["utxo"]
     body = BytesProducer(
         json.dumps({
             "utxo-string": utxostr,
             "freeze": True
         }).encode())
     yield self.do_request(agent, b"POST", addr, body,
                           self.process_utxo_freeze)
     body = BytesProducer(
         json.dumps({
             "utxo-string": utxostr,
             "freeze": False
         }).encode())
     yield self.do_request(agent, b"POST", addr, body,
                           self.process_utxo_freeze)
 def test_direct_send_and_display_wallet(self):
     """ First spend a coin, then check the balance
     via the display wallet output.
     """
     self.daemon.auth_disabled = True
     agent = get_nontor_agent()
     addr = self.get_route_root()
     addr += "/wallet/"
     addr += self.daemon.wallet_name
     addr += "/taker/direct-send"
     addr = addr.encode()
     body = BytesProducer(
         json.dumps({
             "mixdepth": "1",
             "amount_sats": "100000000",
             "destination": "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br"
         }).encode())
     yield self.do_request(agent, b"POST", addr, body,
                           self.process_direct_send_response)
     # before querying the wallet display, set a label to check:
     labeladdr = self.daemon.services["wallet"].get_addr(0, 0, 0)
     self.daemon.services["wallet"].set_address_label(
         labeladdr, "test-wallet-rpc-label")
     # force the wallet service txmonitor to wake up, to see the new
     # tx before querying /display:
     self.daemon.services["wallet"].transaction_monitor()
     addr = self.get_route_root()
     addr += "/wallet/"
     addr += self.daemon.wallet_name
     addr += "/display"
     addr = addr.encode()
     yield self.do_request(agent, b"GET", addr, None,
                           self.process_wallet_display_response)
Exemplo n.º 4
0
    def do_test_payment(self, wc1, wc2, amt=1.1):
        wallet_structures = [self.wallet_structure] * 2
        wallet_cls = (wc1, wc2)
        self.wallet_services = []
        self.wallet_services.append(make_wallets_to_list(make_wallets(
            1, wallet_structures=[wallet_structures[0]],
            mean_amt=self.mean_amt, wallet_cls=wallet_cls[0]))[0])
        self.wallet_services.append(make_wallets_to_list(make_wallets(
                1, wallet_structures=[wallet_structures[1]],
                mean_amt=self.mean_amt, wallet_cls=wallet_cls[1]))[0])
        jm_single().bc_interface.tickchain()
        sync_wallets(self.wallet_services)

        # For accounting purposes, record the balances
        # at the start.
        self.rsb = getbals(self.wallet_services[0], 0)
        self.ssb = getbals(self.wallet_services[1], 0)

        self.cj_amount = int(amt * 10**8)
        def cbStopListening():
            return self.port.stopListening()
        b78rm = JMBIP78ReceiverManager(self.wallet_services[0], 0,
                                       self.cj_amount, 47083)
        resource = DummyBIP78ReceiverResource(jmprint, cbStopListening, b78rm)
        self.site = Site(resource)
        self.site.displayTracebacks = False
        # NB The connectivity aspects of the onion-based BIP78 setup
        # are time heavy. This server is TCP only.
        self.port = reactor.listenTCP(47083, self.site)
        self.addCleanup(cbStopListening)

        # setup of spender
        bip78_btc_amount = amount_to_btc(amount_to_sat(self.cj_amount))
        bip78_uri = encode_bip21_uri(str(b78rm.receiving_address),
                                {"amount": bip78_btc_amount,
                                 "pj": b"http://127.0.0.1:47083"},
                                safe=":/")
        self.manager = parse_payjoin_setup(bip78_uri, self.wallet_services[1], 0)
        self.manager.mode = "testing"
        success, msg = make_payment_psbt(self.manager)
        assert success, msg
        params = make_payjoin_request_params(self.manager)
        # avoiding backend daemon (testing only jmclient code here),
        # we send the http request manually:
        serv = b"http://127.0.0.1:47083"
        agent = get_nontor_agent()
        body = BytesProducer(self.manager.initial_psbt.to_base64().encode("utf-8"))
        url_parts = list(wrapped_urlparse(serv))
        url_parts[4] = urlencode(params).encode("utf-8")
        destination_url = urlparse.urlunparse(url_parts)
        d = agent.request(b"POST", destination_url,
                          Headers({"Content-Type": ["text/plain"]}),
                          bodyProducer=body)
        d.addCallback(bip78_receiver_response, self.manager)
        return d
Exemplo n.º 5
0
    def postRequest(self, body, server, success_callback, headers=None):
        """ Pass body of post request as string, will be encoded here.
	"""
        agent, destination_url = self.getAgentDestination(server)
        body = BytesProducer(body.encode("utf-8"))
        d = agent.request(b"POST",
                          destination_url,
                          Headers({}),
                          bodyProducer=body)
        d.addCallback(success_callback, server)

        # 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, ConnectionLost)
            log.msg(failure.value)
            self.callRemote(SNICKERProposalsServerResponse,
                            response="failure to connect",
                            server=server)

        d.addErrback(noResponse)
    def test_maker_start_stop(self):
        """ Tests that we can start the maker service.
        As for the taker coinjoin test, this is currently
        a simple/artificial test, only checking return status
        codes and state updates, but not checking that an actual
        backend maker service is started.
        """
        self.daemon.auth_disabled = True
        agent = get_nontor_agent()
        addr_start = self.get_route_root()
        addr_start += "/wallet/"
        addr_start += self.daemon.wallet_name
        addr = addr_start + "/maker/start"
        addr = addr.encode()
        body = BytesProducer(
            json.dumps({
                "txfee": "0",
                "cjfee_a": "1000",
                "cjfee_r": "0.0002",
                "ordertype": "reloffer",
                "minsize": "1000000"
            }).encode())
        yield self.do_request(agent, b"POST", addr, body,
                              self.process_maker_start)

        # For the second phase, since we are not currently processing
        # via actual backend connections, we need to mock the client
        # protocol instance that requests shutdown of all message channels:
        class DummyMakerClientProto(object):
            def request_mc_shutdown(self):
                jlog.info("Message channel shutdown request registered.")
        self.daemon.services["maker"].clientfactory.proto_client = \
            DummyMakerClientProto()
        addr = addr_start + "/maker/stop"
        addr = addr.encode()
        yield self.do_request(agent, b"GET", addr, None,
                              self.process_maker_stop)
 def test_do_coinjoin(self):
     """ This slightly weird test curently only
     tests *requesting* a coinjoin; because there are
     no makers running in the test suite, the Taker will
     give up early due to the empty orderbook, but that is
     OK since this API call only makes the request.
     """
     self.daemon.auth_disabled = True
     # in normal operations, the RPC call will trigger
     # the jmclient to connect to an *existing* daemon
     # that was created on startup, but here, that daemon
     # does not yet exist, so we will get 503 Backend Not Ready,
     # unless we manually create it:
     scon, ccon = start_reactor(
         jm_single().config.get("DAEMON", "daemon_host"),
         jm_single().config.getint("DAEMON", "daemon_port"),
         None,
         daemon=True,
         rs=False)
     # must be manually set:
     self.scon = scon
     agent = get_nontor_agent()
     addr = self.get_route_root()
     addr += "/wallet/"
     addr += self.daemon.wallet_name
     addr += "/taker/coinjoin"
     addr = addr.encode()
     body = BytesProducer(
         json.dumps({
             "mixdepth": "1",
             "amount_sats": "22000000",
             "counterparties": "2",
             "destination": "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br"
         }).encode())
     yield self.do_request(agent, b"POST", addr, body,
                           self.process_do_coinjoin_response)
Exemplo n.º 8
0
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
    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)
        log.error(failure.value)
        fallback_nonpayjoin_broadcast(manager, b"connection refused")

    d.addErrback(noResponse)
    return (True, None)
    def test_create_list_lock_unlock(self):
        """ A batch of tests in sequence here,
            so we can track the state of a created
            wallet and check it is what is expected.
            We test create first, so we have a wallet.

        1. create a wallet and have it persisted
           to disk in ./wallets, and get a token.
        2. lock that wallet.
        3. create a second wallet as above.
        4. list wallets and check they contain the new
           wallet.
        5. lock the existing wallet service, using the token.
        6. Unlock the original wallet with /unlock, get a token.
        7. Unlock the second wallet with /unlock, get a token.
        """
        # before starting, we have to shut down the existing
        # wallet service (usually this would be `lock`):
        self.daemon.services["wallet"] = None
        self.daemon.stopService()
        self.daemon.auth_disabled = False

        wfn1 = self.get_wallet_file_name(1)
        wfn2 = self.get_wallet_file_name(2)
        self.wfnames = [wfn1, wfn2]
        agent = get_nontor_agent()
        root = self.get_route_root()

        # 1. Create first
        addr = root + "/wallet/create"
        addr = addr.encode()
        body = BytesProducer(
            json.dumps({
                "walletname": wfn1,
                "password": "******",
                "wallettype": "sw-fb"
            }).encode())
        yield self.do_request(agent, b"POST", addr, body,
                              self.process_create_wallet_response)

        # 1a. Session request with valid token; should succeed
        yield self.do_session_request(agent,
                                      root,
                                      self.authorized_session_request_handler,
                                      token=self.jwt_token)
        # 1b. Session request without token, even though one is active; should succeed
        yield self.do_session_request(agent, root,
                                      self.authorized_session_request_handler)

        # 2. now *lock*
        addr = root + "/wallet/" + wfn1 + "/lock"
        addr = addr.encode()
        jlog.info("Using address: {}".format(addr))
        yield self.do_request(agent,
                              b"GET",
                              addr,
                              None,
                              self.process_lock_response,
                              token=self.jwt_token)

        # 2a. Session request with now invalid token; should fail
        yield self.do_session_request(
            agent,
            root,
            self.unauthorized_session_request_handler,
            token=self.jwt_token)
        # 2b. Session request without token, should still succeed.
        yield self.do_session_request(agent, root,
                                      self.authorized_session_request_handler)

        # 3. Create this secondary wallet (so we can test re-unlock)
        addr = root + "/wallet/create"
        addr = addr.encode()
        body = BytesProducer(
            json.dumps({
                "walletname": wfn2,
                "password": "******",
                "wallettype": "sw"
            }).encode())
        yield self.do_request(agent, b"POST", addr, body,
                              self.process_create_wallet_response)

        # 4. List wallets
        addr = root + "/wallet/all"
        addr = addr.encode()
        # does not require a token, though we just got one.
        yield self.do_request(agent, b"GET", addr, None,
                              self.process_list_wallets_response)

        # 5. now *lock* the active.
        addr = root + "/wallet/" + wfn2 + "/lock"
        addr = addr.encode()
        jlog.info("Using address: {}".format(addr))
        yield self.do_request(agent,
                              b"GET",
                              addr,
                              None,
                              self.process_lock_response,
                              token=self.jwt_token)
        # wallet service should now be stopped.
        # 6. Unlock the original wallet
        addr = root + "/wallet/" + wfn1 + "/unlock"
        addr = addr.encode()
        body = BytesProducer(json.dumps({"password": "******"}).encode())
        yield self.do_request(agent, b"POST", addr, body,
                              self.process_unlock_response)

        # 7. Unlock the second wallet again
        addr = root + "/wallet/" + wfn2 + "/unlock"
        addr = addr.encode()
        body = BytesProducer(json.dumps({"password": "******"}).encode())
        yield self.do_request(agent, b"POST", addr, body,
                              self.process_unlock_response)
def start_test_taker(wallet_service, i, num_ygs):
    # this rpc manager has auth disabled,
    # and the wallet_service is set manually,
    # so no unlock etc.
    mgr = TWalletRPCManager()
    mgr.daemon.services["wallet"] = wallet_service
    # because we are manually setting the wallet_service
    # of the JMWalletDaemon instance, we do not follow the
    # usual flow of `initialize_wallet_service`, we do not set
    # the auth token or start the websocket; so we must manually
    # sync the wallet, including bypassing any restart callback:
    def dummy_restart_callback(msg):
        log.warn("Ignoring rescan request from backend wallet service: " + msg)
    mgr.daemon.services["wallet"].add_restart_callback(dummy_restart_callback)
    mgr.daemon.wallet_name = wallet_name
    mgr.daemon.services["wallet"].startService()
    def get_client_factory():
        clientfactory = RegtestJMClientProtocolFactory(mgr.daemon.taker,
                                                       proto_type="TAKER")
        clientfactory.i = i
        clientfactory.set_directory_nodes(directory_node_indices)
        return clientfactory

    mgr.daemon.get_client_factory = get_client_factory
    # before preparing the RPC call to the wallet daemon,
    # we decide a coinjoin destination, counterparty count and amount.
    # Choosing a destination in the wallet is a bit easier because
    # we can query the mixdepth balance at the end.
    coinjoin_destination = mgr.daemon.services["wallet"].get_internal_addr(4)
    cj_amount = 22000000
    def n_cps_from_n_ygs(n):
        if n > 4:
            return n - 2
        if n > 2:
            return 2
        assert False, "Need at least 3 yield generators to test"
    n_cps = n_cps_from_n_ygs(num_ygs)
    # once the taker is finished we sanity check before
    # shutting down:
    def dummy_taker_finished(res, fromtx=False,
                               waittime=0.0, txdetails=None):
        jmprint("Taker is finished")
        # check that the funds have arrived.
        mbal = mgr.daemon.services["wallet"].get_balance_by_mixdepth()[4]
        assert mbal == cj_amount
        jmprint("Funds: {} sats successfully arrived into mixdepth 4.".format(cj_amount))
        stop_reactor()
    mgr.daemon.taker_finished = dummy_taker_finished
    mgr.start()
    agent = get_nontor_agent()
    addr = mgr.get_route_root()
    addr += "/wallet/"
    addr += mgr.daemon.wallet_name
    addr += "/taker/coinjoin"
    addr = addr.encode()
    body = BytesProducer(json.dumps({"mixdepth": "1",
        "amount_sats": cj_amount,
        "counterparties": str(n_cps),
        "destination": coinjoin_destination}).encode())
    yield mgr.do_request(agent, b"POST", addr, body,
                          process_coinjoin_response)