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: Preserve smart wallets through bulldozer upgrade #7599

Merged
merged 23 commits into from
May 10, 2023

Conversation

gibson042
Copy link
Member

Fixes #7537

Description

Propagate chain storage data in which keys represent addresses into provisionPool, which exports a reviver facet for the wallet factory (that itself uses the wallet factory when called).

This is somewhat convoluted, but avoids making more Far objects in the bootstrap vat.

Security Considerations

I believe all new interfaces (and in particular the one for indicating an address as revivable) are closely held in the bootstrap vat.

Scaling Considerations

Revivable address are stored in a virtual collection, and I don't foresee any other scaling risks.

Documentation Considerations

This is a specific interpretation of exported chain storage data (every path is assumed to be a wallet address), but I don't know how to document it.

Testing Considerations

This is unit tested, but should also be extended to cover upgrade as realistically as possible. Is there a good starting point?

@gibson042 gibson042 requested a review from dckc May 4, 2023 01:36
@gibson042 gibson042 changed the title Gibson 7537 wallet migration fix: Preserve smart wallets through bulldozer upgrade May 4, 2023
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.

mostly looks good, but I don't think the result of startGovernedInstance will have a .walletReviver

golang/cosmos/x/vstorage/vstorage.go Show resolved Hide resolved
{path: "key1", want: `[["child1",null]]`},
{path: "key1", want: `[["child1"]]`},
Copy link
Member

Choose a reason for hiding this comment

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

Why this change? does null act too much like an empty string at some FFI boundary?

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 was requested in #7489 (comment) , and I went with null at the time but reconsidered when I got to this PR because it was unnecessary overhead.

packages/vats/src/core/startWalletFactory.js Outdated Show resolved Hide resolved
Comment on lines 240 to 156
walletReviver: ppFacets.walletReviver,
walletReviver: E(ppFacets.creatorFacet).getWalletReviver(),
Copy link
Member

Choose a reason for hiding this comment

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

nit: seems like more of a fixup than a fix; that is: this doesn't address a defect in master.

const oldAddresses = harden(chainStorageEntries.map(entry => entry[0]));
// Carry forward wallets with an address already in chain storage.
const dataReviver = makeHistoryReviver(chainStorageEntries);
const walletStoragePath = await E(walletStorageNode).getPath();
Copy link
Member

Choose a reason for hiding this comment

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

If someone chose to deploy the new contract with a different storage path, that won't change where the old data is. Hard-coding the published.wallet path seems appropriate.

Copy link
Member Author

Choose a reason for hiding this comment

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

Even though nothing else in this file has awareness of the "published" prefix? I was thinking that if the storage path ever changed, we'd introduce a vat parameter to communicate the old path.

Copy link
Member

Choose a reason for hiding this comment

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

In practice I suppose it doesn't matter.

@gibson042 gibson042 force-pushed the gibson-7537-wallet-migration branch from 892a53c to 5ca0186 Compare May 5, 2023 19:57
@gibson042
Copy link
Member Author

@dckc This should be ready for final review!

@gibson042 gibson042 requested a review from dckc May 8, 2023 14:26
@dckc dckc mentioned this pull request May 8, 2023
Comment on lines +9 to +23
/**
* A map corresponding with a total function such that `get(key)`
* is assumed to always succeed.
*
* @template K, V
* @typedef {{[k in Exclude<keyof Map<K, V>, 'get'>]: Map<K, V>[k]} & {get: (key: K) => V}} TotalMap
*/
/**
* @template T
* @typedef {T extends Map<infer K, infer V> ? TotalMap<K, V> : never} TotalMapFrom
*/
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 wrote these because I got sick of TypeScript complaining that the result of map.get(key) can be undefined in circumstances where we know it won't. They should probably migrate to internal or endo at some point.

Copy link
Member

Choose a reason for hiding this comment

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

👏

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.

the re-bootstrap test looks nifty. Here's hoping I can study it some more.

Meanwhile... the contract API change needs a BREAKING CHANGE indicator

