async def update_balances(self):
        asset_symbols: List[str] = []
        asset_update_tasks: List[asyncio.Task] = []

        for asset_name, contract in self._erc20_contracts.items():
            asset_symbols.append(asset_name)
            asset_update_tasks.append(
                self._ev_loop.run_in_executor(
                    wings.get_executor(),
                    contract.functions.balanceOf(self._account_address).call
                )
            )

        asset_symbols.append("ETH")
        asset_update_tasks.append(self._ev_loop.run_in_executor(
            wings.get_executor(),
            self._w3.eth.getBalance, self._account_address
        ))

        try:
            asset_raw_balances: List[int] = await asyncio.gather(*asset_update_tasks)
            for asset_name, raw_balance in zip(asset_symbols, asset_raw_balances):
                self._raw_account_balances[asset_name] = raw_balance
        except asyncio.CancelledError:
            raise
        except Exception:
            self.logger().error("Unexpected error fetching account balance updates.", exc_info=True)
    async def get_tracking_pairs(self) -> Dict[str, OrderBookTrackerEntry]:
        # Get the currently active markets
        sql: SQLConnectionManager = self._sql
        active_markets: pd.DataFrame = await self.get_active_exchange_markets()

        # Get the latest order book snapshots from database
        now: float = time.time()
        yesterday: float = now - 86400.0
        retval: Dict[str, OrderBookTrackerEntry] = {}
        ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()

        with sql.engine.connect() as conn:
            for symbol in active_markets.index:
                symbol: str = symbol
                stmt: TextClause = text(
                    self.get_snapshot_message_query(symbol))
                try:
                    row = await ev_loop.run_in_executor(
                        wings.get_executor(),
                        lambda: conn.execute(stmt).fetchone())
                except DatabaseError:
                    self.logger().warning(
                        "Cannot find last snapshot for %s, skipping.",
                        symbol,
                        exc_info=True)
                    continue
                if row is None:
                    continue

                snapshot_msg: OrderBookMessage = self.order_book_class.snapshot_message_from_db(
                    row)

                snapshot_timestamp: float = row[0]
                if snapshot_timestamp > yesterday:
                    order_book: OrderBook = self.order_book_class.from_snapshot(
                        snapshot_msg)
                    retval[symbol] = OrderBookTrackerEntry(
                        symbol, snapshot_timestamp, order_book)
                    stmt: TextClause = text(
                        self.get_diff_message_query(symbol))
                    try:
                        rows = await ev_loop.run_in_executor(
                            wings.get_executor(), lambda: conn.execute(
                                stmt,
                                timestamp=(snapshot_timestamp - 60) * 1e3).
                            fetchall())
                        for row in rows:
                            diff_msg: OrderBookMessage = self.order_book_class.diff_message_from_db(
                                row)
                            if diff_msg.update_id > order_book.snapshot_uid:
                                order_book.apply_diffs(diff_msg.bids,
                                                       diff_msg.asks,
                                                       diff_msg.update_id)
                    except DatabaseError:
                        continue
                    finally:
                        self.logger().debug(
                            "Fetched order book snapshot for %s.", symbol)
        return retval
