by @Mack and @Slimchance
Abstract
Subgraph developers would like a way to hydrate the store when the subgraph is deployed. They would also like ways to run a function at a certain block/timestamp or block/time interval.
- The suggestions have a “BlockNumber” and a “TimeStamp” variant. The TimeStamp variants would trigger on the first block following the specified timestamp.
Motivation
Initialisation Handler
Subgraph developers may wish to run a handler on startup:
-
Hydrate the store with data found in one of the mapping files, json or on IPFS. This would be useful in several cases. This could be contant data like enums and other entity types which need to exist before other entities are created. (The current solution to this is to put a function that only runs once in the eventHandler of the first eventType to be emitted.)
-
If a subgraph developer wants to index a list of similar contracts, they would have to create a separate datasource for each one. An initialisation handler allows for a much cleaner pattern: Create a template with the common ABI/ handlers. Then, within the initialisation handler create a dynamic data source for each of the contracts.
initialisationHandler:
handler: myInitialisationHandler
Cron Jobs & Filters
One use case we have thought about for Cron Jobs is mapping code that would only need to be run every x blocks.
Handlers are run on every matching trigger;
- Blockhandlers are run every block
- Event- and callhandlers are run on every matching event/call - This can sometimes be multiple times in a single block.
For some usecases it is redundant to run the mapping code this often. Allowing subgraph developers to define an interval for which to run the mapping logic, would be a massive performance increases for these subgraphs.
- This would be very useful when creating timeseries data
- This would be useful when utilizing blockhandlers
Two types of Cron Jobs:
- On a blockhandler (“Run this handler every x blocks/seconds”)
- On other handlers (“If this handler have been run within x blocks/seconds => Skip”)
blockHandlers:
- handler: myCronHandler
filter:
kind: cron
Decorator => Hook
Subgraph developers may wish to add hooks at certain blocknumbers/timestamps. This would run a piece of code at a certain blocknumber/timestamp.
- Update the state of an entity when an entity attribute blocknumber/timestamp is reached. Runs once.
- Run hook logic when declared blockNumber/timestamp is reached. Runs once.
- Hooks receive the entity that triggered the block- or timetrigger.
- In our example below, Hooks need to be unique to entities.
---- schema.graphql
type Auction @entity {
id: ID!
item: Artwork!
seller: Account!
currency: Bytes!
isLive: Boolean!
startTime: BigInt! @blockTrigger => startAuction() // Trigger on startTime blockNumber
endTime: BigInt! @blockTrigger => endAuction()
winner: Account
}
type Artwork @entity {
id: ID!
isRevealed: Boolean!
revealTimestamp: BigInt! @timeTrigger => revealed() // Trigger at revealTimestamp
}
---- src/hooks/index.ts
function startAuction(entity: Auction) {
entity.isLive = true;
entity.save()
}
function endAuction(entity: Auction) {
entity.isLive = false;
entity.save()
function revealed(entity: Artwork) {
entity.isRevealed = true;
// Update URI from contract tokenURI function and call ipfs for new metadata
entity.save()
}
Suggestions on how to run the Blocktriggers
Our initial suggestion would be to run an internal job in graph-node
that could register each entity that has a blockTrigger with a non-null blockNumber (or store this in a lookup table), attaching the blockHandler to call the required hook, only at registered blocks.
This would in theory reduce the amount of times that the blockHandler would need to run, and would, in theory, reduce the impact blockHandlers have on index and sync time.
The same features could also allow for a timestamp
instead of a blocknumber
. This could be more intuitive to subgraph developers. It could be especially important on networks with variable blocktime. Similarly, there could be a lookup table with all the timestamps that has a trigger or hook. When a new block has a timestamp equal or higher than the smallest timestamp in the lookup table, the handler/code would be run.
# BlockTrigger Lookup Table / Queue
# Could be used for Cron Queue as well
type BlockTriggerLookup @entity {
id: ID
nameOfEntity: String! # Used to Read Entity from Store
entityId: String! # EntityID
blockNumber: BigInt! # When to Trigger Hook
timestamp: BigInt! # Alt. When to Trigger Hook
hookToRun: String! # Which Hook to Run
}
Building on this concept
This could also lay some of the groundwork for dynamic handlers - as this is a dynamic blockHandler that would only map and trigger on certain blocks (or ranges of blocks).
In addition it could also have a skipHook for transactions and events the user would want to skip, and a endHook/endBlock, to stop listening at that block to an event or trigger.
Made with by Graphrica