flow study开源项目

218 阅读4分钟

image.png flow_study开源项目为 Flow 开发者搭建一个开发学习平台。 该项目包括3个模块,代码下载模块,搜索模块,展示模块。flow_study开源项目主要作为flow学习的有效工具和开发平台。本章介绍的是flow_study_spider,flow合约代码的ETL。 详细代码访问

服务 flow_study_server github.com/flowstudy/f…
爬虫入库 flow_study_spider github.com/flowstudy/f…
数据表结构 flow_study_sql github.com/flowstudy/f…
前端 flow_study_web github.com/flowstudy/f…

下图是flow区块的基本结构 1675152106222.png

get_block.py

为了获取区块的全部信息,首先程序 get_block.py通过 flow_py_sdk提供的接口获取最新的区块信息,并将获取的相关信息存入数据库的flow_block表,

    async with flow_client(
            host="access.mainnet.nodes.onflow.org", port=9000
    ) as client:
        latest_block = await client.get_latest_block()
        height = latest_block.height
        parent_id_hex = latest_block.parent_id.hex()
        data = {
            "block_id" : latest_block.id.hex(),
            "signatures" : latest_block.signatures[0].hex(),
            "parent_id" : parent_id_hex,
            "height" : latest_block.height,
            "fetch_time" : time.strftime("%Y-%m-%d %H:%M:%S",time.localtime()),
            "timestamp" : latest_block.timestamp,
        }

访问flowscan.org/ 可查看区块的相关信息 image.png

update_trans_multi.py

上面的函数我们已经把区块的信息存入flow_block表中了,现在可以调用get_trans.py的get_trans函数,通过flow_block表中的区块高度,获取区块的交易信息,如交易名称contract_name,交易地址contract_address,并将获取到的信息存储到flow_trans_data表中。

下面是get_trans.py中通过flow height获取交易信息的部分代码,由flow区块的基本结构可知,get_trans函数获取交易信息需要先获取collection, 通过collection信息,使用以下函数可以获取到交易中的用户地址,交易的脚本代码,合约地址等信息。最后把收集到的信息存在flow_trans_data表中。

async with flow_client(
        host="access.mainnet.nodes.onflow.org", port=9000
) as client:
    # 通过height获取区块信息。
    block = await client.get_block_by_height(height=height)
    #collection是transaction的集合
    trans_list = []
    # 获得区块中的collection列表
    for i in range(len(block.collection_guarantees)):
        collection_id = block.collection_guarantees[i].collection_id  # 获得第i个 collection id
        collection = await client.get_collection_by_i_d(id=collection_id)  # 获得 collection 详情
        for trans_id in collection.transaction_ids: # 获得 collection 中所有transaction 列表,并遍历
            trans_id_hex = trans_id.hex() # 获得交易id,16进制形式
            transaction = await client.get_transaction(id=trans_id) # 根据交易id 获得交易详情
            user_address= transaction.proposal_key.address.hex() # 获得交易中的用户地址
            trans_script = transaction.script.decode("utf-8") # 获得交易的脚本代码
            import_data_list = get_contract_address(trans_script) # 从交易代码中解析合约地址
            # 遍历合约地址,构造需要插入数据库的数据
            for import_data in import_data_list:
                trans_data = {
                    "trans_id":trans_id_hex,
                    "user_address":"0x" + user_address, "contract_name":import_data["contract_name"],
 "contract_address":import_data["contract_address"],
                    "fetch_time":time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()),
                    "height":height,
                } #注意使用的时候,基础的nft等类,可以不用
                #print(trans_data)
                trans_list.append(trans_data)

其中trans_script需要经get_contract_address函数的简单解析才能获取到contract_name,contract_address,具体代码如下。

def get_contract_address(trans_script):
    #step 1,获得所有引用
    p = re.compile('import.{3,30}from 0x\w{10,25}') #引用的正则
    import_list = p.findall(trans_script)

    #step 2,解析所有引用
    import_data_list = []
    for item in import_list:
        item_list = item.split()
        contract_name = item_list[1]
        contract_address = item_list[3]
        #print(contract_name, contract_address)
        import_data = {
            "contract_name":contract_name,
            "contract_address":contract_address,
        }
        import_data_list.append(import_data)
    return import_data_list

update_contract.py 每小时运行一次,把flow_trans_data表中的contract_address去重后,插入flow_contract_address表,相当于接下来要处理的任务表,接下来在get_contract.py中处理这些合约。

get_contract.py

