Skip to content
This repository was archived by the owner on May 13, 2022. It is now read-only.

Commit 1766ec1

Browse files
committed
add Dockerfile and entrypoint.py
1 parent f35b724 commit 1766ec1

File tree

8 files changed

+297
-5
lines changed

8 files changed

+297
-5
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.git/
2+
env/

Dockerfile

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#
2+
# Quick and easy joinmarket
3+
#
4+
# docker build -t "bwstitt/joinmarket:latest" .
5+
#
6+
# Copying of the code is delayed as long as possible so that rebuilding the container while developing is faster
7+
# This also means some of the install steps aren't in the most obvious order and the virtualenv is outside the code
8+
#
9+
10+
FROM bwstitt/library-debian:jessie
11+
12+
# Install packages for joinmarket
13+
RUN docker-apt-install \
14+
gcc \
15+
libsodium13 \
16+
python-dev \
17+
virtualenv
18+
19+
# i needed these when compiling myself, but new versions of pip with wheels save us
20+
#libatlas-dev \
21+
#libblas-dev \
22+
#libfreetype6-dev \
23+
#libpng12-dev \
24+
#libsodium-dev \
25+
#pkg-config \
26+
#python-dev \
27+
28+
# create a user
29+
RUN useradd -ms /bin/bash joinmarket
30+
31+
# install deps that don't depend on the code as the user
32+
USER joinmarket
33+
ENV HOME=/home/joinmarket
34+
WORKDIR /home/joinmarket
35+
# todo: will python3 work?
36+
RUN virtualenv -p python2 ~/pyenv \
37+
&& . ~/pyenv/bin/activate \
38+
&& pip install -U setuptools pip \
39+
&& pip install --no-cache-dir matplotlib numpy
40+
# todo: do something to calculate font cache with matplotlib
41+
# /home/joinmarket/pyenv/local/lib/python2.7/site-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.
42+
43+
# install the code
44+
COPY . /home/joinmarket/app
45+
# todo: i wish copy would keep the user...
46+
USER root
47+
RUN chown -R joinmarket:joinmarket /home/joinmarket/app
48+
USER joinmarket
49+
WORKDIR /home/joinmarket/app
50+
51+
# install deps from the code as the user
52+
RUN . ~/pyenv/bin/activate \
53+
&& pip install --no-cache-dir -r requirements.txt \
54+
&& mkdir -p logs wallets
55+
56+
# setup data volumes for logs and wallets
57+
# todo: handle the blacklist and commitments
58+
VOLUME ["/home/joinmarket/app/logs", "/home/joinmarket/app/wallets"]
59+
60+
ENV MPLBACKEND=agg
61+
ENTRYPOINT ["/home/joinmarket/app/docker/entrypoint.py"]
62+
CMD ["ob_watcher"]

check-config.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from __future__ import absolute_import
2+
3+
import sys
4+
5+
from joinmarket import get_log, load_program_config
6+
7+
8+
log = get_log()
9+
10+
11+
def main():
12+
"""Simple command to make sure the config loads.
13+
14+
This will exit 1 if the config cannot be loaded or the blockchaininterface
15+
doesn't respond.
16+
"""
17+
try:
18+
load_program_config()
19+
except Exception:
20+
log.exception("Error while loading config")
21+
return 1
22+
23+
24+
if __name__ == "__main__":
25+
sys.exit(main())

docker/README

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
todo:
2+
- explain how to setup Tor
3+
- explain how to setup bitcoind
4+
- explain how to setup the config (and figure out how to keep sensitive things out of git)

