general-market-framework/matchmaker.py

123 lines
4.3 KiB
Python

# -*- coding: utf-8 -*-
from ethereum import slogging, utils
from collections import namedtuple
Ticket = namedtuple("Ticket", "id owner epoch preferences")
Offer = namedtuple("Offer", "id epoch buy_id sell_id hash preferences")
class Matchmaker:
name = ''
buyers = []
sellers = []
sealed_offers = []
current_block = -1
def debug(self, data):
print('debug', data)
def reveal_offers(self):
for offer in self.sealed_offers:
win_min = offer.epoch + self.SEALED_WINDOW
win_max = win_min + self.REVEAL_WINDOW
can_reveal = self.current_block > win_min and self.current_block < win_max
if can_reveal:
print(self.name, 'revealing offer', offer.id)
# can probably encode all information in the proof
sha_proof = [offer.epoch, offer.buy_id, offer.sell_id]
self.market.reveal_offer(offer.id, offer.buy_id, offer.sell_id, sha_proof) # TODO preferences
self.sealed_offers.remove(offer)
def make_match(self, buyer, seller):
if buyer.epoch < self.current_block + self.SEALED_WINDOW:
print(self.name, 'making sealed offer', buyer.id, seller.id)
# TODO: check hash for adding sealed offer, ie hash preferences?
# TODO: rework this, see reveal
hash_arr = [buyer.epoch, buyer.id, seller.id]
shasum = utils.sha3(''.join(map(lambda x: utils.zpad(utils.encode_int(x), 32), hash_arr)))
# shasum = utils.sha3(hash_arr)
# TODO check epoch against SEALED_WINDOW
offer_id = self.market.add_sealed_offer(buyer.id, shasum)
print(self.name, 'shasum', utils.decode_int(shasum))
# TODO, combine preferences
# TODO rework this, can probably just have buyer and seller instead of id's only
offer = Offer(offer_id, buyer.epoch, buyer.id, seller.id, shasum, buyer.preferences)
self.sealed_offers.append(offer)
def process(self):
# naively make matches
for s in self.sellers:
for b in self.buyers:
for k, v in s.preferences.viewitems() & b.preferences.viewitems():
print(self.name, 'match found on', k, v)
self.sellers.remove(s)
self.buyers.remove(b)
self.make_match(b, s)
self.reveal_offers()
# TODO:
# reveal offers
# do cleanup
print(self.name, 'processing on block', self.current_block)
def announce(self, data):
''' A new ticket has arrived '''
ticket_id = data[0]
info = self.market.get_info(ticket_id)
# Rebuild Preferences
preferences = self.market.get_preferences(ticket_id)
keys = [utils.encode_int(x) for x in preferences[::2]]
preferences = dict(zip(keys, preferences[1::2]))
del preferences['']
ticket = Ticket(ticket_id, info[0], info[1], preferences)
# Our match maker assumes there is a price
if ticket.preferences['price'] < 0:
ticket.preferences['price'] = abs(ticket.preferences['price'])
self.sellers.append(ticket)
else:
self.buyers.append(ticket)
self.process()
def listener(self, msg):
'''
Dynamically call Methods based on first param
Currently only announce exists
Also calls process on new block number, based on delta event
'''
event = msg['event']
# if event != 'LOG' and event != 'vm':
# print(event)
if event == 'LOG' and msg['to'] == self.market.address:
msg_type = utils.encode_int(msg['topics'][0]).rstrip('\x00')
msg_data = msg['topics'][1:]
if hasattr(self, msg_type):
getattr(self, msg_type)(msg_data)
elif event == 'delta':
if self.current_block < self.state.block.number:
self.current_block = self.state.block.number
self.process()
def __init__(self, state, market, name=''):
self.state = state
self.market = market
self.name = name
windows = self.market.get_windows()
self.SEALED_WINDOW = windows[0]
self.REVEAL_WINDOW = windows[1]
slogging.log_listeners.listeners.append(self.listener)