通过表中的contract_address获取合约代码,具体实现如下列代码,通过合约地址可以获取service_account_address,从而获取到账户信息,接下来解析account就可以得到contract_address,contract_code,contract_name。其中,contract_name需要从contract_code中进行简单的正则匹配筛选出来。

async def get_contract(address):
    async with flow_client(
            host="access.mainnet.nodes.onflow.org", port=9000
    ) as client:
        service_account_address = bytes.fromhex(address)
        #到最新高度的信息
        account = await client.get_account(
            address=service_account_address
        )
        contract_data_list = []
        for key in account.contracts:
            contract = account.contracts[key]
            #print(contract.decode())
            contract_code = contract.decode()
            contract_name = get_contract_name(contract_code)
            contract_data = {
                "contract_address": "0x"+address,
                "contract_name": contract_name,
                "contract_code": contract_code
            }
            contract_data_list.append(contract_data)
        return contract_data_list

contract_name正则匹配代码。

def get_contract_name(code):
    #step 1,获得定义
    #step 1,获得所有引用
    p = re.compile('pub contract.*|access(all) contract.*') #引用的正则
    import_list = p.findall(code) #包含回车

    contract_name_line = import_list[0]
    contract_name_line = contract_name_line.replace(" interface","")
    contract_name_line = contract_name_line.replace(":", " ")
    contract_name_line = contract_name_line.replace("{", " ")

    contract_name_line_item = contract_name_line.split()
    contract_name = contract_name_line_item[2].strip()
    return contract_name

parse_flow_code.py

为flow_code表中的contract_type,contract_category添加属性,获取表中每一行的contract_code,通过正则解析contract_code的contract_type属于interface,contract还是transaction。get_code_category函数解析合约名称得到code_category并把code_category存入flow_code表。

contract_struct_parser2.py

为了获取合约代码中各个代码块的位置,实现快速的跳转,contract_struct_parser2.py调用一个由go语言编写的解析服务,服务具体代码可以在GitHub地址github.com/HoppingChar… 中查看,解析后的code_text包含合约代码的每一部分从第几行开始到第几行结束等位置信息,经过parse_code函数的解析,struct_type代码块的类型,struct_name代码块的名字,start_pos此代码块的开始位置,end_pos此代码块的结束位置。

update_relate_code.py

主要功能分析flow_code中合约的相关代码,存入contract_relation表,update_relate_code函数获取flow_code表中待处理的合约名称,合约地址,合约代码,调用code_relation.py的get_code_related函数解析相关合约的contract_code,contract_name。

code_relation.py

有两个函数,get_code_related 获取代码相关代码,指的是本段合约代码所引用的合约,输出相关合约的合约名称,合约地址,list 格式如下 [{"contract_name":"name1","contract_address":"address1"}, {"contract_name":"name2","contract_address":"address2"}]。

relate_contract_list = []
#step 1 ,获得所有的import行
#step 1,获得所有引用
p = re.compile('import.{3,30}from 0x\w{10,25}') #引用的正则
import_list = p.findall(contract_code)
# step 2 ,解析每一行,获得相关的合约地址和合约名称
for item in import_list:
    item_list = item.split()
    contract_name = item_list[1]
    contract_address = item_list[3]
    #print(contract_name, contract_address)
    import_data = {
        "contract_name":contract_name,
        "contract_address":contract_address,
    }
    relate_contract_list.append(import_data)
return relate_contract_list

get_code_relate_transaction,获取代码相关交易transaction,存入数据库的flow_code_relate_transaction表。 具体关系解释如下:

来源数据库表:contract_relation
存储数据库表:flow_code_relate_transaction

从相关合约表contract_relation 获取关系, 41行的相关交易是指33行的CharityNFT,因为从表中可以看出41被33 引用,并且33的 flow_code contract_type 是transaction。

contract_relation表示例
33Admin0x097bafa4e0b48eefCharityNFT0x097bafa4e0b48eef2022-11-26 19:45:22
41CharityNFT0x097bafa4e0b48eefNonFungibleToken0x1d7e57aa558174482022-11-27 00:32:59

其他代码解释

update_fcode_es.py 全量向es中导入flow_code表数据。

update_trans.py 从 flow_block读取未处理的区块,获得区块内的交易信息,主要是合约代码和名称,插入flow_trans_data。

get_flow_trans_link.py 获得flow转账的events记录,存入flow_token表。

get_flow_trans_link_to.py 获得flow转账的events记录,存入flow_token_to表。