-
Notifications
You must be signed in to change notification settings - Fork 225
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
Conversation
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.
mostly looks good, but I don't think the result of startGovernedInstance
will have a .walletReviver
{path: "key1", want: `[["child1",null]]`}, | ||
{path: "key1", want: `[["child1"]]`}, |
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 this change? does null
act too much like an empty string at some FFI boundary?
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.
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.
walletReviver: ppFacets.walletReviver, | ||
walletReviver: E(ppFacets.creatorFacet).getWalletReviver(), |
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.
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(); |
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.
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.
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.
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.
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.
In practice I suppose it doesn't matter.
892a53c
to
5ca0186
Compare
@dckc This should be ready for final review! |
/** | ||
* 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 | ||
*/ |
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 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.
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.
👏
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.
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({ |
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.
This is a breaking contract API change. It needs a suitable conventional commit message.
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 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.
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.
Reverted.
if (!(bankManager && namesByAddressAdmin && walletFactory)) { | ||
if (!bankManager || !namesByAddressAdmin || !walletFactory) { |
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.
consider assertAllDefined
Hm... it doesn't have a place to put "must do X before Y"
d0943f4
to
7dcba79
Compare
64b1120
to
9e78f54
Compare
addRevivableAddresses(oldAddresses) { | ||
this.state.revivableAddresses.addAll(oldAddresses); |
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.
let's log oldAddresses.length
here, please
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.
Just console.log('revivableAddresses count', oldAddresses.length)
?
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.
good stuff!
good to go once the ci checks pass
[key] is smaller than [key, value] and arguably clearer, and is already accepted by `set`.
18741fd
to
51c3bf7
Compare
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.
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.
51c3bf7
to
60c504f
Compare
// TODO: validate that `metrics.anchorPoolBalance.value` is | ||
// pass-by-copy PureData (e.g., contains no remotables). |
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.
you can use Nat
or M.nat()
. PSM brands have to be fungible.
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?