# standard imports import logging # external imports import celery from cic_eth_registry.error import ( UnknownContractError, NotAContractError, ) from chainlib.status import Status as TxStatus from chainlib.eth.address import to_checksum_address from chainlib.eth.error import RequestMismatchException from chainlib.eth.constant import ZERO_ADDRESS from hexathon import ( strip_0x, add_0x, ) from eth_erc20 import ERC20 from erc20_faucet import Faucet # local imports from .base import SyncFilter from cic_eth.eth.meta import ExtendedTx from cic_eth.encode import tx_normalize logg = logging.getLogger().getChild(__name__) class CallbackFilter(SyncFilter): trusted_addresses = [] def __init__(self, chain_spec, method, queue, caller_address=ZERO_ADDRESS): self.queue = queue self.method = method self.chain_spec = chain_spec self.caller_address = caller_address def parse_transfer(self, tx, conn): if not tx.payload: return (None, None) r = ERC20.parse_transfer_request(tx.payload) transfer_data = {} transfer_data['to'] = tx_normalize.wallet_address(r[0]) transfer_data['value'] = r[1] transfer_data['from'] = tx_normalize.wallet_address(tx.outputs[0]) transfer_data['token_address'] = tx.inputs[0] return ('transfer', transfer_data) def parse_transferfrom(self, tx, conn): if not tx.payload: return (None, None) r = ERC20.parse_transfer_from_request(tx.payload) transfer_data = {} transfer_data['from'] = tx_normalize.wallet_address(r[0]) transfer_data['to'] = tx_normalize.wallet_address(r[1]) transfer_data['value'] = r[2] transfer_data['token_address'] = tx.inputs[0] return ('transferfrom', transfer_data) def parse_giftto(self, tx, conn): if not tx.payload: return (None, None) r = Faucet.parse_give_to_request(tx.payload) transfer_data = {} transfer_data['to'] = tx_normalize.wallet_address(r[0]) transfer_data['value'] = tx.value transfer_data['from'] = tx_normalize.wallet_address(tx.outputs[0]) #transfer_data['token_address'] = tx.inputs[0] faucet_contract = tx.inputs[0] c = Faucet(self.chain_spec) o = c.token(faucet_contract, sender_address=self.caller_address) r = conn.do(o) transfer_data['token_address'] = add_0x(c.parse_token(r)) o = c.token_amount(faucet_contract, sender_address=self.caller_address) r = conn.do(o) transfer_data['value'] = c.parse_token_amount(r) return ('tokengift', transfer_data) def call_back(self, transfer_type, result): result['chain_spec'] = result['chain_spec'].asdict() s = celery.signature( self.method, [ result, transfer_type, int(result['status_code'] != 0), ], queue=self.queue, ) # s_translate = celery.signature( # 'cic_eth.ext.address.translate', # [ # result, # self.trusted_addresses, # chain_str, # ], # queue=self.queue, # ) # s_translate.link(s) # s_translate.apply_async() t = s.apply_async() return t def parse_data(self, tx, conn): transfer_type = None transfer_data = None # TODO: what's with the mix of attributes and dict keys logg.debug('have payload {}'.format(tx.payload)) logg.debug('tx status {}'.format(tx.status)) for parser in [ self.parse_transfer, self.parse_transferfrom, self.parse_giftto, ]: try: if tx: (transfer_type, transfer_data) = parser(tx, conn) if transfer_type == None: continue break except RequestMismatchException: continue logg.debug('resolved method {}'.format(transfer_type)) if transfer_data != None: transfer_data['status'] = tx.status return (transfer_type, transfer_data) def filter(self, conn, block, tx, db_session=None): transfer_data = None transfer_type = None try: (transfer_type, transfer_data) = self.parse_data(tx, conn) except TypeError: logg.debug('invalid method data length for tx {}'.format(tx.hash)) return if len(tx.payload) < 8: logg.debug('callbacks filter data length not sufficient for method signature in tx {}, skipping'.format(tx.hash)) return logg.debug('checking callbacks filter input {}'.format(tx.payload[:8])) if transfer_data != None: token_symbol = None result = None try: tokentx = ExtendedTx(conn, tx.hash, self.chain_spec) tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses, caller_address=self.caller_address) tokentx.set_tokens(transfer_data['token_address'], transfer_data['value']) if transfer_data['status'] == 0: tokentx.set_status(1) else: tokentx.set_status(0) result = tokentx.asdict() t = self.call_back(transfer_type, result) logg.info('callback success task id {} tx {} queue {}'.format(t, tx.hash, t.queue)) except UnknownContractError: logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(self.queue, self.method, transfer_data['to'], tx.hash)) except NotAContractError: logg.debug('callback filter {}:{} skipping "transfer" on non-contract address {} tx {}'.format(self.queue, self.method, transfer_data['to'], tx.hash)) def __str__(self): return 'cic-eth callbacks'