Browse Source

Allow taker peers to not serve onions + bugfixes.

In the previous commit, all peers served an onion.

After this commit, taker client instances will automatically
send a config var to the jmdaemon backend that instructs
the OnionMessageChannel instance to not start an onion service,
and the handshake messages sent by these peers replace the
onion location with a placeholder string NOT-SERVING-ONION.
Directories and maker peers will not therefore to connect outbound
to them, but privmsging still happens p2p with connections from
takers to makers after the directory has communicated their
reachable .onion addresses.
This change reduces the configuration requirement for takers and
is better for their privacy and security (without sacrificing
the gain we get from having p2p connections).
The above comments re: takers also apply to ob-watcher bots.

This commit also fixes a large number of minor bugs and errors in
documentation, as well as many Python cleanups after review from
@PulpCattel. A few concrete items are:
It fixes the ob-watcher functionality to work with the new subclass
of MessageChannel (OnionMessageChannel).
It corrects the on_nick_leave trigger to make dynamic nick switching
between MessageChannels (as implemented in MessageChannelCollection)
work correctly.
It corrects the order of events in the add_peer workflow to ensure that
a handshake can always be sent so that the activation of the connection
always works.
It sets a default messaging config with onion, 2 active IRC servers and
one inactive IRC server. The onion config has 2 signet directory nodes,
so this will change to mainnet after the PR is merged to master.
master
Adam Gibson 4 years ago
parent
commit
830ac22934
No known key found for this signature in database
GPG Key ID: 141001A1AF77F20B
  1. 88
      docs/onion-message-channels.md
  2. 4
      jmbase/jmbase/commands.py
  3. 6
      jmbase/test/test_commands.py
  4. 13
      jmclient/jmclient/client_protocol.py
  5. 138
      jmclient/jmclient/configure.py
  6. 5
      jmclient/jmclient/wallet_rpc.py
  7. 4
      jmclient/test/test_client_protocol.py
  8. 18
      jmdaemon/jmdaemon/daemon_protocol.py
  9. 2
      jmdaemon/jmdaemon/message_channel.py
  10. 551
      jmdaemon/jmdaemon/onionmc.py
  11. 6
      jmdaemon/test/test_daemon_protocol.py
  12. 66
      scripts/obwatch/ob-watcher.py
  13. 85
      test/e2e-coinjoin-test.py
  14. 2
      test/regtest_joinmarket.cfg

88
docs/onion-message-channels.md