makeHandler: M.call({
makeBridgeHandler: M.call({
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 breaking contract API change. It needs a suitable conventional commit message.

Copy link
Contributor

@ivanlei ivanlei May 9, 2023

Choose a reason for hiding this comment

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

I think at this point in the release, a breaking change isn't warranted. Please use the existing name and we can choose to change the name in a future issue.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted.

packages/vats/src/provisionPoolKit.js Outdated Show resolved Hide resolved
packages/vats/test/test-provisionPool.js Show resolved Hide resolved
packages/vats/src/provisionPoolKit.js Outdated Show resolved Hide resolved
Comment on lines 227 to 235
if (!(bankManager && namesByAddressAdmin && walletFactory)) {
if (!bankManager || !namesByAddressAdmin || !walletFactory) {
Copy link
Member

Choose a reason for hiding this comment

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

consider assertAllDefined

Hm... it doesn't have a place to put "must do X before Y"

@gibson042 gibson042 force-pushed the gibson-7537-wallet-migration branch from d0943f4 to 7dcba79 Compare May 9, 2023 21:32
@gibson042 gibson042 requested a review from dckc May 10, 2023 01:15
@gibson042 gibson042 force-pushed the gibson-7537-wallet-migration branch from 64b1120 to 9e78f54 Compare May 10, 2023 01:51
Comment on lines 179 to 181
addRevivableAddresses(oldAddresses) {
this.state.revivableAddresses.addAll(oldAddresses);
Copy link
Member

Choose a reason for hiding this comment

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

let's log oldAddresses.length here, please

Copy link
Member Author

Choose a reason for hiding this comment

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

Just console.log('revivableAddresses count', oldAddresses.length)?

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.

good stuff!

good to go once the ci checks pass

gibson042 added 2 commits May 9, 2023 23:29
[key] is smaller than [key, value] and arguably clearer,
and is already accepted by `set`.
@gibson042 gibson042 force-pushed the gibson-7537-wallet-migration branch from 18741fd to 51c3bf7 Compare May 10, 2023 03:39
gibson042 and others added 5 commits May 10, 2023 00:10
Fixes #7537
Propagate chain storage data in which keys represent addresses
into provisionPool, which exports a reviver facet for the wallet factory
(that itself *uses* the wallet factory when called).

This is somewhat convoluted, but avoids making more Far objects in the
bootstrap vat.
…n `sequence: true`

But for compatibility with preexisting expectations, `sequence` defaults to true.
gibson042 added 16 commits May 10, 2023 00:10
This will be needed for testing bulldozer scenarios
The wallet factory calls the provision pool wallet reviver reviveWallet
method when receiving a bridge message about a currently-unknown
address, and reviveWallet checks that the address is revivable but not
yet revived to either reject the request or to call back into the
factory's provideSmartWallet method with the correct bank and naming hub
(which the provision pool has but the wallet factory does not).
provideSmartWallet, whether called in this way or more directly, follows
wallet construction with a finisher that informs the provision pool
about the corresponding address, which it removes from the collection of
revivable addresses and then reports back whether or not it was present
(i.e., whether or not it recognizes the invocation as completing a
revival). provideSmartWallet consumes this result to report whether or
not the wallet is actually new.

This increases the count of round trips associated with both new wallet
provisioning (adding one from the wallet factory to the reviver) and
with handling messages about not-yet-known addresses (adding one from
the wallet factory to the reviver for what would previously have been a
more direct failure, and in the case of successful revival [which
previously did not exist at all] one more still from the reviver to the
factory). Since wallet creation is relatively rare and a single internal
round trip is not a particularly attractive vector for attacks whose own
initiation has a message cost, this seems acceptable.
The expected shape is a JSON serialization of
{ blockHeight: DecimalString, values: Array<string> }
in which each element of values can be unmarshalled as CapData.
Smallcaps is strict, so input that looks like a remotable must be
unmarshalled to a remotable rather than to a string/undefined/etc.
Instead, decode them to "board remotables" that remember the slot
ID (presumably a board ID) when we don't otherwise care.
@gibson042 gibson042 force-pushed the gibson-7537-wallet-migration branch from 51c3bf7 to 60c504f Compare May 10, 2023 04:12
@gibson042 gibson042 enabled auto-merge May 10, 2023 04:13
@gibson042 gibson042 added this pull request to the merge queue May 10, 2023
Comment on lines +371 to +372
// TODO: validate that `metrics.anchorPoolBalance.value` is
// pass-by-copy PureData (e.g., contains no remotables).
Copy link
Member

Choose a reason for hiding this comment

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

you can use Nat or M.nat(). PSM brands have to be fungible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

smart wallets for those who paid 10 BLD should be preserved
3 participants