Browse Source

Disconnection logic fixes, add btcnet to handshake

Also, exports JMMakerClientProtocol for custom directory node scripts
(stored in the custom-scripts repo).
Modify default config with 2 signet and mainnet directory nodes to
start.
Handles unreachable directory nodes with a human readable error and
adjusts connection timeouts to be realistic.
Changes wording in Qt notifications from "IRC" to message channel.
Updates docs, new directory node information.
master
Adam Gibson 4 years ago
parent
commit
5cc1695154
No known key found for this signature in database
GPG Key ID: 141001A1AF77F20B
  1. 82
      docs/onion-message-channels.md
  2. 2
      jmclient/jmclient/__init__.py
  3. 8
      jmclient/jmclient/configure.py
  4. 118
      jmdaemon/jmdaemon/onionmc.py
  5. 4
      scripts/joinmarket-qt.py
  6. 3
      test/e2e-coinjoin-test.py

82
docs/onion-message-channels.md

@ -17,6 +17,8 @@ introduce any new requirements to your Joinmarket installation, technically, bec
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.
(Note however that taker bots will *not* be required to serve onions; they will only make outbound SOCKS connections, as they currently do on IRC).
The purpose of this new type of message channel is as follows:
* less reliance on any service external to Joinmarket
@ -25,7 +27,7 @@ albeit it was and remains E2E encrypted data, in either case)
* 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
The configuration for a user is simple; in their `joinmarket.cfg` they will get a messaging section like this, if they start from scratch:
The configuration for a user is simple; in their `joinmarket.cfg` they will get a new `[MESSAGING]` section like this, if they start from scratch:
```
[MESSAGING:onion]
@ -64,23 +66,26 @@ hidden_service_dir =
# 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
# be 80 if created in this code.
directory_nodes = rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80
# for MAINNET:
directory_nodes = 3kxw6lf5vf6y26emzwgibzhrzhmhqiw6ekrek3nqfjjmhwznb2moonad.onion,qqd22cwgygaxcy6vdw6mzwkyaxg5urb4ptbc5d74nrj25phspajxjbqd.onion
# for SIGNET (testing network):
# directory_nodes = rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80,k74oyetjqgcamsyhlym2vgbjtvhcrbxr4iowd4nv4zk5sehw4v665jad.onion:80
# This setting is ONLY for developer regtest setups,
# running multiple bots at once. Don't alter it otherwise
regtest_count = 0,0
```
All of these can be left as default for most users, except the field `directory_nodes`.
All of these can be left as default for most users - but most importantly, pay attention to:
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).
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 list of `directory_nodes`, which will be comma separated if multiple directory nodes are configured (we expect there will be 2 or 3 as a normal situation). Make sure to choose the ones for your network (mainnet by default, or signet or otherwise); if it's wrong your bot will just get auto-disconnected.
* The `onion_serving_port` is the port on the local machine on which 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.
### Can/should I still run IRC message channels?
In short, yes.
In short, yes, at least for now, though you are free to disable any message channel you like.
### Do I need to configure Tor, and if so, how?
@ -95,7 +100,7 @@ This onion service will be ephemeral, that is, it will have a different onion ad
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)
#### Running/testing as other bots (taker, ob-watcher)
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.
@ -132,9 +137,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
Joinmarket against a signet backend (so e.g. RPC port of 38332).
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,
for the simplest test, remove the other `[MESSAGING:*]` sections that you have.
You can just uncomment the `directory_nodes` entry listed as SIGNET, and comment out the one for MAINNET.
Then just make sure your bot has some signet coins and try running as maker or taker or both.
@ -148,12 +151,17 @@ 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,
but in any case, very high uptime. For reliability it also makes sense to configure to run as a systemd service.
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).
The currently suggested way to run a directory node is to use the script found [here](https://github.com/JoinMarket-Org/custom-scripts/blob/0eda6154265e012b907c43e2ecdacb895aa9e3ab/start-dn.py); you can place it in your `joinmarket-clientserver/scripts` directory and run it *without* arguments, but with one option flag: `--datadir=/your/chosen/datadir` (as you'll see below).
This slightly unobvious approach is based on the following ideas: we run a Joinmarket script, with a Joinmarket python virtualenv, so that we are able to parse messages; this means that the directory node *can* be a bot, e.g. a maker bot, but need not be - and here it is basically a "crippled" maker bot that cannot do anything. This 'crippling' is actually very useful because (a) we use the `no-blockchain` argument (it is forced in-code; you don't need to set it) so we don't need a running Bitcoin node (of whatever flavour), and (b) we don't need a wallet either.
#### Joinmarket-specific configuration
Add `hidden_service_dir` to your `[MESSAGING:onion]` with a directory accessible to your user. You may want to lock this down
a bit!
Add a non-empty `hidden_service_dir` entry to your `[MESSAGING:onion]` with a directory accessible to your user. You may want to lock this down
a bit, but be careful changing permissions from what is created by the script, because Tor is very finicky about this.
The hostname for your onion service will not change and will be stored permanently in that directory.
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.
(our Tor interface library `txtorcon` needs read access to the Tor HS dir, so it's troublesome to do this another way).
@ -162,58 +170,40 @@ field, actually start an *independent* instance of Tor specifically for serving
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).
A natural retort is: but I don't know my own node's onion service hostname before I start it the first time. Indeed. So, just run it once with the default `directory_nodes` entries, then note down the new onion service hostname you created, and insert that as the only entry in the list.
#### Suggested setup of a service:
You will need two components: bitcoind, and Joinmarket itself, which you can run as a yg.
Since this task is going to be attempted by someone with significant technical knowledge,
only an outline is provided here; several details will need to be filled in.
Here is a sketch of how the systemd service files can be set up for signet:
#### Suggested setup of a systemd service:
If someone wants to put together a docker setup of this for a more "one-click install", that would be great.
1. bitcoin-signet.service
The most basic bare-bones service seems to work fine here:
```
[Unit]
Description=bitcoind signet
Description=My JM signet directory node
Requires=network-online.target
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/bitcoind -signet
ExecStart=/bin/bash -c 'cd /path/to/joinmarket-clientserver && source jmvenv/bin/activate && cd scripts && python start-dn.py --datadir=/path/to/chosen/datadir'
User=user
[Install]
WantedBy=multi-user.target
```
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 Joinmarket below.
... however, you need to kind of 'bootstrap' it the first time. For example:
* run once with systemctl start
2.
* look at log with `journalctl`, service fails due to default `joinmarket.cfg` and quit.
* go to that cfg file. Remove the IRC settings, they serve no purpose here. Change the `hidden_service_dir` to `/yourlocation/hidserv` (the actual directory need not exist, it's better if it doesn't, this first time). Edit the `network` field in `BLOCKCHAIN` to whatever network (mainnet, signet) you intend to support - it can be only one for one directory node, for now.
```
[Unit]
Description=joinmarket directory node on signet
Requires=bitcoin-signet.service
After=bitcoin-signet.service
* `systemctl start` again, now note the onion hostname created from the log or the directory
[Service]
Type=simple
ExecStart=/bin/bash -c 'cd /path/to/joinmarket-clientserver && source jmvenv/bin/activate && cd scripts && echo -n "password" | python yg-privacyenhanced.py --wallet-password-stdin --datadir=/custom/joinmarket-datadir some-signet-wallet.jmdat'
User=user
[Install]
WantedBy=multi-user.target
```
* set that hostname in `directory_nodes` in `joinmarket.cfg`
To state the obvious, the idea here is that this second service will run the JM directory node and have a dependency on the previous one,
to ensure they start up in the correct order.
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 significant coins (and it's much better they don't!).
* now the service should start correctly
TODO: add some material on network hardening/firewalls here, I guess.

2
jmclient/jmclient/__init__.py

@ -32,7 +32,7 @@ from .blockchaininterface import (BlockchainInterface,
from .snicker_receiver import SNICKERError, SNICKERReceiver
from .client_protocol import (JMTakerClientProtocol, JMClientProtocolFactory,
start_reactor, SNICKERClientProtocolFactory,
BIP78ClientProtocolFactory,
BIP78ClientProtocolFactory, JMMakerClientProtocol,
get_daemon_serving_params)
from .podle import (set_commitment_file, get_commitment_file,
add_external_commitments,

8
jmclient/jmclient/configure.py

@ -173,7 +173,11 @@ hidden_service_dir =
# 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
# be 80 if created in this code.
directory_nodes = rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80,k74oyetjqgcamsyhlym2vgbjtvhcrbxr4iowd4nv4zk5sehw4v665jad.onion:80
# for MAINNET:
directory_nodes = 3kxw6lf5vf6y26emzwgibzhrzhmhqiw6ekrek3nqfjjmhwznb2moonad.onion:80,qqd22cwgygaxcy6vdw6mzwkyaxg5urb4ptbc5d74nrj25phspajxjbqd.onion:80
# for SIGNET (testing network):
# directory_nodes = rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad.onion:80,k74oyetjqgcamsyhlym2vgbjtvhcrbxr4iowd4nv4zk5sehw4v665jad.onion:80
# This setting is ONLY for developer regtest setups,
# running multiple bots at once. Don't alter it otherwise
@ -211,7 +215,7 @@ socks5_host = localhost
host = ilitafrzzgxymv6umx2ux7kbz3imyeko6cnqkvy4nisjjj4qpqkrptid.onion
socks5_port = 9050
## IRC SERVER 3) (backup) hackint IRC (Tor, IP)
## IRC SERVER 3: (backup) hackint IRC (Tor, IP)
################################################################################
#[MESSAGING:server3]
# channel = joinmarket-pit

118
jmdaemon/jmdaemon/onionmc.py

@ -9,13 +9,17 @@ from twisted.protocols import basic
from twisted.application.internet import ClientService
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.internet.address import IPv4Address, IPv6Address
from txtorcon.socks import TorSocksEndpoint
from txtorcon.socks import TorSocksEndpoint, HostUnreachableError
log = get_log()
NOT_SERVING_ONION_HOSTNAME = "NOT-SERVING-ONION"
# How many seconds to wait before treating an onion
# as unreachable
CONNECT_TO_ONION_TIMEOUT = 10
def location_tuple_to_str(t: Tuple[str, int]) -> str:
return f"{t[0]}:{t[1]}"
@ -65,14 +69,15 @@ JM_MESSAGE_TYPES = {"privmsg": 685, "pubmsg": 687}
# Used for some control message construction, as detailed below.
NICK_PEERLOCATOR_SEPARATOR = ";"
# location_string and nick must be set before sending,
# location_string, nick and network must be set before sending,
# otherwise invalid:
client_handshake_json = {"app-name": JM_APP_NAME,
"directory": False,
"location-string": "",
"proto-ver": JM_VERSION,
"features": {},
"nick": ""
"nick": "",
"network": ""
}
# default acceptance false; code must switch it on:
@ -83,6 +88,7 @@ server_handshake_json = {"app-name": JM_APP_NAME,
"features": {},
"accepted": False,
"nick": "",
"network": "",
"motd": "Default MOTD, replace with information for the directory."
}
@ -189,6 +195,13 @@ class OnionLineProtocolFactory(protocol.ServerFactory):
return
del self.peers[peer_location]
def disconnect_inbound_peer(self, inbound_peer_str: str) -> None:
if inbound_peer_str not in self.peers:
log.warn("cannot disconnect peer at {}, not found".format(
inbound_peer_str))
proto = self.peers[inbound_peer_str]
proto.transport.loseConnection()
def receive_message(self, message: OnionCustomMessage,
p: OnionLineProtocol) -> None:
self.client.receive_msg(message, network_addr_to_string(
@ -212,6 +225,7 @@ class OnionClientFactory(protocol.ReconnectingClientFactory):
def __init__(self, message_receive_callback: Callable,
connection_callback: Callable,
disconnection_callback: Callable,
message_not_sendable_callback: Callable,
directory: bool,
mc: 'OnionMessageChannel'):
self.proto_client = None
@ -221,6 +235,9 @@ class OnionClientFactory(protocol.ReconnectingClientFactory):
self.connection_callback = connection_callback
# disconnection the same
self.disconnection_callback = disconnection_callback
# a callback that can be fired if we are not able to send messages,
# no args, returns None
self.message_not_sendable_callback = message_not_sendable_callback
# is this connection to a directory?
self.directory = directory
# to keep track of state of overall messagechannel
@ -255,6 +272,11 @@ class OnionClientFactory(protocol.ReconnectingClientFactory):
self.disconnection_callback()
def send(self, msg: OnionCustomMessage) -> bool:
# we may be sending at the time the counterparty
# disconnected
if not self.proto_client:
self.message_not_sendable_callback()
return False
self.proto_client.message(msg)
# Unlike the serving protocol, the client protocol
# is never in a condition of not knowing the counterparty
@ -430,6 +452,15 @@ class OnionPeer(object):
def receive_message(self, message: OnionCustomMessage) -> None:
self.messagechannel.receive_msg(message, self.peer_location())
def notify_message_unsendable(self):
""" Triggered by a failure to send a message on the network,
by the encapsulated ClientFactory. Just used to notify calling
code; no action is triggered.
"""
name = "directory" if self.directory else "peer"
log.warn("Failure to send message to {}: {}.".format(
name, self.peer_location()))
def connect(self) -> None:
""" This method is called to connect, over Tor, to the remote
peer at the given onion host/port.
@ -442,7 +473,7 @@ class OnionPeer(object):
self.factory = OnionClientFactory(self.receive_message,
self.register_connection, self.register_disconnection,
self.directory, self.messagechannel)
self.notify_message_unsendable, self.directory, self.messagechannel)
if testing_mode:
log.debug("{} is making a tcp connection to {}, {}, {},".format(
self.messagechannel.self_as_peer.peer_location(), self.hostname,
@ -450,13 +481,37 @@ class OnionPeer(object):
self.tcp_connector = reactor.connectTCP(self.hostname, self.port,
self.factory)
else:
# non-default timeout; needs to be much lower than our
# 'wait at least a minute for the IRC connections to come up',
# which is used for *all* message channels, together.
torEndpoint = TCP4ClientEndpoint(reactor, self.socks5_host,
self.socks5_port)
self.socks5_port,
timeout=CONNECT_TO_ONION_TIMEOUT)
onionEndpoint = TorSocksEndpoint(torEndpoint, self.hostname,
self.port)
self.reconnecting_service = ClientService(onionEndpoint, self.factory)
# if we want to actually do something about an unreachable host,
# we have to force t.a.i.ClientService to give up after the timeout:
d = self.reconnecting_service.whenConnected(failAfterFailures=1)
d.addErrback(self.respond_to_connection_failure)
self.reconnecting_service.startService()
def respond_to_connection_failure(self, failure):
# the error should be of this type specifically, if the onion
# is down, or was configured wrong:
failure.trap(HostUnreachableError)
# if this is a non-dir reachable peer, we just record
# the failure and explicitly give up:
if not self.directory:
log.info("We failed to connect to peer {}; giving up".format(
self.peer_location()))
self.reconnecting_service.stopService()
else:
# in this case, the still-running ClientService will
# just keep trying:
log.warn("We failed to connect to directory {}; trying "
"again.".format(self.peer_location()))
def register_connection(self) -> None:
self.messagechannel.register_connection(self.peer_location(),
direction=1)
@ -484,12 +539,14 @@ class OnionPeer(object):
if not (self.hostname and self.port > 0):
raise OnionPeerConnectionError(
"Cannot disconnect without host, port info")
d = self.factory.proto_client.transport.loseConnection()
d.addCallback(self.complete_disconnection)
d.addErrback(log.warn, "Failed to disconnect from peer {}.".format(
self.peer_location()))
if self.factory:
d = self.reconnecting_service.stopService()
d.addCallback(self.complete_disconnection)
else:
self.messagechannel.proto_factory.disconnect_inbound_peer(
self.alternate_location)
def complete_disconnection(self) -> None:
def complete_disconnection(self, r) -> None:
log.debug("Disconnected from peer: {}".format(self.peer_location()))
self.update_status(PEER_STATUS_DISCONNECTED)
self.factory = None
@ -530,6 +587,7 @@ class OnionMessageChannel(MessageChannel):
# hostid is a feature to avoid replay attacks across message channels;
# TODO investigate, but for now, treat onion-based as one "server".
self.hostid = "onion-network"
self.btc_network = configdata["btcnet"]
# receives notification that we are shutting down
self.give_up = False
# for backwards compat: make sure MessageChannel log can refer to
@ -759,9 +817,10 @@ class OnionMessageChannel(MessageChannel):
# do not trigger on_welcome event until all directories
# configured are ready:
self.on_welcome_sent = False
self.directory_wait_counter = 0
self.wait_for_directories_loop = task.LoopingCall(
self.wait_for_directories)
self.wait_for_directories_loop.start(10.0)
self.wait_for_directories_loop.start(2.0)
def handshake_as_client(self, peer: OnionPeer) -> None:
assert peer.status() == PEER_STATUS_CONNECTED
@ -772,6 +831,7 @@ class OnionMessageChannel(MessageChannel):
our_hs = copy.deepcopy(client_handshake_json)
our_hs["location-string"] = self.self_as_peer.peer_location()
our_hs["nick"] = self.nick
our_hs["network"] = self.btc_network
our_hs_json = json.dumps(our_hs)
log.info("Sending this handshake: {} to peer {}".format(
our_hs_json, peer.peer_location()))
@ -780,6 +840,7 @@ class OnionMessageChannel(MessageChannel):
def handshake_as_directory(self, peer: OnionPeer, our_hs: dict) -> None:
assert peer.status() == PEER_STATUS_CONNECTED
our_hs["network"] = self.btc_network
our_hs_json = json.dumps(our_hs)
log.info("Sending this handshake as directory: {}".format(
our_hs_json))
@ -1015,10 +1076,12 @@ class OnionMessageChannel(MessageChannel):
features = handshake_json["features"]
accepted = handshake_json["accepted"]
nick = handshake_json["nick"]
net = handshake_json["network"]
assert isinstance(proto_max, int)
assert isinstance(proto_min, int)
assert isinstance(features, dict)
assert isinstance(nick, str)
assert isinstance(net, str)
except Exception as e:
log.warn("Invalid handshake message from: {},"
" exception: {}, message: {},ignoring".format(
@ -1029,11 +1092,19 @@ class OnionMessageChannel(MessageChannel):
# at all.
if not accepted:
log.warn("Directory: {} rejected our handshake.".format(peerid))
# explicitly choose to disconnect (if other side already did,
# this is no-op).
peer.disconnect()
return
if not (app_name == JM_APP_NAME and is_directory and JM_VERSION \
<= proto_max and JM_VERSION >= proto_min and accepted):
log.warn("Handshake from directory is incompatible or "
"rejected: {}".format(handshake_json))
peer.disconnect()
return
if not net == self.btc_network:
log.warn("Handshake from directory is on an incompatible "
"network: {}".format(net))
return
# We received a valid, accepting dn-handshake. Update the peer.
peer.update_status(PEER_STATUS_HANDSHAKED)
@ -1052,9 +1123,11 @@ class OnionMessageChannel(MessageChannel):
features = handshake_json["features"]
full_location_string = handshake_json["location-string"]
nick = handshake_json["nick"]
net = handshake_json["network"]
assert isinstance(proto_ver, int)
assert isinstance(features, dict)
assert isinstance(nick, str)
assert isinstance(net, str)
except Exception as e:
log.warn("(not dn) Invalid handshake message from: {}, "
"exception: {}, message: {}, ignoring".format(
@ -1066,6 +1139,10 @@ class OnionMessageChannel(MessageChannel):
log.warn("Invalid handshake name/version data: {}, from peer: "
"{}, rejecting.".format(message, peerid))
accepted = False
if not net == self.btc_network:
log.warn("Handshake from peer is on an incompatible "
"network: {}".format(net))
accepted = False
# If accepted, we should update the peer to have the full
# location which in general will not yet be present, so as to
# allow publishing their location via `getpeerlist`. Note
@ -1207,10 +1284,27 @@ class OnionMessageChannel(MessageChannel):
def wait_for_directories(self) -> None:
# Notice this is checking for *handshaked* dps;
# the handshake will have been initiated once a
# connection was seen:
# connection was seen.
# Note also that this is *only* called on startup,
# so we are guaranteed to have only directory peers.
if len(self.get_connected_directory_peers()) < len(self.peers):
self.directory_wait_counter += 1
# < 2*11 = 22 seconds; compare with CONNECT_TO_ONION_TIMEOUT;
# with current vals, we get to try twice before entirely
# giving up.
if self.directory_wait_counter < 11:
return
if len(self.get_connected_directory_peers()) == 0:
# at least one handshake must have succeeded, for us
# to continue.
log.error("We failed to connect and handshake with "
"ANY directories; onion messaging is not functioning.")
self.wait_for_directories_loop.stop()
return
# This is what triggers the start of taker/maker workflows.
# Note that even if the preceding (max) 50 seconds failed to
# connect all our configured dps, we will keep trying and they
# can still be used.
if not self.on_welcome_sent:
self.on_welcome(self)
self.on_welcome_sent = True

4
scripts/joinmarket-qt.py

@ -912,10 +912,10 @@ class SpendTab(QWidget):
daemon=daemon,
gui=True)
else:
#This will re-use IRC connections in background (daemon), no restart
#This will re-use message channels in background (daemon), no restart
self.clientfactory.getClient().client = self.taker
self.clientfactory.getClient().clientStart()
mainWindow.statusBar().showMessage("Connecting to IRC ...")
mainWindow.statusBar().showMessage("Connecting to message channels ...")
def takerInfo(self, infotype, infomsg):
if infotype == "INFO":

3
test/e2e-coinjoin-test.py

@ -32,7 +32,7 @@ log = get_log()
# For quicker testing, restrict the range of timelock
# addresses to avoid slow load of multiple bots.
# Note: no need to revert this change as ygrunner runs
# Note: no need to revert this change as test runs
# in isolation.
from jmclient import FidelityBondMixin
FidelityBondMixin.TIMELOCK_ERA_YEARS = 2
@ -62,6 +62,7 @@ def get_onion_messaging_config_regtest(run_num: int, dns=[1], hsd="", mode="TAKE
dn_nodes_list = ",".join(dns_to_use)
log.info("For node: {}, set dn list to: {}".format(run_num, dn_nodes_list))
cf = {"type": "onion",
"btcnet": "testnet",
"socks5_host": "127.0.0.1",
"socks5_port": 9050,
"tor_control_host": "127.0.0.1",

Loading…
Cancel
Save