Exemplo n.º 1
0
class ContractWrapper:
    def __init__(
        self,
        the_code=False,
        the_sig=None,
        the_args=None,
        the_address=False,
        events_callback=False,
        deploy_callback=False,
        blocking_sleep_time=0.1,
        rpc_host=DEFAULT_RPC_HOST,
        rpc_port=DEFAULT_RPC_PORT,
        settings_confirm_states={},
        contract_address=False,
        start_at_current_block=False,
        auto_deploy=True,
        contract_thread_sleep_time=1.0,
        reorg_callback=False,
    ):
        """
        Simple contract wrapper, assists with deploying contract, sending transactions, and tracking event logs.
        
        Args:
        - the_code: solidity code for contract that should be deployed, prior to any operations.
        - the_address: address of already-deployed main contract.
        - contract_address: contract address, from previous `deploy()` call.
        - the_sig: optional constructor signature.
        - the_args: optional constructor args.
        - events_callback: callback for event messages e.g. `TheLog()`, `MintEvent()`, `LockupTokEvent()`, `TransferTokEvent()`.
        - deploy_callback: callback for contract deploys.
        - blocking_sleep_time: time to sleep when blocking and polling for a transaction receipt.
        """

        self.block_details = {}

        self.reorg_callback = reorg_callback
        self.confirmation_tracker = {
        }  ## {'block_hash':{'prev_block_hash':xx, 'block_num':yy}}

        self.done_block_nums = {}  ## {confirm_state:set()}
        self.done_transactions = {}  ## {confirm_state:set()}
        self.prev_block_num = {}  ## {confirm_state:set()}

        self.blocking_sleep_time = blocking_sleep_time

        self.c = EthJsonRpc(rpc_host, rpc_port)

        self.contract_thread_sleep_time = contract_thread_sleep_time

        self.start_at_current_block = start_at_current_block

        self.current_block_at_init = self.c.eth_blockNumber()

        if self.start_at_current_block:
            self.last_incoming_block = max(0, self.current_block_at_init - 1)
        else:
            self.last_incoming_block = 0

        self.starting_block_num = self.last_incoming_block

        self.msgs = {}  ## {block_num:[msg, msg, msg]}

        self.the_code = the_code
        self.the_sig = the_sig
        self.the_args = the_args

        self.contract_address = the_address

        assert self.the_code or self.contract_address

        self.loop_block_num = -1

        self.confirm_states = settings_confirm_states

        self.events_callback = events_callback

        self.pending_transactions = {}  ## {tx:callback}
        self.pending_logs = {}
        self.latest_block_num = -1

        self.latest_block_num_done = 0

        self.send_transaction_queue = Queue()

        self.is_deployed = False

        if auto_deploy:
            if the_address:
                assert self.check_anything_deployed(the_address), (
                    'NOTHING DEPLOYED AT SPECIFIED ADDRESS:', the_address)
                self.is_deployed = True
            elif the_code:
                self.deploy()

    def check_anything_deployed(self, address):
        """ Basic sanity check, checks if ANY code is deployed at provided address. """
        if self.c.eth_getCode(address) == '0x0':
            return False
        return True

    def deploy(
        self,
        the_sig=False,
        the_args=False,
        block=False,
        deploy_from=False,
        callback=False,
    ):
        """ Deploy contract. Optional args_sig and args used to pass arguments to contract constructor."""

        if the_sig is not False:
            self.the_sig = the_sig

        if the_args is not False:
            self.the_args = the_args

        assert self.the_code

        if deploy_from is False:
            deploy_from = self.c.eth_coinbase()

        print('DEPLOYING_CONTRACT...', 'deploy_from:', deploy_from, 'the_sig:',
              the_sig, 'the_args:', the_args)
        # get contract address
        xx = self.c.eth_compileSolidity(self.the_code)
        #print ('GOT',xx)
        compiled = get_compiled_code(xx)

        contract_tx = self.c.create_contract(
            from_=deploy_from,
            code=compiled,
            gas=3000000,
            sig=self.the_sig,
            args=self.the_args,
        )

        if block:
            ## NOTE: @yusef feel free to switch back to this method if you want:
            #print('CONTRACT DEPLOYED, WAITING FOR CONFIRMATION')
            #wait_for_confirmation(self.c, contract_tx)

            print('BLOCKING FOR RECEIPT..')
            while True:
                receipt = self.c.eth_getTransactionReceipt(
                    contract_tx)  ## blocks to ensure transaction is mined
                if receipt:
                    break
                sleep(self.blocking_sleep_time)
            print('GOT RECEIPT')
        else:
            self.pending_transactions[contract_tx] = (callback,
                                                      self.latest_block_num)

        self.contract_address = str(self.c.get_contract_address(contract_tx))
        self.is_deployed = True
        print('DEPLOYED', self.contract_address)
        return self.contract_address

    def loop_once(self):
        assert self.is_deployed, 'Must deploy contract first.'

        had_any_events = False

        if self.c.eth_syncing():
            print('BLOCKCHAIN_STILL_SYNCING')
            return False

        if self.events_callback is not False:
            had_any_events = self.poll_incoming()

        had_any_events = self.poll_outgoing() or had_any_events

        num_fails = 0

        while self.send_transaction_queue.qsize():
            print('TRY_TO_SEND')
            tries, args, kw = self.send_transaction_queue.get()
            try:
                self._send_transaction(*args, **kw)
            except Exception as e:
                print('FAILED_TO_SEND', e, tries, args, kw)
                sleep(1)  ## TODO
                self.send_transaction_queue.put((tries + 1, args, kw))
                break

        return had_any_events

    def check_for_reorg(
        self,
        block_num,
    ):
        """ Check for reorg since last check, and reorgs during our reorg rewinding... """
        print('START check_for_reorg', block_num)

        return
        block_num = ethereum.utils.parse_int_or_hex(block_num)

        while True:

            cur_num = block_num
            had_reorg = False

            while True:
                if cur_num == self.starting_block_num:
                    break

                assert cur_num >= self.starting_block_num, (
                    cur_num, self.starting_block_num)

                ## Get info for prev and current:

                for x_block_num in [block_num, block_num - 1]:
                    if x_block_num not in self.block_details:
                        rh = self.c.eth_getBlockByNumber(x_block_num)

                        ## Strip down to just a couple fields:
                        block_h = {
                            'timestamp':
                            ethereum.utils.parse_int_or_hex(rh['timestamp']),
                            'hash':
                            rh['hash'],
                            'parentHash':
                            rh['parentHash'],
                            'blockNumber':
                            x_block_num,
                        }
                        self.block_details[x_block_num] = block_h

                ## Check for reorg:

                block_h = self.block_details[block_num]

                if block_h['parentHash'] != self.block_details[
                        block_h['blockNumber'] - 1]['hash']:
                    print('!!! REORG', block_num, '->', cur_num)
                    cur_num -= 1
                    self.latest_done_block = cur_num
                    had_reorg = True
                    continue
                break

            ## Rewind state if had_reorg:

            if had_reorg and (self.reorg_callback is not False):
                self.reorg_callback(cur_num)
                self.last_incoming_block = cur_num - 1

            ## If had_reorg, loop again - to detect another reorg that occured while we tracked down the reorg...

            if not had_reorg:
                break

        return had_reorg

    def poll_incoming(self, chunk_size=50):
        """
        https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter

        - track buffer of events from old blocks
        - track pointer to last processed block
        """

        assert self.is_deployed, 'Must deploy contract first.'

        #self.latest_block_num = self.c.eth_blockNumber()

        from_block = self.last_incoming_block + 1

        params = {
            'from_block': fixed_int_to_hex(from_block),
            'to_block': 'latest',  #fixed_int_to_hex(from_block + chunk_size),
            'address': self.contract_address,
        }

        print('eth_newFilter', 'from_block:', from_block, 'params:', params)

        self.the_filter = str(self.c.eth_newFilter(**params))

        num_blocks = len(self.msgs)

        xx_msgs = self.c.eth_getFilterLogs(self.the_filter)

        for msg in xx_msgs:
            msg['blockNumber'] = ethereum.utils.parse_int_or_hex(
                msg['blockNumber'])
            if msg['blockNumber'] not in self.msgs:
                self.msgs[msg['blockNumber']] = []
            self.msgs[msg['blockNumber']].append(msg)

        if num_blocks == len(self.msgs):
            ## Nothing new
            assert not len(xx_msgs), len(xx_msgs)
            return False

        for do_state, state_num_blocks in self.confirm_states.items():

            longest_confirm_state = max(self.confirm_states.values())
            newest_block_num = max(max(self.msgs), self.last_incoming_block)

            ## Oldest to newest:

            for nn in xrange(
                    max(1, self.last_incoming_block - state_num_blocks),
                    newest_block_num + 1,
            ):

                if self.check_for_reorg(nn):
                    ## Just wait for next call to poll_incoming() before resuming.
                    return False

                if nn in self.msgs:
                    for msg in self.msgs[nn]:
                        print('EMIT', do_state, nn, msg['data'])
                        self.events_callback(msg=msg,
                                             receipt=False,
                                             received_via=do_state)

            ## Clear out old buffer:

            for nn in self.msgs.keys():
                if nn < newest_block_num - longest_confirm_state - 1:
                    del self.msgs[nn]

        self.last_incoming_block = newest_block_num

        return True

        if False:
            ## START CHECKS

            if do_state not in self.done_transactions:
                self.done_transactions[do_state] = set()
                self.done_block_nums[do_state] = set()

            msg_block_num = ethereum.utils.parse_int_or_hex(msg['blockNumber'])

            if cm == 0:
                assert msg_block_num not in self.done_block_nums[do_state], (
                    'Seen block twice?',
                    msg_block_num,
                )
                self.done_block_nums[do_state].add(msg_block_num)

            if do_state in self.prev_block_num:
                assert msg_block_num >= self.prev_block_num[do_state], (
                    'REORG?',
                    msg_block_num,
                    self.prev_block_num[do_state],
                )
            self.prev_block_num[do_state] = msg_block_num

            assert msg['transactionHash'] not in self.done_transactions[
                do_state], (
                    'Seen transaction twice?',
                    msg_block_num,
                    msg['transactionHash'],
                )
            self.done_transactions[do_state].add(msg['transactionHash'])

            ## END CHECKS

        return had_any_events

    def _start_contract_thread(
        self,
        terminate_on_exception=False,
    ):
        while True:
            try:
                had_any_events = self.loop_once()
            except Exception as e:
                print('-----LOOP_ONCE_EXCEPTION', e)
                #exit(-1)
                raise

                if terminate_on_exception:
                    raise
                continue

            if not had_any_events:
                print('NO_NEW_EVENTS')

            sleep(self.contract_thread_sleep_time)

    def start_contract_thread(
        self,
        start_in_foreground=False,
        terminate_on_exception=False,
    ):
        """
        Start ContractWrapper loop_once() in background thread, which (in that thread!) calls back to self.process_event()
        """
        if start_in_foreground:
            self._start_contract_thread(
                terminate_on_exception=terminate_on_exception)
        else:
            self.t = Thread(
                target=self._start_contract_thread,
                args=(terminate_on_exception, ),
            )
            self.t.daemon = True
            self.t.start()

    def send_transaction(self, *args, **kw):
        assert len(args) <= 2
        if kw.get('block'):
            self.send_transaction(*args, **kw)
        else:
            self.send_transaction_queue.put((0, args, kw))

    def _send_transaction(
        self,
        args_sig,
        args,
        callback=False,
        send_from=False,
        block=False,
        gas_limit=False,
        gas_price=100,
        value=100000000000,
    ):
        """
        1) Attempt to send transaction.
        2) Get first confirmation via transaction receipt.
        3) Re-check receipt again after N blocks pass.
        
        https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendtransaction
        """

        assert self.is_deployed, 'Must deploy contract first.'

        print('SEND_TRANSACTION:', args_sig, args)

        if send_from is False:
            send_from = self.c.eth_coinbase()

        send_to = self.contract_address

        print('====TRANSACTION')
        print('send_from', send_from)
        print('send_to', send_to)
        print('args_sig', args_sig)
        print('args', args)
        #print ('gas', gas_limit)

        gas_limit = 1000000
        gas_price = self.c.DEFAULT_GAS_PRICE
        value = web3.utils.currency.to_wei(1, 'ether')

        data = self.c._encode_function(args_sig, args)
        data_hex = '0x' + data.encode('hex')

        tx = self.c.eth_sendTransaction(
            from_address=send_from,
            to_address=send_to,
            data=data_hex,
            gas=gas_limit,
            gas_price=gas_price,
            value=value,
        )

        if block:
            print('BLOCKING FOR RECEIPT..')
            while True:
                receipt = self.c.eth_getTransactionReceipt(
                    tx)  ## blocks to ensure transaction is mined
                if receipt:
                    break
                sleep(self.blocking_sleep_time)
            print('GOT RECEIPT')
            #print ('GOT_RECEIPT', receipt)
            #if receipt['blockNumber']:
            #    self.latest_block_num = max(ethereum.utils.parse_int_or_hex(receipt['blockNumber']), self.latest_block_num)
        else:
            self.pending_transactions[tx] = (callback, self.latest_block_num)

        self.latest_block_num = self.c.eth_blockNumber()

        return tx

    def poll_outgoing(self):
        """
        Confirm outgoing transactions.
        """

        assert self.is_deployed, 'Must deploy contract first.'

        had_any_events = False
        if self.pending_transactions:
            had_any_events = True

        for tx, (callback,
                 attempt_block_num) in self.pending_transactions.items():

            ## Compare against the block_number where it attempted to be included:

            if (attempt_block_num <= self.latest_block_num -
                    self.confirm_states['BLOCKCHAIN_CONFIRMED']):
                continue

            receipt = self.c.eth_getTransactionReceipt(tx)

            if receipt is not None and 'blockNumber' in receipt:
                actual_block_number = ethereum.utils.parse_int_or_hex(
                    receipt['blockNumber'])
            else:
                ## TODO: wasn't confirmed after a long time.
                actual_block_number = False

            ## Now compare against the block_number where it was actually included:

            if (actual_block_number is not False) and (
                    actual_block_number >= self.latest_block_num -
                    self.confirm_states['BLOCKCHAIN_CONFIRMED']):
                if callback is not False:
                    callback(receipt)
                del self.pending_transactions[tx]

        return had_any_events

    def read_transaction(self, args_sig, value):
        rr = self.c.call(self.c.eth_coinbase(), self.contract_address,
                         args_sig, value)
        return rr

    def sign(self, user_address, value):
        rr = self.c.eth_sign(self.c.eth_coinbase(), self.contract_address,
                             user_address, value)
        return rr