예제 #3
0
    async def check_transaction_receipts(self):
        """
        Look for failed transactions, and emit transaction fail event if any are found.
        """
        ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        tasks = [
            ev_loop.run_in_executor(wings.get_executor(),
                                    self._w3.eth.getTransactionReceipt,
                                    tx_hash)
            for tx_hash in self._pending_tx_dict.keys()
        ]
        transaction_receipts: List[AttributeDict] = [
            tr for tr in await asyncio.gather(*tasks)
            if (tr is not None and tr.get("blockHash") is not None)
        ]
        block_hash_set: Set[HexBytes] = set(tr.blockHash
                                            for tr in transaction_receipts)
        fetch_block_tasks = [
            ev_loop.run_in_executor(wings.get_executor(),
                                    self._w3.eth.getBlock, block_hash)
            for block_hash in block_hash_set
        ]
        blocks: Dict[HexBytes, AttributeDict] = dict(
            (block.hash, block)
            for block in await asyncio.gather(*fetch_block_tasks)
            if block is not None)

        for receipt in transaction_receipts:
            # Emit gas used event.
            tx_hash: str = receipt.transactionHash.hex()
            gas_price_wei: int = self._pending_tx_dict[tx_hash]
            gas_used: int = receipt.gasUsed
            gas_eth_amount_raw: int = gas_price_wei * gas_used

            if receipt.blockHash in blocks:
                block: AttributeDict = blocks[receipt.blockHash]

                if receipt.status == 0:
                    self.logger().warning(
                        f"The transaction {tx_hash} has failed.")
                    self.trigger_event(WalletEvent.TransactionFailure, tx_hash)

                self.trigger_event(
                    WalletEvent.GasUsed,
                    EthereumGasUsedEvent(float(block.timestamp), tx_hash,
                                         float(gas_price_wei * 1e-9),
                                         gas_price_wei, gas_used,
                                         float(gas_eth_amount_raw * 1e-18),
                                         gas_eth_amount_raw))

                # Stop tracking the transaction.
                self._stop_tx_tracking(tx_hash)
 async def _get_logs(
         self,
         event_filter_params: Dict[str, any],
         max_tries: Optional[int] = 100) -> List[Dict[str, any]]:
     ev_loop: asyncio.BaseEventLoop = self._ev_loop
     count: int = 0
     logs = []
     while True:
         try:
             count += 1
             if count > max_tries:
                 self.logger().error(
                     f"Error fetching logs from block with filters: '{event_filter_params}'."
                 )
                 break
             logs = await ev_loop.run_in_executor(
                 wings.get_executor(),
                 functools.partial(self._w3.eth.getLogs,
                                   event_filter_params))
             break
         except asyncio.CancelledError:
             raise
         except Exception:
             self.logger().debug(
                 f"Block not found with filters: '{event_filter_params}'. Retrying..."
             )
             await asyncio.sleep(0.5)
     return logs
 async def get_timestamp_for_block(self,
                                   block_hash: HexBytes,
                                   max_tries: Optional[int] = 10) -> int:
     counter = 0
     block: AttributeDict = None
     if block_hash in self._blocks_window:
         block = self._blocks_window[block_hash]
         return block.timestamp
     else:
         while block is None:
             try:
                 if counter == max_tries:
                     raise ValueError(
                         f"Block hash {block_hash.hex()} does not exist.")
                 counter += 1
                 async with timeout(10.0):
                     ev_loop: asyncio.BaseEventLoop = self._ev_loop
                     block = await ev_loop.run_in_executor(
                         wings.get_executor(),
                         functools.partial(self._w3.eth.getBlock,
                                           block_hash,
                                           full_transactions=False))
             except TimeoutError:
                 self.logger().error(
                     "Timed out fetching new block - '{block_hash}'.",
                     exc_info=True)
             finally:
                 asyncio.sleep(1.0)
         return block.timestamp
    async def get_block_reorganization(
            self, incoming_block: AttributeDict) -> List[AttributeDict]:
        ev_loop: asyncio.BaseEventLoop = self._ev_loop
        block_reorganization: List[AttributeDict] = []
        expected_parent_hash: HexBytes = incoming_block.parentHash
        while expected_parent_hash not in self._blocks_window and len(
                block_reorganization) < len(self._blocks_window):
            replacement_block = None
            while replacement_block is None:
                replacement_block = await ev_loop.run_in_executor(
                    wings.get_executor(),
                    functools.partial(self._w3.eth.getBlock,
                                      expected_parent_hash,
                                      full_transactions=True))
                if replacement_block is None:
                    asyncio.sleep(1.0)

            replacement_block_number: int = replacement_block.number
            replacement_block_hash: HexBytes = replacement_block.hash
            replacement_block_parent_hash: HexBytes = replacement_block.parentHash
            self._block_number_to_hash_map[
                replacement_block_number] = replacement_block_hash
            self._blocks_window[replacement_block_hash] = replacement_block
            block_reorganization.append(replacement_block)
            expected_parent_hash = replacement_block_parent_hash

        block_reorganization.reverse()
        return block_reorganization
