owned this note
owned this note
Published
Linked with GitHub
# On-chain epoch oracle
> This is a placeholder detailed specification for the **Protocol Chain Epoch Oracle proposal**.
We will introduce an Epoch Block Oracle contract which will track the "Epoch Block" for all networks supported for indexing rewards. Indexers will use block hashes specified by this oracle to close their allocations.
## On-chain Epoch Block Oracle
Create a simple contract which tracks the block number to close allocations for the latest epoch.
A sketch of what this might look like in Solidity:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract EpochOracle is Ownable {
event networkAdded(string _network);
event networkRemoved(string _network);
event newEpochBlock(uint256 _epoch, uint256 _networkId, string _blockHash);
error NetworkExists();
constructor(address _owner) {
transferOwnership(address _owner);
}
// count of supported networks
uint256 public networkCounter;
// mapping of supported networks to IDs to reduce cost of ongoing updates
mapping(string => uint256) public networkIds;
mapping(uint256 => string) public names;
// mapping from internal ID to the latest block hash
mapping(uint => string) public epochBlockLookup;
// current epoch
uint256 public epoch;
// Add support for a new network
function addNetwork(string calldata newNetwork) onlyOwner {
if(networkIds[newNetwork] > 0) revert NetworkExists();
networkCounter++;
networkIds[newNetwork] = networkCounter;
names[networkCounter] = newNetwork;
}
// Remove support for a network (does not decrement counter)
function removeNetwork(string calldata removedNetwork) onlyOwner {
delete epochBlockLookup[networkIds[removedNetwork]];
delete names[networkCounter];
}
// helper to get the latest epochBlock
function getEpochBlock(string calldata _network) view returns(string memory) {
uint256 networkId = networkIds[_network];
return epochBlockLookup[networkId];
}
struct epochBlockUpdate {
uint256 networkId;
string blockHash;
}
// set multiple epoch blocks
function setEpochBlocks(uint256 _epoch, epochBlockUpdate[] calldata _updates) public onlyOwner {
// this could be set automatically using the EpochManager
// but that makes some assumptions about the process
epoch = _epoch;
for (uint256 i = 0; i < _updates.length; i++) {
uint256 networkId = _updates[i].networkId;
// only allow updates for active networks
if(networkId <= networkCounter && !names[networkId]) {
epochBlock[networkId] = _updates[i].blockHash;
emit newEpochBlock(_epoch, networkId, _updates[i].blockHash);
}
}
}
};
```
The Owner would be responsible for fetching the blocks from the different chains, and deciding which ones should be used to close the current Epoch.
The Owner therefore has some operational requirements:
- Connecting to all of the networks supported for indexing on The Graph Network
- Identifying the epoch block across those chains
- The rule for Mainnet Ethereum will be unchanged
- The Owner will need a mechanism to match that block to the simultaneous block those chains
> Precision here is less important than having a block that is on the main chain in an appropriate time-frame
- Calling `setEpochBlock` to update the Epoch Block Oracle
- This will be required once per epoch (~24 hours)
- The oracle will need to wait some time between the start of the Epoch & calling this function to allow for re-orgs
- Monitoring the stability & performance of the Oracle to ensure that Epochs are being updated
Initially the Owner will be managed by members of The Graph Protocol's core development teams, in line with other trusted network components (the gateway, the subgraph oracle).
> This proposal assumes that the Oracle is deployed on Ethereum Mainnet, however it could be deployed elsewhere. That could reduce gas costs for management, but it would reduce security and composability, and increase the operational burden for indexers.
### Update the EpochManager
The Epoch manager will need to be updated to support this new system.
> Notable is the fact that this propsal uses `blockHash`, rather than `blockNumber`, for cross-chain determinism.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IEpochOracle.sol"
contract EpochManager {
/... .../
IEpochOracle public epochOracle;
function setEpochOracle(address newEpochOracle) onlyOwner {
epochOracle = IEPochOracle(newEpochOracle);
}
function currentEpochBlock(string network) view returns(string memory) {
return epochOracle.getEpochBlock(network)
}
};
```
The `currentEpochBlock()` function can remain the same (returning a blockNumber), for backwards compabitility.
Indexers could then query `currentEpochBlock("polygon")` to get the block to use in order to close allocations.
> An alternative implementation could deploy the Oracle on a chain other than Ethereum Mainnet, in which case the above change would not be required, but instead the Indexer Agent would need to fetch the information from wherever the Oracle is deployed.
### Update the Indexer Agent
The Indexer Agent currently fetches the `currentEpochBlock` from the `EpochManager` ([link](https://github.com/graphprotocol/indexer/blob/main/packages/indexer-agent/src/agent.ts#L179)), and finds the blockHash by fetching the block from an Ethereum client.
This will need to be updated to fetch the Epoch Block for the relevant network from the proposed `currentEpochBlock(string network)` function, which will depend on which network a subgraph is indexing.
> An alternative implementation could deploy the Oracle on a chain other than Ethereum Mainnet, in which case the Indexer Agent would need to fetch the information from wherever the Oracle is deployed.
### Track the block for allocation closure
Track the block hash for an allocation closure.
> This is optional: it would improve legibility for other ecosystem participants, and simplify arbitration, but it would also increase the cost of closing an allocation, which is currently a signficant pain point for indexers.
There is an open [Pull Request](https://github.com/graphprotocol/contracts/pull/506) which implements this, and also looks to introduce [recency checking for mainnet subgraphs](https://forum.thegraph.com/t/require-that-more-recent-pois-are-submitted-in-order-to-collect-indexing-rewards-multi-blockhain-pois/2500) - the latter functionality is not feasible for non-mainnet subgraphs.
### Update the Arbitration Charter
The Arbitration Charter will need to reflect the change in requirements for allocation closure - in order to be eligible for Indexing Rewards, an allocation must be closed with a POI for the block specified by the Epoch Block Oracle, for the relevant epoch:network.
To allow for cases where allocations are being closed when the epoch is being updated, the existing N-1 policy should remain in place.
### Update the Subgraph Oracle
The Subgraph Oracle will need to check with the Epoch Block Oracle if there is an Epoch Block available for the network a subgraph is indexing. If there is, then the subgraph will be eligible for indexing rewards.