Exemplo n.º 2
0
class EthereumBlockchain:
    def __init__(self,
                 the_code = False,
                 the_sig = None,
                 the_args = None,
                 the_address = False,
                 rpc_host = DEFAULT_RPC_HOST,
                 rpc_port = DEFAULT_RPC_PORT,
                 background_thread_sleep_time = 1.0,
                 ):
        
        self.con = EthJsonRpc(rpc_host, rpc_port)
        
        self.the_code = the_code
        self.the_sig = the_sig
        self.the_args = the_args
        
        self.contract_address = the_address
        
        assert self.the_code or self.contract_address
        
        self.loop_once_started = False
        
        self.sig_to_topic_id_cache = {}
        
        self.send_transaction_queue = Queue()
        
        self.background_thread_sleep_time = background_thread_sleep_time

        self.pending_transactions = {}  ## {tx:callback}
        
        self.is_deployed = False
        
        if the_address:
            assert self.check_anything_deployed(the_address), ('NOTHING DEPLOYED AT SPECIFIED ADDRESS:', the_address)
            self.is_deployed = True
        elif the_code:
            if auto_deploy:
                self.deploy()

        
    #def setup_create_pending_transaction(func):
    #    self._create_pending_transaction = func
    
    #def setup_logic_callback(func):
    #    self._logic_callback = func
    
    def event_sig_to_topic_id(self, sig):
        """ Compute ethereum topic_ids from function signatures. """
        if sig in self.sig_to_topic_id_cache:
            return self.sig_to_topic_id_cache[sig]
        name = sig[:sig.find('(')]
        types = [x.strip().split()[0] for x in sig[sig.find('(')+1:sig.find(')')].split(',')]
        topic_id = ethereum.utils.int_to_hex(ethereum.abi.event_id(name,types))
        self.sig_to_topic_id_cache[sig] = topic_id
        return topic_id

    def setup_event_callbacks(self,
                              log_handlers,
                              pending_handlers,
                              ):
        self.log_handlers = log_handlers
        self.log_handlers_hashed = {((x == 'DEFAULT') and 'DEFAULT' or self.event_sig_to_topic_id(x)):y
                                    for x,y
                                    in log_handlers.items()}
        self.pending_handlers = pending_handlers
    
    def logic_callback(self, msg, *args, **kw):
        default_func = self.log_handlers_hashed.get('DEFAULT', False)
        
        if 'topics' not in msg:
            assert default_func is not False, ('Unknown topic_id and no DEFAULT handler.')
            print ('PROXY logic_callback()', '->', default_func, kw)
            default_func(msg, *args, **kw)
            return
        
        for topic in msg['topics']:
            func = self.log_handlers_hashed.get(topic, default_func)
            print ('PROXY logic_callback()', topic, '->', func)
            assert func is not False, ('Unknown topic_id and no DEFAULT handler.', topic, kw)
            func(msg, *args, **kw)
    
    def simulate_pending(self,
                         args_sig,
                         args,
                         *aa, **bb):
        """ 
        See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#events 
        """
        ## TODO: support indexed topics?
        
        func = self.pending_handlers.get(args_sig, self.pending_handlers.get('DEFAULT', False))
        
        print ('Proxy: simulate_pending()', args_sig, '->', func)
        
        return func(args_sig, args, *aa, **bb)

    def get_block_by_hash_callback(self, block_hash):
        """
        Get block by blockHash.
        
        https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash
        """
        print ('bcc.get_block_by_hash_callback()', block_hash)

        rh = self.con.eth_getBlockByHash(block_hash)
        
        for k in ['number', 'timestamp']:
            rh[k] = ethereum.utils.parse_int_or_hex(rh[k])
        
        return rh
        
    
    def get_logs_by_block_num_callback(self, block_num):
        """
        Get event logs for a particular block num. It's OK if the block_num has an unexpected hash, 
        that'll be taken care of by caller.
        
        https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
        """
        print ('bcc.get_logs_by_block_num_callback()', block_num)
        
        the_filter = self.con.eth_newFilter(from_block =  fixed_int_to_hex(block_num),
                                            to_block = fixed_int_to_hex(block_num),
                                            address = self.contract_address,
                                            )
        
        rr =  self.con.eth_getFilterLogs(str(the_filter))

        rr2 = []
        for msg in rr:
            #assert 'topics' in msg, list(sorted(msg.keys()))
            got_num = ethereum.utils.parse_int_or_hex(msg['blockNumber'])
            if got_num != block_num:
                continue
            msg['data'] = solidity_string_decode(msg['data'])
            msg['blockNumber'] = got_num
            msg["logIndex"] = ethereum.utils.parse_int_or_hex(msg['logIndex'])
            msg["transactionIndex"] = ethereum.utils.parse_int_or_hex(msg['transactionIndex'])
            #msg_data = loads_compact(msg['data'])
            #payload_decoded = loads_compact(msg_data['payload'])
            rr2.append(msg)
        rr = rr2
        
        return rr

    def get_latest_block_number(self):
        print ('bcc.get_latest_block_number()')
        bn = self.con.eth_blockNumber()
        return bn
    
    def get_latest_block_callback(self):
        """
        Returns the single latest block. Missing intermediate blocks will be automatically looked up by caller.
        """
        print ('bcc.get_latest_block_callback()')
        bn = self.get_latest_block_number()
        block = self.con.eth_getBlockByNumber(bn)
        return block

                
    def check_anything_deployed(self, address):
        """ Basic sanity check, checks if ANY code is deployed at provided address. """
        print ('bcc.check_anything_deployed()', address)
        if self.con.eth_getCode(address) == '0x0':
            return False
        return True
            
    def deploy(self,
               the_sig = False,
               the_args = False,
               block = False,
               deploy_from = False,
               callback = False,
               ):
        """ Deploy contract. Optional args_sig and args used to pass arguments to contract constructor."""
        print ('bcc.deploy()')

        if the_sig is not False:
            self.contract_sig = the_sig
        
        if the_args is not False:
            self.contract_args = the_args

        assert self.the_code

        if deploy_from is False:
            deploy_from = self.con.eth_coinbase()
        
        print ('DEPLOYING_CONTRACT...', 'deploy_from:', deploy_from, 'the_sig:', the_sig, 'the_args:', the_args)        
        # get contract address
        xx = self.con.eth_compileSolidity(self.the_code)
        #print ('GOT',xx)
        
        compiled = None
        
        try:
            compiled = xx['code']
        except KeyError:
            # geth seems to like putting the compiler output into an inner dict keyed by input filename,
            # e.g {'CCCoinToken.sol': {'code': '...', 'etc': '...'}
            for k, v in xx.iteritems():
                if isinstance(v, dict) and 'code' in v:
                    compiled = v['code']
                    break
        
        assert compiled
        
        contract_tx = self.con.create_contract(from_ = deploy_from,
                                               code = compiled,
                                               gas = 3000000,
                                               sig = self.contract_sig,
                                               args = self.contract_args,
                                               )

        if block:
            ## NOTE: @yusef feel free to switch back to this method if you want:
            #print('CONTRACT DEPLOYED, WAITING FOR CONFIRMATION')
            #wait_for_confirmation(self.c, contract_tx)
            
            print ('BLOCKING FOR RECEIPT..')
            while True:
                receipt = self.con.eth_getTransactionReceipt(contract_tx) ## blocks to ensure transaction is mined
                if receipt:
                    break
                sleep(self.blocking_sleep_time)
            print ('GOT RECEIPT')
        else:
            self.pending_transactions[contract_tx] = callback
        
        self.contract_address = str(self.con.get_contract_address(contract_tx))
        self.is_deployed = True
        print ('DEPLOYED', self.contract_address)
        return self.contract_address

    def send_transaction(self, *args, **kw):
        """
        1) If possible create simulated outputs of transactions, to use as pending state.
        2) Queue the transaction for blockchain commit.
        
        Used for e.g.:
        - addLog(bytes)
        - mintTokens(address, uint, uint, uint, uint, uint, uint)
        - withdrawTok(bytes)
        - lockupTok(bytes)
        """
        print ('bcc.send_transaction()')

        assert len(args) <= 2
        #assert self.loop_once_started, 'loop_once() not started?'

        ## Potentially slow blocking call to commit it to the blockchain:

        if kw.get('block'):
            ## Send transaction in blocking mode, and grab actual event logs that are committed:

            rh = self.inner_send_transaction(*args, **kw)
            pending_logs = rh['pending_logs']
            is_pending = False
            
        else:
            ## Run callbacks, return simulated event logs where possible:

            self.send_transaction_queue.put((0, args, kw))
            pending_logs = self.simulate_pending(*args, **kw)
            is_pending = True
        
        ## Run logic_callback() against pending transactions:
        
        for log in pending_logs:
            print ('CALLING', 'is_pending:', is_pending, 'is_noop:', log.get('is_noop', False))
                                
            self.logic_callback(log,
                                is_pending = is_pending,
                                is_noop = log.get('is_noop', False)
                                )

        return pending_logs
            
    def inner_send_transaction(self,
                               args_sig,
                               args,
                               callback = False,
                               send_from = False,
                               block = False,
                               gas_limit = False,
                               gas_price = 100,
                               value = 100000000000,
                               ):
        """
        1) Attempt to send transaction.
        2) Get first confirmation via transaction receipt.
        3) Re-check receipt again after N blocks pass.
        
        https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendtransaction
        """
        print ('bcc.inner_send_transaction()')

        assert self.is_deployed, 'Must deploy contract first.'

        print ('SEND_TRANSACTION:', args_sig, args)

        if send_from is False:
            send_from = self.con.eth_coinbase()
        
        send_to = self.contract_address 

        print ('====TRANSACTION')
        print ('send_from', send_from)
        print ('send_to', send_to)
        print ('args_sig', args_sig)
        print ('args', args)
        #print ('gas', gas_limit)
        
        gas_limit = 1000000
        gas_price = self.con.DEFAULT_GAS_PRICE
        value = web3.utils.currency.to_wei(1,'ether')

        data = self.con._encode_function(args_sig, args)
        data_hex = '0x' + data.encode('hex')
        
        tx = self.con.eth_sendTransaction(from_address = send_from,
                                          to_address = send_to,
                                          data = data_hex,
                                          gas = gas_limit,
                                          gas_price = gas_price,
                                          value = value,
                                          )
        
        if block:
            print ('BLOCKING FOR RECEIPT..')
            while True:
                receipt = self.con.eth_getTransactionReceipt(tx) ## blocks to ensure transaction is mined
                if receipt:
                    break
                sleep(self.blocking_sleep_time)
            print ('GOT RECEIPT')
            #print ('GOT_RECEIPT', receipt)
            #if receipt['blockNumber']:
            #    self.latest_block_num = max(ethereum.utils.parse_int_or_hex(receipt['blockNumber']), self.latest_block_num)
        else:
            self.pending_transactions[tx] = callback
        
        #self.latest_block_num = self.con.eth_blockNumber()
        
        return {'tx':tx}

    def poll_outgoing_receipts(self):
        """
        Check for transaction receipts on transactions sent from this node.
        TODO - why not replace this with just watching for incoming transactions???
        """
        
        assert self.is_deployed, 'Must deploy contract first.'

        had_any_events = False
        if self.pending_transactions:
            had_any_events = True
        
        for tx, callback in self.pending_transactions.items():
            
            receipt = self.con.eth_getTransactionReceipt(tx)
            
            if receipt is not None and 'blockNumber' in receipt:
                actual_block_number = ethereum.utils.parse_int_or_hex(receipt['blockNumber'])
            else:
                ## TODO: wasn't confirmed after a long time.
                actual_block_number = False
            
            ## Now compare against the block_number where it was actually included:
            
            if actual_block_number is not False:
                if callback is not False:
                    callback(receipt)
                del self.pending_transactions[tx]
        
        return had_any_events
    
    
    def _start_background_thread(self,
                                 terminate_on_exception = False,
                                 ):
        last_event = False
        
        while True:
            try:
                had_any_events = self.loop_once_blockchain()
            except Exception as e:
                print ('-----LOOP_ONCE_EXCEPTION', e)
                #exit(-1)
                raise
                
                if terminate_on_exception:
                    raise
                continue
            
            if had_any_events:
                last_event = time()
            else:
                print ('NO_NEW_EVENTS', last_event and (time() - last_event))
            
            sleep(self.background_thread_sleep_time)
    
    def start_background_thread(self,
                                start_in_foreground = False,
                                terminate_on_exception = False,
                                ):
        """
        Start ContractWrapper loop_once() in background thread, which (in that thread!) calls back to self.process_event()
        """
        if start_in_foreground:
            self._start_background_thread(terminate_on_exception = terminate_on_exception)
        else:
            self.t = Thread(target = self._start_background_thread,
                            args = (terminate_on_exception,),
                            )
            self.t.daemon = True
            self.t.start()
    
            

    def loop_once_blockchain(self):
        ## Check for available write receipts:
        #self.poll_outgoing_receipts()

        self.loop_once_started = True
        
        ## Do write transactions transactions:
        
        while self.send_transaction_queue.qsize():
            print ('TRY_TO_SEND')
            tries, args, kw = self.send_transaction_queue.get()
            try:
                self.inner_send_transaction(*args, **kw)
            except Exception as e:
                print ('FAILED_TO_SEND', e, tries, args, kw)
                sleep(1) ## TODO
                self.send_transaction_queue.put((tries + 1, args, kw))
                break