2020 was a huge year for us at Chainlink, and 2021 is lined up to be even bigger. We’re getting so much done, and there’s so much more to be done.
Often, I struggle to take a step back and appreciate the cool things I work on before moving onto the next. This is because I absolutely love what I’m working on, and can’t wait for the next thing that comes up. But from experience, I know that this can lead to burnout. I’m not at the burnout stage, but I will be unless I take stock and recalibrate every so often.
To combat this, I will start writing about some of the cool stuff that I’ve personally worked on. Initially, I will be relaying things that we pushed late last year, how I contributed and why I think they’re cool. Once I’ve gotten through some of the recent backlogs, I’ll aim to write a regular overview of what I’ve worked on during each week/fortnight.
(Don’t expect to find any alpha here, everything I intend to write about is already open source!)
Part 1: Validating Chainlink Price Feeds for Staleness
Find out more about price feeds and how they’re aggregated on the Chainlink developer docs.
Consuming the price of something (like ETH) in a smart contract is very simple. A call to
latestRoundData() on a Chainlink feed will return the current price of the asset. This currently facilitates billions of dollars locked in DeFi on Ethereum. Considering this astronomic value, validating price data is important.
So, what can we validate against?
For each round, every node in the network aggregates the current price from multiple data providers. It publishes them on-chain, where each node’s result is then aggregated, confirmed in a “round” and made readable to consumer contracts.
Chainlink nodes have several threshold parameters which they monitor on a feed, any one of which can kickstart a new round of data.
RunLog was version 1 of price feed aggregation, this is version 2:
FluxAggregator. It will be deprecated once version 3 is rolled out.)
The primary parameter which kickstarts new rounds is price deviation. This is initiated when a node in the network notices that the aggregate price that they’ve calculated from multiple data providers deviates from the latest on-chain price by 0.5% or more.
The secondary parameter is time-based. If a price deviation exceeding the threshold hasn’t been noticed for x amount of time, but it’s been y minutes since the last round, kick off a new round of aggregation anyway.
In times of high volatility, the primary parameter will be setting off rounds like nobodies business. So it’s unlikely that the feed will have stale price data. However, in low volatility, data could be hours old. Any high TVL protocol would want to validate the price data to ensure that it isn’t stale.
Data is my friend
The function used to consume data from price feeds (
latestRoundData()) returns a timestamp representing the time the latest round was confirmed. This means that staleness validation can be performed on-chain.
StalenessFlaggingValidator , named for its ability to validate staleness, and raise a flag when it sees it (Wordy, I know. If you can think of a better name for it, please let me know).
This contract maintains a mapping called
s_thresholdSeconds which defines the number of seconds that need to have passed since an update for a feed to be considered “stale”. This mapping is configured and maintained by the owner.
// feed address => staleness threshold
mapping(address => uint256) private s_thresholdSeconds;
(FYI, we use the
s_ prefix for contract storage variables)
The contract also exposes functions:
check(address memory)— a view which returns an array of stale addresses, without changing the state.
update(address memory)— which raises a flag on-chain for each address that is stale, returning said addresses in an array.
Using these functions, any contract or off-chain reader can periodically check if the price of a feed they’re using is stale using the
check view function. Anytime staleness is detected, the
update function can be called to raise a flag for that feed.
Why is this cool? In theory, this can be used to notify the oracle network to kick off a new round, representing an on-chain external parameter to the already internal deviation and heartbeat parameters. Price feed consumers can contribute to the data quality of Chainlink node networks!
Those with a keen eye on the smart contract space will notice this shares a similar pattern with Andre Cronje’s Keeper3r Network. In December, Andre announced that Chainlink node operators “will eventually start to run a growing number of key keeper jobs, leading to a network where the most relied upon keepers come to meet the high standard for security, reliability and sybil-resistance provided by Chainlink”, and this validation pattern will likely play a part in the collaboration of Chainlink and Keep3r.
This validation is just the first step in improving on-chain validation for Chainlink price feeds.