@ -14,7 +14,7 @@
This is a new way for Joinmarket bots to communicate, namely by serving and connecting to Tor onion services. This does not This is a new way for Joinmarket bots to communicate, namely by serving and connecting to Tor onion services. This does not
introduce any new requirements to your Joinmarket installation, technically, because the use of Payjoin already required the need introduce any new requirements to your Joinmarket installation, technically, because the use of Payjoin already required the need
to service such onion services, and connecting to IRC used a SOCKS5 proxy (by default, and used by almost all users) over Tor to to run such onion services, and connecting to IRC used a SOCKS5 proxy (used by almost all users) over Tor to
a remote onion service. a remote onion service.
The purpose of this new type of message channel is as follows: The purpose of this new type of message channel is as follows:
@ -25,23 +25,57 @@ albeit it was and remains E2E encrypted data, in either case)
* the above can lead to better scalability at large numbers * the above can lead to better scalability at large numbers
* a substantial increase in the speed of transaction negotiation; this is mostly related to the throttling of high bursts of traffic on IRC * a substantial increase in the speed of transaction negotiation; this is mostly related to the throttling of high bursts of traffic on IRC
The configuration for a user is simple; in their `joinmarket.cfg` they will add a messaging section like this: The configuration for a user is simple; in their `joinmarket.cfg` they will get a messaging section like this, if they start from scratch:
``` ```
[MESSAGING:onion1] [MESSAGING:onion]
# onion based message channels must have the exact type 'onion'
# (while the section name above can be MESSAGING:whatever), and there must
# be only ONE such message channel configured (note the directory servers
# can be multiple, below):
type = onion type = onion
onion_serving_port = 8082
socks5_host = localhost
socks5_port = 9050
# the tor control configuration.
# for most people running the tor daemon
# on Linux, no changes are required here:
tor_control_host = localhost
# or, to use a UNIX socket
# tor_control_host = unix:/var/run/tor/control
tor_control_port = 9051
# the host/port actually serving the hidden service
# (note the *virtual port*, that the client uses,
# is hardcoded to 80):
onion_serving_host = 127.0.0.1
onion_serving_port = 8080
# directory node configuration
#
# This is mandatory for directory nodes (who must also set their
# own *.onion:port as the only directory in directory_nodes, below),
# but NOT TO BE USED by non-directory nodes (which is you, unless
# you know otherwise!), as it will greatly degrade your privacy.
# (note the default is no value, don't replace it with "").
hidden_service_dir =
#
# This is a comma separated list (comma can be omitted if only one item). # This is a comma separated list (comma can be omitted if only one item).
# Each item has format host:port # Each item has format host:port ; both are required, though port will
# be 80 if created in this code.
directory_nodes = rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80 directory_nodes = rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80
# This setting is ONLY for developer regtest setups,
# running multiple bots at once. Don't alter it otherwise
regtest_count = 0,0
``` ```
Here, I have deliberately omitted the several other settings in this section which will almost always be fine as default; All of these can be left as default for most users, except the field `directory_nodes`.
see `jmclient/jmclient/configure.py` for what those defaults are, and the extensive comments explaining.
The main point is the list of **directory nodes** (the one shown here is one being run on signet, right now), which will The list of **directory nodes** (the one shown here is one being run on signet, right now), which will
be comma separated if multiple directory nodes are configured (we expect there will be 2 or 3 as a normal situation). be comma separated if multiple directory nodes are configured (we expect there will be 2 or 3 as a normal situation).
The `onion_serving_port` is on which port on the local machine the onion service is served. The `onion_serving_port` is on which port on the local machine the onion service is served; you won't usually need to use it, but it mustn't conflict with some other usage (so if you have something running on port 8080, change it).
The `type` field must always be `onion` in this case, and distinguishes it from IRC message channels and others. The `type` field must always be `onion` in this case, and distinguishes it from IRC message channels and others.
### Can/should I still run IRC message channels? ### Can/should I still run IRC message channels?
@ -50,11 +84,24 @@ In short, yes.
### Do I need to configure Tor, and if so, how? ### Do I need to configure Tor, and if so, how?
These message channels use both outbound and inbound connections to onion services (or "hidden services"). To make outbound Tor connections to other onions in the network, you will need to configure the
SOCKS5 proxy settings (so, only directory nodes may *not* need this; everyone else does).
This is identical to what we already do for IRC, except that in this case, we disallow clearnet connections.
#### Running/testing as a maker
A maker will additionally allow *inbound* connections to an onion service.
This onion service will be ephemeral, that is, it will have a different onion address every time
you restart. This should work automatically, using your existing Tor daemon (here, we are using
the same code as we use when running the `receive-payjoin` script, essentially).
#### Running/testing as other bots (taker)
A taker will not attempt to serve an onion; it will only use outbound connections, first to directory
nodes and then, as according to need, to individual makers, also.
As previously mentioned, both of these features were already in use in Joinmarket. If you never served an As previously mentioned, both of these features - inbound and outbound, to onion, Tor connections - were already in use in Joinmarket. If you want to run/test as a maker bot, but never served an onion service before, it should work fine as long as you have the Tor service running in the background,
onion service before, it should work fine as long as you have the Tor service running in the background, and the default control port 9051 (if not, change that value in the `joinmarket.cfg`, see above).
and the default control port 9051 (if not, change that value in the `joinmarket.cfg`, see above.
#### Why not use Lightning based onions? #### Why not use Lightning based onions?
@ -85,7 +132,7 @@ and pay attention to the settings in `regtest_joinmarket.cfg`.)
There is no separate/special configuration for signet other than the configuration that is already needed for running There is no separate/special configuration for signet other than the configuration that is already needed for running
Joinmarket against a signet backend (so e.g. RPC port of 38332). Joinmarket against a signet backend (so e.g. RPC port of 38332).
Add the `[MESSAGING:onion1]` message channel section to your `joinmarket.cfg`, as listed above, including the Add the `[MESSAGING:onion]` message channel section to your `joinmarket.cfg`, as listed above, including the
signet directory node listed above (rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80), and, signet directory node listed above (rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80), and,
for the simplest test, remove the other `[MESSAGING:*]` sections that you have. for the simplest test, remove the other `[MESSAGING:*]` sections that you have.
@ -101,19 +148,19 @@ who would like to help by running a directory node. You can ignore it if that do
This requires a long running bot. It should be on a server you can keep running permanently, so perhaps a VPS, This requires a long running bot. It should be on a server you can keep running permanently, so perhaps a VPS,
but in any case, very high uptime. For reliability it also makes sense to configure to run as a systemd service. but in any case, very high uptime. For reliability it also makes sense to configure to run as a systemd service.
A note: in this early stage, the usage of Lightning is only really network-layer stuff, and the usage of bitcoin, is none; feel free to add elements that remove any need for a backend bitcoin blockchain, but beware: future upgrades *could* mean that the directory node really does need the bitcoin backend. A note: the most natural way to run the directory is as a Joinmarket *maker* bot, i.e. run `yg-privacyenhanced.py`, with configuration as described below. For now it will actually offer to do coinjoins - we will want to fix this in future so no coins are needed (but it can just be a trivial size).
#### Joinmarket-specific configuration #### Joinmarket-specific configuration
Add `hidden_service_dir` to your `[MESSAGING:onion1]` with a directory accessible to your user. You may want to lock this down Add `hidden_service_dir` to your `[MESSAGING:onion]` with a directory accessible to your user. You may want to lock this down
a bit! a bit!
The point to understand is: Joinmarket's `jmbase.JMHiddenService` will, if configured with a non-empty `hidden_service_dir` The point to understand is: Joinmarket's `jmbase.JMHiddenService` will, if configured with a non-empty `hidden_service_dir`
field, actually start an *independent* instance of Tor specifically for serving this, under the current user. field, actually start an *independent* instance of Tor specifically for serving this, under the current user.
(our tor interface library `txtorcon` needs read access to the Tor HS dir, so it's troublesome to do this another way). (our Tor interface library `txtorcon` needs read access to the Tor HS dir, so it's troublesome to do this another way).
##### Question: How to configure the `directory-nodes` list in our `joinmarket.cfg` for this directory node bot? ##### Question: How to configure the `directory-nodes` list in our `joinmarket.cfg` for this directory node bot?
Answer: **you must only enter your own node in this list!** (otherwise you may find your bot infinitely rebroadcasting messages). Answer: **you must only enter your own node in this list!**. This way your bot will recognize that it is a directory node and it avoids weird edge case behaviour (so don't add *other* known directory nodes; you won't be talking to them).
#### Suggested setup of a service: #### Suggested setup of a service:
@ -143,7 +190,7 @@ WantedBy=multi-user.target
``` ```
This is deliberately a super-basic setup (see above). Don't forget to setup your `bitcoin.conf` as usual, This is deliberately a super-basic setup (see above). Don't forget to setup your `bitcoin.conf` as usual,
for the bitcoin user, and make it match (specifically in terms of RPC) what you set up for Lightning below. for the bitcoin user, and make it match (specifically in terms of RPC) what you set up for Joinmarket below.
2. 2.
@ -167,7 +214,6 @@ To state the obvious, the idea here is that this second service will run the JM
to ensure they start up in the correct order. to ensure they start up in the correct order.
Re: password echo, obviously this kind of password entry is bad; Re: password echo, obviously this kind of password entry is bad;
for now we needn't worry as these nodes don't need to carry any real coins (and it's better they don't!). for now we needn't worry as these nodes don't need to carry significant coins (and it's much better they don't!).
Later we may need to change that (though of course you can use standard measures to protect the box).
TODO: add some material on network hardening/firewalls here, I guess. TODO: add some material on network hardening/firewalls here, I guess.

4
jmbase/jmbase/commands.py

