Ejemplo n.º 1
0
def getSignedUTCTimestamp():
    # Get a signed timestamp
    bt = BlocTime()
    signedUTCNow = bt.signedUTCNow()

    tsOutput = {'timeStampUTC': str(signedUTCNow['timeStampUTC']),
                'timeStampUTCSignature': str(signedUTCNow['timeStampUTCSignature']),
                'verifyKey': signedUTCNow['verifyKey']}
    return json.dumps(tsOutput)
    def __init__(self):

        # self.engine = create_engine('sqlite:///:memory:', echo=True)
        # self.engine = create_engine("postgresql://*****:*****@localhost/blocparty",
        #                              isolation_level="AUTOCOMMIT")
        # Note postgress needs AUTOCOMMIT or else postgress hangs when it gets to a matching trade
        # DATABASE_URL = 'sqlite:///pmarket.db'
        # DATABASE_URL = 'postgresql://*****:*****@ec2-54-83-8-246.compute-1.amazonaws.com:5432/dbee8j5ki95jfn'

        if platform.system() == 'Darwin':
            # Use local postgres if on mac
            DATABASE_URL = "postgresql://*****:*****@localhost/blocparty"
        elif platform.version().find('Ubuntu') != -1:
            DATABASE_URL = "postgresql://*****:*****@localhost/blocparty"
        else:
            # Use DATABASE_URL from env otherwise
            DATABASE_URL = os.environ['HEROKU_POSTGRESQL_JADE_URL']

        self.engine = create_engine(DATABASE_URL,
                                    isolation_level="AUTOCOMMIT",
                                    poolclass=NullPool)
        self.engine.echo = False
        self.metadata = MetaData(self.engine)
        # Users
        self.userTable = Table(
            'userTable',
            self.metadata,
            Column('traderId', Integer, primary_key=True),
            Column('verifyKey', String),
        )
        # Timestamp servers for signing UTC timestamps
        self.timeStampServerTable = Table(
            'timeStampServerTable',
            self.metadata,
            Column('tssId', Integer, primary_key=True, autoincrement=True),
            Column('verifyKey', String),
        )
        # Order book for all trades, including including order book,
        # matched, and linked trades
        self.orderBook = Table(
            'orderBook',
            self.metadata,
            Column('tradeId', Integer, primary_key=True, autoincrement=True),
            Column('price', Integer),
            Column('quantity', Integer),
            Column('marketId', Integer),
            Column(
                'traderId',
                Integer,
            ),
            Column('previousSig', LargeBinary),
            Column('signature', LargeBinary),
            Column('iMatched', Boolean),
            Column('iRemoved', Boolean),
            Column('timeStampUTC', TIMESTAMP),
            Column('timeStampUTCSignature', LargeBinary),
        )

        # Market table with minimum and maximum of each market.
        self.marketTable = Table('marketTable', self.metadata,
                                 Column('marketRootId', Integer),
                                 Column('marketBranchId', Integer),
                                 Column('marketId', Integer),
                                 Column('marketMin', Integer),
                                 Column('marketMax', Integer),
                                 Column('traderId', Integer),
                                 Column('previousSig', LargeBinary),
                                 Column('signature', LargeBinary),
                                 Column('timeStampUTC', TIMESTAMP),
                                 Column('timeStampUTCSignature', LargeBinary),
                                 Column('marketDesc', JSON))

        # Possible combinations of root market outcomes
        self.marketBounds = Table(
            'marketBounds',
            self.metadata,
            Column('marketBounds', Integer),
            Column('marketRootId', Integer),
            Column('marketBranchId', Integer),
            Column('marketMin', Integer),
            Column('marketMax', Integer),
        )

        # Market state (possible combinations)
        self.outcomeCombinations = Table(
            'outcomeCombinations',
            self.metadata,
            Column('outcomeId', Integer, primary_key=True),
            Column('marketRootId', Integer),
            Column('marketBranchId', Integer),
            Column('marketMin', Integer),
            Column('marketMax', Integer),
        )

        # SP tables
        '''
        spevent -  info for event
        spmarket - markets on events
        sprecord - odds records
        spscore - score records
        '''
        self.spevent = Table(
            'spevent', self.metadata,
            Column('eventid', Integer, primary_key=True, autoincrement=True),
            Column('sport', VARCHAR), Column('competition', VARCHAR),
            Column('event', JSON), Column('starttimestamputc', TIMESTAMP))

        self.spmarket = Table(
            'spmarket', self.metadata,
            Column('marketid', Integer, primary_key=True, autoincrement=True),
            Column('eventid', Integer), Column('markettype', VARCHAR),
            Column('runners', JSON), Column('marketparameters', JSON),
            Column('notes', VARCHAR))

        self.sprecord = Table(
            'sprecord', self.metadata,
            Column('recordid', Integer, primary_key=True, autoincrement=True),
            Column('source', VARCHAR), Column('marketid', Integer),
            Column('runnerid', Integer), Column('timestamputc', TIMESTAMP),
            Column('handicap', Float), Column('odds', Float),
            Column('stake', Float), Column('islay', Boolean),
            Column('isplaced', Boolean), Column('notes', VARCHAR))

        self.spscore = Table(
            'spscore', self.metadata,
            Column('scoreid', Integer, primary_key=True, autoincrement=True),
            Column('eventid', Integer), Column('runnerid', Integer),
            Column('timestamputc', TIMESTAMP), Column('measure', VARCHAR),
            Column('value', Integer), Column('isfinal', Boolean))

        # Create all tables
        self.metadata.create_all(self.engine)
        self.conn = self.engine.connect()

        # Collateral limit
        self.COLLATERAL_LIMIT = 1e9
        # Number of markets limit (too many will make too many outcome combinations)
        self.ROOT_MARKET_LIMIT = 5
        self.BRANCH_MARKET_LIMIT = 10

        # Temporary local variables
        self.marketOutcomes = np.array([])  # Market corners
        self.p = np.array([0])
        self.q = np.array([0])
        self.mInd = np.array([0])
        self.tInd = np.array([0])
        self.iMatched = np.array([False])
        self.sig = np.array([0])

        # Time server
        self.TimeServer = BlocTime()
        # Register time series server
        ts = self.TimeServer.signedUTCNow()
        tssTable = pd.read_sql_query(
            'SELECT * FROM "timeStampServerTable" WHERE "verifyKey" = \'%s\'' %
            (ts['verifyKey']), self.conn)
        if tssTable.empty:
            newTSS = dict(verifyKey=ts['verifyKey'])
            # Insert to timeStampServerTable (autoincrements tssID)
            self.conn.execute(self.timeStampServerTable.insert(), [
                newTSS,
            ])

        self.tssTable = pd.read_sql_query(
            'SELECT * FROM "timeStampServerTable" WHERE "verifyKey" = \'%s\'' %
            (ts['verifyKey']), self.conn)
