def resolver_receipt(cls, receipt): block_height = digit.hex_to_int(receipt['blockNumber']) block_hash = receipt['blockHash'] tx_hash = receipt['transactionHash'] sender = receipt['from'] receiver = receipt['to'] contract = receipt['contractAddress'] status = digit.hex_to_int(receipt['status']) gas_used = digit.hex_to_int(receipt['gasUsed']) return TxReceipt(block_height, block_hash, tx_hash, sender, receiver, contract, status, gas_used)
def resolver_block(cls, block, detail=True): """不包含交易""" block_height = digit.hex_to_int(block['number']) block_hash = block['hash'] block_time = digit.hex_to_int(block['timestamp']) if detail: transactions = [ cls.resolver_transaction(tx) for tx in block['transactions'] ] else: transactions = [] return Block(height=block_height, hash=block_hash, timestamp=block_time, transactions=transactions)
def get_wallet_balance(self, contract=None, block_height='latest', *, exclude: list = None): if exclude is None: exclude = set() else: exclude = set(exclude) addresses = self.personal_list_accounts() if isinstance(addresses, list): addresses = set(addresses) else: self.logger.warning( "personal_listAccounts address list not is list, it's {}". format(addresses)) return 0 check_addresses = list((exclude ^ addresses) & addresses) balance = 0 offset = 100 for s in range(0, len(check_addresses), offset): batch_address = check_addresses[s:s + offset] balances = self.get_balance(batch_address, contract, block_height) for _ in balances: if _: balance += digit.hex_to_int(_) else: self.logger.error("地址获取余额错误: {}".format(_)) return balance
def get_block_height(self): sync_method = 'eth_syncing' number_method = 'eth_blockNumber' sync, number = self._diff_post([sync_method, number_method], [None, None]) if sync: block_height = BlockHeight(digit.hex_to_int(sync['currentBlock']), digit.hex_to_int(sync['highestBlock'])) elif number: block_height = BlockHeight(digit.hex_to_int(number), digit.hex_to_int(number)) else: self.logger.error( 'get_block_height 请求, 两个方式均未获取到正常拿. sync: {} number: {}'. format(sync, number)) raise JsonRpcError( code=1, message='get_block_height 请求, 两个方式均未获取到正常拿. sync: {} number: {}' .format(sync, number)) return block_height
def resolver_transaction(cls, tx): """ 只支持标准 erc20 与 eth交易, 其他交易暂时不支持 :param tx: :return: """ block_height = digit.hex_to_int(tx['blockNumber']) block_hash = tx['blockHash'] tx_hash = tx['hash'] sender = cls.get_address(tx['from']) receiver = cls.get_address(tx['to']) value = digit.hex_to_int(tx['value']) gas = digit.hex_to_int(tx['gas']) gas_price = digit.hex_to_int(tx['gasPrice']) nonce = digit.hex_to_int(tx['nonce']) status = TxStatusEnum.UNKNOWN.value data = None contract = None # 去掉开头 0x if tx['input'].startswith(cls.TRANSFER_ABI): _input = tx['input'] data = _input contract = tx['to'] abi_length = len(cls.TRANSFER_ABI) abi, address, amount = (_input[0:abi_length], _input[abi_length:abi_length + cls.ADDRESS_FULL_LENGTH], _input[abi_length + cls.ADDRESS_FULL_LENGTH:]) receiver = cls.get_address(address[cls.ADDRESS_FILL_LENGTH:]) value = digit.hex_to_int(amount) return Tx(block_height, block_hash, tx_hash, sender, receiver, value, gas, gas_price, nonce, data, contract, status)
def get_balance_by_address(address: str): """这里只能查单个地址的余额""" rpc = RpcConfig.get_rpc() if rpc is None: return ResponseObject.error(**out_data_missing) coin = Coin.get_erc20_usdt_coin() if coin is None: return ResponseObject.error(**coin_missing) try: balance_hex = rpc.get_balance(address, coin.contract) except Exception as e: return ResponseObject.error(**rpc_service_error) if balance_hex: balance = safe_math.divided(hex_to_int(balance_hex), safe_math.e_calc(coin.decimal)) else: return ResponseObject.error(**balance_rpc_error) return ResponseObject.success(data=balance.to_eng_string())
def send_transaction(self, sender: str, receiver: str, value: int, passphrase: str, gas: int = None, gas_price: int = None, fee: int = None, contract: str = None, comment: str = None, **kwargs): """目前只支持单交易发送, 暂时没有想到更好的数据结构""" method = 'personal_signAndSendTransaction' if gas is None: gas = 21000 if gas_price is None: gas_price = digit.hex_to_int(self.gas_price()) params = EthereumResolver.get_transfer_body(sender, receiver, int(gas), int(gas_price), value, contract) payload = self.get_params(params, passphrase) return self._single_post(method, payload, ignore_err=False)
def parse_abi_total(cls, total_abi): return digit.hex_to_int(total_abi)
def parse_abi_decimal(cls, decimal_abi): return digit.hex_to_int(decimal_abi)
def scan(self): self.block_info = self.rpc.get_block_height() self.newest_height = self.block_info.current_height self.highest_height = self.block_info.highest_height # 延迟扫 SCAN_DELAY_NUMBER 个块 need_to_height = self.newest_height - self.SCAN_DELAY_NUMBER self.logger.info('起始扫块高度:{} 最新高度:{} 需要同步:{}'.format( self.current_scan_height, self.newest_height, need_to_height - self.current_scan_height)) while self.current_scan_height < need_to_height: self.logger.info('当前已扫块高度:{} 最新高度:{} 需要同步:{} 节点最高高度:{}'.format( self.current_scan_height, self.newest_height, need_to_height - self.current_scan_height, self.highest_height)) for height in range(self.current_scan_height, need_to_height, self.SCAN_HEIGHT_NUMBER): # 分批处理, 一次处理 SCAN_HEIGHT_NUMBER 或 剩余要处理的块 block_batch = min(self.SCAN_HEIGHT_NUMBER, need_to_height - self.current_scan_height) blocks = self.rpc.get_block_by_number([ digit.int_to_hex(height) for height in range(height, height + block_batch) ]) save_tx_count = 0 with runtime.app.app_context(): # 一次处理一批 session = db.session() try: for block in blocks: if block is None: return block_height = digit.hex_to_int(block['number']) block_hash = block['hash'] block_timestamp = digit.hex_to_int( block['timestamp']) block_time = datetime.fromtimestamp( block_timestamp) session.begin(subtransactions=True) db_block = Block(height=block_height, block_hash=block_hash, block_time=block_time) session.add(db_block) session.commit() for transaction in block.get('transactions', []): tx = EthereumResolver.resolver_transaction( transaction) if tx.sender in runtime.project_address: # 提现的暂时不要 continue if tx.receiver in runtime.project_address: receipt_raw_tx = self.rpc.get_transaction_receipt( tx.tx_hash) if tx.contract: coin = runtime.coins.get(tx.contract) else: coin = runtime.coins.get( self.COIN_NAME) if coin is None: continue if receipt_raw_tx: receipt_tx = EthereumResolver.resolver_receipt( receipt_raw_tx) else: self.logger.error( '请求 {} receipt 错误, 重新处理') raise tx.status = receipt_tx.status # session.begin(subtransactions=True) # db_tx = Transaction(block_id=db_block.id, coin_id=coin['coin_id'], # tx_hash=tx.tx_hash, height=db_block.height, # block_time=block_timestamp, # amount=tx.value, sender=tx.sender, receiver=tx.receiver, # gas=tx.gas, gas_price=tx.gas_price, # is_send=SendEnum.NOT_PUSH.value, # fee=receipt_tx.gas_used * tx.gas_price, # contract=tx.contract, status=receipt_tx.status, # type=TxTypeEnum.DEPOSIT.value) Transaction.add_transaction_or_update( block_id=db_block.id, coin_id=coin['coin_id'], tx_hash=tx.tx_hash, height=db_block.height, block_time=block_timestamp, amount=tx.value, sender=tx.sender, receiver=tx.receiver, gas=tx.gas, gas_price=tx.gas_price, is_send=SendEnum.NOT_PUSH.value, fee=receipt_tx.gas_used * tx.gas_price, contract=tx.contract, status=receipt_tx.status, type=TxTypeEnum.DEPOSIT.value, session=session, commit=False) save_tx_count += 1 # session.add(db_tx) # session.commit() # 添加推送信息 session.query(SyncConfig).filter( SyncConfig.id == self.config_id).update({ 'synced_height': height + block_batch, 'highest_height': self.highest_height }) self.current_scan_height = height + block_batch session.commit() self.logger.info("本次同步高度为:{} -- {}, 保存交易: {} 笔".format( height, height + block_batch, save_tx_count)) except Exception as e: self.logger.error('同步块出现异常, 事务回滚. {}'.format(e)) session.rollback() return self.logger.info("扫链结束, 本次同步")
def render(self): self.logger.info('开始补充手续费进程') for pid, p in self.project_addresses.items(): project_address = p['address'] for ck, coin in runtime.coins.items(): if coin['coin_name'] == self.COIN_NAME: self.logger.warning('币种名称为: {}, 不需要补充手续费!'.format( coin['coin_name'])) continue contract = coin['contract'] offset, count = 0, self.BALANCE_QUERY_NUMBER for s in range(0, len(project_address), count): addresses = project_address[offset:count] balances = self.rpc.get_balance(addresses, contract) balances_sum = sum([ digit.hex_to_int(balance) for balance in balances if balance ]) if not balances_sum: self.logger.info("本 {} 个地址无额外, 不需要补充手续费".format( len(addresses))) continue for idx, balance in enumerate(balances): balance_int = hex_to_int(balance) if not balance_int: continue balance_eth_int = hex_to_int( self.rpc.get_balance(address=addresses[idx])) if hasattr(config, 'GAS'): gas = config.GAS else: gas = self.rpc.get_smart_fee(contract=contract) if hasattr(config, 'GAS_PRICE'): gas_price = config.GAS_PRICE else: gas_price = self.rpc.gas_price() if gas is None: self.logger.info("未找到合适 gas . {}".format(gas)) continue if gas_price is None: self.logger.info( "未找到合适 gas_price . {}".format(gas_price)) continue gas, gas_price = hex_to_int(gas), hex_to_int(gas_price) fee = gas * gas_price if balance_eth_int > fee: self.logger.info("地址: {} 手续费足够, 不需要补充手续费".format( addresses[idx])) continue render_amount = int(config.COLLECTION_MIN_ETH * 1e18) tx_hash = self.rpc.send_transaction( sender=config.RENDER_ADDRESS, receiver=addresses[idx], value=render_amount, passphrase=p['passphrase'], gas=gas, gas_price=gas_price, contract=contract) if not tx_hash: self.logger.error("给地址: {} 补充手续费失败".format( addresses[idx])) continue self.logger.info("给地址: {} 补充手续费成功".format( addresses[idx])) with runtime.app.app_context(): saved = Transaction.add_transaction( coin_id=coin['coin_id'], tx_hash=tx_hash, block_time=datetime.now().timestamp(), sender=config.RENDER_ADDRESS, receiver=addresses[idx], amount=render_amount, status=TxStatusEnum.UNKNOWN.value, type=TxTypeEnum.RENDER.value, block_id=-1, height=-1, gas=gas, gas_price=gas_price, contract=contract) self.logger.info('结束补充手续费进程')
def collection(self): self.logger.info('开始归集进程') for pid, p in self.project_addresses.items(): project_address = p['address'] for ck, coin in runtime.coins.items(): contract = coin['contract'] offset, count = 0, self.BALANCE_QUERY_NUMBER for s in range(0, len(project_address), count): addresses = project_address[offset:count] balances = self.rpc.get_balance(addresses, contract) balances_sum = sum([ digit.hex_to_int(balance) for balance in balances if balance ]) if not balances_sum: self.logger.info("本 {} 个地址不需要归集".format( len(addresses))) continue for idx, balance in enumerate(balances): balance_int = hex_to_int(balance) if not balance_int: continue if hasattr(config, 'GAS'): gas = config.GAS else: gas = self.rpc.get_smart_fee(contract=contract) if hasattr(config, 'GAS_PRICE'): gas_price = config.GAS_PRICE else: gas_price = self.rpc.gas_price() if gas is None: self.logger.info("未找到合适 gas . {}".format(gas)) continue if gas_price is None: self.logger.info( "未找到合适 gas_price . {}".format(gas_price)) continue gas, gas_price = hex_to_int(gas), hex_to_int(gas_price) fee = gas * gas_price if coin['symbol'] == 'ETH': send_value = max( balance_int - max(int(config.COLLECTION_MIN_ETH * 1e18), fee), 0) else: send_value = balance_int if send_value <= 0: self.logger.info("地址 {} 需要归集金额低于 0".format( addresses[idx])) continue tx_hash = self.rpc.send_transaction( sender=addresses[idx], receiver=config.COLLECTION_ADDRESS, value=send_value, passphrase=p['passphrase'], gas=gas, gas_price=gas_price, contract=contract) with runtime.app.app_context(): saved = Transaction.add_transaction( coin_id=coin['coin_id'], tx_hash=tx_hash, block_time=datetime.now().timestamp(), sender=addresses[idx], receiver=config.COLLECTION_ADDRESS, amount=send_value, status=TxStatusEnum.UNKNOWN.value, type=TxTypeEnum.COLLECTION.value, block_id=-1, height=-1, gas=gas, gas_price=gas_price, contract=contract) offset += count self.logger.info("结束归集进程")