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

Commit

Permalink
add Dockerfile and entrypoint.py
Browse files Browse the repository at this point in the history
  • Loading branch information
BlinkyStitt committed Nov 21, 2016
1 parent c9aa523 commit f2725c5
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.git/
env/
67 changes: 67 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#
# Quick and easy joinmarket
#
# docker build -t "bwstitt/joinmarket:latest" .
#
# Copying of the code is delayed as long as possible so that rebuilding the container while developing is faster
# This also means some of the install steps aren't in the most obvious order and the virtualenv is outside the code
#

FROM bwstitt/library-debian:jessie

# Install packages for joinmarket
RUN docker-apt-install \
gcc \
libsodium13 \
python-dev \
virtualenv

# i needed these when compiling myself, but new versions of pip with wheels save us
#libatlas-dev \
#libblas-dev \
#libfreetype6-dev \
#libpng12-dev \
#libsodium-dev \
#pkg-config \
#python-dev \

# create a user
RUN useradd -ms /bin/bash joinmarket

# install deps that don't depend on the code as the user
USER joinmarket
ENV HOME=/home/joinmarket
WORKDIR /home/joinmarket
# todo: will python3 work?
RUN virtualenv -p python2 ~/pyenv \
&& . ~/pyenv/bin/activate \
&& pip install -U setuptools pip \
&& pip install --no-cache-dir matplotlib numpy
# todo: do something to calculate font cache with matplotlib
# /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.

# copy requirements before code. this will make the image builds faster when developing
COPY requirements.txt /home/joinmarket/app/
RUN . ~/pyenv/bin/activate \
&& pip install --no-cache-dir -r /home/joinmarket/app/requirements.txt

# install the code
COPY . /home/joinmarket/app
# todo: i wish copy would keep the user...
USER root
RUN chown -R joinmarket:joinmarket /home/joinmarket/app
USER joinmarket
WORKDIR /home/joinmarket/app

# install deps from the code as the user
RUN . ~/pyenv/bin/activate \
&& pip install --no-cache-dir -r requirements.txt \
&& mkdir -p logs wallets

# setup data volumes for logs and wallets
# todo: handle the blacklist and commitments
VOLUME ["/home/joinmarket/app/logs", "/home/joinmarket/app/wallets"]

ENV MPLBACKEND=agg
ENTRYPOINT ["/home/joinmarket/app/docker/entrypoint.py"]
CMD ["ob_watcher"]
25 changes: 25 additions & 0 deletions check-config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from __future__ import absolute_import

import sys

from joinmarket import get_log, load_program_config


log = get_log()


def main():
"""Simple command to make sure the config loads.
This will exit 1 if the config cannot be loaded or the blockchaininterface
doesn't respond.
"""
try:
load_program_config()
except Exception:
log.exception("Error while loading config")
return 1


if __name__ == "__main__":
sys.exit(main())
4 changes: 4 additions & 0 deletions docker/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
todo:
- explain how to setup Tor
- explain how to setup bitcoind
- explain how to setup the config (and figure out how to keep sensitive things out of git)
208 changes: 208 additions & 0 deletions docker/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#!/home/joinmarket/pyenv/bin/python
"""Entrypoints for Docker containers to run joinmarket.
todo: wait_for_file should probably be a decorator
todo: either always force wallet.json or make waiting for the wallet smarter
"""
import argparse
import logging
import os
import os.path
import subprocess
import sys
import time


#
# Helpers
#

DEBUG_LOG_FORMAT = (
'-' * 80 + '\n' +
'%(asctime)s %(levelname)s in %(name)s @ %(threadName)s:\n' +
'%(pathname)s:%(lineno)d\n' +
'%(message)s\n' +
'-' * 80
)
DEFAULT_LOG_FORMAT = '%(asctime)s %(levelname)s: %(threadName)s: %(name)s: %(message)s' # noqa

log = logging.getLogger(__name__)


def run(*args):
"""Run a python command inside the joinmarket virtualenv.
Raises subprocess.CalledProcessError if the command fails.
"""
if not args:
raise ValueError("run needs at least one arg")

command_list = [sys.executable] + map(str, args)

log.info("Running %s...", command_list[1])
log.debug("Full command: %s", command_list)

return subprocess.check_call(command_list, env=os.environ)


def run_or_exit(*args):
"""Run a python command inside the joinmarket virtualenv.
Logs and exits if the command fails.
"""
try:
return run(*args)
except subprocess.CalledProcessError as e:
log.error("%s", e)
sys.exit(e.returncode)


def wait_for_config(*args):
"""Sleep until config loads.
Config loading includes bitcoind responding to getblockchaininfo
Todo: exponential backoff of the sleep. maybe log less, too
Todo: args here are hacky. make this function and the command seperate
"""
while True:
try:
run('check-config.py')
except subprocess.CalledProcessError as e:
# TODO: this is too verbose
log.error("Unable to load config: %s. Sleeping..." % e)
time.sleep(60)
else:
break

return True


def wait_for_file(filename, sleep=10):
"""Sleep until a given file exists."""
if os.path.exists(filename):
return

log.info("'%s' does not exist. Check the README", filename)

log.info("Sleeping until '%s' exists...", filename)
while not os.path.exists(filename):
time.sleep(sleep)

log.info("Found '%s'", filename)
return


#
# Commands
#

