Exemple #1
0
def match(db, tx):

    cursor = db.cursor()

    # Get bet in question.
    bets = list(
        cursor.execute(
            """SELECT * FROM bets\
                                  WHERE (tx_index = ? AND status = ?)""",
            (tx["tx_index"], "open"),
        )
    )
    if not bets:
        cursor.close()
        return
    else:
        assert len(bets) == 1
    tx1 = bets[0]

    # Get counterbet_type.
    if tx1["bet_type"] % 2:
        counterbet_type = tx1["bet_type"] - 1
    else:
        counterbet_type = tx1["bet_type"] + 1

    feed_address = tx1["feed_address"]

    cursor.execute(
        """SELECT * FROM bets\
                             WHERE (feed_address=? AND status=? AND bet_type=?)""",
        (tx1["feed_address"], "open", counterbet_type),
    )
    tx1_wager_remaining = tx1["wager_remaining"]
    tx1_counterwager_remaining = tx1["counterwager_remaining"]
    bet_matches = cursor.fetchall()
    if tx["block_index"] > 284500 or config.TESTNET:  # Protocol change.
        sorted(bet_matches, key=lambda x: x["tx_index"])  # Sort by tx index second.
        sorted(
            bet_matches, key=lambda x: util.price(x["wager_quantity"], x["counterwager_quantity"])
        )  # Sort by price first.

    tx1_status = tx1["status"]
    for tx0 in bet_matches:
        if tx1_status != "open":
            break

        logger.debug("Considering: " + tx0["tx_hash"])
        tx0_wager_remaining = tx0["wager_remaining"]
        tx0_counterwager_remaining = tx0["counterwager_remaining"]

        # Bet types must be opposite.
        if counterbet_type != tx0["bet_type"]:
            logger.debug("Skipping: bet types disagree.")
            continue

        # Leverages must agree exactly
        if tx0["leverage"] != tx1["leverage"]:
            logger.debug("Skipping: leverages disagree.")
            continue

        # Target values must agree exactly.
        if tx0["target_value"] != tx1["target_value"]:
            logger.debug("Skipping: target values disagree.")
            continue

        # Fee fractions must agree exactly.
        if tx0["fee_fraction_int"] != tx1["fee_fraction_int"]:
            logger.debug("Skipping: fee fractions disagree.")
            continue

        # Deadlines must agree exactly.
        if tx0["deadline"] != tx1["deadline"]:
            logger.debug("Skipping: deadlines disagree.")
            continue

        # If the odds agree, make the trade. The found order sets the odds,
        # and they trade as much as they can.
        tx0_odds = util.price(tx0["wager_quantity"], tx0["counterwager_quantity"])
        tx0_inverse_odds = util.price(tx0["counterwager_quantity"], tx0["wager_quantity"])
        tx1_odds = util.price(tx1["wager_quantity"], tx1["counterwager_quantity"])

        if tx["block_index"] < 286000:
            tx0_inverse_odds = util.price(1, tx0_odds)  # Protocol change.

        logger.debug("Tx0 Inverse Odds: {}; Tx1 Odds: {}".format(float(tx0_inverse_odds), float(tx1_odds)))
        if tx0_inverse_odds > tx1_odds:
            logger.debug("Skipping: price mismatch.")
        else:
            logger.debug(
                "Potential forward quantities: {}, {}".format(
                    tx0_wager_remaining, int(util.price(tx1_wager_remaining, tx1_odds))
                )
            )
            forward_quantity = int(min(tx0_wager_remaining, int(util.price(tx1_wager_remaining, tx1_odds))))
            logger.debug("Forward Quantity: {}".format(forward_quantity))
            backward_quantity = round(forward_quantity / tx0_odds)
            logger.debug("Backward Quantity: {}".format(backward_quantity))

            if not forward_quantity:
                logger.debug("Skipping: zero forward quantity.")
                continue
            if tx1["block_index"] >= 286500 or config.TESTNET:  # Protocol change.
                if not backward_quantity:
                    logger.debug("Skipping: zero backward quantity.")
                    continue

            bet_match_id = util.make_id(tx0["tx_hash"], tx1["tx_hash"])

            # Debit the order.
            # Counterwager remainings may be negative.
            tx0_wager_remaining = tx0_wager_remaining - forward_quantity
            tx0_counterwager_remaining = tx0_counterwager_remaining - backward_quantity
            tx1_wager_remaining = tx1_wager_remaining - backward_quantity
            tx1_counterwager_remaining = tx1_counterwager_remaining - forward_quantity

            # tx0
            tx0_status = "open"
            if tx0_wager_remaining <= 0 or tx0_counterwager_remaining <= 0:
                # Fill order, and recredit give_remaining.
                tx0_status = "filled"
                util.credit(db, tx0["source"], config.SHP, tx0_wager_remaining, event=tx1["tx_hash"], action="filled")
            bindings = {
                "wager_remaining": tx0_wager_remaining,
                "counterwager_remaining": tx0_counterwager_remaining,
                "status": tx0_status,
                "tx_hash": tx0["tx_hash"],
            }
            sql = "update bets set wager_remaining = :wager_remaining, counterwager_remaining = :counterwager_remaining, status = :status where tx_hash = :tx_hash"
            cursor.execute(sql, bindings)
            log.message(db, tx["block_index"], "update", "bets", bindings)

            if tx1["block_index"] >= 292000 or config.TESTNET:  # Protocol change
                if tx1_wager_remaining <= 0 or tx1_counterwager_remaining <= 0:
                    # Fill order, and recredit give_remaining.
                    tx1_status = "filled"
                    util.credit(
                        db, tx1["source"], config.SHP, tx1_wager_remaining, event=tx1["tx_hash"], action="filled"
                    )
            # tx1
            bindings = {
                "wager_remaining": tx1_wager_remaining,
                "counterwager_remaining": tx1_counterwager_remaining,
                "status": tx1_status,
                "tx_hash": tx1["tx_hash"],
            }
            sql = "update bets set wager_remaining = :wager_remaining, counterwager_remaining = :counterwager_remaining, status = :status where tx_hash = :tx_hash"
            cursor.execute(sql, bindings)
            log.message(db, tx["block_index"], "update", "bets", bindings)

            # Get last value of feed.
            broadcasts = list(
                cursor.execute(
                    """SELECT * FROM broadcasts WHERE (status = ? AND source = ?) ORDER BY tx_index ASC""",
                    ("valid", feed_address),
                )
            )
            initial_value = broadcasts[-1]["value"]

            # Record bet fulfillment.
            bindings = {
                "id": util.make_id(tx0["tx_hash"], tx["tx_hash"]),
                "tx0_index": tx0["tx_index"],
                "tx0_hash": tx0["tx_hash"],
                "tx0_address": tx0["source"],
                "tx1_index": tx1["tx_index"],
                "tx1_hash": tx1["tx_hash"],
                "tx1_address": tx1["source"],
                "tx0_bet_type": tx0["bet_type"],
                "tx1_bet_type": tx1["bet_type"],
                "feed_address": tx1["feed_address"],
                "initial_value": initial_value,
                "deadline": tx1["deadline"],
                "target_value": tx1["target_value"],
                "leverage": tx1["leverage"],
                "forward_quantity": forward_quantity,
                "backward_quantity": backward_quantity,
                "tx0_block_index": tx0["block_index"],
                "tx1_block_index": tx1["block_index"],
                "block_index": max(tx0["block_index"], tx1["block_index"]),
                "tx0_expiration": tx0["expiration"],
                "tx1_expiration": tx1["expiration"],
                "match_expire_index": min(tx0["expire_index"], tx1["expire_index"]),
                "fee_fraction_int": tx1["fee_fraction_int"],
                "status": "pending",
            }
            sql = "insert into bet_matches values(:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :tx0_bet_type, :tx1_bet_type, :feed_address, :initial_value, :deadline, :target_value, :leverage, :forward_quantity, :backward_quantity, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :fee_fraction_int, :status)"
            cursor.execute(sql, bindings)

    cursor.close()
    return
