UNIT = 100000000        # The same across currencies.
CLIENT_VERSION_MAJOR = 1
CLIENT_VERSION_MINOR = 5
CLIENT_VERSION = float(str(CLIENT_VERSION_MAJOR) + '.' + str(CLIENT_VERSION_MINOR))
DB_VERSION_MAJOR = 1        # Major version changes the blocks or transactions table.
DB_VERSION_MINOR = 5        # Minor version changes just the parsing.
DB_VERSION = float(str(DB_VERSION_MAJOR) + '.' + str(DB_VERSION_MINOR))
BITCOIND_RPC_PORT_LIVE = '8332'
BITCOIND_RPC_PORT_TESTNET = '18332'
RPC_HOST = 'localhost'
RPC_PORT_TESTNET = '14000'
RPC_PORT_LIVE = '4000'
BITCOIND_RPC_CONNECT = 'localhost'
BITCOIND_RPC_USER = 'bitcoinrpc'
RPC_USER = 'rpc'
TESTNET = False
TESTCOIN = False
UNITTEST_PREFIX = b'TESTXXXX'
PREFIX_TESTCOIN = b'CH'
PREFIX_LIVECOIN = b'CHANCECO'
APPAUTHOR = 'Chancecoin'
APPNAME = 'chancecoin'
API_LOG = 'api.error.log'
LOGGING_LOG = 'logging.log'
CONFIG_FILENAME = 'chancecoin.conf'
DATA_DIR_TESTNET = 'chancecoind.testnet.log'
DATA_DIR_LIVE = 'chancecoin.log'
DATABASE_NAME_PREFIX = 'chancecoin'
UNSPENDABLE_TESTNET_TESTCOIN = 'mvChancecoinXXXXXXXXXXXXXXXXW24Hef'
UNSPENDABLE_TESTNET_LIVECOIN = 'mvChancecoinXXXXXXXXXXXXXXXXW24Hef'
UNSPENDABLE_LIVE_TESTCOIN = '1ChancecoinXXXXXXXXXXXXXXXXXZELUFD'
UNSPENDABLE_LIVE_LIVECOIN = '1ChancecoinXXXXXXXXXXXXXXXXXZELUFD'
TESTNET_TESTCOIN_FIRST = 154908
TESTNET_TESTCOIN_START = 154908
TESTNET_TESTCOIN_END = 4017708
TESTNET_LIVECOIN_FIRST = 154908
TESTNET_LIVECOIN_START = 154908
TESTNET_LIVECOIN_END = 4017708
LIVE_TESTCOIN_FIRST = 288270
LIVE_TESTCOIN_START = 288310
LIVE_TESTCOIN_END = 2500000
LIVE_LIVECOIN_FIRST = 291860
LIVE_LIVECOIN_START = LIVE_LIVECOIN_FIRST
LIVE_LIVECOIN_END = LIVE_LIVECOIN_START+6*24*45 #45 days
MAX_BURN = 1000000
MULTIPLIER = 1000
MULTIPLIER_INITIAL = 1500
HOUSE_EDGE = 0.02 #0.02 means the house takes 2% edge
MAX_PROFIT = 0.01 #0.01 means most you can win is 1% of the house bankroll
DUST_SIZE = 5430 * 2    # Multi‐sig (TODO: This is just a guess.)
MIN_FEE = 10000         # Chancecoin transactions are all under 1KB in size.
DATA_VALUE = 0
TXTYPE_FORMAT = '>I'
TWO_WEEKS = 2 * 7 * 24 * 3600
MAX_EXPIRATION = 4 * 2016   # Two months
MAX_INT = 2**63 - 1
FEE_REQUIRED_DEFAULT = .01   # 1%
FEE_PROVIDED_DEFAULT = .01   # 1% D = decimal.Decimal
DO_FILTER_OPERATORS = {
    '==': operator.eq,
    '!=': operator.ne,
    '<': operator.lt,
    '>': operator.gt,
    '<=': operator.le,
    '>=': operator.ge,
}
def price (numerator, denominator):
def log (db, command, category, bindings):
    def output (amount, asset):
def rowtracer(cursor, sql):
def exectracer(cursor, sql, bindings):
def connect_to_db():
def versions_check (db):
def is_bitcoind_connected():
def bitcoind_check (db):
def database_check (db):
def get_status (db):
def do_filter(results, filters, filterop):
def do_order_by(results, order_by, order_dir):
def get_limit_to_blocks(start_block, end_block, col_names=['block_index',]):
def isodt (epoch_time):
def cha_supply (db):
def last_block (db):
def get_block (db, block_index):
def get_asset_id (asset):
def get_asset_name (asset_id):
def debit (db, block_index, address, asset, amount, event=None):
def credit (db, block_index, address, asset, amount, event=None):
def devise (db, quantity, asset, dest, divisible=None):
    def norm(num, places):
