|
| 1 | +--- |
| 2 | +title: Substreams-powered subgraphs |
| 3 | +--- |
| 4 | + |
| 5 | +[Substreams](/substreams/README) is a new framework for processing blockchain data, developed by StreamingFast for The Graph Network. A substreams modules can output entity changes, which are compatible with Subgraph entities. A subgraph can use such a Substreams module as a data source, bringing the indexing speed and additional data of Substreams to subgraph developers. |
| 6 | + |
| 7 | +> This cookbook uses this [Substreams-powered subgraph as a reference](https://github.com/graphprotocol/graph-tooling/tree/main/examples/substreams-powered-subgraph). |
| 8 | +
|
| 9 | +## Requirements |
| 10 | + |
| 11 | +This cookbook requires [yarn](https://yarnpkg.com/) as well as [the dependencies necessary for local Substreams development](https://substreams.streamingfast.io/developers-guide/installation-requirements). |
| 12 | + |
| 13 | +## Defining a Substreams package |
| 14 | + |
| 15 | +A Substreams package is composed of types (defined as [Protocol Buffers](https://protobuf.dev/)), modules (written in Rust), and a `substreams.yaml` file which references the types, and specifies how modules are triggered. [Learn more about Substreams development](/substreams/README). |
| 16 | + |
| 17 | +The Substreams package in question detects contract deployments on Mainnet Ethereum, tracking the creation block and timestamp for all newly deployed contracts. To do this, there is a dedicated `Contract` type in `/proto/example.proto` ([learn more about defining Protocol Buffers](https://protobuf.dev/programming-guides/proto3/#simple)): |
| 18 | + |
| 19 | +```proto |
| 20 | +syntax = "proto3"; |
| 21 | +
|
| 22 | +package example; |
| 23 | +
|
| 24 | +message Contracts { |
| 25 | + repeated Contract contracts = 1; |
| 26 | +} |
| 27 | +
|
| 28 | +message Contract { |
| 29 | + string address = 1; |
| 30 | + uint64 blockNumber = 2; |
| 31 | + string timestamp = 3; |
| 32 | + uint64 ordinal = 4; |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +The core logic of the Substreams package is a `map_contract` module in `lib.rs`, which processes every block, filtering for Create calls which did not revert, returning `Contracts`: |
| 37 | + |
| 38 | +``` |
| 39 | +#[substreams::handlers::map] |
| 40 | +fn map_contract(block: eth::v2::Block) -> Result<Contracts, substreams::errors::Error> { |
| 41 | + let contracts = block |
| 42 | + .transactions() |
| 43 | + .flat_map(|tx| { |
| 44 | + tx.calls |
| 45 | + .iter() |
| 46 | + .filter(|call| !call.state_reverted) |
| 47 | + .filter(|call| call.call_type == eth::v2::CallType::Create as i32) |
| 48 | + .map(|call| Contract { |
| 49 | + address: format!("0x{}", Hex(&call.address)), |
| 50 | + block_number: block.number, |
| 51 | + timestamp: block.timestamp_seconds().to_string(), |
| 52 | + ordinal: tx.begin_ordinal, |
| 53 | + }) |
| 54 | + }) |
| 55 | + .collect(); |
| 56 | + Ok(Contracts { contracts }) |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +A Substreams package can be used by a subgraph as long as it has a module which outputs compatible entity changes. The example Substreams package has an additional `graph_out` module in `lib.rs` which returns a `substreams_entity_change::pb::entity::EntityChanges` output, which can be processed by Graph Node. |
| 61 | + |
| 62 | +> The `substreams_entity_change` crate also has a dedicated `Tables` function for simply generating entity changes ([documentation](https://docs.rs/substreams-entity-change/1.2.2/substreams_entity_change/tables/index.html)). The Entity Changes generated must be compatible with the `schema.graphql` entities defined in the `subgraph.graphql` of the corresponding subgraph. |
| 63 | +
|
| 64 | +``` |
| 65 | +#[substreams::handlers::map] |
| 66 | +pub fn graph_out(contracts: Contracts) -> Result<EntityChanges, substreams::errors::Error> { |
| 67 | + // hash map of name to a table |
| 68 | + let mut tables = Tables::new(); |
| 69 | +
|
| 70 | + for contract in contracts.contracts.into_iter() { |
| 71 | + tables |
| 72 | + .create_row("Contract", contract.address) |
| 73 | + .set("timestamp", contract.timestamp) |
| 74 | + .set("blockNumber", contract.block_number); |
| 75 | + } |
| 76 | +
|
| 77 | + Ok(tables.to_entity_changes()) |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +These types and modules are pulled together in `substreams.yaml`: |
| 82 | + |
| 83 | +``` |
| 84 | +specVersion: v0.1.0 |
| 85 | +package: |
| 86 | + name: 'substreams_test' # the name to be used in the .spkg |
| 87 | + version: v1.0.1 # the version to use when creating the .spkg |
| 88 | +
|
| 89 | +imports: # dependencies |
| 90 | + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v0.2.1/substreams-entity-change-v0.2.1.spkg |
| 91 | +
|
| 92 | +protobuf: # specifies custom types for use by Substreams modules |
| 93 | + files: |
| 94 | + - example.proto |
| 95 | + importPaths: |
| 96 | + - ./proto |
| 97 | +
|
| 98 | +binaries: |
| 99 | + default: |
| 100 | + type: wasm/rust-v1 |
| 101 | + file: ./target/wasm32-unknown-unknown/release/substreams.wasm |
| 102 | +
|
| 103 | +modules: # specify modules with their inputs and outputs. |
| 104 | + - name: map_contract |
| 105 | + kind: map |
| 106 | + inputs: |
| 107 | + - source: sf.ethereum.type.v2.Block |
| 108 | + output: |
| 109 | + type: proto:test.Contracts |
| 110 | +
|
| 111 | + - name: graph_out |
| 112 | + kind: map |
| 113 | + inputs: |
| 114 | + - map: map_contract |
| 115 | + output: |
| 116 | + type: proto:substreams.entity.v1.EntityChanges # this type can be consumed by Graph Node |
| 117 | +
|
| 118 | +``` |
| 119 | + |
| 120 | +You can check the overall "flow" from a Block, to `map_contract` to `graph_out` by running `substreams graph`: |
| 121 | + |
| 122 | +```mermaid |
| 123 | +graph TD; |
| 124 | + map_contract[map: map_contract]; |
| 125 | + sf.ethereum.type.v2.Block[source: sf.ethereum.type.v2.Block] --> map_contract; |
| 126 | + graph_out[map: graph_out]; |
| 127 | + map_contract --> graph_out; |
| 128 | +``` |
| 129 | + |
| 130 | +To prepare this Substreams package for consumption by a subgraph, you must run the following commands: |
| 131 | + |
| 132 | +```bash |
| 133 | +yarn substreams:protogen # generates types in /src/pb |
| 134 | +yarn substreams:build # builds the substreams |
| 135 | +yarn substreams:package # packages the substreams in a .spkg file |
| 136 | + |
| 137 | +# alternatively, yarn substreams:prepare calls all of the above commands |
| 138 | +``` |
| 139 | + |
| 140 | +> These scripts are defined in the `package.json` file if you want to understand the underlying substreams commands |
| 141 | +
|
| 142 | +This generates a `spkg` file based on the package name and version from `substreams.yaml`. The `spkg` file has all the information which Graph Node needs to ingest this Substreams package. |
| 143 | + |
| 144 | +> If you update the Substreams package, depending on the changes you make, you may need to run some or all of the above commands so that the `spkg` is up to date. |
| 145 | +
|
| 146 | +## Defining a Substreams-powered subgraph |
| 147 | + |
| 148 | +Substreams-powered subgraphs introduce a new `kind` of data source, "substreams". Such subgraphs can only have one data source. This data source must specify the Substreams network, the Substreams package (`spkg`) as a relative file location, and the module within that Substreams package which produces subgraph-compatible entity changes (in this case `map_entity_changes`, from the Substreams package above). The mapping is specified, but simply identifies the mapping kind ("substreams/graph-entities") and the apiVersion. |
| 149 | + |
| 150 | +```yaml |
| 151 | +specVersion: 0.0.4 |
| 152 | +description: Ethereum Contract Tracking Subgraph (powered by Substreams) |
| 153 | +repository: https://github.com/graphprotocol/graph-tooling |
| 154 | +schema: |
| 155 | + file: schema.graphql |
| 156 | +dataSources: |
| 157 | + - kind: substreams |
| 158 | + name: substream_test |
| 159 | + network: mainnet |
| 160 | + source: |
| 161 | + package: |
| 162 | + moduleName: graph_out |
| 163 | + file: substreams-test-v1.0.1.spkg |
| 164 | + mapping: |
| 165 | + kind: substreams/graph-entities |
| 166 | + apiVersion: 0.0.5 |
| 167 | +``` |
| 168 | +
|
| 169 | +The `subgraph.yaml` also references a schema file. The requirements for this file are unchanged, but the entities specified must be compatible with the entity changes produced by the Substreams module referenced in the `subgraph.yaml`. |
| 170 | + |
| 171 | +```graphql |
| 172 | +type Contract @entity { |
| 173 | + id: ID! |
| 174 | +
|
| 175 | + "The timestamp when the contract was deployed" |
| 176 | + timestamp: String! |
| 177 | +
|
| 178 | + "The block number of the contract deployment" |
| 179 | + blockNumber: BigInt! |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +Given the above, subgraph developers can use Graph CLI to deploy this Substreams-powered subgraph. |
| 184 | + |
| 185 | +> Substreams-powered subgraphs indexing mainnet Ethereum can be deployed to the [Subgraph Studio](https://thegraph.com/studio/). |
| 186 | + |
| 187 | +```bash |
| 188 | +yarn install # install graph-cli |
| 189 | +yarn subgraph:build # build the subgraph |
| 190 | +yarn subgraph:deploy # deploy the subgraph |
| 191 | +``` |
| 192 | + |
| 193 | +That's it! You have built and deployed a Substreams-powered subgraph. |
| 194 | + |
| 195 | +## Serving Substreams-powered subgraphs |
| 196 | + |
| 197 | +In order to serve Substreams-powered subgraphs, Graph Node must be configured with a Substreams provider for the relevant network, as well as a Firehose or RPC to track the chain head. These providers can be configured via a `config.toml` file: |
| 198 | + |
| 199 | +```toml |
| 200 | +[chains.mainnet] |
| 201 | +shard = "main" |
| 202 | +protocol = "ethereum" |
| 203 | +provider = [ |
| 204 | + { label = "substreams-provider-mainnet", |
| 205 | + details = { type = "substreams", |
| 206 | + url = "https://mainnet-substreams-url.grpc.substreams.io/", |
| 207 | + token = "exampletokenhere" }}, |
| 208 | + { label = "firehose-provider-mainnet", |
| 209 | + details = { type = "firehose", |
| 210 | + url = "https://mainnet-firehose-url.grpc.firehose.io/", |
| 211 | + token = "exampletokenhere" }}, |
| 212 | +] |
| 213 | +``` |
0 commit comments