Skip to content
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

fix(inter-protocol): ensure PSM availability after staging failure #6102

Merged
merged 7 commits into from
Sep 2, 2022

Conversation

dckc
Copy link
Member

@dckc dckc commented Sep 1, 2022

closes: #6096

Description

When an attempt to trade IST for anchor/reference token fails due to lack of anchor funds in the PSM, seat stagings were left in an inconsistent state, preventing further service. This fix clears the seat stagings on failure.

@erights @Chris-Hibbert this also includes a couple zoe tweaks that @dtribble helped me with.

Security Considerations

availability is good :)

Testing Considerations

added a reasonably thorough unit test

stage.clear();
anchorPool.clear();
feePool.clear();
// NOTE someday, reallocate should guarantee that this case cannot happen
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ticket? we probably will want to reference it often and it can serve to teach why and when to clear.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno. That comment pre-dates this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Since it's written in this contract twice and relevant to other contracts, I'm requesting a ticket to document the footgun and that it be referenced from the places like this that say "someday…"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that only one of the comments is valid (the one before the mintGains). The assert got copied to provide a better diagnostic, but the comment needs to be deleted.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added #6116 and cited it. I don't understand that issue very well, so I'd appreciate it if someone else would clarify the title / description. @michaelfig helped me a little.

Comment on lines +205 to +207
stage.clear();
anchorPool.clear();
feePool.clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we codify this pattern now with a contract helper? something like,

atomicReallocate(zcf, [
  [seat, anchorPool, { In: given }, { Anchor: given }],
  [stage, seat, { Stable: afterFee }, { Out: afterFee }],
  [stage, feePool, { Stable: fee }],
]);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it needs an onFail: clause to invoke burnLosses

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

postponed to #6116

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Progress on #6116 at #6577 , where stageMgr.commit() replaces zcf.reallocate(...) and does guarantee that all staging are cleared, whether the commit succeeded or failed.

@dtribble 's suggested stageMgr.transfer operation is much like @turadg 's rows of atomicReallocate above. The contrast between the list and the reified stageMgr is approx the contrast between #6577 and #4327

/**
* @param {Amount<'nat'>} giveAnchor
* @param {Amount<'nat'>} [wantStable]
* @param {*} [out]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay for typing. this param would benefit from more docs:

```suggestion
     * @param {{seat: UserSeat, offerResult: *}} [out] will be filled

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I find the need for this suspicious. This function is returning payouts to abstract away seat and offerResult… but here we are needing to punch through. Will contract users need to also? If so we should just change the return type to hide less: {seat, offerResult, payouts}.

Or would it be sufficient to have {numWantsSatisfied, payouts}?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was too lazy to change the other call sites. Returning just the seat might be most straightforward.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored the driver to have additional methods that expose the seat.

Comment on lines 419 to 421
let ix = 0;
for (const [kind, give, want, result] of trades) {
switch (kind) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this data driven test calls for Ava macros. See #5900

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the items have to be used within one test. The whole point is to not make a new PSM between them. Do macros work that way?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually things that need be shared between tests are configured with before and beforeEach. but I won't block on this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it needs to be shared between these trades but no other tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then another file would work well. this test function is doing a lot of work to replicate some of the before and macro semantics.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's punt on this for today.

@@ -335,7 +337,7 @@ export const createSeatManager = (
);
return amountKeywordRecord;
},
clear: ignoreContext(clear),
clear: ({ self }) => clear(self),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just parroting the linter here: gotta remove the ignoreContext import

Copy link
Member

@turadg turadg Sep 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though, why delegate here at all? we can delete the const clear above.

Suggested change
clear: ({ self }) => clear(self),
clear: ({ self }) => {
if (zcfSeatToStagedAllocations.has(self)) {
zcfSeatToStagedAllocations.delete(self);
}
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think MarkM hinted at the right choice here. Please inline the method, so it'll be more apparent that the parameter handling is parallel to the sibling methods.

@@ -114,7 +114,9 @@ export const createSeatManager = (
};

const clear = zcfSeat => {
zcfSeatToStagedAllocations.delete(zcfSeat);
if (zcfSeatToStagedAllocations.has(zcfSeat)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a change to the throw behavior of clear. was the earlier behavior documented or tested?

I'll go google it since our API docs aren't on the APIs ;-) https://docs.agoric.com/zoe/api/zoe-contract-facet.html#zcfseat-clear Nope.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only are there no tests for clear, there appear to be no tests for zcfSeat.

[not suggesting this PR add anything.]

Copy link
Contributor

@Chris-Hibbert Chris-Hibbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's more discussion going on, so I'm going to submit my current comments and wait to see how it settles before reviewing again.

Comment on lines 334 to 337
['give', 200, 190, false],
['want', 101, 100, 1],
['give', 50, 50, false],
['give', 51, 50, 1],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though I've been working with JS for a couple years now, seeing false and 1 as two possible values of a flag strikes me as wrong.

@@ -160,11 +160,24 @@ export const start = async (zcf, privateArgs, baggage) => {
AmountMath.isGTE(maxAnchor, wanted),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the TODO on line 158 interact with the current problem at all? Can it be updated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we certainly have relevant tests; I'm getting rid of that TODO in favor of #6116

Comment on lines +205 to +207
stage.clear();
anchorPool.clear();
feePool.clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it needs an onFail: clause to invoke burnLosses

@@ -335,7 +337,7 @@ export const createSeatManager = (
);
return amountKeywordRecord;
},
clear: ignoreContext(clear),
clear: ({ self }) => clear(self),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think MarkM hinted at the right choice here. Please inline the method, so it'll be more apparent that the parameter handling is parallel to the sibling methods.

Copy link
Member

@turadg turadg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM given #6116 (which I've added to PSM audit release)

@dckc dckc added automerge:no-update (expert!) Automatically merge without updates bypass:integration Prevent integration tests from running on PR labels Sep 2, 2022
@dckc dckc changed the title fix(inter-protocol): ensure PSM availability after failure fix(inter-protocol): ensure PSM availability after staging failure Sep 2, 2022
E(publicFacet).makeWantStableInvitation(),
harden({
give: { In: giveAnchor },
...(wantStable ? { want: { Out: wantStable } } : {}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. It's a definite improvement on my approach of making the proposal, and then adding the want later.

Comment on lines +345 to +348
['give', 200, 190, false],
['want', 101, 100, true, 1],
['give', 50, 50, false],
['give', 51, 50, true, 1],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get what this is doing now, but only because of side conversations about ava macros. It would be nice if it said that the first line was intended to fail because the PSM doesn't have any anchor until after a want completes. Does the third fail because it doesn't include fees?

@mergify mergify bot merged commit f338d2e into master Sep 2, 2022
@mergify mergify bot deleted the 6096-psm-clear-seats branch September 2, 2022 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:no-update (expert!) Automatically merge without updates bypass:integration Prevent integration tests from running on PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

failed PSM trade prevents further service due to inconsistent seat staging
5 participants