def get_debits (db, address=None, asset=None, filters=None, order_by=None, order_dir='asc', start_block=None, end_block=None, filterop='and'):
def get_credits (db, address=None, asset=None, filters=None, order_by=None, order_dir='asc', start_block=None, end_block=None, filterop='and'):
def get_balances (db, address=None, asset=None, filters=None, order_by=None, order_dir='asc', filterop='and'):
def get_sends (db, validity=None, source=None, destination=None, filters=None, order_by='tx_index', order_dir='asc', start_block=None, end_block=None, filterop='and'):
def get_orders (db, validity=None, source=None, show_empty=True, show_expired=True, filters=None, order_by=None, order_dir='asc', start_block=None, end_block=None, filterop='and'):
    def filter_expired(e):
def get_order_matches (db, validity=None, is_mine=False, address=None, tx0_hash=None, tx1_hash=None, filters=None, order_by='tx1_index', order_dir='asc', start_block=None, end_block=None, filterop='and'):
    def filter_is_mine(e):
def get_btcpays (db, validity=None, filters=None, order_by='tx_index', order_dir='asc', start_block=None, end_block=None, filterop='and'):
def get_bets (db, validity=None, source=None, filters=None, order_by=None, order_dir='desc', start_block=None, end_block=None, filterop='and'):
def get_burns (db, validity=True, source=None, filters=None, order_by='tx_index', order_dir='asc', start_block=None, end_block=None, filterop='and'):
def get_cancels (db, validity=True, source=None, filters=None, order_by=None, order_dir=None, start_block=None, end_block=None, filterop='and'):
def get_order_expirations (db, source=None, filters=None, order_by=None, order_dir=None, start_block=None, end_block=None, filterop='and'):
def get_order_match_expirations (db, address=None, filters=None, order_by=None, order_dir=None, start_block=None, end_block=None, filterop='and'):
def get_address (db, address, start_block=None, end_block=None): class ConfigurationError (Exception):
class DatabaseError (Exception):
class VersionError (Exception):
class ClientVersionError (VersionError):
class DatabaseVersionError (VersionError):
class TransactionError(Exception):
class InputError(Exception):
class BitcoindError (Exception):
class BitcoindRPCError (BitcoindError):
class ZeroMQError (Exception):
class FeeError (Exception):
class BalanceError (Exception):
class QuantityError(Exception):
class InvalidAddressError (Exception):
class VersionByteError (InvalidAddressError):
class Base58Error (InvalidAddressError):
class InvalidBase58Error (Base58Error):
class Base58ChecksumError (Base58Error):
class AssetError (Exception):
class AssetNameError (AssetError):
class AssetIDError (AssetError):
class MessageError (Exception):
class BurnError (MessageError):
class SendError (MessageError):
class OrderError (MessageError):
class BetError (MessageError):
class BTCPayError (MessageError):
class CancelError (MessageError): OP_RETURN = b'\x6a'
OP_PUSHDATA1 = b'\x4c'
OP_DUP = b'\x76'
OP_HASH160 = b'\xa9'
OP_EQUALVERIFY = b'\x88'
OP_CHECKSIG = b'\xac'
OP_1 = b'\x51'
OP_2 = b'\x52'
OP_CHECKMULTISIG = b'\xae'
    TRIES = 12
        CURR_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))
def connect (host, payload, headers):
def rpc (method, params):
def base58_decode (s, version):
def var_int (i):
def op_push (i):
def serialise (inputs, destination_output=None, data_output=None, change_output=None, multisig=False, source=None, unsigned=False):
def get_inputs (source, total_btc_out, unittest=False, unsigned=False):
def transaction (source, destination, btc_amount, fee, data, unittest=False, multisig=True, unsigned=False):
        def chunks(l, n):