@ -27,11 +27,11 @@ class JMInit(JMCommand):
"""Communicates the client's required setup """Communicates the client's required setup
configuration. configuration.
Blockchain source is communicated only as a naming Blockchain source is communicated only as a naming
tag for messagechannels (currently IRC 'realname' field). tag for messagechannels (for IRC, 'realname' field).
""" """
arguments = [(b'bcsource', Unicode()), arguments = [(b'bcsource', Unicode()),
(b'network', Unicode()), (b'network', Unicode()),
(b'irc_configs', JsonEncodable()), (b'chan_configs', JsonEncodable()),
(b'minmakers', Integer()), (b'minmakers', Integer()),
(b'maker_timeout_sec', Integer()), (b'maker_timeout_sec', Integer()),
(b'dust_threshold', Integer()), (b'dust_threshold', Integer()),

6
jmbase/test/test_commands.py

@ -43,9 +43,9 @@ def end_test():
class JMTestServerProtocol(JMBaseProtocol): class JMTestServerProtocol(JMBaseProtocol):
@JMInit.responder @JMInit.responder
def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, def on_JM_INIT(self, bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location): maker_timeout_sec, dust_threshold, blacklist_location):
show_receipt("JMINIT", bcsource, network, irc_configs, minmakers, show_receipt("JMINIT", bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location) maker_timeout_sec, dust_threshold, blacklist_location)
d = self.callRemote(JMInitProto, d = self.callRemote(JMInitProto,
nick_hash_length=1, nick_hash_length=1,
@ -137,7 +137,7 @@ class JMTestClientProtocol(JMBaseProtocol):
d = self.callRemote(JMInit, d = self.callRemote(JMInit,
bcsource="dummyblockchain", bcsource="dummyblockchain",
network="dummynetwork", network="dummynetwork",
irc_configs=['dummy', 'irc', 'config'], chan_configs=['dummy', 'irc', 'config'],
minmakers=7, minmakers=7,
maker_timeout_sec=8, maker_timeout_sec=8,
dust_threshold=1500, dust_threshold=1500,

13
jmclient/jmclient/client_protocol.py

@ -434,7 +434,7 @@ class JMMakerClientProtocol(JMClientProtocol):
"blockchain_source") "blockchain_source")
#needed only for channel naming convention #needed only for channel naming convention
network = jm_single().config.get("BLOCKCHAIN", "network") network = jm_single().config.get("BLOCKCHAIN", "network")
irc_configs = self.factory.get_mchannels() chan_configs = self.factory.get_mchannels(mode="MAKER")
#only here because Init message uses this field; not used by makers TODO #only here because Init message uses this field; not used by makers TODO
minmakers = jm_single().config.getint("POLICY", "minimum_makers") minmakers = jm_single().config.getint("POLICY", "minimum_makers")
maker_timeout_sec = jm_single().maker_timeout_sec maker_timeout_sec = jm_single().maker_timeout_sec
@ -442,7 +442,7 @@ class JMMakerClientProtocol(JMClientProtocol):
d = self.callRemote(commands.JMInit, d = self.callRemote(commands.JMInit,
bcsource=blockchain_source, bcsource=blockchain_source,
network=network, network=network,
irc_configs=irc_configs, chan_configs=chan_configs,
minmakers=minmakers, minmakers=minmakers,
maker_timeout_sec=maker_timeout_sec, maker_timeout_sec=maker_timeout_sec,
dust_threshold=jm_single().DUST_THRESHOLD, dust_threshold=jm_single().DUST_THRESHOLD,
@ -601,7 +601,7 @@ class JMTakerClientProtocol(JMClientProtocol):
"blockchain_source") "blockchain_source")
#needed only for channel naming convention #needed only for channel naming convention
network = jm_single().config.get("BLOCKCHAIN", "network") network = jm_single().config.get("BLOCKCHAIN", "network")
irc_configs = self.factory.get_mchannels() chan_configs = self.factory.get_mchannels(mode="TAKER")
minmakers = jm_single().config.getint("POLICY", "minimum_makers") minmakers = jm_single().config.getint("POLICY", "minimum_makers")
maker_timeout_sec = jm_single().maker_timeout_sec maker_timeout_sec = jm_single().maker_timeout_sec
@ -614,7 +614,7 @@ class JMTakerClientProtocol(JMClientProtocol):
d = self.callRemote(commands.JMInit, d = self.callRemote(commands.JMInit,
bcsource=blockchain_source, bcsource=blockchain_source,
network=network, network=network,
irc_configs=irc_configs, chan_configs=chan_configs,
minmakers=minmakers, minmakers=minmakers,
maker_timeout_sec=maker_timeout_sec, maker_timeout_sec=maker_timeout_sec,
dust_threshold=jm_single().DUST_THRESHOLD, dust_threshold=jm_single().DUST_THRESHOLD,
@ -789,19 +789,20 @@ class JMClientProtocolFactory(protocol.ClientFactory):
def setClient(self, client): def setClient(self, client):
self.proto_client = client self.proto_client = client
def getClient(self): def getClient(self):
return self.proto_client return self.proto_client
def buildProtocol(self, addr): def buildProtocol(self, addr):
return self.protocol(self, self.client) return self.protocol(self, self.client)
def get_mchannels(self): def get_mchannels(self, mode):
""" A transparent wrapper that allows override, """ A transparent wrapper that allows override,
so that a script can return a customised set of so that a script can return a customised set of
message channel configs; currently used for testing message channel configs; currently used for testing
multiple bots on regtest. multiple bots on regtest.
""" """
return get_mchannels() return get_mchannels(mode)
def start_reactor(host, port, factory=None, snickerfactory=None, def start_reactor(host, port, factory=None, snickerfactory=None,
bip78=False, jm_coinjoin=True, ish=True, bip78=False, jm_coinjoin=True, ish=True,

138
jmclient/jmclient/configure.py

@ -137,27 +137,7 @@ rpc_password = password
# information. # information.
rpc_wallet_file = rpc_wallet_file =
## SERVER 1/3) Darkscience IRC (Tor, IP) [MESSAGING:onion]
################################################################################
[MESSAGING:server1]
# by default the legacy format without a `type` field is
# understood to be IRC, but you can, optionally, add it:
# type = irc
channel = joinmarket-pit
port = 6697
usessl = true
# For traditional IP (default):
host = irc.darkscience.net
socks5 = false
# For Tor (recommended as clearnet alternative):
#host = darkirc6tqgpnwd3blln3yfv5ckl47eg7llfxkmtovrv7c7iwohhb6ad.onion
#socks5 = true
#socks5_host = localhost
#socks5_port = 9050
[MESSAGING:onion1]
# onion based message channels must have the exact type 'onion' # onion based message channels must have the exact type 'onion'
# (while the section name above can be MESSAGING:whatever), and there must # (while the section name above can be MESSAGING:whatever), and there must
# be only ONE such message channel configured (note the directory servers # be only ONE such message channel configured (note the directory servers
@ -193,23 +173,59 @@ hidden_service_dir =
# This is a comma separated list (comma can be omitted if only one item). # This is a comma separated list (comma can be omitted if only one item).
# Each item has format host:port ; both are required, though port will # Each item has format host:port ; both are required, though port will
# be 80 if created in this code. # be 80 if created in this code.
directory_nodes = rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80 directory_nodes = rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80,k74oyetjqgcamsyhlym2vgbjtvhcrbxr4iowd4nv4zk5sehw4v665jad.onion:80
# This setting is ONLY for developer regtest setups, # This setting is ONLY for developer regtest setups,
# running multiple bots at once. Don't alter it otherwise # running multiple bots at once. Don't alter it otherwise
regtest_count = 0,0 regtest_count = 0,0
## SERVER 3/3) ILITA IRC (Tor - disabled by default) ## IRC SERVER 1: Darkscience IRC (Tor, IP)
################################################################################
[MESSAGING:server1]
# by default the legacy format without a `type` field is
# understood to be IRC, but you can, optionally, add it:
# type = irc
channel = joinmarket-pit
port = 6697
usessl = true
# For traditional IP:
#host = irc.darkscience.net
#socks5 = false
# For Tor (recommended as clearnet alternative):
host = darkirc6tqgpnwd3blln3yfv5ckl47eg7llfxkmtovrv7c7iwohhb6ad.onion
socks5 = true
socks5_host = localhost
socks5_port = 9050
## IRC SERVER 2: ILITA IRC (optional IRC alternate, Tor only)
################################################################################
[MESSAGING:server2]
channel = joinmarket-pit
port = 6667
usessl = false
socks5 = true
socks5_host = localhost
host = ilitafrzzgxymv6umx2ux7kbz3imyeko6cnqkvy4nisjjj4qpqkrptid.onion
socks5_port = 9050
## IRC SERVER 3) (backup) hackint IRC (Tor, IP)
################################################################################ ################################################################################
#[MESSAGING:server3] #[MESSAGING:server3]
#channel = joinmarket-pit # channel = joinmarket-pit
# For traditional IP:
## host = irc.hackint.org
## port = 6697
## usessl = true
## socks5 = false
# For Tor (default):
#host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion
#port = 6667 #port = 6667
#usessl = false #usessl = false
#socks5 = true #socks5 = true
#socks5_host = localhost #socks5_host = localhost
# For Tor (recommended):
#host = ilitafrzzgxymv6umx2ux7kbz3imyeko6cnqkvy4nisjjj4qpqkrptid.onion
#socks5_port = 9050 #socks5_port = 9050
[LOGGING] [LOGGING]
@ -510,7 +526,7 @@ def set_config(cfg, bcint=None):
global_singleton.bc_interface = bcint global_singleton.bc_interface = bcint
def get_mchannels(): def get_mchannels(mode="TAKER"):
SECTION_NAME = 'MESSAGING' SECTION_NAME = 'MESSAGING'
# FIXME: remove in future release # FIXME: remove in future release
if jm_single().config.has_section(SECTION_NAME): if jm_single().config.has_section(SECTION_NAME):
@ -521,65 +537,64 @@ def get_mchannels():
return _get_irc_mchannels_old() return _get_irc_mchannels_old()
SECTION_NAME += ':' SECTION_NAME += ':'
sections = []
for s in jm_single().config.sections():
if s.startswith(SECTION_NAME):
sections.append(s)
assert sections
irc_fields = [("host", str), ("port", int), ("channel", str), ("usessl", str), irc_fields = [("host", str), ("port", int), ("channel", str), ("usessl", str),
("socks5", str), ("socks5_host", str), ("socks5_port", str)] ("socks5", str), ("socks5_host", str), ("socks5_port", int)]
onion_fields = [("type", str), ("directory_nodes", str), ("regtest_count", str), onion_fields = [("type", str), ("directory_nodes", str), ("regtest_count", str),
("socks5_host", str), ("socks5_port", int), ("socks5_host", str), ("socks5_port", int),
("tor_control_host", str), ("tor_control_port", int), ("tor_control_host", str), ("tor_control_port", int),
("onion_serving_host", str), ("onion_serving_port", int), ("onion_serving_host", str), ("onion_serving_port", int),
("hidden_service_dir", str)] ("hidden_service_dir", str)]
configs = [] def get_irc_section(s):
# processing the IRC sections:
for section in sections:
if jm_single().config.has_option(section, "type"):
# legacy IRC configs do not have "type" but just
# in case, we'll allow the "irc" type:
if not jm_single().config.get(section, "type").lower(
) == "irc":
break
server_data = {} server_data = {}
# check if socks5 is enabled for tor and load relevant config if so # check if socks5 is enabled for tor and load relevant config if so
try: try:
server_data["socks5"] = jm_single().config.get(section, "socks5") server_data["socks5"] = jm_single().config.get(s, "socks5")
except NoOptionError: except NoOptionError:
server_data["socks5"] = "false" server_data["socks5"] = "false"
if server_data["socks5"].lower() == 'true': if server_data["socks5"].lower() == 'true':
server_data["socks5_host"] = jm_single().config.get(section, "socks5_host") server_data["socks5_host"] = jm_single().config.get(s, "socks5_host")
server_data["socks5_port"] = jm_single().config.get(section, "socks5_port") server_data["socks5_port"] = jm_single().config.get(s, "socks5_port")
for option, otype in irc_fields: for option, otype in irc_fields:
val = jm_single().config.get(section, option) val = jm_single().config.get(s, option)
server_data[option] = otype(val) server_data[option] = otype(val)
server_data['btcnet'] = get_network() server_data['btcnet'] = get_network()
configs.append(server_data) return server_data
# processing the onion sections: def get_onion_section(s):
for section in sections:
if not jm_single().config.has_option(section, "type") or \
not jm_single().config.get(section, "type").lower() == "onion":
continue
onion_data = {} onion_data = {}
for option, otype in onion_fields: for option, otype in onion_fields:
try: try:
val = jm_single().config.get(section, option) val = jm_single().config.get(s, option)
except NoOptionError: except NoOptionError:
continue continue
onion_data[option] = otype(val) onion_data[option] = otype(val)
# the onion messaging section must specify whether
# to serve an onion:
onion_data["serving"] = mode == "MAKER"
onion_data['btcnet'] = get_network() onion_data['btcnet'] = get_network()
# Just to allow a dynamic set of var: # Just to allow a dynamic set of var:
onion_data["section-name"] = section onion_data["section-name"] = s
configs.append(onion_data) return onion_data
return configs onion_sections = []
irc_sections = []
for section in jm_single().config.sections():
if not section.startswith(SECTION_NAME):
continue
if jm_single().config.has_option(section, "type"):
channel_type = jm_single().config.get(section, "type").lower()
if channel_type == "onion":
onion_sections.append(get_onion_section(section))
elif channel_type == "irc":
irc_sections.append(get_irc_section(section))
else:
irc_sections.append(get_irc_section(section))
assert irc_sections or onion_sections
assert len(onion_sections) < 2
return irc_sections + onion_sections
def _get_irc_mchannels_old(): def _get_irc_mchannels_old():
fields = [("host", str), ("port", int), ("channel", str), ("usessl", str), fields = [("host", str), ("port", int), ("channel", str), ("usessl", str),
@ -777,11 +792,6 @@ def load_program_config(config_path="", bs=None, plugin_services=[]):
if not os.path.exists(plogsdir): if not os.path.exists(plogsdir):
os.makedirs(plogsdir) os.makedirs(plogsdir)
p.set_log_dir(plogsdir) p.set_log_dir(plogsdir)
# Check if a onion message channel was configured, and if so,
# check there is only 1; multiple directory nodes will be inside the config.
chans = get_mchannels()
onion_chans = [x for x in chans if "type" in x and x["type"] == "onion"]
assert len(onion_chans) < 2
def load_test_config(**kwargs): def load_test_config(**kwargs):
if "config_path" not in kwargs: if "config_path" not in kwargs:

5
jmclient/jmclient/wallet_rpc.py

@ -423,8 +423,7 @@ class JMWalletDaemon(Service):
walletname=self.wallet_name, walletname=self.wallet_name,
token=self.cookie) token=self.cookie)
def taker_finished(self, res, fromtx=False, def taker_finished(self, res, fromtx=False, waittime=0.0, txdetails=None):
waittime=0.0, txdetails=None):
# This is a slimmed down version compared with what is seen in # This is a slimmed down version compared with what is seen in
# the CLI code, since that code encompasses schedules with multiple # the CLI code, since that code encompasses schedules with multiple
# entries; for now, the RPC only supports single joins. # entries; for now, the RPC only supports single joins.
@ -1007,7 +1006,7 @@ class JMWalletDaemon(Service):
self.taker = Taker(self.services["wallet"], schedule, self.taker = Taker(self.services["wallet"], schedule,
max_cj_fee = max_cj_fee, max_cj_fee = max_cj_fee,
callbacks=(self.filter_orders_callback, callbacks=(self.filter_orders_callback,
None, self.taker_finished)) None, self.taker_finished))
# TODO ; this makes use of a pre-existing hack to allow # TODO ; this makes use of a pre-existing hack to allow
# selectively disabling the stallMonitor function that checks # selectively disabling the stallMonitor function that checks
# if transactions went through or not; here we want to cleanly # if transactions went through or not; here we want to cleanly

4
jmclient/test/test_client_protocol.py

@ -167,9 +167,9 @@ def end_test():
class JMTestServerProtocol(JMBaseProtocol): class JMTestServerProtocol(JMBaseProtocol):
@JMInit.responder @JMInit.responder
def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, def on_JM_INIT(self, bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location): maker_timeout_sec, dust_threshold, blacklist_location):
show_receipt("JMINIT", bcsource, network, irc_configs, minmakers, show_receipt("JMINIT", bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location) maker_timeout_sec, dust_threshold, blacklist_location)
d = self.callRemote(JMInitProto, d = self.callRemote(JMInitProto,
nick_hash_length=1, nick_hash_length=1,

18
jmdaemon/jmdaemon/daemon_protocol.py

@ -475,7 +475,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
self.factory = factory self.factory = factory
self.jm_state = 0 self.jm_state = 0
self.restart_mc_required = False self.restart_mc_required = False
self.irc_configs = None self.chan_configs = None
self.mcc = None self.mcc = None
#Default role is TAKER; must be overriden to MAKER in JMSetup message. #Default role is TAKER; must be overriden to MAKER in JMSetup message.
self.role = "TAKER" self.role = "TAKER"
@ -504,7 +504,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
d.addErrback(self.defaultErrback) d.addErrback(self.defaultErrback)
@JMInit.responder @JMInit.responder
def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, def on_JM_INIT(self, bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location): maker_timeout_sec, dust_threshold, blacklist_location):
"""Reads in required configuration from client for a new """Reads in required configuration from client for a new
session; feeds back joinmarket messaging protocol constants session; feeds back joinmarket messaging protocol constants
@ -518,25 +518,25 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
self.dust_threshold = int(dust_threshold) self.dust_threshold = int(dust_threshold)
#(bitcoin) network only referenced in channel name construction #(bitcoin) network only referenced in channel name construction
self.network = network self.network = network
if irc_configs == self.irc_configs: if chan_configs == self.chan_configs:
self.restart_mc_required = False self.restart_mc_required = False
log.msg("New init received did not require a new message channel" log.msg("New init received did not require a new message channel"
" setup.") " setup.")
else: else:
if self.irc_configs: if self.chan_configs:
#close the existing connections #close the existing connections
self.mc_shutdown() self.mc_shutdown()
self.irc_configs = irc_configs self.chan_configs = chan_configs
self.restart_mc_required = True self.restart_mc_required = True
mcs = [] mcs = []
for c in self.irc_configs: for c in self.chan_configs:
if "type" in c and c["type"] == "onion": if "type" in c and c["type"] == "onion":
mcs.append(OnionMessageChannel(c, daemon=self)) mcs.append(OnionMessageChannel(c, daemon=self))
else: else:
# default is IRC; TODO allow others # default is IRC; TODO allow others
mcs.append(IRCMessageChannel(c, mcs.append(IRCMessageChannel(c,
daemon=self, daemon=self,
realname='btcint=' + bcsource)) realname='btcint=' + bcsource))
self.mcc = MessageChannelCollection(mcs) self.mcc = MessageChannelCollection(mcs)
OrderbookWatch.set_msgchan(self, self.mcc) OrderbookWatch.set_msgchan(self, self.mcc)
#register taker-specific msgchan callbacks here #register taker-specific msgchan callbacks here
@ -952,7 +952,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
for a new transaction; effectively means any previous for a new transaction; effectively means any previous
incomplete transaction is wiped. incomplete transaction is wiped.
""" """
self.jm_state = 0 #uninited self.jm_state = 0
self.mcc.set_nick(nick) self.mcc.set_nick(nick)
if self.restart_mc_required: if self.restart_mc_required:
self.mcc.run() self.mcc.run()

2
jmdaemon/jmdaemon/message_channel.py

@ -259,7 +259,7 @@ class MessageChannelCollection(object):
for x in self.available_channels() for x in self.available_channels()
if mc == x.hostid] if mc == x.hostid]
if len(matching_channels) != 1: #pragma: no cover if len(matching_channels) != 1: #pragma: no cover
#this can happen if an IRC goes down shortly before a message #this can happen if a m-channel goes down shortly before a message
#is supposed to be sent. There used to be an exception raise. #is supposed to be sent. There used to be an exception raise.
#to prevent a crash (especially in makers), we just inform #to prevent a crash (especially in makers), we just inform
#the user about it for now #the user about it for now

551
jmdaemon/jmdaemon/onionmc.py

File diff suppressed because it is too large Load Diff

6
jmdaemon/test/test_daemon_protocol.py

@ -59,11 +59,11 @@ class JMTestClientProtocol(JMBaseProtocol):
def clientStart(self): def clientStart(self):
self.sigs_received = 0 self.sigs_received = 0
irc = [get_mchannels()[0]] chan_configs = [get_mchannels()[0]]
d = self.callRemote(JMInit, d = self.callRemote(JMInit,
bcsource="dummyblockchain", bcsource="dummyblockchain",
network="dummynetwork", network="dummynetwork",
irc_configs=irc, chan_configs=chan_configs,
minmakers=2, minmakers=2,
maker_timeout_sec=3, maker_timeout_sec=3,
dust_threshold=27300, dust_threshold=27300,
@ -212,7 +212,7 @@ class JMDaemonTestServerProtocol(JMDaemonServerProtocol):
return super().on_JM_REQUEST_OFFERS() return super().on_JM_REQUEST_OFFERS()
@JMInit.responder @JMInit.responder
def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, def on_JM_INIT(self, bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location): maker_timeout_sec, dust_threshold, blacklist_location):
self.maker_timeout_sec = maker_timeout_sec self.maker_timeout_sec = maker_timeout_sec
self.dust_threshold = int(dust_threshold) self.dust_threshold = int(dust_threshold)

66
scripts/obwatch/ob-watcher.py

@ -45,7 +45,8 @@ if 'matplotlib' in sys.modules:
from jmclient import jm_single, load_program_config, calc_cj_fee, \ from jmclient import jm_single, load_program_config, calc_cj_fee, \
get_mchannels, add_base_options get_mchannels, add_base_options
from jmdaemon import OrderbookWatch, MessageChannelCollection, IRCMessageChannel from jmdaemon import (OrderbookWatch, MessageChannelCollection,
OnionMessageChannel, IRCMessageChannel)
#TODO this is only for base58, find a solution for a client without jmbitcoin #TODO this is only for base58, find a solution for a client without jmbitcoin
import jmbitcoin as btc import jmbitcoin as btc
from jmdaemon.protocol import * from jmdaemon.protocol import *
@ -737,32 +738,32 @@ class ObBasic(OrderbookWatch):
def request_orderbook(self): def request_orderbook(self):
self.msgchan.request_orderbook() self.msgchan.request_orderbook()
class ObIRCMessageChannel(IRCMessageChannel):
"""A customisation of the message channel """An override for MessageChannel classes,
to allow receipt of privmsgs without the to allow receipt of privmsgs without the
verification hooks in client-daemon communication.""" verification hooks in client-daemon communication."""
def on_privmsg(self, nick, message): def on_privmsg(inst, nick, message):
if len(message) < 2: if len(message) < 2:
return return
if message[0] != COMMAND_PREFIX: if message[0] != COMMAND_PREFIX:
log.debug('message not a cmd') log.debug('message not a cmd')
return return
cmd_string = message[1:].split(' ')[0] cmd_string = message[1:].split(' ')[0]
if cmd_string not in offername_list: if cmd_string not in offername_list:
log.debug('non-offer ignored') log.debug('non-offer ignored')
return return
#Ignore sigs (TODO better to include check) #Ignore sigs (TODO better to include check)
sig = message[1:].split(' ')[-2:] sig = message[1:].split(' ')[-2:]
#reconstruct original message without cmd pref #reconstruct original message without cmd pref
rawmessage = ' '.join(message[1:].split(' ')[:-2]) rawmessage = ' '.join(message[1:].split(' ')[:-2])
for command in rawmessage.split(COMMAND_PREFIX): for command in rawmessage.split(COMMAND_PREFIX):
_chunks = command.split(" ") _chunks = command.split(" ")
try: try:
self.check_for_orders(nick, _chunks) inst.check_for_orders(nick, _chunks)
self.check_for_fidelity_bond(nick, _chunks) inst.check_for_fidelity_bond(nick, _chunks)
except: except:
pass pass
def get_dummy_nick(): def get_dummy_nick():
@ -804,7 +805,16 @@ def main():
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
load_program_config(config_path=options.datadir) load_program_config(config_path=options.datadir)
hostport = (options.host, options.port) hostport = (options.host, options.port)
mcs = [ObIRCMessageChannel(c) for c in get_mchannels()] mcs = []
chan_configs = get_mchannels()
for c in chan_configs:
if "type" in c and c["type"] == "onion":
mcs.append(OnionMessageChannel(c))
else:
# default is IRC; TODO allow others
mcs.append(IRCMessageChannel(c))
IRCMessageChannel.on_privmsg = on_privmsg
OnionMessageChannel.on_privmsg = on_privmsg
mcc = MessageChannelCollection(mcs) mcc = MessageChannelCollection(mcs)
mcc.set_nick(get_dummy_nick()) mcc.set_nick(get_dummy_nick())
taker = ObBasic(mcc, hostport) taker = ObBasic(mcc, hostport)

85
test/e2e-coinjoin-test.py

@ -11,7 +11,7 @@
pytest \ pytest \
--btcroot=/path/to/bitcoin/bin/ \ --btcroot=/path/to/bitcoin/bin/ \
--btcpwd=123456abcdef --btcconf=/blah/bitcoin.conf \ --btcpwd=123456abcdef --btcconf=/blah/bitcoin.conf \
-s test/ln-ygrunner.py -s test/e2e-coinjoin-test.py
''' '''
from twisted.internet import reactor, defer from twisted.internet import reactor, defer
from twisted.web.client import readBody, Headers from twisted.web.client import readBody, Headers
@ -21,7 +21,7 @@ import random
import json import json
from datetime import datetime from datetime import datetime
from jmbase import (get_nontor_agent, BytesProducer, jmprint, from jmbase import (get_nontor_agent, BytesProducer, jmprint,
get_log, stop_reactor, hextobin, bintohex) get_log, stop_reactor)
from jmclient import (YieldGeneratorBasic, load_test_config, jm_single, from jmclient import (YieldGeneratorBasic, load_test_config, jm_single,
JMClientProtocolFactory, start_reactor, SegwitWallet, get_mchannels, JMClientProtocolFactory, start_reactor, SegwitWallet, get_mchannels,
SegwitLegacyWallet, JMWalletDaemon) SegwitLegacyWallet, JMWalletDaemon)
@ -45,8 +45,7 @@ mean_amt = 2.0
directory_node_indices = [1] directory_node_indices = [1]
# def get_onion_messaging_config_regtest(run_num: int, dns=[1], hsd="", mode="TAKER"):
def get_onion_messaging_config_regtest(run_num: int, dns=[1], hsd=""):
""" Sets a onion messaging channel section for a regtest instance """ Sets a onion messaging channel section for a regtest instance
indexed by `run_num`. The indices to be used as directory nodes indexed by `run_num`. The indices to be used as directory nodes
should be passed as `dns`, as a list of ints. should be passed as `dns`, as a list of ints.
@ -72,6 +71,10 @@ def get_onion_messaging_config_regtest(run_num: int, dns=[1], hsd=""):
"hidden_service_dir": "", "hidden_service_dir": "",
"directory_nodes": dn_nodes_list, "directory_nodes": dn_nodes_list,
"regtest_count": "1, 1"} "regtest_count": "1, 1"}
if mode == "MAKER":
cf["serving"] = True
else:
cf["serving"] = False
if run_num in dns: if run_num in dns:
# only directories need to use fixed hidden service directories: # only directories need to use fixed hidden service directories:
cf["hidden_service_dir"] = hsd cf["hidden_service_dir"] = hsd
@ -85,11 +88,11 @@ class RegtestJMClientProtocolFactory(JMClientProtocolFactory):
# for this test: # for this test:
self.dns = dns self.dns = dns
def get_mchannels(self): def get_mchannels(self, mode="TAKER"):
# swaps out any existing lightning configs # swaps out any existing onionmc configs
# in the config settings on startup, for one # in the config settings on startup, for one
# that's indexed to the regtest counter var: # that's indexed to the regtest counter var:
default_chans = get_mchannels() default_chans = get_mchannels(mode=mode)
new_chans = [] new_chans = []
onion_found = False onion_found = False
hsd = "" hsd = ""
@ -103,7 +106,7 @@ class RegtestJMClientProtocolFactory(JMClientProtocolFactory):
new_chans.append(c) new_chans.append(c)
if onion_found: if onion_found:
new_chans.append(get_onion_messaging_config_regtest( new_chans.append(get_onion_messaging_config_regtest(
self.i, self.dns, hsd)) self.i, self.dns, hsd, mode=mode))
return new_chans return new_chans
class JMWalletDaemonT(JMWalletDaemon): class JMWalletDaemonT(JMWalletDaemon):
@ -183,7 +186,7 @@ def test_start_yg_and_taker_setup(setup_onion_ygrunner):
walletclass = SegwitLegacyWallet walletclass = SegwitLegacyWallet
start_bot_num, end_bot_num = [int(x) for x in jm_single().config.get( start_bot_num, end_bot_num = [int(x) for x in jm_single().config.get(
"MESSAGING:onion1", "regtest_count").split(",")] "MESSAGING:onion", "regtest_count").split(",")]
num_ygs = end_bot_num - start_bot_num num_ygs = end_bot_num - start_bot_num
# specify the number of wallets and bots of each type: # specify the number of wallets and bots of each type:
wallet_services = make_wallets(num_ygs + 1, wallet_services = make_wallets(num_ygs + 1,
@ -254,21 +257,21 @@ def test_start_yg_and_taker_setup(setup_onion_ygrunner):
# This ensures that this bot knows which other bots are directory nodes: # This ensures that this bot knows which other bots are directory nodes:
clientfactory.set_directory_nodes(directory_node_indices) clientfactory.set_directory_nodes(directory_node_indices)
nodaemon = jm_single().config.getint("DAEMON", "no_daemon") nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
daemon = True if nodaemon == 1 else False daemon = bool(nodaemon)
#rs = True if i == num_ygs - 1 else False #rs = True if i == num_ygs - 1 else False
start_reactor(jm_single().config.get("DAEMON", "daemon_host"), start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
jm_single().config.getint("DAEMON", "daemon_port"), jm_single().config.getint("DAEMON", "daemon_port"),
clientfactory, daemon=daemon, rs=False) clientfactory, daemon=daemon, rs=False)
reactor.callLater(1.0, start_test_taker, wallet_services[end_bot_num - 1]['wallet'], end_bot_num) reactor.callLater(1.0, start_test_taker, wallet_services[end_bot_num - 1]['wallet'], end_bot_num, num_ygs)
reactor.run() reactor.run()
@defer.inlineCallbacks @defer.inlineCallbacks
def start_test_taker(wallet_service, i): def start_test_taker(wallet_service, i, num_ygs):
# this rpc manager has auth disabled, # this rpc manager has auth disabled,
# and the wallet_service is set manually, # and the wallet_service is set manually,
# so no unlock etc. # so no unlock etc.
mgr = TWalletRPCManager() mgr = TWalletRPCManager()
mgr.daemon.wallet_service = wallet_service mgr.daemon.services["wallet"] = wallet_service
# because we are manually setting the wallet_service # because we are manually setting the wallet_service
# of the JMWalletDaemon instance, we do not follow the # of the JMWalletDaemon instance, we do not follow the
# usual flow of `initialize_wallet_service`, we do not set # usual flow of `initialize_wallet_service`, we do not set
@ -276,11 +279,9 @@ def start_test_taker(wallet_service, i):
# sync the wallet, including bypassing any restart callback: # sync the wallet, including bypassing any restart callback:
def dummy_restart_callback(msg): def dummy_restart_callback(msg):
log.warn("Ignoring rescan request from backend wallet service: " + msg) log.warn("Ignoring rescan request from backend wallet service: " + msg)
mgr.daemon.wallet_service.add_restart_callback(dummy_restart_callback) mgr.daemon.services["wallet"].add_restart_callback(dummy_restart_callback)
mgr.daemon.wallet_name = wallet_name mgr.daemon.wallet_name = wallet_name
while not mgr.daemon.wallet_service.synced: mgr.daemon.services["wallet"].startService()
mgr.daemon.wallet_service.sync_wallet(fast=True)
mgr.daemon.wallet_service.startService()
def get_client_factory(): def get_client_factory():
clientfactory = RegtestJMClientProtocolFactory(mgr.daemon.taker, clientfactory = RegtestJMClientProtocolFactory(mgr.daemon.taker,
proto_type="TAKER") proto_type="TAKER")
@ -290,18 +291,25 @@ def start_test_taker(wallet_service, i):
mgr.daemon.get_client_factory = get_client_factory mgr.daemon.get_client_factory = get_client_factory
# before preparing the RPC call to the wallet daemon, # before preparing the RPC call to the wallet daemon,
# we decide a coinjoin destination and amount. Choosing # we decide a coinjoin destination, counterparty count and amount.
# a destination in the wallet is a bit easier because # Choosing a destination in the wallet is a bit easier because
# we can query the mixdepth balance at the end. # we can query the mixdepth balance at the end.
coinjoin_destination = mgr.daemon.wallet_service.get_internal_addr(4) coinjoin_destination = mgr.daemon.services["wallet"].get_internal_addr(4)
cj_amount = 22000000 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 # once the taker is finished we sanity check before
# shutting down: # shutting down:
def dummy_taker_finished(res, fromtx=False, def dummy_taker_finished(res, fromtx=False,
waittime=0.0, txdetails=None): waittime=0.0, txdetails=None):
jmprint("Taker is finished") jmprint("Taker is finished")
# check that the funds have arrived. # check that the funds have arrived.
mbal = mgr.daemon.wallet_service.get_balance_by_mixdepth()[4] mbal = mgr.daemon.services["wallet"].get_balance_by_mixdepth()[4]
assert mbal == cj_amount assert mbal == cj_amount
jmprint("Funds: {} sats successfully arrived into mixdepth 4.".format(cj_amount)) jmprint("Funds: {} sats successfully arrived into mixdepth 4.".format(cj_amount))
stop_reactor() stop_reactor()
@ -315,7 +323,7 @@ def start_test_taker(wallet_service, i):
addr = addr.encode() addr = addr.encode()
body = BytesProducer(json.dumps({"mixdepth": "1", body = BytesProducer(json.dumps({"mixdepth": "1",
"amount_sats": cj_amount, "amount_sats": cj_amount,
"counterparties": "2", "counterparties": str(n_cps),
"destination": coinjoin_destination}).encode()) "destination": coinjoin_destination}).encode())
yield mgr.do_request(agent, b"POST", addr, body, yield mgr.do_request(agent, b"POST", addr, body,
process_coinjoin_response) process_coinjoin_response)
@ -324,39 +332,6 @@ def process_coinjoin_response(response):
json_body = json.loads(response.decode("utf-8")) json_body = json.loads(response.decode("utf-8"))
print("coinjoin response: {}".format(json_body)) print("coinjoin response: {}".format(json_body))
def get_addr_and_fund(yg):
""" This function allows us to create
and publish a fidelity bond for a particular
yield generator object after the wallet has reached
a synced state and is therefore ready to serve up
timelock addresses. We create the TL address, fund it,
refresh the wallet and then republish our offers, which
will also publish the new FB.
"""
if not yg.wallet_service.synced:
return
if yg.wallet_service.timelock_funded:
return
addr = wallet_gettimelockaddress(yg.wallet_service.wallet, "2021-11")
print("Got timelockaddress: {}".format(addr))
# pay into it; amount is randomized for now.
# Note that grab_coins already mines 1 block.
fb_amt = random.randint(1, 5)
jm_single().bc_interface.grab_coins(addr, fb_amt)
# we no longer have to run this loop (TODO kill with nonlocal)
yg.wallet_service.timelock_funded = True
# force wallet to check for the new coins so the new
# yg offers will include them:
yg.wallet_service.transaction_monitor()
# publish a new offer:
yg.offerlist = yg.create_my_orders()
yg.fidelity_bond = yg.get_fidelity_bond_template()
jmprint('updated offerlist={}'.format(yg.offerlist))
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def setup_onion_ygrunner(): def setup_onion_ygrunner():
load_test_config() load_test_config()

2
test/regtest_joinmarket.cfg

@ -37,7 +37,7 @@ socks5 = false
socks5_host = localhost socks5_host = localhost
socks5_port = 9150 socks5_port = 9150
[MESSAGING:onion1] [MESSAGING:onion]
# onion based message channels must have the exact type 'onion' # onion based message channels must have the exact type 'onion'
# (while the section name above can be MESSAGING:whatever), and there must # (while the section name above can be MESSAGING:whatever), and there must
# be only ONE such message channel configured (note the directory servers # be only ONE such message channel configured (note the directory servers

Loading…
Cancel
Save