docker/entrypoint.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/home/joinmarket/pyenv/bin/python
2+
"""Entrypoints for Docker containers to run joinmarket.
3+
4+
todo: wait_for_file should probably be a decorator
5+
todo: either always force wallet.json or make waiting for the wallet smarter
6+
"""
7+
import argparse
8+
import logging
9+
import os
10+
import os.path
11+
import subprocess
12+
import sys
13+
import time
14+
15+
16+
#
17+
# Helpers
18+
#
19+
20+
DEBUG_LOG_FORMAT = (
21+
'-' * 80 + '\n' +
22+
'%(asctime)s %(levelname)s in %(name)s @ %(threadName)s:\n' +
23+
'%(pathname)s:%(lineno)d\n' +
24+
'%(message)s\n' +
25+
'-' * 80
26+
)
27+
DEFAULT_LOG_FORMAT = '%(asctime)s %(levelname)s: %(threadName)s: %(name)s: %(message)s' # noqa
28+
29+
log = logging.getLogger(__name__)
30+
31+
32+
def run(*args):
33+
"""Run a python command inside the joinmarket virtualenv.
34+
35+
Raises subprocess.CalledProcessError if the command fails.
36+
"""
37+
if not args:
38+
raise ValueError("run needs at least one arg")
39+
40+
command_list = [sys.executable] + map(str, args)
41+
42+
log.info("Running %s...", command_list[1])
43+
log.debug("Full command: %s", command_list)
44+
return subprocess.check_call(command_list, env=os.environ)
45+
46+
47+
def wait_for_config(*args):
48+
"""Sleep until config loads.
49+
50+
Config loading includes bitcoind responding to getblockchaininfo
51+
52+
Todo: exponential backoff of the sleep. maybe log less, too
53+
Todo: args here are hacky. make this function and the command seperate
54+
"""
55+
while True:
56+
try:
57+
run('check-config.py')
58+
except subprocess.CalledProcessError as e:
59+
log.error("Unable to load config: %s. Sleeping..." % e)
60+
time.sleep(60)
61+
else:
62+
break
63+
64+
return True
65+
66+
67+
def wait_for_file(filename, sleep=10):
68+
"""Sleep until a given file exists."""
69+
if os.path.exists(filename):
70+
return
71+
72+
log.info("'%s' does not exist. Check the README", filename)
73+
74+
log.info("Sleeping until '%s' exists...", filename)
75+
while not os.path.exists(filename):
76+
time.sleep(sleep)
77+
78+
log.info("Found '%s'", filename)
79+
return
80+
81+
82+
#
83+
# Commands
84+
#
85+
86+
def get_parser():
87+
"""Create an argument parser that routes to the command functions."""
88+
# create the top-level parser
89+
parser = argparse.ArgumentParser()
90+
# todo: configurable log level
91+
subparsers = parser.add_subparsers()
92+
93+
# create the parser for the "maker" command
94+
parser_maker = subparsers.add_parser('maker')
95+
parser_maker.set_defaults(func=maker)
96+
97+
# create the parser for the "ob_watcher" command
98+
parser_ob_watcher = subparsers.add_parser('ob_watcher')
99+
parser_ob_watcher.set_defaults(func=ob_watcher)
100+
101+
# create the parser for the "sendpayment" command
102+
parser_sendpayment = subparsers.add_parser('sendpayment')
103+
parser_sendpayment.set_defaults(func=sendpayment)
104+
105+
# create the parser for the "taker" command
106+
parser_tumbler = subparsers.add_parser('tumbler')
107+
parser_tumbler.set_defaults(func=tumbler)
108+
109+
# create the parser for the "wallet_tool" command
110+
parser_wallet_tool = subparsers.add_parser('wallet_tool')
111+
parser_wallet_tool.set_defaults(func=wallet_tool)
112+
113+
# other scripts might find waiting for the config helpful, too
114+
parser_wait_for_config = subparsers.add_parser('wait_for_config')
115+
parser_wait_for_config.set_defaults(func=wait_for_config)
116+
117+
return parser
118+
119+
120+
def maker(args):
121+
"""Earn Bitcoins and privacy."""
122+
wallet_filename = 'wallet.json'
123+
wait_for_file("wallets/%s" % wallet_filename)
124+
125+
# wait for bitcoind to respond
126+
wait_for_config()
127+
128+
run('yg-pe.py', wallet_filename, *args)
129+
130+
131+
def ob_watcher(args):
132+
"""Watch the orderbook."""
133+
# wait for bitcoind to respond
134+
# todo: although, why does the orderbook need bitcoind?
135+
wait_for_config()
136+
137+
run('ob-watcher.py', *args)
138+
139+
140+
def sendpayment(args):
141+
""""Send Bitcoins with privacy.
142+
143+
todo: make sure we only sendpayment with coins that have already been
144+
joined at least once.
145+
"""
146+
wallet_filename = 'wallet.json'
147+
wait_for_file("wallets/%s" % wallet_filename)
148+
149+
# wait for bitcoind to respond
150+
wait_for_config()
151+
152+
run('sendpayment.py', *args)
153+
154+
155+
def tumbler(args):
156+
""""Send Bitcoins with layers of privacy."""
157+
wallet_filename = 'wallet.json'
158+
wait_for_file("wallets/%s" % wallet_filename)
159+
160+
# wait for bitcoind to respond
161+
wait_for_config()
162+
163+
run('tumbler.py', *args)
164+
165+
166+
def wallet_tool(args):
167+
"""Inspect and manage your Bitcoin wallet."""
168+
run('wallet-tool.py', *args)
169+
170+
171+
def main():
172+
"""Manage joinmarket."""
173+
parser = get_parser()
174+
args, passthrough_args = parser.parse_known_args()
175+
176+
log_level = logging.DEBUG # todo: get log_level from args
177+
178+
if log_level == logging.DEBUG:
179+
log_format = DEBUG_LOG_FORMAT
180+
else:
181+
log_format = DEFAULT_LOG_FORMAT
182+
183+
logging.basicConfig(
184+
format=log_format,
185+
level=log_level,
186+
stream=sys.stdout,
187+
)
188+
log.debug("Hello!")
189+
190+
args.func(passthrough_args)
191+
192+
193+
if __name__ == '__main__':
194+
sys.exit(main())

