-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add API client & function to calculate max gas price #4
Conversation
Looks great so far. Do you think we would be calling the gas price APIs through the same interface we call the APIs (i.e. external adapter) in the future? Or would these calls go directly to axios as they do now? |
I'm not sure. Would that be a single endpoint that the serverless function calls instead of 4-5 APIs directly? Would that be an endpoint we hosted? Calling these APIs directly is nice because one or more could be down and we would still get an answer. If there was a single endpoint, that could be better down and we would have problems. Calling them directly also seems better for transparency. |
No I mean if CoinAPI was hosting this node, the node would be calling the CoinAPI API along with these gas price APIs. I'm asking if we would have two classes of calls (the API call and gas price calls) or would they all be treated the same as much as possible? |
I'm not really sure yet. I would need to see what the other class of call would look like. I don't really see a problem with having different ways of calling APIs at the moment. They will all share a common interface (in I updated the way it calls each API to remove a lot of the duplication. You simply need to declare each API at the top with a URL and |
These are also only the gas prices for mainnet. We'll need to think about what to do for testnets like ropsten |
Okay. I agree that it's early, I was mostly thinking out loud.
Yeah that's pretty elegant.
I like Chainlink's approach of not giving a fuck and using mainnet gas prices because
If it comes to that, we can always use our on-chain feed to override gas prices, both on mainnet and Ropsten. |
It also needs a fallback gas price for when none of the sources return anything useful. I think that should be 40 gwei nowadays. There's also a max gas price of 1000 gwei in the Chainlink version. It used to be 50 gwei (?), but then the gas price actually exceeded that and that wrecked all nodes. 1000 gwei is better than nothing because a gas price API can return a very high price and cause the node wallet to be drained instantly. On the other hand, we don't want to set something that we may want to change later. In that sense, 1000 gwei doesn't sound that bad. Again, the Chainlink version adds 1 gwei to the gas price but that's pretty useless at the current prices. The extrapolation I added is an adaptive solution, but it requires the gas price history, so we can't use it here. So we can forget about it for now. In the future, I'm hoping that we're going to be able to send these kind of configuration variables to the node over the chain. You make a transaction on ChainAPI, that emits a configuration event (variables are encoded in CBOR), the node reads the most recent configuration event at the start of each loop to set these. |
Thanks. With the current setup, it will get mainnet gas prices from the 4 APIs and the data feed from ropsten and then take the maximum across the successful responses. I'll just leave it at that until it becomes a problem. I quite like the idea of the node being able to configure itself based on onchain events. We can definitely work towards that eventually.
|
I'm okay with all of those. I think gas price API configuration is quite doable, though it's probably not needed for now. We can configure that through events too, but updating that by redeploying the node (just as you would update integration specs) sounds more sensible to me. |
Alright cool. I'll just make a note about API configuration so we don't forget |
I think this is ready to go but waiting for #5 to be merged first |
return process.env.ETHEREUM_NETWORK || 'ropsten'; | ||
} | ||
|
||
export function getProvider() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use this pattern here?
https://github.com/clc-group/oracle-monitor/blob/master/src/eth.ts#L5-L9
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to avoid having any singleton global variables in the node. It looks like they have weird behaviour:
https://medium.com/tensult/aws-lambda-function-issues-with-global-variables-eb5785d4b876
I've been thinking about how best to manage state. I've been thinking of possibly using a simple state object that would store things we don't want to keep initializing repeatedly or passing around individually.
Something like this that would get created on each function call. Each property then gets set once and the state gets passed around to functions lower down:
const state = {
provider: null,
gasPrice: null,
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they are also using that weird behavior as a shitty way to persist state between function invocations
https://engineering.flosports.tv/persistent-data-in-lambda-it-works-ca0c1b25879e
1. Change all gas prices to be denominated in Wei instead of Gwei 2. Add a global state object intended to reduce calls to the Ethereum node and re-use objects 3. Add utils for Wei/Gwei conversions
// Do this upfront to reduce potential extra calls later on | ||
const [err, network] = await go(provider.getNetwork()); | ||
if (err) { | ||
// TODO: not sure how exactly to handle errors here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that we could use two providers simultaneously (both to detect requests and make transactions to fulfill them), but Airnode depends on the Ethereum node's version of truth and if we try to use two Ethereum nodes with different versions of truth, we may end up with weird problems. So in a single sleep-wake cycle, the node should decide to use a provider (e.g. the one with the higher blockNumber) and stick with it. Then, it can retry provider calls until the serverless function times out (in 60 seconds total), as we can't really skip any of the provider calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But yes, I think all provider calls need a wrapper that handles what provider to use
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think that sounds like a good idea. I was wondering about how we could control the function time out, but I see serverless provides that for us. Yeah I'll need to implement the ability to retry provider calls in a separate PR
In the interests of starting small and keeping PRs easy to review, here's a small change that will get gas prices from 2 APIs and calculate the maximum "fastest" gas price of these.
Added
getMaxGasPrice
function that makes multiple requests and then gets the maximum gas price from all of the successful responses. Failed responses are discarded.Currently this will get gas prices from:
a. Etherchain
b. Eth Gas Station
c. (disabled) Honeycomb Gas Price Feed
I see the Honeycomb Gas Price Feed on Ropsten has not been updated in around 2 months so I've left that out for now, but the parts are there to enable it again.
TODO
I'll add tests if you're happy with what I've got so far