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

Record instances that will be replaced so we can manage them #10680

Merged
merged 2 commits into from
Dec 13, 2024

Conversation

Chris-Hibbert
Copy link
Contributor

closes: #10669

Description

Record instances of contracts that will be replaced so we can find them later and be able to manage them.

Security Considerations

We've lost the handle to some vats before, and it's a hassle.

Scaling Considerations

The problem gets worse as we upgrade more vats.

Documentation Considerations

None.

Testing Considerations

Verified that Auctions and Committee are present. The priceFeeds vary too much across test environments to be worth checking.

Upgrade Considerations

Yes..

@Chris-Hibbert Chris-Hibbert added deployment Chain deployment mechanism (e.g. testnet) code-style defensive correctness patterns; readability thru consistency Inter-protocol Overarching Inter Protocol contract-upgrade next-release about next agoric-sdk or endo release labels Dec 11, 2024
@Chris-Hibbert Chris-Hibbert self-assigned this Dec 11, 2024
@Chris-Hibbert Chris-Hibbert requested a review from a team as a code owner December 11, 2024 19:17
Copy link

cloudflare-workers-and-pages bot commented Dec 11, 2024

Deploying agoric-sdk with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1581127
Status: ✅  Deploy successful!
Preview URL: https://2f7a3e79.agoric-sdk.pages.dev
Branch Preview URL: https://10669-saveinstances.agoric-sdk.pages.dev

View logs

@Chris-Hibbert Chris-Hibbert added the force:integration Force integration tests to run on PR label Dec 11, 2024
@Chris-Hibbert Chris-Hibbert force-pushed the 10669-saveInstances branch 2 times, most recently from 941288e to 64d75a1 Compare December 11, 2024 20:07
@toliaqat toliaqat requested review from warner and mhofman December 11, 2024 23:19
@Chris-Hibbert Chris-Hibbert requested review from mhofman and removed request for mhofman and AgoricTriage December 12, 2024 00:18
Copy link
Member

@mhofman mhofman left a comment

Choose a reason for hiding this comment

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

My first reaction is that the explicit naming per "upgrade action" feels error prone. Is there a reason we cannot make this retired instance collection a SetStore and just push into it? Maybe have the retiredContractInstances be a MapStore<string, SetStore<Instance>> ?

);
const econeconomicCommitteeOriginal = await economicCommitteeOriginalP;
contractInstanceMap.init('electorate-v24', econeconomicCommitteeOriginal);
retiredContractInstances.resolve(contractInstanceMap);
Copy link
Member

Choose a reason for hiding this comment

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

What's the frequency (or "per-ness", as Chip would say) of this resolution? Relatedly, how frequently does this replaceElectorate.js proposal get run? I don't see a provide pattern here, so I'm worried that this might work correctly during upgrade18, but the next time this proposal is run, we'll create a lame-duck constractInstanceMap, put something important in it, and then the retiredContractInstances.resolve() will be ignored because that key is already in the promise-space. That would lose a reference.

I don't know how to square a provide-pattern with the promise space. Maybe there should be a separate (effectively idempotent) function whose only job is to create contractInstanceMap and resolve it into the promise space. Then the electorate replacement function would only consume (and not produce), and we wouldn't run the risk of this ignored-duplicate-resolve problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whatever next time is, it'll have to reset() in order to re-assign the Map. This is brittle, and will need to be modified the next time it's used, but that isn't the failure mode.

Then the electorate replacement function would only consume (and not produce), and we wouldn't run the risk of this ignored-duplicate-resolve problem.

I'd be happier if I knew how to do that, but I couldn't think of a way. If that's more important than getting this in tonight, I'll talk to @dckc about how to do that tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dckc reminded me of the standard pattern for this. Each intended writer creates a MapStore and attempts to save it as retiredContractInstances. One of them will win, the others will be ignored. Each then consumes retiredContractInstances, and adds a value to it. They'll all consume the same thing, and they won't know or care whether they were the one that won the first race.

Copy link
Member

Choose a reason for hiding this comment

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