def serialise_multioutput (inputs, outputs, change_output=None, source=None, unsigned=False):
def transaction_multioutput (source, destination_btc_amounts, fee, unittest=False, unsigned=False):
def transmit (unsigned_tx_hex, ask=True, unsigned=False): D = decimal.Decimal
def parse_tx (db, tx):
def parse_block (db, block_index, block_time):
def initialise(db):
def get_tx_info (tx):
def reparse (db, block_index=None, quiet=False):
def reorg (db):
def follow (db): FORMAT = '>QQ'
LENGTH = 8 + 8
ID = 0
def validate (db, source, destination, amount, asset):
def create (db, source, destination, amount, asset, unsigned=False):
def parse (db, tx, message): D = decimal.Decimal
FORMAT = '>QQQQHQ'
LENGTH = 8 + 8 + 8 + 8 + 2 + 8
ID = 10
def validate (db, source, give_asset, give_amount, get_asset, get_amount, expiration, fee_required):
def create (db, source, give_asset, give_amount, get_asset, get_amount, expiration, fee_required, fee_provided, unsigned=False):
def parse (db, tx, message):
def match (db, tx):
def expire (db, block_index): FORMAT = '>32s32s'
LENGTH = 32 + 32
ID = 11
def validate (db, order_match_id):
def create (db, order_match_id, unsigned=False):
def parse (db, tx, message): D = decimal.Decimal
FORMAT = '>Qdd'
LENGTH = 8 + 8 + 8
ID = 40
                    N = combinations(80,20)
def validate (db, source, bet, chance, payout):
def create (db, source, bet, chance, payout, unsigned=False):
def parse (db, tx, message):
def resolve(db):
    def combinations(n,k): D = decimal.Decimal