def match(db, tx, block_index=None):

    cursor = db.cursor()

    # Get order in question.
    orders = list(
        cursor.execute(
            """SELECT * FROM orders\
                                    WHERE (tx_index = ? AND status = ?)""",
            (tx["tx_index"], "open"),
        )
    )
    if not orders:
        cursor.close()
        return
    else:
        assert len(orders) == 1
    tx1 = orders[0]

    cursor.execute(
        """SELECT * FROM orders \
                      WHERE (give_asset=? AND get_asset=? AND status=? AND tx_hash != ?)""",
        (tx1["get_asset"], tx1["give_asset"], "open", tx1["tx_hash"]),
    )

    tx1_give_remaining = tx1["give_remaining"]
    tx1_get_remaining = tx1["get_remaining"]

    order_matches = cursor.fetchall()
    if tx["block_index"] > 284500 or config.TESTNET:  # Protocol change.
        order_matches = sorted(order_matches, key=lambda x: x["tx_index"])  # Sort by tx index second.
        order_matches = sorted(
            order_matches, key=lambda x: util.price(x["get_quantity"], x["give_quantity"])
        )  # Sort by price first.

    # Get fee remaining.
    tx1_fee_required_remaining = tx1["fee_required_remaining"]
    tx1_fee_provided_remaining = tx1["fee_provided_remaining"]

    tx1_status = tx1["status"]
    for tx0 in order_matches:
        order_match_id = util.make_id(tx0["tx_hash"], tx1["tx_hash"])
        if not block_index:
            block_index = max(tx0["block_index"], tx1["block_index"])
        if tx1_status != "open":
            break

        logger.debug("Considering: " + tx0["tx_hash"])
        tx0_give_remaining = tx0["give_remaining"]
        tx0_get_remaining = tx0["get_remaining"]

        # Ignore previous matches. (Both directions, just to be sure.)
        cursor.execute(
            """SELECT * FROM order_matches
                          WHERE id = ? """,
            (util.make_id(tx0["tx_hash"], tx1["tx_hash"]),),
        )
        if list(cursor):
            logger.debug("Skipping: previous match")
            continue
        cursor.execute(
            """SELECT * FROM order_matches
                          WHERE id = ? """,
            (util.make_id(tx1["tx_hash"], tx0["tx_hash"]),),
        )
        if list(cursor):
            logger.debug("Skipping: previous match")
            continue

        # Get fee provided remaining.
        tx0_fee_required_remaining = tx0["fee_required_remaining"]
        tx0_fee_provided_remaining = tx0["fee_provided_remaining"]

        # Make sure that that both orders still have funds remaining (if order involves SCH, and so cannot be ‘filled’).
        if tx0["give_asset"] == config.SCH or tx0["get_asset"] == config.SCH:  # Gratuitous
            if tx0_give_remaining <= 0 or tx1_give_remaining <= 0:
                logger.debug("Skipping: negative give quantity remaining")
                continue
            if block_index >= 292000 and block_index <= 310500 and not config.TESTNET:  # Protocol changes
                if tx0_get_remaining <= 0 or tx1_get_remaining <= 0:
                    logger.debug("Skipping: negative get quantity remaining")
                    continue

            if block_index >= 294000 or config.TESTNET:  # Protocol change.
                if tx0["fee_required_remaining"] < 0:
                    logger.debug("Skipping: negative tx0 fee required remaining")
                    continue
                if tx0["fee_provided_remaining"] < 0:
                    logger.debug("Skipping: negative tx0 fee provided remaining")
                    continue
                if tx1_fee_provided_remaining < 0:
                    logger.debug("Skipping: negative tx1 fee provided remaining")
                    continue
                if tx1_fee_required_remaining < 0:
                    logger.debug("Skipping: negative tx1 fee required remaining")
                    continue

        # If the prices agree, make the trade. The found order sets the price,
        # and they trade as much as they can.
        tx0_price = util.price(tx0["get_quantity"], tx0["give_quantity"])
        tx1_price = util.price(tx1["get_quantity"], tx1["give_quantity"])
        tx1_inverse_price = util.price(tx1["give_quantity"], tx1["get_quantity"])

        # Protocol change.
        if tx["block_index"] < 286000:
            tx1_inverse_price = util.price(1, tx1_price)

        logger.debug("Tx0 Price: {}; Tx1 Inverse Price: {}".format(float(tx0_price), float(tx1_inverse_price)))
        if tx0_price > tx1_inverse_price:
            logger.debug("Skipping: price mismatch.")
        else:
            logger.debug(
                "Potential forward quantities: {}, {}".format(
                    tx0_give_remaining, int(util.price(tx1_give_remaining, tx0_price))
                )
            )
            forward_quantity = int(min(tx0_give_remaining, int(util.price(tx1_give_remaining, tx0_price))))
            logger.debug("Forward Quantity: {}".format(forward_quantity))
            backward_quantity = round(forward_quantity * tx0_price)
            logger.debug("Backward Quantity: {}".format(backward_quantity))

            if not forward_quantity:
                logger.debug("Skipping: zero forward quantity.")
                continue
            if block_index >= 286500 or config.TESTNET:  # Protocol change.
                if not backward_quantity:
                    logger.debug("Skipping: zero backward quantity.")
                    continue

            forward_asset, backward_asset = tx1["get_asset"], tx1["give_asset"]

            if block_index >= 313900 or config.TESTNET:  # Protocol change.
                min_shell_quantity = 0.001 * config.UNIT  # 0.001 SCH
                if (forward_asset == config.SCH and forward_quantity <= min_shell_quantity) or (
                    backward_asset == config.SCH and backward_quantity <= min_shell_quantity
                ):
                    logger.debug("Skipping: below minimum {} quantity".format(config.SCH))
                    continue

            # Check and update fee remainings.
            fee = 0
            if (
                block_index >= 286500 or config.TESTNET
            ):  # Protocol change. Deduct fee_required from provided_remaining, etc., if possible (else don’t match).
                if tx1["get_asset"] == config.SCH:

                    if block_index >= 310500 or config.TESTNET:  # Protocol change.
                        fee = int(tx1["fee_required"] * util.price(backward_quantity, tx1["give_quantity"]))
                    else:
                        fee = int(tx1["fee_required_remaining"] * util.price(forward_quantity, tx1_get_remaining))

                    logger.debug(
                        "Tx0 fee provided remaining: {}; required fee: {}".format(
                            tx0_fee_provided_remaining / config.UNIT, fee / config.UNIT
                        )
                    )
                    if tx0_fee_provided_remaining < fee:
                        logger.debug("Skipping: tx0 fee provided remaining is too low.")
                        continue
                    else:
                        tx0_fee_provided_remaining -= fee
                        if block_index >= 287800 or config.TESTNET:  # Protocol change.
                            tx1_fee_required_remaining -= fee

                elif tx1["give_asset"] == config.SCH:

                    if block_index >= 310500 or config.TESTNET:  # Protocol change.
                        fee = int(tx0["fee_required"] * util.price(backward_quantity, tx0["give_quantity"]))
                    else:
                        fee = int(tx0["fee_required_remaining"] * util.price(backward_quantity, tx0_get_remaining))

                    logger.debug(
                        "Tx1 fee provided remaining: {}; required fee: {}".format(
                            tx1_fee_provided_remaining / config.UNIT, fee / config.UNIT
                        )
                    )
                    if tx1_fee_provided_remaining < fee:
                        logger.debug("Skipping: tx1 fee provided remaining is too low.")
                        continue
                    else:
                        tx1_fee_provided_remaining -= fee
                        if block_index >= 287800 or config.TESTNET:  # Protocol change.
                            tx0_fee_required_remaining -= fee

            else:  # Don’t deduct.
                if tx1["get_asset"] == config.SCH:
                    if tx0_fee_provided_remaining < tx1["fee_required"]:
                        continue
                elif tx1["give_asset"] == config.SCH:
                    if tx1_fee_provided_remaining < tx0["fee_required"]:
                        continue

            if config.SCH in (tx1["give_asset"], tx1["get_asset"]):
                status = "pending"
            else:
                status = "completed"
                # Credit.
                util.credit(
                    db, tx1["source"], tx1["get_asset"], forward_quantity, action="order match", event=order_match_id
                )
                util.credit(
                    db, tx0["source"], tx0["get_asset"], backward_quantity, action="order match", event=order_match_id
                )

            # Debit the order, even if it involves giving SatoshiChains, and so one
            # can't debit the sending account.
            # Get remainings may be negative.
            tx0_give_remaining -= forward_quantity
            tx0_get_remaining -= backward_quantity
            tx1_give_remaining -= backward_quantity
            tx1_get_remaining -= forward_quantity

            # Update give_remaining, get_remaining.
            # tx0
            tx0_status = "open"
            if tx0_give_remaining <= 0 or (
                tx0_get_remaining <= 0 and (block_index >= 292000 or config.TESTNET)
            ):  # Protocol change
                if tx0["give_asset"] != config.SCH and tx0["get_asset"] != config.SCH:
                    # Fill order, and recredit give_remaining.
                    tx0_status = "filled"
                    util.credit(
                        db, tx0["source"], tx0["give_asset"], tx0_give_remaining, event=tx1["tx_hash"], action="filled"
                    )
            bindings = {
                "give_remaining": tx0_give_remaining,
                "get_remaining": tx0_get_remaining,
                "fee_required_remaining": tx0_fee_required_remaining,
                "fee_provided_remaining": tx0_fee_provided_remaining,
                "status": tx0_status,
                "tx_hash": tx0["tx_hash"],
            }
            sql = "update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, fee_provided_remaining = :fee_provided_remaining, status = :status where tx_hash = :tx_hash"
            cursor.execute(sql, bindings)
            log.message(db, block_index, "update", "orders", bindings)
            # tx1
            if tx1_give_remaining <= 0 or (
                tx1_get_remaining <= 0 and (block_index >= 292000 or config.TESTNET)
            ):  # Protocol change
                if tx1["give_asset"] != config.SCH and tx1["get_asset"] != config.SCH:
                    # Fill order, and recredit give_remaining.
                    tx1_status = "filled"
                    util.credit(
                        db, tx1["source"], tx1["give_asset"], tx1_give_remaining, event=tx0["tx_hash"], action="filled"
                    )
            bindings = {
                "give_remaining": tx1_give_remaining,
                "get_remaining": tx1_get_remaining,
                "fee_required_remaining": tx1_fee_required_remaining,
                "fee_provided_remaining": tx1_fee_provided_remaining,
                "status": tx1_status,
                "tx_hash": tx1["tx_hash"],
            }
            sql = "update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, fee_provided_remaining = :fee_provided_remaining, status = :status where tx_hash = :tx_hash"
            cursor.execute(sql, bindings)
            log.message(db, block_index, "update", "orders", bindings)

            # Calculate when the match will expire.
            if block_index >= 308000 or config.TESTNET:  # Protocol change.
                match_expire_index = block_index + 20
            elif block_index >= 286500 or config.TESTNET:  # Protocol change.
                match_expire_index = block_index + 10
            else:
                match_expire_index = min(tx0["expire_index"], tx1["expire_index"])

            # Record order match.
            bindings = {
                "id": util.make_id(tx0["tx_hash"], tx["tx_hash"]),
                "tx0_index": tx0["tx_index"],
                "tx0_hash": tx0["tx_hash"],
                "tx0_address": tx0["source"],
                "tx1_index": tx1["tx_index"],
                "tx1_hash": tx1["tx_hash"],
                "tx1_address": tx1["source"],
                "forward_asset": forward_asset,
                "forward_quantity": forward_quantity,
                "backward_asset": backward_asset,
                "backward_quantity": backward_quantity,
                "tx0_block_index": tx0["block_index"],
                "tx1_block_index": tx1["block_index"],
                "block_index": block_index,
                "tx0_expiration": tx0["expiration"],
                "tx1_expiration": tx1["expiration"],
                "match_expire_index": match_expire_index,
                "fee_paid": fee,
                "status": status,
            }
            sql = "insert into order_matches values(:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :forward_asset, :forward_quantity, :backward_asset, :backward_quantity, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :fee_paid, :status)"
            cursor.execute(sql, bindings)

            if tx1_status == "filled":
                break

    cursor.close()
    return