def get_parser():
"""Create an argument parser that routes to the command functions."""
# create the top-level parser
parser = argparse.ArgumentParser()
# todo: configurable log level
subparsers = parser.add_subparsers()

# create the parser for the "maker" command
parser_maker = subparsers.add_parser('maker')
parser_maker.set_defaults(func=maker)

# create the parser for the "ob_watcher" command
parser_ob_watcher = subparsers.add_parser('ob_watcher')
parser_ob_watcher.set_defaults(func=ob_watcher)

# create the parser for the "sendpayment" command
parser_sendpayment = subparsers.add_parser('sendpayment')
parser_sendpayment.set_defaults(func=sendpayment)

# create the parser for the "taker" command
parser_tumbler = subparsers.add_parser('tumbler')
parser_tumbler.set_defaults(func=tumbler)

# create the parser for the "wallet_tool" command
parser_wallet_tool = subparsers.add_parser('wallet_tool')
parser_wallet_tool.set_defaults(func=wallet_tool)

# other scripts might find waiting for the config helpful, too
parser_wait_for_config = subparsers.add_parser('wait_for_config')
parser_wait_for_config.set_defaults(func=wait_for_config)

return parser


def maker(args):
"""Earn Bitcoins and privacy."""
wallet_filename = 'wallet.json'
wait_for_file("wallets/%s" % wallet_filename)

# wait for bitcoind to respond
wait_for_config()

run_or_exit('yg-pe.py', wallet_filename, *args)


def ob_watcher(args):
"""Watch the orderbook."""
# wait for bitcoind to respond
# todo: although, why does the orderbook need bitcoind?
wait_for_config()

run_or_exit('ob-watcher.py', *args)


def sendpayment(args):
""""Send Bitcoins with privacy.
todo: make sure we only sendpayment with coins that have already been
joined at least once.
"""
wallet_filename = 'wallet.json'
wait_for_file("wallets/%s" % wallet_filename)

# wait for bitcoind to respond
wait_for_config()

run_or_exit('sendpayment.py', *args)


def tumbler(args):
""""Send Bitcoins with layers of privacy."""
wallet_filename = 'wallet.json'
wait_for_file("wallets/%s" % wallet_filename)

# wait for bitcoind to respond
wait_for_config()

run_or_exit('tumbler.py', *args)


def wallet_tool(args):
"""Inspect and manage your Bitcoin wallet."""
run_or_exit('wallet-tool.py', *args)


def main():
"""Manage joinmarket."""
parser = get_parser()
args, passthrough_args = parser.parse_known_args()

log_level = logging.DEBUG # todo: get log_level from args

if log_level == logging.DEBUG:
log_format = DEBUG_LOG_FORMAT
else:
log_format = DEFAULT_LOG_FORMAT

logging.basicConfig(
format=log_format,
level=log_level,
stream=sys.stdout,
)
log.debug("Hello!")

args.func(passthrough_args)


if __name__ == '__main__':
sys.exit(main())
2 changes: 1 addition & 1 deletion joinmarket/blockchaininterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ def add_watchonly_addresses(self, addr_list, wallet_name):
print('restart Bitcoin Core with -rescan if you\'re '
'recovering an existing wallet from backup seed')
print(' otherwise just restart this joinmarket script')
sys.exit(0)
sys.exit(2)

def sync_wallet(self, wallet, fast=False):
#trigger fast sync if the index_cache is available
Expand Down
12 changes: 7 additions & 5 deletions joinmarket/jsonrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ def __init__(self, obj):
self.code = obj["code"]
self.message = obj["message"]

def __str__(self):
return "%s %s: %s" % (self.__class__.__name__, self.code, self.message)


class JsonRpcConnectionError(Exception):
"""
Expand Down Expand Up @@ -83,14 +86,13 @@ def queryHTTP(self, obj):
raise JsonRpcConnectionError(
"authentication for JSON-RPC failed")

# All of the codes below are 'fine' from a JSON-RPC point of view.
if response.status not in [200, 404, 500]:
conn.close()
raise JsonRpcConnectionError("unknown error in JSON-RPC")

data = response.read()
conn.close()

# All of the codes below are 'fine' from a JSON-RPC point of view.
if response.status not in [200, 404, 500]:
raise JsonRpcConnectionError("unknown error %s in JSON-RPC: %s" % (response.status, data))

return json.loads(data)

except JsonRpcConnectionError as exc:
Expand Down
10 changes: 6 additions & 4 deletions joinmarket/yieldgenerator.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#! /usr/bin/env python
#!/usr/bin/env python
from __future__ import absolute_import, print_function

import datetime
import os
import sys
import time
import abc
from optparse import OptionParser
Expand Down Expand Up @@ -143,9 +144,10 @@ def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='reloffer'
'https://github.com/chris-belcher/joinmarket/wiki/Running'
'-JoinMarket-with-Bitcoin-Core-full-node')
print(c)
ret = raw_input('\nContinue? (y/n):')
if ret[0] != 'y':
return
if sys.stdout.isatty():
ret = raw_input('\nContinue? (y/n):')
if ret[0] != 'y':
return

wallet = Wallet(seed, max_mix_depth=mix_levels, gaplimit=gaplimit)
sync_wallet(wallet, fast=options.fastsync)
Expand Down

0 comments on commit f2725c5

Please sign in to comment.