23 分钟
子图清单
概述
子图清单 subgraph.yaml
定义了您的子图索引的智能合约和网络,这些合约中需要关注的事件,以及如何将事件数据映射到 Graph 节点存储并允许查询的实体。
子图定义由几个文件组成:
-
subgraph.yaml
: 包含子图清单 -
schema.graphql
: 一个 GraphQL 模式文件,它定义了为您的子图存储哪些数据,以及如何通过 GraphQL 查询这些数据 -
mapping.ts
:AssemblyScript映射将事件数据转换为模式中定义的实体的代码(例如本指南中的mapping.ts
)
子图功能
一个子图可以:
-
索引来自多个智能合约(但不是多个网络)的数据。
-
使用文件数据源对IPFS文件中的数据进行索引。
-
为每个需要索引到
dataSources
数组的合约添加一个条目。
子图清单的完整规范可以在这里找到。
对于上面列出的示例子图,subgraph.yaml
是:
1specVersion: 1.3.02description: Gravatar for Ethereum3repository: https://github.com/graphprotocol/graph-tooling4schema:5 file: ./schema.graphql6indexerHints:7 prune: auto8dataSources:9 - kind: ethereum/contract10 name: Gravity11 network: mainnet12 source:13 address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'14 abi: Gravity15 startBlock: 617524416 endBlock: 717524517 context:18 foo:19 type: Bool20 data: true21 bar:22 type: String23 data: 'bar'24 mapping:25 kind: ethereum/events26 apiVersion: 0.0.927 language: wasm/assemblyscript28 entities:29 - Gravatar30 abis:31 - name: Gravity32 file: ./abis/Gravity.json33 eventHandlers:34 - event: NewGravatar(uint256,address,string,string)35 handler: handleNewGravatar36 - event: UpdatedGravatar(uint256,address,string,string)37 handler: handleUpdatedGravatar38 callHandlers:39 - function: createGravatar(string,string)40 handler: handleCreateGravatar41 blockHandlers:42 - handler: handleBlock43 - handler: handleBlockWithCall44 filter:45 kind: call46 file: ./src/mapping.ts
子图条目
重要提示:请确保您将子图清单与所有处理程序和 entities一起填充。
清单中要更新的重要条目是:
-
specVersion
:标识子图支持的清单结构和功能的semver版本。最新版本是1.3.0
。有关功能和版本的更多详细信息,请参阅specVersion版本 部分。 -
description
:关于子图是什么的人类可读的描述。 当子图部署到Subgraph Studio时,Graph Explorer 会显示此描述。 -
repository
:可以找到子图清单存储库的 URL。 这也由 Graph Explorer显示。 -
features:
是所有使用的功能名称的列表。 -
indexerHints.prune
:定义子图的历史区块数据的保留情况。请参见indexerHints 章节中的prune。 -
dataSources.source
:智能合约子图源的地址,以及要使用的智能合约的ABI。 地址是可选的; 省略它允许索引来自所有合约的匹配事件。 -
dataSources.source.startBlock
:数据源开始索引的区块的可选编号。 在大多数情况下,我们建议使用创建合约的区块。 -
dataSources.source.endBlock
:数据源停止索引的区块的可选编号,包括该区块。所需的最低版本为:0.0.9
。 -
dataSources.context
:可以在子图映射中使用的键值对。支持各种数据类型,如Bool
,String
,Int
,Int8
,BigDecimal
,Bytes
,List
, 和BigInt
。每个变量需要指定其type
和data
。这些背景变量随后可以在映射文件中访问,为子图开发提供更多可配置选项。 -
dataSources.mapping.entities
:数据源写入存储的实体。 每个实体的模式在 schema.graphql 文件中定义。 -
dataSources.mapping.abis
:源合约以及您在映射中与之交互的任何其他智能合约的一个或多个命名 ABI 文件。 -
dataSources.mapping.eventHandlers
:列出此子图响应的智能合约事件,映射中的处理程序—示例中为./src/mapping.ts—也将这些事件转换为存储中的实体。 -
dataSources.mapping.callHandlers
:列出此子图响应的智能合约函数以及映射中的处理程序,该映射将输入和输出转换为函数调用到存储中的实体。 -
dataSources.mapping.blockHandlers
:列出此子图响应的区块以及映射中的处理程序,以便在将区块附加到链时运行。 如果没有过滤器,区块处理程序将在每个区块中运行。 可以通过向处理程序添加为以下类型字段提供可选的调用filter
-kind: call
。 如果区块包含至少一个对数据源合约的调用,则调用筛子将运行处理程序。
通过为每个需要将数据索引到 dataSources
数组的合约添加一个条目,单个子图可以索引来自多个智能合约的数据。
事件处理程序
子图中的事件处理程序对区块链上的智能合约发出的特定事件以及子图清单中定义的触发器处理程序做出反应。这使得子图能够根据定义的逻辑处理和存储事件数据。
定义事件处理程序
事件处理程序在子图的YAML配置中的数据源内声明。它指定了要监听的事件以及检测到这些事件时要执行的相应函数。
1dataSources:2 - kind: ethereum/contract3 name: Gravity4 network: dev5 source:6 address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'7 abi: Gravity8 mapping:9 kind: ethereum/events10 apiVersion: 0.0.911 language: wasm/assemblyscript12 entities:13 - Gravatar14 - Transaction15 abis:16 - name: Gravity17 file: ./abis/Gravity.json18 eventHandlers:19 - event: Approval(address,address,uint256)20 handler: handleApproval21 - event: Transfer(address,address,uint256)22 handler: handleTransfer23 topic1: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', '0xc8dA6BF26964aF9D7eEd9e03E53415D37aA96325'] # Optional topic filter which filters only events with the specified topic.
调用处理程序
虽然事件提供了一种收集合约状态相关变换的有效方法,但许多合约避免生成日志以优化燃气成本。 在这些情况下,子图可以订阅对数据源合约的调用。 这是通过定义引用函数签名的调用处理程序,及处理对该函数调用的映射处理程序来实现的。 为了处理这些调用,映射处理程序将接收一个 ethereum.Call
作为参数,其中包含调用的类型化输入和输出。 在交易调用链中的任何深度进行的调用都会触发映射,从而捕获通过代理合约与数据源合约的交互活动。
调用处理程序只会在以下两种情况之一触发:当指定的函数被合约本身以外的账户调用时,或者当它在 Solidity 中被标记为外部,并作为同一合约中另一个函数的一部分被调用时。
**注意: **调用处理程序目前依赖于 Parity 跟踪 API。某些网络,如 BNB 链和 Arbitrum,不支持此 API。如果索引其中一个网络的子图包含一个或多个调用处理程序,它将不会开始同步。子图开发人员应该使用事件处理程序。它们比调用处理程序性能好得多,并且在每个 evm 网络上都受到支持。
定义调用处理程序
要在清单中定义调用处理程序,只需在您要订阅的数据源下添加一个 callHandlers
数组。
1dataSources:2 - kind: ethereum/contract3 name: Gravity4 network: mainnet5 source:6 address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'7 abi: Gravity8 mapping:9 kind: ethereum/events10 apiVersion: 0.0.911 language: wasm/assemblyscript12 entities:13 - Gravatar14 - Transaction15 abis:16 - name: Gravity17 file: ./abis/Gravity.json18 callHandlers:19 - function: createGravatar(string,string)20 handler: handleCreateGravatar
function
是用于筛选调用的规范化函数签名。 handler
属性是映射中您希望在数据源合约中调用目标函数时执行的函数名称。
映射函数
每个调用处理程序都有一个参数,该参数的类型对应于被调用函数的名称。 在上面的示例子图中,映射包含一个处理程序,用于调用 createGravatar
函数并接收 CreateGravatarCall
参数作为参数:
1import { CreateGravatarCall } from '../generated/Gravity/Gravity'2import { Transaction } from '../generated/schema'34export function handleCreateGravatar(call: CreateGravatarCall): void {5 let id = call.transaction.hash6 let transaction = new Transaction(id)7 transaction.displayName = call.inputs._displayName8 transaction.imageUrl = call.inputs._imageUrl9 transaction.save()10}
handleCreateGravatar
函数接受一个新的 CreateGravatarCall
,它是 @graphprotocol/graph-ts
提供的ethereum.Call
的子类,包括调用的输入和输出。 CreateGravatarCall
类型是在您运行 graph codegen
时为您生成的。
区块处理程序
除了订阅合约事件或函数调用之外,子图可能还希望在将新区块附加到链上时更新其数据。 为了实现这一点,子图可以在每个区块之后,或匹配预定义过滤器的区块之后,运行一个函数。
支持的过滤器
调用过滤器
1filter:2 kind: call
对于每个包含对定义处理程序的合约(数据源)调用的区块,相应的处理程序都会被调用一次。
注意: 调用
过滤器目前依赖于 Parity 跟踪 API。某些网络,如 BNB 链和 Arbitrum,不支持此 API。如果索引其中一个网络的子图包含一个或多个带过滤器的区块调用
处理程序,它将不会开始同步。
块处理程序没有过滤器将确保每个块都调用处理程序。对于每种过滤器类型,一个数据源只能包含一个块处理程序。
1dataSources:2 - kind: ethereum/contract3 name: Gravity4 network: dev5 source:6 address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'7 abi: Gravity8 mapping:9 kind: ethereum/events10 apiVersion: 0.0.911 language: wasm/assemblyscript12 entities:13 - Gravatar14 - Transaction15 abis:16 - name: Gravity17 file: ./abis/Gravity.json18 blockHandlers:19 - handler: handleBlock20 - handler: handleBlockWithCallToContract21 filter:22 kind: call
投票过滤器
要求 规范版本
>= 0.0.8
注意: 投票过滤器仅适用于kind: ethereum
的数据源。
1blockHandlers:2 - handler: handleBlock3 filter:4 kind: polling5 every: 10
所定义的处理程序将在每n
个块上被调用一次,其中n
的值由every
字段提供。这种配置允许子图以固定的区块间隔执行特定的操作。
一次性过滤器
要求 规范版本
>= 0.0.8
注意:一次性过滤器仅适用于kind: ethereum
的数据源。
1blockHandlers:2 - handler: handleOnce3 filter:4 kind: once
带有 “once filter” 的所定义处理程序将在所有其他处理程序运行之前仅被调用一次。这种配置允许子图将该处理程序用作初始化处理程序,在索引开始时执行特定任务。
1export function handleOnce(block: ethereum.Block): void {2 let data = new InitialData(Bytes.fromUTF8('initial'))3 data.data = 'Setup data here'4 data.save()5}
映射函数
映射函数将接收 ethereum.Block
作为其唯一参数。 与事件的映射函数一样,此函数可以访问存储中现有的子图实体、调用智能合约、以及创建或更新实体。
1import { ethereum } from '@graphprotocol/graph-ts'23export function handleBlock(block: ethereum.Block): void {4 let id = block.hash5 let entity = new Block(id)6 entity.save()7}
匿名事件
如果您需要在 Solidity 中处理匿名事件,可以通过提供事件的主题 0 来实现,如示例所示:
1eventHandlers:2 - event: LogNote(bytes4,address,bytes32,bytes32,uint256,bytes)3 topic0: '0x644843f351d3fba4abcd60109eaff9f54bac8fb8ccf0bab941009c21df21cf31'4 handler: handleGive
只有当签名和主题0都匹配时,才会触发事件。默认情况下,topic0
等于事件签名的哈希值。
事件处理程序中的交易接收
从specVersion
0.0.5
和 apiVersion
0.0.7
开始,事件处理程序可以访问发出它们的交易接收。
为此,必须在子图清单中使用新的receict:true
键声明事件处理程序,该键是可选的,默认为false。
1eventHandlers:2 - event: NewGravatar(uint256,address,string,string)3 handler: handleNewGravatar4 receipt: true
在处理函数中,收据可以在 Event.receivt
字段中访问。 当receipt
键设置为 false
或在清单中省略时,将返回null
值。
触发处理程序的顺序
区块内数据源的触发器使用以下流程进行排序:
- 事件和调用触发器首先按区块内的交易索引排序。
- 同一交易中的事件和调用触发器使用约定进行排序:首先是事件触发器,然后是调用触发器,每种类型都遵循它们在清单中定义的顺序。
- 区块触发器按照它们在清单中定义的顺序,在事件和调用触发器之后运行。
这些排序规则可能会发生变化。
注意:当创建新的动态数据源时,为动态数据源定义的处理程序只会在所有现有数据源处理程序处理完毕后开始处理,并且每次触发时都会按相同的顺序重复处理。
数据源模板
EVM兼容智能合约中的一种常见模式是使用注册表或工厂合约,其中一个合约创建、管理或引用任意数量的其他合约,每个合约都有自己的状态和事件。
这些子合约的地址可能事先知道,也可能不知道,其中许多合约可能会随着时间的推移而创建和/或添加。这就是为什么在这种情况下,定义单个数据源或固定数量的数据源是不可能的,需要一种更动态的方法:data source templates。
主合约的数据源
首先,您需要为主合约定义一个常规数据源。 下面的代码片段显示了Uniswap 交换工厂合约的简化示例数据源。 注意 NewExchange(address,address)
事件处理程序。 当工厂合约在链上创建新交换合约时,会发出此消息。
1dataSources:2 - kind: ethereum/contract3 name: Factory4 network: mainnet5 source:6 address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'7 abi: Factory8 mapping:9 kind: ethereum/events10 apiVersion: 0.0.911 language: wasm/assemblyscript12 file: ./src/mappings/factory.ts13 entities:14 - Directory15 abis:16 - name: Factory17 file: ./abis/factory.json18 eventHandlers:19 - event: NewExchange(address,address)20 handler: handleNewExchange
动态创建合约的数据源模板
然后,将data source templates 添加到清单中。 它们与常规数据源相同,只是在 source
下缺少预先定义的合约地址。 通常,您需要为母合约管理或引用的每种类型的子合约定义一个模板。
1dataSources:2 - kind: ethereum/contract3 name: Factory4 # ... other source fields for the main contract ...5templates:6 - name: Exchange7 kind: ethereum/contract8 network: mainnet9 source:10 abi: Exchange11 mapping:12 kind: ethereum/events13 apiVersion: 0.0.614 language: wasm/assemblyscript15 file: ./src/mappings/exchange.ts16 entities:17 - Exchange18 abis:19 - name: Exchange20 file: ./abis/exchange.json21 eventHandlers:22 - event: TokenPurchase(address,uint256,uint256)23 handler: handleTokenPurchase24 - event: EthPurchase(address,uint256,uint256)25 handler: handleEthPurchase26 - event: AddLiquidity(address,uint256,uint256)27 handler: handleAddLiquidity28 - event: RemoveLiquidity(address,uint256,uint256)29 handler: handleRemoveLiquidity
实例化数据源模板
在最后一步中,您可以更新主合约映射,以便从其中一个模板创建动态数据源实例。 在此示例中,您将更改主合约映射以导入 Exchange
模板,并在其上调用 Exchange.create(address)
方法,从而开始索引新交换合约。
1import { Exchange } from '../generated/templates'23export function handleNewExchange(event: NewExchange): void {4 // Start indexing the exchange; `event.params.exchange` is the5 // address of the new exchange contract6 Exchange.create(event.params.exchange)7}
注意: 新的数据源只会处理创建它的区块和所有后续区块的调用和事件,而不会处理历史数据,也就是包含在先前区块中的数据。
如果先前的区块包含与新数据源相关的数据,最好通过读取合约的当前状态,并在创建新数据源时创建表示该状态的实体来索引该数据。
数据源背景
数据源上下文允许在实例化模板时传递额外的配置。在我们的示例中,假设交易所与特定的交易对相关联,该交易对包含在NewExchange
事件中。这些信息可以传递到实例化的数据源中,如下所示:
1import { Exchange } from '../generated/templates'23export function handleNewExchange(event: NewExchange): void {4 let context = new DataSourceContext()5 context.setString('tradingPair', event.params.tradingPair)6 Exchange.createWithContext(event.params.exchange, context)7}
在 Exchange
模板的映射中,可以访问背景:
1import { dataSource } from '@graphprotocol/graph-ts'23let context = dataSource.context()4let tradingPair = context.getString('tradingPair')
对于所有的值类型,都有像 setString
和 getString
这样的 setter 和 getter。
起始区块
startBlock
是一个可选配置,允许您定义数据源从区块链中的哪个区块开始索引。 设置起始区块允许数据源跳过潜在的数百万个不相关的区块。 通常,子图开发人员会将 startBlock
设置为创建数据源智能合约的区块。
1dataSources:2 - kind: ethereum/contract3 name: ExampleSource4 network: mainnet5 source:6 address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'7 abi: ExampleContract8 startBlock: 66279179 mapping:10 kind: ethereum/events11 apiVersion: 0.0.912 language: wasm/assemblyscript13 file: ./src/mappings/factory.ts14 entities:15 - User16 abis:17 - name: ExampleContract18 file: ./abis/ExampleContract.json19 eventHandlers:20 - event: NewEvent(address,address)21 handler: handleNewEvent
注意: 合约创建区块可以在 Etherscan 上快速查找:
- 通过在搜索栏中输入合约地址来搜索合约。
- 单击
Contract Creator
部分中的创建交易hash。 - 加载交易详情页面,您将在其中找到该合约的起始区块。
索引人提示
indexerHints
设置位于子图的清单文件中,为索引人提供有关处理和管理子图的指令。它影响数据处理、索引策略和优化等操作决策。目前,它包括prune
选项,用于管理历史数据的保留或修剪。
此功能可从specVersion:1.0.0获得
修剪
indexerHints.prune
:定义子图的历史区块数据的保留策略。可选项包括:
never
:不进行历史数据的修剪;保留全部历史记录。auto
:保留索引人设置的最小必要历史记录,优化查询性能。- 指定一个具体的数字:设置保留历史区块的自定义限制数量。这允许开发者根据特定的应用需求和存储容量,精确控制保留的历史区块数,从而在维持历史数据完整性和优化资源使用之间找到平衡。
1indexerHints:2 prune: auto
在子图的上下文中,“历史”一词是关于存储反映可变实体旧状态的数据。
以下情况需要给定区块的历史记录:
- Time travel queries,可以查询子图历史中特定块处这些实体的过去状态。
- 在该区块,将子图用作另一个子图中的graft base。
- 将子图倒回该块。
如果截至该块的历史数据已被修剪,则上述功能将不可用。
通常建议使用自动
,因为它可以最大限度地提高查询性能,对于大多数不需要访问大量历史数据的用户来说已经足够了。
对于利用time travel querie的子图,建议设置一个特定的区块数来保留历史数据,或使用prune: never
以保留所有历史实体状态。以下是在子图设置中配置这两个选项的示例:
要保留特定数量的历史数据:
1indexerHints:2 prune: 1000 # Replace 1000 with the desired number of blocks to retain
要保留实体状态的完整历史记录:
1indexerHints:2 prune: never
视图版本发布
版本 | Release 说明 |
---|---|
1.3.0 | 添加了对 Subgraph 合成 的支持。 |
1.2.0 | 添加了对索引参数过滤器 的支持,并声明了eth_call 。 |
1.1.0 | 支持时间序列和聚合。为id 添加了对Int8 类型的支持。 |
1.0.0 | 支持indexerHints 功能以修剪子图。 |
0.0.9 | 支持endBlock 功能。 |
0.0.8 | 添加了对轮询块处理程序和初始化处理程序的支持。 |
0.0.7 | 添加了对文件数据源的支持。 |
0.0.6 | 支持快速的索引证明 计算变体。 |
0.0.5 | 添加了对可以访问交易收据的事件处理程序的支持。 |
0.0.4 | 添加了对管理子图功能的支持。 |