Exemple #3
0
def parse(db, tx, message):
    bet_parse_cursor = db.cursor()

    # Unpack message.
    try:
        if len(message) != LENGTH:
            raise exceptions.UnpackError
        (bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration) = struct.unpack(
            FORMAT, message
        )
        status = "open"
    except (exceptions.UnpackError, struct.error):
        (
            bet_type,
            deadline,
            wager_quantity,
            counterwager_quantity,
            target_value,
            leverage,
            expiration,
            fee_fraction_int,
        ) = (0, 0, 0, 0, 0, 0, 0, 0)
        status = "invalid: could not unpack"

    odds, fee_fraction = 0, 0
    feed_address = tx["destination"]
    if status == "open":
        try:
            odds = util.price(wager_quantity, counterwager_quantity)
        except ZeroDivisionError:
            odds = 0

        fee_fraction = get_fee_fraction(db, feed_address)

        # Overbet
        bet_parse_cursor.execute(
            """SELECT * FROM balances \
                                    WHERE (address = ? AND asset = ?)""",
            (tx["source"], config.SHP),
        )
        balances = list(bet_parse_cursor)
        if not balances:
            wager_quantity = 0
        else:
            balance = balances[0]["quantity"]
            if balance < wager_quantity:
                wager_quantity = balance
                counterwager_quantity = int(util.price(wager_quantity, odds))

        problems, leverage = validate(
            db,
            tx["source"],
            feed_address,
            bet_type,
            deadline,
            wager_quantity,
            counterwager_quantity,
            target_value,
            leverage,
            expiration,
            tx["block_index"],
        )
        if problems:
            status = "invalid: " + "; ".join(problems)

    # Debit quantity wagered. (Escrow.)
    if status == "open":
        util.debit(db, tx["source"], config.SHP, wager_quantity, action="bet", event=tx["tx_hash"])

    # Add parsed transaction to message-type–specific table.
    bindings = {
        "tx_index": tx["tx_index"],
        "tx_hash": tx["tx_hash"],
        "block_index": tx["block_index"],
        "source": tx["source"],
        "feed_address": feed_address,
        "bet_type": bet_type,
        "deadline": deadline,
        "wager_quantity": wager_quantity,
        "wager_remaining": wager_quantity,
        "counterwager_quantity": counterwager_quantity,
        "counterwager_remaining": counterwager_quantity,
        "target_value": target_value,
        "leverage": leverage,
        "expiration": expiration,
        "expire_index": tx["block_index"] + expiration,
        "fee_fraction_int": fee_fraction * 1e8,
        "status": status,
    }
    sql = "insert into bets values(:tx_index, :tx_hash, :block_index, :source, :feed_address, :bet_type, :deadline, :wager_quantity, :wager_remaining, :counterwager_quantity, :counterwager_remaining, :target_value, :leverage, :expiration, :expire_index, :fee_fraction_int, :status)"
    bet_parse_cursor.execute(sql, bindings)

    # Match.
    if status == "open" and tx["block_index"] != config.MEMPOOL_BLOCK_INDEX:
        match(db, tx)

    bet_parse_cursor.close()
