def cancel_order(db, order, status, block_index): cursor = db.cursor() # Update status of order. bindings = {"status": status, "tx_hash": order["tx_hash"]} sql = "update orders set status = :status where tx_hash = :tx_hash" cursor.execute(sql, bindings) log.message(db, block_index, "update", "orders", bindings) if order["give_asset"] != config.SCH: # Can’t credit SCH. util.credit( db, order["source"], order["give_asset"], order["give_remaining"], action="cancel order", event=order["tx_hash"], ) if status == "expired": # Record offer expiration. bindings = { "order_index": order["tx_index"], "order_hash": order["tx_hash"], "source": order["source"], "block_index": block_index, } sql = "insert into order_expirations values(:order_index, :order_hash, :source, :block_index)" cursor.execute(sql, bindings) cursor.close()
def cancel_bet_match(db, bet_match, status, block_index): # Does not re‐open, re‐fill, etc. constituent bets. cursor = db.cursor() # Recredit tx0 address. util.credit( db, bet_match["tx0_address"], config.SHP, bet_match["forward_quantity"], action="recredit forward quantity", event=bet_match["id"], ) # Recredit tx1 address. util.credit( db, bet_match["tx1_address"], config.SHP, bet_match["backward_quantity"], action="recredit backward quantity", event=bet_match["id"], ) # Update status of bet match. bindings = {"status": status, "bet_match_id": bet_match["id"]} sql = "update bet_matches set status = :status where id = :bet_match_id" cursor.execute(sql, bindings) log.message(db, block_index, "update", "bet_matches", bindings) cursor.close()
def create_contract(self, code, endowment=0, sender=''): if not sender: sender = '82a978b3f5962a5b0957d9ee9eef472ee55b42f1' # PyEthereum uses ECDSA to derive this string from `sender = 0`. util.credit(db, sender, config.SHP, max(endowment*2, 100000000), action='unit test', event='facefeed') success, data = tester.state.do_send(self, sender, '', endowment, data=code) contract_id = data return contract_id
def parse (db, tx, message): cursor = db.cursor() # Unpack message. try: if len(message) != LENGTH: raise exceptions.UnpackError tx0_hash_bytes, tx1_hash_bytes = struct.unpack(FORMAT, message) tx0_hash, tx1_hash = binascii.hexlify(tx0_hash_bytes).decode('utf-8'), binascii.hexlify(tx1_hash_bytes).decode('utf-8') order_match_id = util.make_id(tx0_hash, tx1_hash) status = 'valid' except (exceptions.UnpackError, struct.error) as e: tx0_hash, tx1_hash, order_match_id = None, None, None status = 'invalid: could not unpack' if status == 'valid': destination, shell_quantity, escrowed_asset, escrowed_quantity, order_match, problems = validate(db, tx['source'], order_match_id, tx['block_index']) if problems: order_match = None status = 'invalid: ' + '; '.join(problems) if status == 'valid': # SCH must be paid all at once. if tx['shell_amount'] >= shell_quantity: # Credit source address for the currency that he bought with the SatoshiChains. util.credit(db, tx['source'], escrowed_asset, escrowed_quantity, action='shellpay', event=tx['tx_hash']) status = 'valid' # Update order match. bindings = { 'status': 'completed', 'order_match_id': order_match_id } sql='update order_matches set status = :status where id = :order_match_id' cursor.execute(sql, bindings) log.message(db, tx['block_index'], 'update', 'order_matches', bindings) # 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'], 'destination': tx['destination'], 'shell_amount': tx['shell_amount'], 'order_match_id': order_match_id, 'status': status, } sql='insert into shellpays values(:tx_index, :tx_hash, :block_index, :source, :destination, :shell_amount, :order_match_id, :status)' cursor.execute(sql, bindings) cursor.close()
def cancel_bet(db, bet, status, block_index): cursor = db.cursor() # Update status of bet. bindings = {"status": status, "tx_hash": bet["tx_hash"]} sql = "update bets set status = :status where tx_hash = :tx_hash" cursor.execute(sql, bindings) log.message(db, block_index, "update", "bets", bindings) util.credit( db, bet["source"], config.SHP, bet["wager_remaining"], action="recredit wager remaining", event=bet["tx_hash"] ) cursor = db.cursor()
def send (self, sender, to, value, data=[]): # print('tuple', sender, to, value, data) # Encode data. data = serpent.encode_datalist(data) data = binascii.unhexlify(data) # Execute contract. # print('qux', data, type(data)) util.credit(db, sender, config.SHP, value + 100000000, action='unit test', event='facefeed') success, output = tester.state.do_send(self, sender, to, value, data=data) if output: return rlp.decode_datalist(bytes(output)) else: return []
def cancel_rps (db, rps, status, block_index): cursor = db.cursor() # Update status of rps. bindings = { 'status': status, 'tx_hash': rps['tx_hash'] } sql='''UPDATE rps SET status = :status WHERE tx_hash = :tx_hash''' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'rps', bindings) util.credit(db, rps['source'], 'SHP', rps['wager'], action='recredit wager', event=rps['tx_hash']) cursor.close()
def update_rps_match_status (db, rps_match, status, block_index): cursor = db.cursor() if status in ['expired', 'concluded: tie']: # Recredit tx0 address. util.credit(db, rps_match['tx0_address'], 'SHP', rps_match['wager'], action='recredit wager', event=rps_match['id']) # Recredit tx1 address. util.credit(db, rps_match['tx1_address'], 'SHP', rps_match['wager'], action='recredit wager', event=rps_match['id']) elif status.startswith('concluded'): # Credit the winner winner = rps_match['tx0_address'] if status == 'concluded: first player wins' else rps_match['tx1_address'] util.credit(db, winner, 'SHP', 2 * rps_match['wager'], action='wins', event=rps_match['id']) # Update status of rps match. bindings = { 'status': status, 'rps_match_id': rps_match['id'] } sql='UPDATE rps_matches SET status = :status WHERE id = :rps_match_id' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'rps_matches', bindings) cursor.close()
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 parse (db, tx, message): issuance_parse_cursor = db.cursor() # Unpack message. try: if (tx['block_index'] > 283271 or config.TESTNET) and len(message) >= LENGTH_2: # Protocol change. if len(message) - LENGTH_2 <= 42: curr_format = FORMAT_2 + '{}p'.format(len(message) - LENGTH_2) else: curr_format = FORMAT_2 + '{}s'.format(len(message) - LENGTH_2) asset_id, quantity, divisible, callable_, call_date, call_price, description = struct.unpack(curr_format, message) call_price = round(call_price, 6) # TODO: arbitrary try: description = description.decode('utf-8') except UnicodeDecodeError: description = '' else: if len(message) != LENGTH_1: raise exceptions.UnpackError asset_id, quantity, divisible = struct.unpack(FORMAT_1, message) callable_, call_date, call_price, description = False, 0, 0.0, '' try: asset = util.generate_asset_name(asset_id, tx['block_index']) except exceptions.AssetNameError: asset = None status = 'invalid: bad asset name' status = 'valid' except exceptions.UnpackError as e: asset, quantity, divisible, callable_, call_date, call_price, description = None, None, None, None, None, None, None status = 'invalid: could not unpack' fee = 0 if status == 'valid': call_date, call_price, problems, fee, description, divisible, reissuance = validate(db, tx['source'], tx['destination'], asset, quantity, divisible, callable_, call_date, call_price, description, block_index=tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if 'total quantity overflow' in problems: quantity = 0 if tx['destination']: issuer = tx['destination'] transfer = True quantity = 0 else: issuer = tx['source'] transfer = False # Debit fee. if status == 'valid': util.debit(db, tx['source'], config.SHP, fee, action="issuance fee", event=tx['tx_hash']) # Lock? lock = False if status == 'valid': if description and description.lower() == 'lock': lock = True cursor = db.cursor() issuances = list(cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index ASC''', ('valid', asset))) cursor.close() description = issuances[-1]['description'] # Use last description. (Assume previous issuance exists because tx is valid.) timestamp, value_int, fee_fraction_int = None, None, None if not reissuance: # Add to table of assets. bindings= { 'asset_id': str(asset_id), 'asset_name': str(asset), 'block_index': tx['block_index'], } sql='insert into assets values(:asset_id, :asset_name, :block_index)' issuance_parse_cursor.execute(sql, bindings) # Add parsed transaction to message-type–specific table. bindings= { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'asset': asset, 'quantity': quantity, 'divisible': divisible, 'source': tx['source'], 'issuer': issuer, 'transfer': transfer, 'callable': callable_, 'call_date': call_date, 'call_price': call_price, 'description': description, 'fee_paid': fee, 'locked': lock, 'status': status, } sql='insert into issuances values(:tx_index, :tx_hash, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status)' issuance_parse_cursor.execute(sql, bindings) # Credit. if status == 'valid' and quantity: util.credit(db, tx['source'], asset, quantity, action="issuance", event=tx['tx_hash']) issuance_parse_cursor.close()
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
def cancel_order_match(db, order_match, status, block_index): """The only cancelling is an expiration. """ cursor = db.cursor() # Skip order matches just expired as a penalty. (Not very efficient.) if not (block_index >= 314250 or config.TESTNET): # Protocol change. order_matches = list( cursor.execute( """SELECT * FROM order_matches \ WHERE (id = ? AND status = ?)""", (order_match["id"], "expired"), ) ) if order_matches: cursor.close() return # Update status of order match. bindings = {"status": status, "order_match_id": order_match["id"]} sql = "update order_matches set status = :status where id = :order_match_id" cursor.execute(sql, bindings) log.message(db, block_index, "update", "order_matches", bindings) order_match_id = util.make_id(order_match["tx0_hash"], order_match["tx1_hash"]) # If tx0 is dead, credit address directly; if not, replenish give remaining, get remaining, and fee required remaining. orders = list( cursor.execute( """SELECT * FROM orders \ WHERE tx_index = ?""", (order_match["tx0_index"],), ) ) assert len(orders) == 1 tx0_order = orders[0] if tx0_order["status"] in ("expired", "cancelled"): tx0_order_status = tx0_order["status"] if order_match["forward_asset"] != config.SCH: util.credit( db, order_match["tx0_address"], order_match["forward_asset"], order_match["forward_quantity"], action="order {}".format(tx0_order_status), event=order_match["id"], ) else: tx0_give_remaining = tx0_order["give_remaining"] + order_match["forward_quantity"] tx0_get_remaining = tx0_order["get_remaining"] + order_match["backward_quantity"] if tx0_order["get_asset"] == config.SCH and (block_index >= 297000 or config.TESTNET): # Protocol change. tx0_fee_required_remaining = tx0_order["fee_required_remaining"] + order_match["fee_paid"] else: tx0_fee_required_remaining = tx0_order["fee_required_remaining"] tx0_order_status = tx0_order["status"] bindings = { "give_remaining": tx0_give_remaining, "get_remaining": tx0_get_remaining, "status": tx0_order_status, "fee_required_remaining": tx0_fee_required_remaining, "tx_hash": order_match["tx0_hash"], } sql = "update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining where tx_hash = :tx_hash" cursor.execute(sql, bindings) log.message(db, block_index, "update", "orders", bindings) # If tx1 is dead, credit address directly; if not, replenish give remaining, get remaining, and fee required remaining. orders = list( cursor.execute( """SELECT * FROM orders \ WHERE tx_index = ?""", (order_match["tx1_index"],), ) ) assert len(orders) == 1 tx1_order = orders[0] if tx1_order["status"] in ("expired", "cancelled"): tx1_order_status = tx1_order["status"] if order_match["backward_asset"] != config.SCH: util.credit( db, order_match["tx1_address"], order_match["backward_asset"], order_match["backward_quantity"], action="order {}".format(tx1_order_status), event=order_match["id"], ) else: tx1_give_remaining = tx1_order["give_remaining"] + order_match["backward_quantity"] tx1_get_remaining = tx1_order["get_remaining"] + order_match["forward_quantity"] if tx1_order["get_asset"] == config.SCH and (block_index >= 297000 or config.TESTNET): # Protocol change. tx1_fee_required_remaining = tx1_order["fee_required_remaining"] + order_match["fee_paid"] else: tx1_fee_required_remaining = tx1_order["fee_required_remaining"] tx1_order_status = tx1_order["status"] bindings = { "give_remaining": tx1_give_remaining, "get_remaining": tx1_get_remaining, "status": tx1_order_status, "fee_required_remaining": tx1_fee_required_remaining, "tx_hash": order_match["tx1_hash"], } sql = "update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining where tx_hash = :tx_hash" cursor.execute(sql, bindings) log.message(db, block_index, "update", "orders", bindings) if block_index < 286500: # Protocol change. # Sanity check: one of the two must have expired. tx0_order_time_left = tx0_order["expire_index"] - block_index tx1_order_time_left = tx1_order["expire_index"] - block_index assert tx0_order_time_left or tx1_order_time_left # Penalize tardiness. if block_index >= 313900 or config.TESTNET: # Protocol change. if tx0_order["status"] == "expired" and order_match["forward_asset"] == config.SCH: exact_penalty(db, order_match["tx0_address"], block_index, order_match["id"]) if tx1_order["status"] == "expired" and order_match["backward_asset"] == config.SCH: exact_penalty(db, order_match["tx1_address"], block_index, order_match["id"]) # Re‐match. if block_index >= 310000 or config.TESTNET: # Protocol change. if not (block_index >= 315000 or config.TESTNET): # Protocol change. cursor.execute( """SELECT * FROM transactions\ WHERE tx_hash = ?""", (tx0_order["tx_hash"],), ) match(db, list(cursor)[0], block_index) cursor.execute( """SELECT * FROM transactions\ WHERE tx_hash = ?""", (tx1_order["tx_hash"],), ) match(db, list(cursor)[0], block_index) if status == "expired": # Record order match expiration. bindings = { "order_match_id": order_match["id"], "tx0_address": order_match["tx0_address"], "tx1_address": order_match["tx1_address"], "block_index": block_index, } sql = "insert into order_match_expirations values(:order_match_id, :tx0_address, :tx1_address, :block_index)" cursor.execute(sql, bindings) cursor.close()
def parse (db, tx, MAINNET_BURNS, message=None): burn_parse_cursor = db.cursor() if config.TESTNET: status = 'valid' if status == 'valid': problems = validate(db, tx['source'], tx['destination'], tx['shell_amount'], tx['block_index'], overburn=False) if problems: status = 'invalid: ' + '; '.join(problems) if tx['shell_amount'] != None: sent = tx['shell_amount'] else: sent = 0 if status == 'valid': # Calculate quantity of SHP earned. (Maximum 1 SCH in total, ever.) cursor = db.cursor() cursor.execute('''SELECT * FROM burns WHERE (status = ? AND source = ?)''', ('valid', tx['source'])) burns = cursor.fetchall() already_burned = sum([burn['burned'] for burn in burns]) ONE = 1 * config.UNIT max_burn = ONE - already_burned if sent > max_burn: burned = max_burn # Exceeded maximum burn; earn what you can. else: burned = sent total_time = config.BURN_END - config.BURN_START partial_time = config.BURN_END - tx['block_index'] multiplier = (1000 + (500 * Fraction(partial_time, total_time))) earned = round(burned * multiplier) # Credit source address with earned SHP. util.credit(db, tx['source'], config.SHP, earned, action='burn', event=tx['tx_hash']) else: burned = 0 earned = 0 tx_index = tx['tx_index'] tx_hash = tx['tx_hash'] block_index = tx['block_index'] source = tx['source'] else: # Mainnet burns are hard‐coded. try: line = MAINNET_BURNS[tx['tx_hash']] except KeyError: return util.credit(db, line['source'], config.SHP, int(line['earned']), action='burn', event=line['tx_hash']) tx_index = tx['tx_index'] tx_hash = line['tx_hash'] block_index = line['block_index'] source = line['source'] burned = line['burned'] earned = line['earned'] status = 'valid' # Add parsed transaction to message-type–specific table. # TODO: store sent in table bindings = { 'tx_index': tx_index, 'tx_hash': tx_hash, 'block_index': block_index, 'source': source, 'burned': burned, 'earned': earned, 'status': status, } sql='insert into burns values(:tx_index, :tx_hash, :block_index, :source, :burned, :earned, :status)' burn_parse_cursor.execute(sql, bindings) burn_parse_cursor.close()
def transfer_value(self, tx, source, destination, quantity, asset=config.SHP): if source: util.debit(self.db, source, asset, quantity, action='transfer value', event=tx.tx_hash) if destination: util.credit(self.db, destination, asset, quantity, action='transfer value', event=tx.tx_hash) return True