I'll defer to his advice, and I also don't know how to code it the way I suggested (it's in the "looks easy because I haven't tried" category :), but that pattern doesn't sound great to me. "The others will be ignored" might be reliable, but it relies upon non-obvious behavior of the promise-space resolve, which makes it less legible and an ongoing source of questions ("why read this back out of consume when you just put it in there on the previous line?" and "why doesn't this duplicate resolve throw?").

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"The others will be ignored" might be reliable, but it relies upon non-obvious behavior of the promise-space resolve, which makes it less legible and an ongoing source of questions ("why read this back out of consume when you just put it in there on the previous line?" and "why doesn't this duplicate resolve throw?").

I agree that it's less than obvious, but it's reliable.

If we ever change our mind about "silently ignore attempts to assign values that are already set", we'll have to change a large number of other things.

@Chris-Hibbert
Copy link
Contributor Author

My first reaction is that the explicit naming per "upgrade action" feels error prone. Is there a reason we cannot make this retired instance collection a SetStore and just push into it? Maybe have the retiredContractInstances be a MapStore<string, SetStore> ?

We definitely could do that, but it feels to me like that would make it hard to distinguish the various instances of any contract. I want them to have labels, though I admit I don't see a way to automate it that makes me happy.

retiredContractInstances.init(
// XXX tail of label needs to vary with upgrade. BundleId would be different
// from previous, but is not necessarily unique.
`priceFeed-${AGORIC_INSTANCE_NAME}-u18`,
Copy link
Member

Choose a reason for hiding this comment

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

Yeah the -u18 suffix is probably the best we can do, short of using a list and appending to it (which would make future proposals that need to reference a specific one harder to write and review). At least if someone re-uses this proposal and forgets to update the suffix, that .init() will throw and we should catch it during testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

boardID works better and can be automated.

// save the auctioneer instance so we can manage it later
const retiredContractInstances = await retiredContractInstancesP;
const legacyInstance = legacyKit.instance;
retiredContractInstances.init('auction-vat157', legacyInstance);
Copy link
Member

Choose a reason for hiding this comment

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

maybe add a note that this suffix needs to be changed each time this proposal is re-used

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dckc suggests using the boardId instead.I think that would work.

@Chris-Hibbert Chris-Hibbert requested review from dckc and turadg December 12, 2024 00:59
retiredInstanceWriter.resolve(contractInstanceMap);

// get the actual retiredContractInstances
const retiredInstancesWriter = await retiredInstancesP;
Copy link
Member

Choose a reason for hiding this comment

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

nit: having both retiredInstanceWriter and retiredInstancesWriter (it took me a few minutes to spot the extra s in the second one) is confusing. Could the second one simply be called retiredInstances?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, definitelly. I don't know how that happened. The latter is not even a writer.

// get the actual retiredContractInstances
const retiredInstances = await retiredInstancesP;
// Record the retired electorate vat so we can manage it later.
const econeconomicCommitteeOriginal = await economicCommitteeOriginalP;
Copy link
Member

Choose a reason for hiding this comment

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

nit: spelling

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

},
}) => {
trace('Start');
const retiredContractInstances = await retiredContractInstancesP;
Copy link
Member

Choose a reason for hiding this comment

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

we do this a lot. I wonder if it would be better not to destructure consume in the params so we could do,

  const { contractKits, retiredContractInstances } = deeplyFulfilled(consume);

totally optional

Copy link
Member

Choose a reason for hiding this comment

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

deeplyFulfilled(consume) sounds bad. At best, it would try to grab everything in the space. But I suspect the properties are not even enumerable.

Copy link
Member

Choose a reason for hiding this comment

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

oh right. we'd need something like await space(consume, ['retiredContractInstances')

packages/inter-protocol/src/proposals/add-auction.js Outdated Show resolved Hide resolved
Comment on lines 230 to 235
// if retiredContractInstances doesn't exist, create it.
const contractInstanceMap = makeScalarBigMapStore(
'retiredContractInstances',
{ durable: true },
);
retiredInstanceWriter.resolve(contractInstanceMap);
Copy link
Member

Choose a reason for hiding this comment

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

please extract a function that documents why it can be called in multiple places at any time.

it should also document the naming scheme: {agoricNames.instance key}-{boardID}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The most natural form is generic creation of a Map in promiseSpace, but this doesn't leave a place to document the usage of retiredContractInstances. Would you rather have a shared function called createRetiredContractInstancesMap, which could document the pattern?

Copy link
Member

Choose a reason for hiding this comment

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

f4c67ec is okay but consider something more direct: 515f772

@@ -5,6 +5,7 @@ import { evalBundles } from '@agoric/synthetic-chain';
const SUBMISSION_DIR = 'recorded-instances-submission';

test(`recorded instances in u18`, async t => {
await evalBundles(SUBMISSION_DIR);
const result = await evalBundles(SUBMISSION_DIR);
console.log('recorded retired instance result:', result);
Copy link
Member

Choose a reason for hiding this comment

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

I forgot to mention this and it's not worth a rev: if you tested the result you wouldn't need a t.pass. It would also help document what's expected

@Chris-Hibbert Chris-Hibbert added the automerge:rebase Automatically rebase updates, then merge label Dec 13, 2024
Comment on lines +28 to +30
// I don't know why it's neither in governedContractKits nor contractKits
// assert(await E(governedContractKits).get(auctionInstance));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I draw your attention to this inability, but I'm going to merge it anyway. The old auction instance has been stored, but I haven't been able to look it up in in governedContractKits or contractKits. the economicCommitttee instance is in contractKits.

Copy link
Member

Choose a reason for hiding this comment

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

kits typically make it into governedContractKits by way of startGovernedUpgradable, but startAuctioneer pre-dates that. It calls E(zoe).startInstance(contractGovernorInstallation, ...) directly.

@Chris-Hibbert Chris-Hibbert force-pushed the 10669-saveInstances branch 2 times, most recently from a1ad58f to 616fdfe Compare December 13, 2024 06:30
Copy link
Contributor

mergify bot commented Dec 13, 2024

This pull request has been removed from the queue for the following reason: checks failed.

The merge conditions cannot be satisfied due to failing checks:

You should look at the reason for the failure and decide if the pull request needs to be fixed or if you want to requeue it.

If you want to requeue this pull request, you need to post a comment with the text: @mergifyio requeue

Copy link
Member

@dckc dckc left a comment

Choose a reason for hiding this comment

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

to the robots: I'm done reviewing this; please take it off my queue.

@mergify mergify bot merged commit d35659b into master Dec 13, 2024
81 checks passed
@mergify mergify bot deleted the 10669-saveInstances branch December 13, 2024 16:44
mujahidkay pushed a commit that referenced this pull request Dec 13, 2024
closes: #10669

## Description

Record instances of contracts that will be replaced so we can find them later and be able to manage them.

### Security Considerations

We've lost the handle to some vats before, and it's a hassle.

### Scaling Considerations

The problem gets worse as we upgrade more vats.

### Documentation Considerations

None.

### Testing Considerations

Verified that Auctions and Committee are present. The priceFeeds vary too much across test environments to be worth checking.

### Upgrade Considerations

Yes..
mujahidkay added a commit that referenced this pull request Dec 13, 2024
### Description

Cherry-picks the following commits from master:
- #10672
(3b478fb)
- #10668
(a74161c)
- #10680 (c883c39,
1581127)
 
 No new upgrade name has been added. 
 
 Done partially via git cherry-pick and via the following rebase-todo:
 ```
# PR #10680 Branch
Record-instances-that-will-be-replaced-so-we-can-manage-them-10680-
label
base-Record-instances-that-will-be-replaced-so-we-can-manage-them-10680-
pick c883c39 feat: record instances that will be replaced so we can
manage them
pick 1581127 refactor: provideRetiredInstances
label
pr-10680--Record-instances-that-will-be-replaced-so-we-can-manage-them-10680-
reset
base-Record-instances-that-will-be-replaced-so-we-can-manage-them-10680-
merge -C d35659b
pr-10680--Record-instances-that-will-be-replaced-so-we-can-manage-them-10680-
# Record instances that will be replaced so we can manage them (#10680)
 ```
mergify bot pushed a commit that referenced this pull request Dec 14, 2024
refs: #10680

## Description

We misplaced the adminFacets that would allow us to manage Auctions after they've been replaced and were about to do it again. The auctions were [last upgraded](https://github.com/Agoric/agoric-sdk/blob/43345a561fbdf7621c369abb15e6839f7c696565/packages/inter-protocol/src/proposals/add-auction.js#L157) in `agoric-upgrade-16av`. That code fails to save the instance's adminFacet, and only stores the contractKit in bootstrap promise space under the name `auctioneerKit`, where it will be overwritten on upgrade. Our other contracts now save a copy of the `contractKit` in either `contractKits` or `governedContractKits`, indexed by the instance, so the facets will hang around. This saves the old auctioneer during upgrade so we can manage it later (upgrade, terminate, change parameters).

### Security Considerations

Losing our last handle for vats is a problem.

### Scaling Considerations

We're upgrading vats to deal with scaling.

### Documentation Considerations

None.

### Testing Considerations

there was a test in #10680 which looked for this kit in `governedContractKits`, but I commented it out when it didn't succeed. It succeeds now.

### Upgrade Considerations

Yes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:rebase Automatically rebase updates, then merge code-style defensive correctness patterns; readability thru consistency contract-upgrade deployment Chain deployment mechanism (e.g. testnet) force:integration Force integration tests to run on PR Inter-protocol Overarching Inter Protocol next-release about next agoric-sdk or endo release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Save some facets to enable admin of replaced contracts and vats
5 participants