ID = 60
def validate (db, source, destination, quantity, block_index=None):
def create (db, source, quantity, unsigned=False):
def parse (db, tx, message=None): FORMAT = '>32s'
LENGTH = 32
ID = 70
def validate (db, offer_hash, source=None):
def create (db, offer_hash, unsigned=False):
def parse (db, tx, message):
import os
import argparse
import json
import random
import string
import decimal
import sys
import logging
import requests
import unicodedata
import time
import dateutil.parser
import calendar
import appdirs
import logging
import configparser
import shutil
from logging import handlers
from prettytable import PrettyTable
from threading import Thread
from lib import (config, util, exceptions, bitcoin, blocks)
from lib import (send, order, btcpay, bet, burn, cancel)
    from lib import util_windows

    parser_btcpay= subparsers.add_parser('btcpay', help='create and broadcast a *BTCpay* message, to settle an Order Match for which you owe BTC')
    parser_btcpay.add_argument('--order-match-id', required=True, help='the concatenation of the hashes of the two transactions which compose the order match')

    parser_bet = subparsers.add_parser('bet', help='offer to make a bet on the value of a feed')
    parser_bet.add_argument('--source', required=True, help='the source address')
    parser_bet.add_argument('--bet', required=True, help='the quantity of CHA to bet')
    parser_bet.add_argument('--chance', required=True, help='chance of winning')
    parser_bet.add_argument('--payout', required=True, help='payout if you win (must be congruent with chance of winning)')

    parser_burn = subparsers.add_parser('burn', help='destroy bitcoins to earn CHA, during an initial period of time')
    parser_burn.add_argument('--source', required=True, help='the source address')
    parser_burn.add_argument('--quantity', required=True, help='quantity of BTC to be destroyed')

    parser_cancel= subparsers.add_parser('cancel', help='cancel an open order or bet you created')
    parser_cancel.add_argument('--offer-hash', required=True, help='the transaction hash of the order or bet')

    parser_address = subparsers.add_parser('balances', help='display the balances of a Chancecoin address')
    parser_address.add_argument('address', help='the address you are interested in')

    parser_wallet = subparsers.add_parser('wallet', help='list the addresses in your Bitcoind wallet along with their balances in all Chancecoin assets')

    parser_reparse = subparsers.add_parser('reparse', help='reparse all transactions in the database (WARNING: not thread‐safe)')

    parser_rollback = subparsers.add_parser('rollback', help='rollback database (WARNING: not thread‐safe)')
    parser_rollback.add_argument('block_index', type=int, help='the index of the last known good block')

    args = parser.parse_args()

    # Configuration
    set_options(data_dir=args.data_dir, bitcoind_rpc_connect=args.bitcoind_rpc_connect, bitcoind_rpc_port=args.bitcoind_rpc_port,
                 bitcoind_rpc_user=args.bitcoind_rpc_user, bitcoind_rpc_password=args.bitcoind_rpc_password, rpc_host=args.rpc_host, rpc_port=args.rpc_port,
                 rpc_user=args.rpc_user, rpc_password=args.rpc_password, log_file=args.log_file, database_file=args.database_file, testnet=args.testnet, testcoin=args.testcoin, unittest=False, headless=args.headless)

    # Database
    db = util.connect_to_db()

    # Logging (to file and console).
    logger = logging.getLogger() #get root logger
    logger.setLevel(logging.DEBUG if args.verbose else logging.INFO)
    #Console logging
    console = logging.StreamHandler()
    console.setLevel(logging.DEBUG if args.verbose else logging.INFO)
    formatter = logging.Formatter('%(message)s')
    console.setFormatter(formatter)
    logger.addHandler(console)
    #File logging (rotated)
    max_log_size = 2 * 1024 * 1024 #max log size of 2 MB before rotation (make configurable later)
    if os.name == 'nt':
        fileh = util_windows.SanitizedRotatingFileHandler(config.LOG, maxBytes=max_log_size, backupCount=5)
    else:
        fileh = logging.handlers.RotatingFileHandler(config.LOG, maxBytes=max_log_size, backupCount=5)
    fileh.setLevel(logging.DEBUG if args.verbose else logging.INFO)
    formatter = logging.Formatter('%(asctime)s %(message)s', '%Y-%m-%d-T%H:%M:%S%z')
    fileh.setFormatter(formatter)
    logger.addHandler(fileh)
    #API requests logging (don't show on console in normal operation)
    requests_log = logging.getLogger("requests")
    requests_log.setLevel(logging.DEBUG if args.verbose else logging.WARNING)

    if args.action == None: args.action = 'server'

    # Check versions.
    util.versions_check(db)

    # Check that bitcoind is running, communicable, and caught up with the blockchain.
    # Check that the database has caught up with bitcoind.
    if not args.force:
        util.bitcoind_check(db)
        if args.action not in ('server', 'reparse', 'rollback'):
            util.database_check(db)

    # Do something.
    if args.action == 'send':
        quantity = util.devise(db, args.quantity, args.asset, 'input')
        unsigned_tx_hex = send.create(db, args.source, args.destination,
                                      quantity, args.asset, unsigned=args.unsigned)
        print(unsigned_tx_hex) if args.unsigned else json_print(bitcoin.transmit(unsigned_tx_hex))

    elif args.action == 'order':
        fee_required, fee_provided = D(args.fee_required), D(args.fee_provided)
        give_quantity, get_quantity = D(args.give_quantity), D(args.get_quantity)

        # Fee argument is either fee_required or fee_provided, as necessary.
        if args.give_asset == 'BTC':
            fee_required = 0
            fee_provided = util.devise(db, fee_provided, 'fraction', 'input')
            fee_provided = round(D(fee_provided) * D(give_quantity) * D(config.UNIT))
            if fee_provided < config.MIN_FEE:
                raise exceptions.InputError('Fee provided less than minimum necessary for acceptance in a block.')
        elif args.get_asset == 'BTC':
            fee_provided = config.MIN_FEE
            fee_required = util.devise(db, fee_required, 'fraction', 'input')
            fee_required = round(D(fee_required) * D(get_quantity) * D(config.UNIT))
        else:
            fee_required = 0
            fee_provided = config.MIN_FEE

        give_quantity = util.devise(db, give_quantity, args.give_asset, 'input')
        get_quantity = util.devise(db, get_quantity, args.get_asset, 'input')
        unsigned_tx_hex = order.create(db, args.source, args.give_asset, give_quantity,
                                args.get_asset, get_quantity,
                                args.expiration, fee_required, fee_provided, unsigned=args.unsigned)
        print(unsigned_tx_hex) if args.unsigned else json_print(bitcoin.transmit(unsigned_tx_hex))

    elif args.action == 'btcpay':
        unsigned_tx_hex = btcpay.create(db, args.order_match_id, unsigned=args.unsigned)
        print(unsigned_tx_hex) if args.unsigned else json_print(bitcoin.transmit(unsigned_tx_hex))

    elif args.action == 'bet':
        bet = util.devise(db, args.bet, 'CHA', 'input')
        chance = util.devise(db, args.chance, 'value', 'input')
        payout = util.devise(db, args.payout, 'value', 'input')

        unsigned_tx_hex = bet.create(db, args.source, bet, chance, payout, unsigned=args.unsigned)
        print(unsigned_tx_hex) if args.unsigned else json_print(bitcoin.transmit(unsigned_tx_hex))

    elif args.action == 'burn':
        quantity = util.devise(db, args.quantity, 'BTC', 'input')
        unsigned_tx_hex = burn.create(db, args.source, quantity, unsigned=args.unsigned)

        print(unsigned_tx_hex) if args.unsigned else json_print(bitcoin.transmit(unsigned_tx_hex))