예제 #7
0
 async def call_async(self,
                      func: Callable,
                      *args,
                      timeout_seconds: float = 5.0) -> any:
     coro: Coroutine = self._ev_loop.run_in_executor(
         wings.get_executor(), func, *args)
     return await self.schedule_async_call(coro, timeout_seconds)
    async def fetch_new_blocks_loop(self):
        ev_loop: asyncio.BaseEventLoop = self._ev_loop
        while True:
            try:
                async with timeout(30.0):
                    incoming_block: AttributeDict = await ev_loop.run_in_executor(
                        wings.get_executor(),
                        functools.partial(self._w3.eth.getBlock,
                                          self._block_number_to_fetch,
                                          full_transactions=True))
                    if incoming_block is not None:
                        current_block_hash: HexBytes = self._block_number_to_hash_map.get(
                            self._current_block_number, None)
                        incoming_block_hash: HexBytes = incoming_block.hash
                        incoming_block_parent_hash: HexBytes = incoming_block.parentHash
                        new_blocks: List[AttributeDict] = []
                        if current_block_hash is not None and current_block_hash != incoming_block_parent_hash:
                            block_reorganization: List[
                                AttributeDict] = await self.get_block_reorganization(
                                    incoming_block)
                            new_blocks += block_reorganization

                        self._block_number_to_hash_map[
                            self._block_number_to_fetch] = incoming_block_hash
                        self._blocks_window[
                            incoming_block_hash] = incoming_block
                        new_blocks.append(incoming_block)

                        self._current_block_number = self._block_number_to_fetch
                        self._block_number_to_fetch += 1
                        self.trigger_event(NewBlocksWatcherEvent.NewBlocks,
                                           new_blocks)

                        while len(
                                self._blocks_window) > self._block_window_size:
                            block_hash = self._block_number_to_hash_map.popitem(
                                last=False)[1]
                            del self._blocks_window[block_hash]

            except asyncio.CancelledError:
                raise
            except asyncio.TimeoutError:
                self.logger().error("Timed out fetching new block from node.",
                                    exc_info=True)
            except Exception:
                self.logger().error("Error fetching new block from node.",
                                    exc_info=True)

            now: float = time.time()
            next_second: float = (now // 1) + 1
            await asyncio.sleep(next_second - now)
 async def outgoing_eth_transactions_loop(self):
     ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
     while True:
         signed_transaction: AttributeDict = await self._outgoing_transactions_queue.get()
         tx_hash: str = signed_transaction.hash.hex()
         try:
             await ev_loop.run_in_executor(wings.get_executor(), self._w3.eth.sendRawTransaction,
                                           signed_transaction.rawTransaction)
         except asyncio.CancelledError:
             self.logger().error('Cancelled Error', exc_info=True)
             raise
         except Exception:
             self.logger().error(f"Error sending transaction {tx_hash}.", exc_info=True)
             self.trigger_event(WalletEvent.TransactionFailure, tx_hash)
             self._local_nonce -= 1
예제 #10
0
    async def check_incoming_eth(self, new_blocks: List[AttributeDict]):
        watch_addresses: Set[str] = self._watch_addresses
        filtered_blocks: List[AttributeDict] = [
            block for block in new_blocks if block is not None
        ]
        block_to_timestamp: Dict[str, float] = dict(
            (block.hash, float(block.timestamp)) for block in filtered_blocks)
        transactions: List[AttributeDict] = list(
            cytoolz.concat(b.transactions for b in filtered_blocks))
        incoming_eth_transactions: List[AttributeDict] = [
            t for t in transactions
            if ((t.get("to") in watch_addresses) and (t.get("value", 0) > 0))
        ]

        get_receipt_tasks: List[asyncio.Task] = [
            self._ev_loop.run_in_executor(wings.get_executor(),
                                          self._w3.eth.getTransactionReceipt,
                                          t.hash)
            for t in incoming_eth_transactions
        ]
        transaction_receipts: List[AttributeDict] = await asyncio.gather(
            *get_receipt_tasks)

        for incoming_transaction, receipt in zip(incoming_eth_transactions,
                                                 transaction_receipts):
            # Filter out failed transactions.
            if receipt.status != 1:
                continue

            # Emit event.
            raw_eth_value: int = incoming_transaction.get("value")
            eth_value: float = raw_eth_value * 1e-18
            from_address: str = incoming_transaction.get("from")
            to_address: str = incoming_transaction.get("to")
            timestamp: float = block_to_timestamp[incoming_transaction.get(
                "blockHash")]
            self.trigger_event(
                IncomingEthWatcherEvent.ReceivedEther,
                WalletReceivedAssetEvent(timestamp,
                                         incoming_transaction.hash.hex(),
                                         from_address, to_address, "ETH",
                                         eth_value, raw_eth_value))
예제 #11
0
    async def _get_contract_info(self):
        if self._name is not None and self._symbol is not None and self._decimals is not None:
            return

        ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
        tasks: List[asyncio.Task] = [
            ev_loop.run_in_executor(wings.get_executor(), func, *args)
            for func, args in [(self.get_name_from_contract, [self._contract]),
                               (self.get_symbol_from_contract,
                                [self._contract]),
                               (self._contract.functions.decimals().call, [])]
        ]

        try:
            name, symbol, decimals = await asyncio.gather(*tasks)
            self._name = name
            self._symbol = symbol
            self._decimals = decimals
        except asyncio.CancelledError:
            raise
        except Exception:
            self.logger().error(
                f"Could not fetch token info for {self._contract.address}.",
                exc_info=True)