joinmarket/blockchaininterface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ def add_watchonly_addresses(self, addr_list, wallet_name):
597597
print('restart Bitcoin Core with -rescan if you\'re '
598598
'recovering an existing wallet from backup seed')
599599
print(' otherwise just restart this joinmarket script')
600-
sys.exit(0)
600+
sys.exit(2)
601601

602602
def sync_wallet(self, wallet, fast=False):
603603
#trigger fast sync if the index_cache is available

joinmarket/jsonrpc.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ def __init__(self, obj):
3434
self.code = obj["code"]
3535
self.message = obj["message"]
3636

37+
def __str__(self):
38+
return "%s %s: %s" % (self.__class__.__name__, self.code, self.message)
39+
3740

3841
class JsonRpcConnectionError(Exception):
3942
"""

joinmarket/yieldgenerator.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
#! /usr/bin/env python
1+
#!/usr/bin/env python
22
from __future__ import absolute_import, print_function
33

44
import datetime
55
import os
6+
import sys
67
import time
78
import abc
89
from optparse import OptionParser
@@ -143,9 +144,10 @@ def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='reloffer'
143144
'https://github.com/chris-belcher/joinmarket/wiki/Running'
144145
'-JoinMarket-with-Bitcoin-Core-full-node')
145146
print(c)
146-
ret = raw_input('\nContinue? (y/n):')
147-
if ret[0] != 'y':
148-
return
147+
if sys.stdout.isatty():
148+
ret = raw_input('\nContinue? (y/n):')
149+
if ret[0] != 'y':
150+
return
149151

150152
wallet = Wallet(seed, max_mix_depth=mix_levels, gaplimit=gaplimit)
151153
sync_wallet(wallet, fast=options.fastsync)

0 commit comments

Comments
 (0)