class BlocServer(object):
    def __init__(self):

        # self.engine = create_engine('sqlite:///:memory:', echo=True)
        # self.engine = create_engine("postgresql://*****:*****@localhost/blocparty",
        #                              isolation_level="AUTOCOMMIT")
        # Note postgress needs AUTOCOMMIT or else postgress hangs when it gets to a matching trade
        # DATABASE_URL = 'sqlite:///pmarket.db'
        # DATABASE_URL = 'postgresql://*****:*****@ec2-54-83-8-246.compute-1.amazonaws.com:5432/dbee8j5ki95jfn'

        if platform.system() == 'Darwin':
            # Use local postgres if on mac
            DATABASE_URL = "postgresql://*****:*****@localhost/blocparty"
        elif platform.version().find('Ubuntu') != -1:
            DATABASE_URL = "postgresql://*****:*****@localhost/blocparty"
        else:
            # Use DATABASE_URL from env otherwise
            DATABASE_URL = os.environ['HEROKU_POSTGRESQL_JADE_URL']

        self.engine = create_engine(DATABASE_URL,
                                    isolation_level="AUTOCOMMIT",
                                    poolclass=NullPool)
        self.engine.echo = False
        self.metadata = MetaData(self.engine)
        # Users
        self.userTable = Table(
            'userTable',
            self.metadata,
            Column('traderId', Integer, primary_key=True),
            Column('verifyKey', String),
        )
        # Timestamp servers for signing UTC timestamps
        self.timeStampServerTable = Table(
            'timeStampServerTable',
            self.metadata,
            Column('tssId', Integer, primary_key=True, autoincrement=True),
            Column('verifyKey', String),
        )
        # Order book for all trades, including including order book,
        # matched, and linked trades
        self.orderBook = Table(
            'orderBook',
            self.metadata,
            Column('tradeId', Integer, primary_key=True, autoincrement=True),
            Column('price', Integer),
            Column('quantity', Integer),
            Column('marketId', Integer),
            Column(
                'traderId',
                Integer,
            ),
            Column('previousSig', LargeBinary),
            Column('signature', LargeBinary),
            Column('iMatched', Boolean),
            Column('iRemoved', Boolean),
            Column('timeStampUTC', TIMESTAMP),
            Column('timeStampUTCSignature', LargeBinary),
        )

        # Market table with minimum and maximum of each market.
        self.marketTable = Table('marketTable', self.metadata,
                                 Column('marketRootId', Integer),
                                 Column('marketBranchId', Integer),
                                 Column('marketId', Integer),
                                 Column('marketMin', Integer),
                                 Column('marketMax', Integer),
                                 Column('traderId', Integer),
                                 Column('previousSig', LargeBinary),
                                 Column('signature', LargeBinary),
                                 Column('timeStampUTC', TIMESTAMP),
                                 Column('timeStampUTCSignature', LargeBinary),
                                 Column('marketDesc', JSON))

        # Possible combinations of root market outcomes
        self.marketBounds = Table(
            'marketBounds',
            self.metadata,
            Column('marketBounds', Integer),
            Column('marketRootId', Integer),
            Column('marketBranchId', Integer),
            Column('marketMin', Integer),
            Column('marketMax', Integer),
        )

        # Market state (possible combinations)
        self.outcomeCombinations = Table(
            'outcomeCombinations',
            self.metadata,
            Column('outcomeId', Integer, primary_key=True),
            Column('marketRootId', Integer),
            Column('marketBranchId', Integer),
            Column('marketMin', Integer),
            Column('marketMax', Integer),
        )

        # SP tables
        '''
        spevent -  info for event
        spmarket - markets on events
        sprecord - odds records
        spscore - score records
        '''
        self.spevent = Table(
            'spevent', self.metadata,
            Column('eventid', Integer, primary_key=True, autoincrement=True),
            Column('sport', VARCHAR), Column('competition', VARCHAR),
            Column('event', JSON), Column('starttimestamputc', TIMESTAMP))

        self.spmarket = Table(
            'spmarket', self.metadata,
            Column('marketid', Integer, primary_key=True, autoincrement=True),
            Column('eventid', Integer), Column('markettype', VARCHAR),
            Column('runners', JSON), Column('marketparameters', JSON),
            Column('notes', VARCHAR))

        self.sprecord = Table(
            'sprecord', self.metadata,
            Column('recordid', Integer, primary_key=True, autoincrement=True),
            Column('source', VARCHAR), Column('marketid', Integer),
            Column('runnerid', Integer), Column('timestamputc', TIMESTAMP),
            Column('handicap', Float), Column('odds', Float),
            Column('stake', Float), Column('islay', Boolean),
            Column('isplaced', Boolean), Column('notes', VARCHAR))

        self.spscore = Table(
            'spscore', self.metadata,
            Column('scoreid', Integer, primary_key=True, autoincrement=True),
            Column('eventid', Integer), Column('runnerid', Integer),
            Column('timestamputc', TIMESTAMP), Column('measure', VARCHAR),
            Column('value', Integer), Column('isfinal', Boolean))

        # Create all tables
        self.metadata.create_all(self.engine)
        self.conn = self.engine.connect()

        # Collateral limit
        self.COLLATERAL_LIMIT = 1e9
        # Number of markets limit (too many will make too many outcome combinations)
        self.ROOT_MARKET_LIMIT = 5
        self.BRANCH_MARKET_LIMIT = 10

        # Temporary local variables
        self.marketOutcomes = np.array([])  # Market corners
        self.p = np.array([0])
        self.q = np.array([0])
        self.mInd = np.array([0])
        self.tInd = np.array([0])
        self.iMatched = np.array([False])
        self.sig = np.array([0])

        # Time server
        self.TimeServer = BlocTime()
        # Register time series server
        ts = self.TimeServer.signedUTCNow()
        tssTable = pd.read_sql_query(
            'SELECT * FROM "timeStampServerTable" WHERE "verifyKey" = \'%s\'' %
            (ts['verifyKey']), self.conn)
        if tssTable.empty:
            newTSS = dict(verifyKey=ts['verifyKey'])
            # Insert to timeStampServerTable (autoincrements tssID)
            self.conn.execute(self.timeStampServerTable.insert(), [
                newTSS,
            ])

        self.tssTable = pd.read_sql_query(
            'SELECT * FROM "timeStampServerTable" WHERE "verifyKey" = \'%s\'' %
            (ts['verifyKey']), self.conn)

    def purgeTables(self):
        """ Purge all tables before starting a test. """
        self.userTable.delete().execute()
        self.orderBook.delete().execute()
        self.marketTable.delete().execute()
        self.marketBounds.delete().execute()
        self.outcomeCombinations.delete().execute()
        print("All tables deleted.")

    def purgeNonUserTables(self):
        """ Purge all tables before starting a test"""
        self.orderBook.delete().execute()
        self.marketTable.delete().execute()
        self.marketBounds.delete().execute()
        self.outcomeCombinations.delete().execute()
        print("All tables except userTable deleted.")

    # Basic API:
    # createUser
    # createMarket
    # createTrade

    def createUser(self, verifyKey: str) -> object:
        """ Create a new user and adds to userTable.

            :param verifyKey: (str) verify key
            :return newUsrRow: (DataFrame) new user row
            :return self.userTable: (sql table) new row of userTable

            :Example:

            bs = BlocServer()
            bs.createUser('8d708ff647f671b29709a39c5f1529b06d6841fa268f03a834ebf6aad5e6d8e4')

            :Example:

            bc = BlocClient()
            bc.generateSignatureKeys
            bs = BlocServer()
            bs.createUser(bc.verifyKey)

            .. note::
            - Verify key constructed with BlocClient.generateSignatureKeys()
            - Successful call adds new column to userTable.

            .. todo:: check that this is a valid key.
            """

        # Check if this key is already in userTable
        userTable = pd.read_sql_query(
            'SELECT * FROM "userTable" WHERE "verifyKey" = \'%s\'' %
            (verifyKey), self.conn)
        if not userTable.empty:
            print('Username already exists, sorry buddy.')
            return False
        else:
            traderId = len(pd.read_sql_table("userTable", self.conn)) + 1
            # Create the new user
            newUsr = dict(verifyKey=verifyKey, traderId=int(traderId))
            # Insert to usertable (autoincrements traderId)
            self.conn.execute(self.userTable.insert(), [
                newUsr,
            ])
            # Pull back row to get traderID
            newUsrRow = pd.read_sql_query(
                'SELECT * FROM "userTable" WHERE "verifyKey" = \'%s\'' %
                (verifyKey), self.conn)

        # Return new user
        return newUsrRow.loc[0].to_dict()

    def createMarket(self, marketRootId: int, marketBranchId: int, marketMin: int,\
                     marketMax: int, traderId: int, previousSig: bytes,\
                     signature: bytes, verifyKey: str, marketDesc: str) -> bool:
        """
            Create a new row in marketTable. Update existing market
            with new bounds if market already exists.

            :param marketRootId
            :param marketBranchId
            :param marketMin
            :param marketMax
            :param traderId
            :param previousSig
            :param signature
            :param verifyKey
            marketMax, marketRootId, marketBranchId, previousSignature,
            signatureMsg, signature]
            :return checks: (boolean) - True if checks pass
            :return self.marketTable: - new row in market table
            :return self.outputCombinations: - updated output combinations  table
            :return: self.marketBounds: - updated market bounds

            :Example:

            .. todo:: Example
            """
        # Force/check inputs

        try:
            marketRootId = np.int64(marketRootId)
            marketBranchId = np.int64(marketBranchId)
            marketMin = np.int64(marketMin)
            marketMax = np.int64(marketMax)
            traderId = np.int64(traderId)
            assert isinstance(marketRootId, np.int64) and marketRootId > 0
            assert isinstance(marketBranchId, np.int64) and marketBranchId > 0
            assert isinstance(marketMin, np.int64)
            assert isinstance(marketMax, np.int64)
            assert isinstance(traderId, np.int64) and traderId > 0
            inputChk = True
        except:
            inputChk = False

        # Check if trader has correct verify key
        traderIdChk = self.getVerifyKey(traderId) == verifyKey

        mT = pd.read_sql_table('marketTable', self.conn)
        numRootMarkets = len(set(mT['marketRootId']))

        # If the market exists, use previous Id
        marketId = (mT[(mT['marketRootId'] == marketRootId)
                       & (mT['marketBranchId'] == marketBranchId)]['marketId']
                    ).unique()

        # Create market id
        if mT.empty:
            marketId = 1
        elif marketId.size == 0:
            # Market doesn't exist
            marketId = mT['marketId'].max() + 1
        else:
            # Market does exist
            marketId = marketId[0]

        # Limit number of root markets
        if marketBranchId > 1:
            marketLimitChk = (marketBranchId <= self.BRANCH_MARKET_LIMIT
                              ) and np.any(mT['marketRootId'] == marketRootId)
        else:
            marketLimitChk = numRootMarkets <= self.ROOT_MARKET_LIMIT

        # Sign current UTC timestamp
        ts = self.TimeServer.signedUTCNow()
        maxTS = pd.read_sql_query(
            'select max("timeStampUTC") from "marketTable"',
            self.conn)['max'][0]
        if maxTS is None:
            maxTS = datetime.datetime.strptime(ts['timeStampUTC'],
                                               '%Y-%m-%d %H:%M:%S.%f')
        # Check time signature is valid and time server public key is registered and signature is newer than the recent maximum
        timeChk = self.verifyMessage(signatureMsg=bytes(ts['timeStampUTC'], 'utf-8'),
                                     signature=ts['timeStampUTCSignature'], verifyKey=ts['verifyKey']) and\
                                     np.any(ts['verifyKey']==self.tssTable['verifyKey']) and\
                                     datetime.datetime.strptime(ts['timeStampUTC'], '%Y-%m-%d %H:%M:%S.%f')>= maxTS
        newMarket = pd.DataFrame({
            'marketId': [marketId],
            'marketRootId': [marketRootId],
            'marketBranchId': [marketBranchId],
            'marketMin': [marketMin],
            'marketMax': [marketMax],
            'traderId': [traderId],
            'previousSig':
            previousSig,
            'signature':
            signature,
            'timeStampUTC':
            ts['timeStampUTC'],
            'timeStampUTCSignature':
            ts['timeStampUTCSignature'],
            'marketDesc':
            marketDesc
        })

        # Check signature chain for market
        chainChk = False
        if mT.empty:
            # If there are no existing markets chain is ok
            chainChk = True
        else:
            # Check that the previous sig of new market is the sig of the
            # previous market
            prevMarket = self.getPreviousMarket()
            chainChk = bytes(prevMarket['signature'][0]) == previousSig

        # If market exists...
        matchCurrentMarket = pd.merge(left=mT,
                                      right=newMarket,
                                      on=['marketRootId', 'marketBranchId'])
        ownerChk = True
        if not matchCurrentMarket.empty:
            # Check that trader owns it
            ownerChk = matchCurrentMarket.loc[
                0, 'traderId_x'] == matchCurrentMarket.loc[0, 'traderId_y']

        # Verify market signature is valid
        sigMsg = \
            str(marketRootId).encode("utf-8") + \
            str(marketBranchId).encode("utf-8") + \
            str(marketMin).encode("utf-8") + \
            str(marketMax).encode("utf-8") + \
            str(traderId).encode("utf-8") + \
            previousSig + b'end'

        sigChk = self.checkSignature(signature, sigMsg, verifyKey)

        # Convert sigChk to logical
        if isinstance(sigChk, bytes):
            sigChk = True
        # Convert time signature check to logical
        if isinstance(timeChk, bytes):
            timeChk = True
        # Check market range
        marketRangeChk = newMarket.loc[0, 'marketMin'] <= newMarket.loc[
            0, 'marketMax']
        marketIndChk = newMarket.loc[
            0, 'marketBranchId'] > 0 and newMarket.loc[0, 'marketRootId'] > 0
        # Checks (correct market number, signature relative to parent, range)
        checks = inputChk and marketLimitChk and marketIndChk and traderIdChk and marketRangeChk and sigChk and chainChk and ownerChk and timeChk

        #  Add market to table if checks pass
        if checks:
            # Append new market
            newMarket.to_sql(name='marketTable',
                             con=self.conn,
                             if_exists='append',
                             index=False)
            # Update all possible combinations of root markets
            self.updateOutcomeCombinations()
        else:
            print(
                'Signature does not match, bad signature chain, or else marketMin > marketMax. Market not added.'
            )

        # Return True if checks pass and market added
        return checks, {'inputChk': inputChk,'marketLimitChk': marketLimitChk, 'traderIdChk': traderIdChk, 'marketId': str(marketId), 'marketRangeChk':marketRangeChk, 'marketIndChk': marketIndChk, 'sigChk': sigChk, 'chainChk':chainChk,\
                        'ownerChk':ownerChk,  'timeChk': timeChk}

    def createTrade(self, p_: int, q_: int, mInd_: int, tInd_: int,
                    previousSig: bytes, signature: bytes,
                    verifyKey: str) -> bool:
        """
        Create a new row in marketTable. Update existing market with new bounds if market already exists.

        :param p_: price
        :param q_: quantity
        :param mInd_: market index
        :param tInd_: trader index
        :param previousSig: Previous signature
        :param signature: Signature
        :param verifyKey: Verify key
        :return: colChk: Collateral check

        :Example:

        .. todo: Example
        """
        # Force/check inputs

        try:
            p_ = np.int64(p_)
            q_ = np.int64(q_)
            mInd_ = np.int64(mInd_)
            tInd = np.int64(tInd_)
            assert isinstance(p_, np.int64)
            assert isinstance(q_, np.int64)
            assert isinstance(mInd_, np.int64) and mInd_ > 0
            assert isinstance(tInd, np.int64) and mInd_ > 0
            inputChk = True
        except:
            inputChk = False

        # This creates the self.marketOutcomes array
        self.updateOutcomeCombinations(fromTrade=True)

        # Check if trader has correct verify key
        traderIdChk = self.getVerifyKey(tInd_) == verifyKey

        # Check if market exists
        marketIds = pd.read_sql(
            'select distinct "marketId" from "marketBounds"',
            self.conn)["marketId"]
        marketChk = np.any(mInd_ == marketIds)

        # Check signature is right
        chainChk = previousSig == bytes(
            self.getPreviousOrder()['signature'][0])
        # Check signature for trade (Created in client)
        sigMsg =\
            str(p_).encode("utf-8")+\
            str(q_).encode("utf-8")+\
            str(mInd_).encode('utf-8')+\
            str(tInd_).encode("utf-8")+\
            previousSig + b'end'

        sigChk = self.checkSignature(signature, sigMsg, verifyKey)
        # Convert sigChk to logical
        if isinstance(sigChk, bytes):
            sigChk = True

        # Sign current UTC timestamp
        ts = self.TimeServer.signedUTCNow()
        maxTS = pd.read_sql_query(
            'select max("timeStampUTC") from "orderBook"', self.conn)['max'][0]
        if maxTS is None:
            maxTS = datetime.datetime.strptime(ts['timeStampUTC'],
                                               '%Y-%m-%d %H:%M:%S.%f')
        # Check time signature is valid and time server public key is registered and signature is newer than the recent maximum
        timeChk = self.verifyMessage(signatureMsg=bytes(ts['timeStampUTC'], 'utf-8'),
                                     signature=ts['timeStampUTCSignature'], verifyKey=ts['verifyKey']) and \
                  np.any(ts['verifyKey'] == self.tssTable['verifyKey']) and \
                  datetime.datetime.strptime(ts['timeStampUTC'], '%Y-%m-%d %H:%M:%S.%f') >= \
                  maxTS

        # Convert time signature check to logical
        if isinstance(timeChk, bytes):
            timeChk = True

        newTradeId = np.nan
        colChk = False
        if inputChk and traderIdChk and marketChk and sigChk and chainChk and timeChk:
            colChk, deets = self.checkCollateral(p_, q_, mInd_, tInd_)
            if colChk:
                # Append new trade

                newTrade = pd.DataFrame({
                    'price': [p_],
                    'quantity': [q_],
                    'marketId': [mInd_],
                    'traderId': [tInd_],
                    'previousSig':
                    previousSig,
                    'signature':
                    signature,
                    'iMatched': [False],
                    'iRemoved': [False],
                    'timeStampUTC':
                    ts['timeStampUTC'],
                    'timeStampUTCSignature':
                    ts['timeStampUTCSignature']
                })
                newTrade.to_sql(name='orderBook',
                                con=self.conn,
                                if_exists='append',
                                index=False)

                # Check newTrade is there and get its id.
                # TODO: match signature directly in query if possible
                checkTrade = pd.read_sql_query(
                    'SELECT "tradeId", "signature" FROM "orderBook" WHERE "tradeId" = (SELECT max("tradeId") from "orderBook")',
                    self.conn)
                if bytes(checkTrade.loc[0, 'signature']) == signature:
                    newTradeId = checkTrade.loc[0, 'tradeId']
                else:
                    newTradeId = np.nan

                # Check for matches
                matchTrade = pd.read_sql_query(
                    'SELECT "tradeId" FROM "orderBook" WHERE "price" = %s AND "quantity" = %s AND "iMatched" = FALSE AND "iRemoved" = FALSE'
                    % (p_, -q_), self.conn)  # Find a match
                # Update
                if not matchTrade.empty:
                    # Update iMatched for matching trades
                    self.conn.execute(
                        'UPDATE "orderBook" SET "iMatched"= TRUE where "tradeId" IN (%s, (SELECT MAX("tradeId") FROM "orderBook"))'
                        % (matchTrade['tradeId'][0]))

            # Clean up trades causing collateral to fail
            allClear = False
            while not allClear:
                colChk, deets = self.checkCollateral(tInd_=tInd_)
                if colChk:
                    allClear = True
                else:
                    self.killMarginalOpenTrade(tInd_)

        return colChk, {
            'tradeId': newTradeId,
            'inputChk': inputChk,
            'traderIdChk': traderIdChk,
            'marketChk': marketChk,
            'sigChk': sigChk,
            'chainChk': chainChk,
            'timeChk': timeChk,
            'colChk': colChk
        }

    # Collateral check

    def checkCollateral(self, p_=[], q_=[], mInd_=[], tInd_=None) -> object:
        """Check collateral for new trade.

        :param p_: price
        :param q_: quantity
        :param mInd_: market index
        :param tInd_: trader index


        :Example:


               Example::
               ms = MarketServer()
               ... set up trade users/markets
               ms = ms.updateOutcomeCombinations

               """

        # Check collateral for new trade.
        #
        # p_, q_, mInd_, tInd_ - New trade
        # p, q, mInd, tInd - Existing trades
        # M - Market state payoffs
        # iMatched - Indicator for matched trades

        # Get p, q, mInd, tInd for trader
        data = pd.read_sql_query(
            'SELECT "price", "quantity", "marketId", "traderId", "iMatched" FROM "orderBook" WHERE "traderId" = \'%s\' AND "iRemoved" = FALSE'
            % (tInd_), self.conn)
        self.p = np.int64(data['price'])
        self.q = np.int64(data['quantity'])
        self.mInd = np.int64(data['marketId'])  # (sort out unique marketId)
        self.tInd = np.int64(data['traderId'])
        self.iMatched = data['iMatched']
        # Test by appending test trade
        p = np.array(np.append(self.p, p_))
        q = np.array(np.append(self.q, q_))
        # If price is given, append.
        if p_:
            mInd = np.append(self.mInd, mInd_)
            tInd = np.append(self.tInd, tInd_)
            iMatched = np.append(self.iMatched, False)
        else:
            mInd = self.mInd
            tInd = self.tInd
            iMatched = self.iMatched

        M = self.marketOutcomes
        C, N = M.shape
        D = tInd.max()
        # Derived
        iUnmatched = np.logical_not(iMatched)
        T = len(p)  # Number of trades
        QD = np.tile(q, (D, 1)).T  # Tiled quantity (traders)
        QC = np.tile(q, (C, 1))  # Tiled quantity (states)
        IM = self.ind2vec(mInd - 1, N).T  # Indicator for market
        IQ = self.ind2vec(tInd - 1, D)  # Indicator for trader
        QDstar = QD * IQ  # Tiled quantity distribured across traders
        Pstar = np.tile(p, (C, 1))  # Tiled price distributed across states
        Mstar = np.dot(M, IM)  # Market outcomes across states and trades

        # Collateral calculation
        NC = np.dot((Mstar - Pstar), QDstar)
        NCstar = (Mstar - Pstar) * QC

        # Split out matched and unmatched
        matchedInd = np.where(iMatched)[0]
        unmatchedInd = np.where(iUnmatched)[0]
        NCstar_matched = NCstar[:, matchedInd]
        NCstar_unmatched = NCstar[:, unmatchedInd]

        #TC = np.sum(NCstar_matched, axis=1) + np.min(NCstar_unmatched, axis=1)

        # Total collateral calculation
        if NCstar_matched.shape[1] == 0 and NCstar_unmatched.shape[1] == 0:
            TC = NCstar_matched + NCstar_unmatched
        elif NCstar_matched.shape[1] == 0 and NCstar_unmatched.shape[1] > 0:
            TC = np.min(NCstar_unmatched, axis=1)
        elif NCstar_matched.shape[1] > 0 and NCstar_unmatched.shape[1] == 0:
            TC = np.sum(NCstar_matched, axis=1)
        else:
            TC = np.sum(NCstar_matched, axis=1) + np.min(NCstar_unmatched,
                                                         axis=1)

        worstCollateral = TC + self.COLLATERAL_LIMIT

        colChk = np.all(worstCollateral >= 0)

        collateralDetails = dict(price=self.p,
                                 quantity=self.q,
                                 traderId=self.tInd,
                                 marketId=self.mInd,
                                 iMatched=self.iMatched,
                                 outcomes=NCstar,
                                 worstCollateral=worstCollateral)
        '''
        The collateral condition can be calculated simultaneously across all traders in one step by
        taking each column D columns of the second term as the minimum unmatched collateral for all 
        trades for each trader. 
        
        TODO: Do whole thing as a table operation. Also cuts down on 
        '''

        return colChk, collateralDetails

    # Function group:
    # updateOutcomeCombinations
    # updateBounds

    def updateOutcomeCombinations(self, fromTrade: bool = False) -> None:
        """Update outcome combinations taking into account mins/maxes on
        branches.

        :param: None
        :return: self.outputCombinations:  (sql table) possible market states
        :return: self.marketOutcomes: (numpy nd array) Matrix of market outcome in each state
        :return: self.marketBounds: (sql table) Upper and lower bounds for all markets

        Example::
        ms = MarketServer()
        ... set up trade users/markets
        ms = ms.updateOutcomeCombinations

        """
        if not fromTrade:
            mT = pd.read_sql_table('marketTable', self.conn)
            # Root markets have marketBranchId ==1
            rootMarkets = mT.loc[mT['marketBranchId'] == 1, :].reset_index(
                drop=True)
            # Construct outcome combinations in root markets
            oC = self.constructOutcomeCombinations(rootMarkets)
            oC = oC.reset_index(drop=True)
            oC.to_sql('outcomeCombinations',
                      self.conn,
                      if_exists='replace',
                      index=False)
            # Construct market bounds in all markets
            mB = self.constructMarketBounds(mT)
            marketFields = [
                'marketId', 'marketRootId', 'marketBranchId', 'marketMin',
                'marketMax'
            ]
            mB = mB.loc[:, marketFields].reset_index(drop=True)
            # Full replace of market bounds
            mB.to_sql('marketBounds',
                      self.conn,
                      if_exists='replace',
                      index=False)
        else:
            mB = pd.read_sql_table('marketBounds', self.conn)
            oC = pd.read_sql_table('outcomeCombinations', self.conn)

        numMarkets = len(mB)
        numStates = oC.loc[:, 'outcomeId'].max() + 1

        # Preallocate market outcomes
        M = np.zeros((numStates, numMarkets))
        if not fromTrade:
            for iOutcome in range(numStates):
                # Get outcome for root market
                outcomeRow = oC.loc[oC['outcomeId'] == iOutcome, :]
                # Add outcome to market table
                # todo: more elegant way to do this
                allOutcome = mT.loc[:, marketFields].append(
                    outcomeRow[marketFields], ignore_index=True)
                # Construct new bounds given outcome
                settleOutcome = self.constructMarketBounds(allOutcome)
                # Markets settle at marketMin=marketMax so choose either
                M[iOutcome, ] = settleOutcome.loc[:, 'marketMin'].values
                mTable = pd.DataFrame()
        # marketOutcomes is a (numStates * numMarkets) matrix of extreme market
        # states.
        self.marketOutcomes = M

    @staticmethod
    def updateBounds(L: int, U: int, l: int, u: int) -> object:
        """Update bounds from lower branches

        :param: L: (ndarray) lower bound for current market
        :param: U: (ndarray) upper bound for current market
        :param: l: (int64) lower bound for lower branches
        :param: u: (int64) upper bound for lower branches

        :return: L_new: (int64) new lower bound
        :return: U_new: (int64) new upper bound


        .. note::

        """

        L_new = np.min([np.max([L, l]), U])
        U_new = np.max([np.min([U, u]), L])

        return L_new, U_new

    # Function group:
    # constructOutcomeCombinations
    # constructMarketBounds
    # constructCartesianProduct
    # constructUnitVector

    def constructOutcomeCombinations(self,
                                     marketTable: object) -> pd.DataFrame:
        """Construct all possible outcome combinations for some table of markets.

        :param: marketTable: (DataFrame) marketTable with same columns as the SQL table
        :return: marketOutcomes: (DataFrame) [marketRootId, marketBranchId,
                                              marketMin, marketMax, outcomeId]


        .. note:: Market outcome ids created new when a new market is added.

        """
        marketExtrema = self.constructMarketBounds(marketTable)
        marketExtrema = \
            marketExtrema.loc[:, ['marketRootId', 'marketMin', 'marketMax']].drop_duplicates().reset_index(drop=True)

        # TODO: This should pull out the rows into an array (something less ugly)
        exOutcome = np.zeros((len(marketExtrema), 2))
        for iRow, mRow in marketExtrema.iterrows():
            exOutcome[iRow] = [mRow['marketMin'], mRow['marketMax']]

        # Construct all combinations of output
        marketCombinations = self.constructCartesianProduct(exOutcome)
        numCombinations = len(marketCombinations)
        numMarkets = len(marketCombinations[0])

        # Get unique markets
        mT = marketTable.loc[:, ['marketId', 'marketRootId', 'marketBranchId'
                                 ]].drop_duplicates().reset_index(drop=True)

        marketIds = mT.loc[:, 'marketRootId']
        mT.loc[:, 'marketMin'] = np.nan
        mT.loc[:, 'marketMax'] = np.nan

        marketOutcomes = pd.DataFrame()
        for iOutcome in range(numCombinations):
            for iMarket in range(numMarkets):
                mT.loc[mT['marketRootId'] == marketIds.loc[iMarket],
                       ['marketMin']] = marketCombinations[iOutcome][iMarket]
                mT.loc[mT['marketRootId'] == marketIds.loc[iMarket],
                       ['marketMax']] = marketCombinations[iOutcome][iMarket]
                mT['outcomeId'] = iOutcome

            marketOutcomes = pd.concat([marketOutcomes, mT], ignore_index=True)

        return marketOutcomes.reset_index(drop=True).drop_duplicates()

    def constructMarketBounds(self, marketTable: pd.DataFrame) -> pd.DataFrame:
        """Construct upper and lower bounds for all markets, taking into
        account the bounds of lower branchess.

        :param: marketTable: (DataFrame) marketTable with same columns as the SQL table
        :return: marketBounds: (DataFrame) with [marketRootId, marketBranchId, marketMin, marketMax]


        .. note::

        """

        # Pull market table
        mT = pd.read_sql_table('marketTable', self.conn)

        mT = mT.loc[:, ['marketId', 'marketRootId', 'marketBranchId'
                        ]].drop_duplicates().reset_index(drop=True)
        mT['marketMin'] = np.nan
        mT['marketMax'] = np.nan

        for iMarket, marketRow in mT.iterrows():
            mRId = marketRow['marketRootId']
            mBId = marketRow['marketBranchId']
            # Get markets with the same root on equal or lower branch
            mTmp = marketTable.loc[(marketTable['marketRootId'] == mRId) & \
                                   (marketTable['marketBranchId'] <= mBId), :].reset_index(drop=True)

            L_ = np.zeros((len(mTmp), 1))
            U_ = np.zeros((len(mTmp), 1))
            for jMarket, mRow in mTmp.iterrows():
                L_tmp = mRow['marketMin']
                U_tmp = mRow['marketMax']
                if jMarket == 0:
                    L_[jMarket] = L_tmp
                    U_[jMarket] = U_tmp
                else:
                    # Update upper and lower bounds using lower branches
                    L_new, U_new = self.updateBounds(L_[jMarket - 1],
                                                     U_[jMarket - 1], L_tmp,
                                                     U_tmp)
                    L_[jMarket] = L_new
                    U_[jMarket] = U_new

            # Take last element of each
            mT.loc[iMarket, 'marketMin'] = L_[-1][0]
            mT.loc[iMarket, 'marketMax'] = U_[-1][0]

        # Take what we need back
        marketBounds = mT.loc[:, [
            'marketId', 'marketRootId', 'marketBranchId', 'marketMin',
            'marketMax'
        ]]

        return marketBounds.reset_index(drop=True)

    @staticmethod
    def constructCartesianProduct(input: np.ndarray) -> list:
        """Construct all possible combinations of a set

        :param: input: (ndarray) input set

        :return: cp: (list) combinations
        """
        cp = list(itertools.product(*input))
        return cp

    @staticmethod
    def constructUnitVector(L: int, x: int):
        """Make a vector of length L with a one in the x'th position

        :param: L: (int64) Length of unit vector
        :param: x: (int64) position of 1

        :return: u: (ndarray) unit vector

        """
        u = np.eye(int(L))[int(x)]
        return u

    # Function group:
    # getPreviousOrder

    def checkSignature(self, signature: bytes, sigMsg: bytes, verifyKey: str):
        # Check signature message
        return self.verifyMessage(signature=signature,
                                  signatureMsg=sigMsg,
                                  verifyKey=verifyKey)

    def getPreviousOrder(self):
        # Get previous order. If there are no orders return a dummy order
        data = pd.read_sql_query(
            'SELECT "tradeId", "signature" FROM "orderBook" WHERE "tradeId" = (SELECT max("tradeId") FROM "orderBook")',
            self.conn)  # Find a match
        if not data.empty:
            return data
        else:
            return pd.DataFrame({
                'tradeId': [0],
                'signature': ['sig'.encode('utf-8')]
            })

    def killMarginalOpenTrade(self, tInd_: int) -> None:
        # Find earliest unmatched trade
        data = pd.read_sql_query(
            'SELECT min("tradeId") FROM "orderBook" WHERE "traderId" = %s and "iMatched" = FALSE AND "iRemoved" = FALSE'
            % (tInd_), self.conn)  # Find a match
        # Kill earliest unmatched trade
        self.conn.execute(
            'UPDATE "orderBook" SET "iRemoved"= TRUE where "tradeId" = %s' %
            (data['min'][0]))

    def getPreviousMarket(self) -> pd.DataFrame:
        """Get most recent market signature.

        Example::
             bs = BlocServer()
             bc = BlocClient()
             prevTrade = bs.getPreviousMarket()


        .. note:: Returns last trade the table or dummy market with
             signature = 's'
        .. :todo:: Better query to get most recent market


        :param None

        :return: previousMarket: (DataFrame) row of previous valid market
        """

        data = pd.read_sql_query(
            'SELECT * FROM "marketTable" order by "marketId" DESC limit 1',
            self.conn)  # Find a match
        if not data.empty:
            return data
        else:
            return pd.DataFrame({
                'tradeId': [0],
                'signature': ['sig'.encode('utf-8')]
            })

    # Function group:
    # getVerifyKey
    # signMessage
    # verifyMessage
    # verifyTradeSignature
    # verifyMarketSignature
    # verifySignature

    def getVerifyKey(self, traderId: int) -> str:
        """Get verify key for trader

        :param: traderId: (int64) traderId

        :return: verifyKey: (str) verify key for traderId

        """
        queryStr = 'SELECT "verifyKey" FROM "userTable" WHERE "traderId" = \'%s\'' % (
            traderId)
        res = pd.read_sql(queryStr, self.conn)
        if not res.empty:
            verifyKey = res.verifyKey[0]
        else:
            verifyKey = 'null'
        return verifyKey

    @staticmethod
    def signMessage(msg: object, signingKey: object) -> object:
        """Sign a message

        :param: msg: message to sign
        :param: signingKey: signing key as hex

        :return: signed: signed message

        """

        # Convert hex key to bytes
        signingKey_bytes = b'%s' % str.encode(signingKey)
        # Generate signing key
        signingKey = nacl.signing.SigningKey(signingKey_bytes,
                                             encoder=nacl.encoding.HexEncoder)
        # Sign message
        signed = signingKey.sign(msg)
        return signed

    @staticmethod
    def verifyMessage(signature: bytes, signatureMsg: bytes,
                      verifyKey: str) -> object:
        """Verify a signature

        :param: signature: (bytes) signature to check
        :param: signatureMsg: (bytes) message that signature is from
        :param: verifyKey: (str) verification key as string

        :return: verified: (bytes) returns signatureMsg if verified

        """

        verifyKey = nacl.signing.VerifyKey(verifyKey,
                                           encoder=nacl.encoding.HexEncoder)
        verified = verifyKey.verify(signatureMsg, signature=signature)
        return verified

    def verifySignature(self, traderId: int, signature: bytes,
                        signatureMsg: bytes):
        """Vefify a signature message by looking up the verify key and checking

        :param: traderId: (int64) trader id
        :param: signature: (bytes) signature
        :param: signatureMsg: (bytes) signature message

        :return: sigChk: returns if verified

        """

        verifyKey = self.getVerifyKey(traderId=traderId)
        # Verify the message against the signature and verify key
        sigChk = self.verifyMessage(signature=signature,
                                    signatureMsg=signatureMsg,
                                    verifyKey=verifyKey)
        return sigChk

    def ind2vec(self, ind, N=None):
        ind = np.asarray(ind)
        if N is None:
            N = ind.max() + 1
        return (np.arange(N) == ind[:, None]).astype(int)

    def __repr__(self):
        return 'bloc is a limit order chain'