def _check_uncle(self, block): """ 检查当前区块的叔块,看是否需要回滚 """ # 检查当前传入的区块高度和最新的区块高度是否一致,相差大于100说明当前是处理比较老的区块了则不用检查叔块 current_height = hex_to_int(block["number"]) newest_height = self.rpc.eth_block_number() if newest_height - current_height > 100: return False # 没有叔块也不用检查叔块 uncles = block["uncles"] if not uncles: return False G_LOGGER.info(f"区块{current_height}发现叔块:{','.join(uncles)}") # 获取叔块对应最小区块高度的hash uncle_height = [] for uncle in uncles: uncle_block = self.rpc.eth_get_block_by_hash(uncle, tx_detail=False) if uncle_block: uncle_height.append(hex_to_int(uncle_block["number"])) if uncle_height: min_height = min(uncle_height) # 查询缓存的区块中是否有uncle hash push_block_hash = self.db.redis.get_cache_block(min_height) # 已经推送的该高度对应的区块hash如果不在uncles中,说明之前推送的区块就是正确的区块,不用继续处理 if push_block_hash not in uncles: return False raise ForkError(min_height, f"{min_height}高度同步过程中出现临时分叉")
def eth_block_number(self): """ 获取最新高度 :return: """ method = "eth_blockNumber" return hex_to_int(self._single_post(method))
def parse_block(self, block_num): """ 解析区块的交易列表(支持多发送) :return: 返回待推送信息列表 """ while True: try: block = self.rpc.eth_get_block_by_number(block_num) if block is None: raise Exception(f"获取到最新高度{block_num}区块详情为None,触发异常,尝试重新获取") G_LOGGER.info( f"当前推送的区块:高度{int(block.get('number', '0x0'), 16)},哈希{block.get('hash', '')}" ) self._check_uncle(block) # 遍历交易 push_list = [] if block.get("transactions") is None: time.sleep(3) continue tx_hashes = [tx["hash"] for tx in block["transactions"]] receipts = self.rpc.eth_get_transaction_receipt( tx_hashes) if tx_hashes else [] assert len(receipts) == len(block["transactions"]) for i, tx in enumerate(block["transactions"]): _input = tx.get('input', '') _to = tx.get('to', '') _from = tx.get('from', '') multi_send_token = self.is_multi_send_token(_input) mq_tx = G_PUSH_TEMPLATE.copy() mq_tx["BlockNumber"] = hex_to_int(tx["blockNumber"]) mq_tx["Txid"] = tx["hash"] mq_tx["Type"] = self.coin_type mq_tx["From"] = tx["from"] mq_tx["Time"] = block["timestamp"] is_token_transfer = self._is_token(_input) # 交易状态 mq_tx["Valid"] = self.get_status(receipts[i], is_token_transfer) mq_tx["status"] = self.get_tx_status( receipts[i], is_token_transfer) # 手续费 gas_price = tx.get("gasPrice", "0x0") gas_used = receipts[i].get("gasUsed", "0x0") if receipts[i] else "0x0" mq_tx["Fee"] = hex(int(gas_price, 16) * int(gas_used, 16)) if is_token_transfer: if _from != _to: tx['contract'] = _to _to = self._get_token_to_address(_input) _value = self._get_token_to_value(_input) tx["to"] = _to if _to != "0x" else f"0x{'0'*40}" tx["value"] = _value if _value != "0x" else "0x0" elif multi_send_token: continue _len = self.multi_abi_len _del_0x_input = del_0x(_input) if multi_send_token == 'create': # 多发送合约创建的交易,保存到数据库存起来 data = dict() data['contract'] = tx['contract'] = tx['creates'] data['blockNumber'] = hex_to_int(tx["blockNumber"]) data['timestamp'] = block["timestamp"] data['hash'] = tx["hash"] # 保存合约 self.db.mysql.contract.insert_contract(data) G_LOGGER.info(f"发现新的多发送合约{data['contract']}并保存") continue else: token = '' address_list = amount_list = [] # 不是我们自己的多发送合约则不处理 contract = self.db.mysql.contract.get_by_contract( tx["to"]) if not contract: continue if multi_send_token == 'multisend': _multisend_abi = del_0x( self.multi_abi_deal['multisend']) result = self.parse_multi_data( _del_0x_input[len(_multisend_abi):]) address_count = hex_to_int(result[2]) address_list = result[3:3 + address_count] amount_count = hex_to_int( result[3 + address_count]) amount_list = result[4 + address_count:4 + address_count + amount_count] elif multi_send_token == 'multisendToken': _multisend_token_abi = del_0x( self.multi_abi_deal['multisendToken']) token = add_0x( self.parse_multi_data_pos( _del_0x_input, _multisend_token_abi, 0)) result = self.parse_multi_data( _del_0x_input[len(_multisend_token_abi):]) address_count = hex_to_int(result[3]) address_list = result[4:4 + address_count] amount_count = hex_to_int( result[4 + address_count]) amount_list = result[5 + address_count:5 + address_count + amount_count] for k, v in enumerate(address_list): mq_tx = mq_tx.copy() mq_tx["To"] = v mq_tx["Amount"] = int_to_hex( hex_to_int(amount_list[k])) mq_tx["Contract"] = token push_list.append(mq_tx) return push_list if tx["to"]: mq_tx["To"] = tx["to"] mq_tx["Amount"] = tx["value"] if 'contract' in tx.keys(): mq_tx["Contract"] = tx["contract"] push_list.append(mq_tx) return push_list except ForkError as ex: raise ForkError(ex.height, ex.msg) except Exception as ex: traceback.print_exc() G_LOGGER.info(f"获取块出现异常,尝试重新获取。异常原因:{str(ex)}") time.sleep(10)
def parse_multi_data_pos(self, _input, _abi, pos): _len = self.multi_abi_len return int_to_hex( hex_to_int( add_0x(_input[len(_abi) + _len * pos:len(_abi) + _len * (pos + 1)])))
def parse_block(self, block_num): """ 解析区块的交易列表 """ try: block = self.rpc.eth_get_block_by_number(block_num) if block is None: raise Exception(f"获取区块高度{block_num}区块详情为None,触发异常,尝试重新获取") G_LOGGER.info( f"当前推送的区块:高度{int(block.get('number', '0x0'), 16)},哈希{block.get('hash', '')}" ) self._check_uncle(block) # 遍历交易 push_list = [] if block.get("transactions") is None: raise Exception( f"获取区块高度{block_num}区块transactions为None,触发异常,尝试重新获取") tx_hashes = [tx["hash"] for tx in block["transactions"]] receipts = self.rpc.eth_get_transaction_receipt( tx_hashes) if tx_hashes else [] assert len(receipts) == len(block["transactions"]) for i, tx in enumerate(block["transactions"]): _input = tx.get('input', '') _to = tx.get('to', '') _from = tx.get('from', '') is_token_transfer = self._is_token(_input) if is_token_transfer: if _from != _to: tx['contract'] = _to _to = self._get_token_to_address(_input) _value = self._get_token_to_value(_input) tx["to"] = _to if _to != "0x" else f"0x{'0'*40}" tx["value"] = _value if _value != "0x" else "0x0" mq_tx = G_PUSH_TEMPLATE.copy() mq_tx["BlockNumber"] = int(tx["blockNumber"], 16) mq_tx["Txid"] = tx["hash"] mq_tx["Type"] = self.coin_type mq_tx["From"] = tx["from"] # 排除to为空的 if not tx["to"]: continue mq_tx["To"] = tx["to"] mq_tx["Time"] = block["timestamp"] mq_tx["Amount"] = tx["value"] # 手续费 gas_price = tx.get("gasPrice", "0x0") gas_used = receipts[i].get("gasUsed", "0x0") if receipts[i] else "0x0" mq_tx["Fee"] = hex(int(gas_price, 16) * int(gas_used, 16)) # 交易状态 mq_tx["Valid"] = self.get_status(receipts[i], is_token_transfer) mq_tx["status"] = self.get_tx_status(receipts[i], is_token_transfer) if 'contract' in tx.keys(): mq_tx["Contract"] = tx["contract"] push_list.append(mq_tx) # 缓存已推送的区块高度和hash self.db.redis.save_cache_block(hex_to_int(block["number"]), block["hash"]) return push_list except ForkError as ex: raise ForkError(ex.height, ex.msg) except Exception as e: traceback.print_exc() G_LOGGER.info(f"获取块出现异常,尝试重新获取。异常原因:{str(e)}")
def parse_tx(self, tx, push_cache=None): """ 解析交易详情 :param tx: 交易详情 :return: """ if not isinstance(tx, dict): return [] mq_tx = G_PUSH_TEMPLATE.copy() mq_tx["Txid"] = del_0x(tx["txid"]) if self.rollback and push_cache and (mq_tx["Txid"] in push_cache): return [] mq_tx["Type"] = "NEO" push_list = [] # 待推送交易列表 vins = tx["vin"] vin_address = [] tx_index = 0 # 同笔交易中待推送的交易索引 for vin in vins: tx_id = vin["txid"] vout = vin["vout"] vin_transactions = self.rpc.get_transaction(tx_id) for tran in vin_transactions["vout"]: address = tran["address"] n = tran["n"] if vout == n: vin_address.append(address) break vouts = tx["vout"] for i, vout in enumerate(vouts): mq_tx = mq_tx.copy() mq_tx["From"] = vin_address[0] if len(vin_address) > 0 else "" mq_tx["To"] = vout["address"] mq_tx["status"] = "true" mq_tx["Amount"] = vout["value"] mq_tx["Contract"] = del_0x(vout["asset"]) if vout["address"] not in vin_address: mq_tx["TxIndex"] = tx_index push_list.append(mq_tx) tx_index += 1 # 如果是NEP5资产,还需要调用getapplicationlog查询交易详情 if tx["type"] == "InvocationTransaction": result = self.rpc.get_application_log(tx["txid"]) if result: result = result.get("executions") result = result[0] if result and len(result) > 0 else {} vmstate = result.get("vmstate") notifications = result.get("notifications", []) if vmstate and vmstate.find("FAULT") == -1: for ns in notifications: contract = ns.get("contract") state_type = ns["state"]["type"] state_value = ns["state"]["value"] if state_type == "Array": event_type = state_value[0]["type"] event_value = state_value[0]["value"] if event_type == "ByteArray": event_value = bytes.fromhex(event_value).decode() if event_value == "transfer": from_type = state_value[1]['type'] from_value = state_value[1]['value'] to_type = state_value[2]["type"] to_value = state_value[2]["value"] amount_type = state_value[3]["type"] amount_value = state_value[3]["value"] if from_type == 'ByteArray': from_value = b58encode_check(b'\x17' + hex_to_bytes(from_value)).decode() if to_type == "ByteArray": to_value = b58encode_check(b"\x17" + hex_to_bytes(to_value)).decode() if amount_type == "ByteArray": amount_value = str(hex_to_int(size_convert(amount_value))) mq_tx = mq_tx.copy() mq_tx["From"] = from_value mq_tx["To"] = to_value mq_tx["status"] = "true" mq_tx["Amount"] = amount_value mq_tx["Contract"] = del_0x(contract) if to_value not in vin_address: mq_tx["TxIndex"] = tx_index push_list.append(mq_tx) tx_index += 1 return push_list