def parse(db, tx, message):
    order_parse_cursor = db.cursor()

    # Unpack message.
    try:
        if len(message) != LENGTH:
            raise exceptions.UnpackError
        give_id, give_quantity, get_id, get_quantity, expiration, fee_required = struct.unpack(FORMAT, message)
        give_asset = util.get_asset_name(db, give_id, tx["block_index"])
        get_asset = util.get_asset_name(db, get_id, tx["block_index"])
        status = "open"
    except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e:
        give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required = 0, 0, 0, 0, 0, 0
        status = "invalid: could not unpack"

    price = 0
    if status == "open":
        try:
            price = util.price(get_quantity, give_quantity)
        except ZeroDivisionError:
            price = 0

        # Overorder
        order_parse_cursor.execute(
            """SELECT * FROM balances \
                                      WHERE (address = ? AND asset = ?)""",
            (tx["source"], give_asset),
        )
        balances = list(order_parse_cursor)
        if give_asset != config.SCH:
            if not balances:
                give_quantity = 0
            else:
                balance = balances[0]["quantity"]
                if balance < give_quantity:
                    give_quantity = balance
                    get_quantity = int(price * give_quantity)

        problems = validate(
            db,
            tx["source"],
            give_asset,
            give_quantity,
            get_asset,
            get_quantity,
            expiration,
            fee_required,
            tx["block_index"],
        )
        if problems:
            status = "invalid: " + "; ".join(problems)

    # Debit give quantity. (Escrow.)
    if status == "open":
        if give_asset != config.SCH:  # No need (or way) to debit SCH.
            util.debit(db, tx["source"], give_asset, give_quantity, action="open order", event=tx["tx_hash"])

    # Add parsed transaction to message-type–specific table.
    bindings = {
        "tx_index": tx["tx_index"],
        "tx_hash": tx["tx_hash"],
        "block_index": tx["block_index"],
        "source": tx["source"],
        "give_asset": give_asset,
        "give_quantity": give_quantity,
        "give_remaining": give_quantity,
        "get_asset": get_asset,
        "get_quantity": get_quantity,
        "get_remaining": get_quantity,
        "expiration": expiration,
        "expire_index": tx["block_index"] + expiration,
        "fee_required": fee_required,
        "fee_required_remaining": fee_required,
        "fee_provided": tx["fee"],
        "fee_provided_remaining": tx["fee"],
        "status": status,
    }
    sql = "insert into orders values(:tx_index, :tx_hash, :block_index, :source, :give_asset, :give_quantity, :give_remaining, :get_asset, :get_quantity, :get_remaining, :expiration, :expire_index, :fee_required, :fee_required_remaining, :fee_provided, :fee_provided_remaining, :status)"
    order_parse_cursor.execute(sql, bindings)

    # Match.
    if status == "open" and tx["block_index"] != config.MEMPOOL_BLOCK_INDEX:
        match(db, tx)

    order_parse_cursor.close()