-
Notifications
You must be signed in to change notification settings - Fork 212
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
don't reschedule auction's price notifier if we already have one #10615
Changes from all commits
3e19377
a12a965
ba64933
77f2260
ee6c0a1
bff3484
59406eb
e88aebe
466d3b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -118,6 +118,8 @@ export const makeOfferSpecShape = (bidBrand, collateralBrand) => { | |
export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { | ||
const makeScaledBidBook = prepareScaledBidBook(baggage); | ||
const makePriceBook = preparePriceBook(baggage); | ||
// Brands that have or are making active quoteNotifier Observers | ||
const observedBrands = new Set(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we explain why it's ok to have this be a heap Set? Both from a cardinality and upgradability pov. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Brian and I talked through this. If we decide to upgrade auctions, then the restart won't have an observer running, but it won't have reset any tracking variable we create, so it's important that the tracking go away on restart. I think the cardinality question is about the fact that we're sharing a Set across auctoinBooks. We don't have ephemeral per-object state, but we can have ephemeral shared state. The number of brands handled by the auction is small and seldom grows, so the set won't get too big. We're already keeping all the auctionBooks in a single vat. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I figured as much for the upgradability side, but I would prefer this to be written out as comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, we lost the race with the merge. Please let me know if you think it's worth a new PR to add the comment. I'm happy to do it if you ask. |
||
|
||
const AuctionBookStateShape = harden({ | ||
collateralBrand: M.any(), | ||
|
@@ -454,38 +456,59 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { | |
}); | ||
return state.bookDataKit.recorder.write(bookData); | ||
}, | ||
observeQuoteNotifier() { | ||
// Ensure that there is an observer monitoring the quoteNotifier. We | ||
// assume that all failure modes for quoteNotifier eventually lead to | ||
// fail or finish. | ||
Comment on lines
+459
to
+461
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I read this as "We rely on SOMETHING to see that all failure modes for quoteNotifier eventually lead to fail or finish"? What is the SOMETHING? If we don't rely on something else, we have to ensure it here, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The assumption is baked into the design of the notifiers and observers. There's no way to interrupt it from the consumer end, and no way to verify that it's still alive. Its contract says if it every ceases to continue producing new values it will either call |
||
ensureQuoteNotifierObserved() { | ||
const { state, facets } = this; | ||
const { collateralBrand, bidBrand, priceAuthority } = state; | ||
|
||
if (observedBrands.has(collateralBrand)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rest of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So there is never multiple bid brands for a given collateral? I'm just wary of assumptions like this that aren't super clear or documented in code comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of the bids are in IST, so there's no chance of that. With Vaults, we explicitly intended that we might want to have multiple vaultManagers for a given currency that could have different minimums, or other requirements, and charge different rates. With auctions, it's a mistake to split the bidding equity across multiple auctions, so the design was always that each auctionBook would handle all the bids and asks for a particular asset. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I think having a single bidding denom is the assumption that wasn't clear to me. |
||
return; | ||
} | ||
observedBrands.add(collateralBrand); | ||
trace('observing'); | ||
|
||
const quoteNotifier = E(priceAuthority).makeQuoteNotifier( | ||
const quoteNotifierP = E(priceAuthority).makeQuoteNotifier( | ||
AmountMath.make(collateralBrand, QUOTE_SCALE), | ||
bidBrand, | ||
); | ||
void observeNotifier(quoteNotifier, { | ||
updateState: quote => { | ||
trace( | ||
`BOOK notifier ${priceFrom(quote).numerator.value}/${ | ||
priceFrom(quote).denominator.value | ||
}`, | ||
); | ||
state.updatingOracleQuote = priceFrom(quote); | ||
}, | ||
fail: reason => { | ||
trace(`Failure from quoteNotifier (${reason}) setting to null`); | ||
// lack of quote will trigger restart | ||
state.updatingOracleQuote = null; | ||
}, | ||
finish: done => { | ||
trace( | ||
`quoteNotifier invoked finish(${done}). setting quote to null`, | ||
); | ||
// lack of quote will trigger restart | ||
|
||
void E.when( | ||
quoteNotifierP, | ||
quoteNotifier => | ||
observeNotifier(quoteNotifier, { | ||
updateState: quote => { | ||
trace( | ||
`BOOK notifier ${priceFrom(quote).numerator.value}/${ | ||
priceFrom(quote).denominator.value | ||
}`, | ||
); | ||
state.updatingOracleQuote = priceFrom(quote); | ||
}, | ||
fail: reason => { | ||
trace( | ||
`Failure from quoteNotifier (${reason}) setting to null`, | ||
); | ||
// lack of quote will trigger restart | ||
state.updatingOracleQuote = null; | ||
observedBrands.delete(collateralBrand); | ||
}, | ||
finish: done => { | ||
trace( | ||
`quoteNotifier invoked finish(${done}). setting quote to null`, | ||
); | ||
// lack of quote will trigger restart | ||
state.updatingOracleQuote = null; | ||
observedBrands.delete(collateralBrand); | ||
}, | ||
}), | ||
e => { | ||
trace('makeQuoteNotifier failed, resetting', e); | ||
state.updatingOracleQuote = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it necessary to reset the state here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's safe because we'll check for a notifier again at the next auction. It's a good idea because we can't count on the notifier continuing to produce events if there's an error at this level. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just don't quite understand in which case this isn't already There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We didn't explore it deeply, but I think if a previous version was happily updating the state, then it upgrades, then anything which samples the price at the beginning of the new incarnation (before any new updates happen) will get the old price. If an auction then starts and initiates the observer process, but fails (maybe because the price feed vat is getting upgraded too?), then we'll hit this case, clear the ephemeral flag (so the next auction can initiate a new one), and also clear the durable I suspect that's a bit weird. Not sure if it's a problem though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On line 469, we add the brand to the set, and on line 472, we attempt to create the notifier. On line 477, we await the notifier. If the creation promise fails, we end up here, and want to ensure the brand is removed from the set, or we'll never try again to create a notifier. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I have no problem with the ephemeral set being reset on error. My concern is with how the durable Brian mentions a case where this durable state may not be But beyond that question, the problem is that the IMO, we need to switch the call to Regarding the early bail out of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, yeah, if the auctionBook has a price (in durable I agree with @mhofman that the |
||
observedBrands.delete(collateralBrand); | ||
}, | ||
}); | ||
); | ||
|
||
void facets.helper.publishBookData(); | ||
}, | ||
|
@@ -645,8 +668,9 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { | |
|
||
trace(`capturing oracle price `, state.updatingOracleQuote); | ||
if (!state.updatingOracleQuote) { | ||
// if the price has feed has died, try restarting it. | ||
facets.helper.observeQuoteNotifier(); | ||
// if the price feed has died (or hasn't been started for this | ||
// incarnation), (re)start it. | ||
facets.helper.ensureQuoteNotifierObserved(); | ||
return; | ||
} | ||
|
||
|
@@ -750,7 +774,7 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { | |
const { collateralBrand, bidBrand, priceAuthority } = state; | ||
assertAllDefined({ collateralBrand, bidBrand, priceAuthority }); | ||
|
||
facets.helper.observeQuoteNotifier(); | ||
facets.helper.ensureQuoteNotifierObserved(); | ||
}, | ||
stateShape: AuctionBookStateShape, | ||
}, | ||
|
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'm struggling to get the relevant invariant in my head.
Something that combines
observedBrands
withstate.updatingOracleQuote
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.
state.updatingOracleQuote
has a value when we've received an update since the last auction.observedBrands
contains the brand if the priceNotifier observer has been started and hasn't returned an error or calledfinish
.In the normal case,
observedBrands
doesn't change, butstate.updatingOracleQuote
gets reset every auction.The interesting cases are startup and if the notifier ever breaks. The problem with startup is that it can take an arbitrary timespan for the oracles to start reporting prices after the auction starts. We want to start the observer, but we don't want to do it multiple time (as we were previously doing) because each creates a new observer that persists.