-
Notifications
You must be signed in to change notification settings - Fork 220
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
transaction vstorage #10633
transaction vstorage #10633
Conversation
Deploying agoric-sdk with Cloudflare Pages
|
01ab575
to
c6133c3
Compare
refs: #10633 ## Description Miscellaneous small fixes, docs and refactorings. These are made in the course of #10633 but that has other changes that need more work. Best reviewed by commit. ### Security Considerations none ### Scaling Considerations none ### Documentation Considerations none ### Testing Considerations CI ### Upgrade Considerations not yet deployed
c7552eb
to
5731f20
Compare
@@ -11,6 +11,7 @@ import type { PendingTxStatus } from './constants.js'; | |||
import type { FastUsdcTerms } from './fast-usdc.contract.js'; | |||
|
|||
export type EvmHash = `0x${string}`; | |||
export type EvmAddress = `0x${string & { length: 40 }}`; |
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.
neat: string & { length: 40 }
3607cf6
to
e81fdcd
Compare
e81fdcd
to
5351b38
Compare
5351b38
to
4223710
Compare
packages/internal/src/js-utils.js
Outdated
* @param {string} str | ||
* @returns unknown | ||
*/ | ||
export const parseWithBigint = str => |
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 don't like that this approach is vulnerable to collisions and "upgrades" any all-decimal-digits string to a bigint, even if it was user input that should remain a string. What's the reason for not using smallcaps?
const rejectOCap = cap => Fail`${cap} is not pure data`;
const { toCapData, fromCapData } =
makeMarshal(rejectOCap, rejectOCap, { serializeBodyFormat: "smallcaps" });
// Constraining to pure data guarantees empty slots and allows for direct use of
// `body`.
const serialize = value => {
const { body, slots } = toCapData(value);
slots.length === 0 || Fail`slots ${slots} unexpected for pure data`;
return body;
}
const unserialize = text => fromCapData({ body: text, slots: [] });
const value = harden({ number: 42, bigint: 42n, strings: ["42", "+42"] });
const serialized = serialize(value);
const restored = unserialize(serialized);
console.log({ value, serialized, restored });
/* =>
{
value: { number: 42, bigint: 42n, strings: [ '42', '+42' ] },
serialized: '#{"bigint":"+42","number":42,"strings":["42","!+42"]}',
restored: { bigint: 42n, number: 42, strings: [ '42', '+42' ] }
}
*/
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 it's a good idea worth exploring more. There are lots of trade-offs to consider so I'm opting to take it out of the critical path for FU by using the more verbose but well trodden "JSON-stringified CapData".
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.
For ref, I track this possible optimization in/around..
07e5c5a
to
33173f9
Compare
sequence: false, | ||
}); | ||
const storage = makeFakeStorageKit( | ||
'fun', // Fast USDC Node |
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.
Optional: Could we just name this fastUsdc? Is it intentionally different than the real node? Seeing "fun" in the test file was confusing at first.
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.
Is it intentionally different than the real node
It is intentional, to show that the contract root is configured by the core-eval and not the contract itself.
I do think that's worth keeping. I chose a three letter one to keep the paths short and wrap lines less.
packages/fast-usdc/test/supports.ts
Outdated
const storage = makeFakeStorageKit( | ||
'fun', // Fast USDC Node | ||
); | ||
storage.getPureData = 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.
Optional: It might help to document this a bit. I gather it can't have any remotables or bigints.
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 point, it should have a jsdoc.
remotables aren't pure data but bigints are. (bigint is the main motivation; otherwise JSON is a good encoding of pure data)
> The example below illustrates the schema of the data published there. | ||
> | ||
> See also board marshalling conventions (_to appear_). | ||
|
||
[ | ||
[ | ||
'published.fastUsdc.status.0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702', | ||
'ADVANCING', |
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.
Question: I'm a bit confused. Below we have:
t.deepEqual(
storage.getPureData(`fun.txns.${mockEvidence.txHash}`),
[mockEvidence, { status: PendingTxStatus.Advancing }],
'tx is Advancing',
);
I see the { status: PendingTxStatus.Advancing }
part here, but not the mockEvidence
. Wouldn't that be expected here?
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 this one is truncating the array to show the HEAD value. Do you want it to have the whole sequence? That's only per block and in practice the state changes would happen in different blocks but the fake vstorage treats everything as block height zero
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 appears from looking at status-manager.js
that the evidence is written in the same block as the status
agoric-sdk/packages/fast-usdc/src/exos/status-manager.js
Lines 161 to 162 in 33173f9
publishEvidence(txHash, evidence); | |
publishStatus(txHash, status); |
sequence: true
when creating the node ensures they're both included.
I guess this test is just looking at the last cell, but if it's important behavior that the evidence is included in the first cell, I think we should capture that in the test somehow. Why not include it in all cells like { evidence: {}, status: "" }
? Are we assuming the indexer will just pick up the evidence in the first cell and save it?
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 not include it in all cells like { evidence: {}, status: "" }?
Great idea. I'll do that.
Are we assuming the indexer will just pick up the evidence in the first cell and save it?
The indexer will accumulate events to update the db row, yeah.
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 was thinking of including it in all cells so we wouldn't have to track anything but the latest, it's kind of moot if we only include the evidence in the first cell, even though it's one less cell to track it's still more than one. However, it looks like documentStorageSchema doesn't support multiple cells. That probably doesn't have to be in scope, but a regular assert for the original cell with the evidence would suffice. I think it's important to verify that the evidence is published since the indexer relies on it.
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 was thinking of including it in all cells so we wouldn't have to track anything but the latest,
Oh, the evidence isn't stored in a way that it can be looked up for each update.
I think it's important to verify that the evidence is published since the indexer relies on it.
I think that's covered by the unit tests but I've added the assertion to the boot test too. Do you think the A3P tests also need it?
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.
Oh, the evidence isn't stored in a way that it can be looked up for each update.
Ah I see, yea not worth changing that part.
I think that's covered by the unit tests
I guess that makes sense. Unit tests for exhaustiveness, and a boot test just to make sure the vstorage write works for any path. It was confusing seeing the different results between the two though so thanks for updating.
Do you think the A3P tests also need it?
I'm not sure the scope of the a3p tests, seems like they're not super important for fast-usdc because they're on a single chain, but the multichain-testing tests might want to have test cases for the status updates. There weren't any such existing test cases there before this PR though so I don't think it's in scope.
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 a high level view.
I'd like to see a typed pattern for what a client might see at one of these vstorage keys.
@@ -161,14 +161,14 @@ Generated by [AVA](https://avajs.dev). | |||
|
|||
## makes usdc advance | |||
|
|||
> Under "published", the "fastUsdc.status" node is delegated to the statuses of fast USDC transfers identified by their tx hashes. | |||
> Under "published", the "fastUsdc.txns" node is delegated to the Ethereum transactions upon which Fast USDC is acting. |
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.
> Under "published", the "fastUsdc.txns" node is delegated to the Ethereum transactions upon which Fast USDC is acting. | |
> Under "published", the "fastUsdc.txns" node is delegated to the Ethereum transactions upon which Fast USDC is acting. |
I suppose txns
is an abbreviation for transactions
. Why not spell it out?
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.
- concision
- the evidence uses
txn
- to make clear this is an Ethereum-identified transaction
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.
ok, so txns
isn't an abbreviation of transactions; it's a plural of the evidence property name.
Re txn
distinguing between ethereum and others such as cosmos-sdk... I seem to have a gap in my education. If you have a moment to point me at what I missed, I'd appreciate it.
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.
Re txn distinguing between ethereum and others such as cosmos-sdk
I retract #3. I didn't think it through carefully.
txs
is the common Ethereum abbreviation for "transactions" and it's also used in Cosmos.
So I don't have a good reason to use txns
. I do prefer txs
to transactions
but could go either way if you have a principle for the latter.
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.
using conventional dictionary words for identifiers that have scopes larger than a few lines pays off, IME.
"txn" is a sufficiently established term-of-art that I find it acceptable.
sequence: true, // avoid overwriting other output in the block | ||
}); | ||
void E(txNode).setValue( | ||
JSON.stringify(pureDataMarshaller.toCapData(harden(record))), |
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.
harden(...)
doesn't hurt (much) here, but if it's already a CopyRecord
, it's already hardened.
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.
harden(...) doesn't hurt (much) here
The marshaller threw without it.
if it's already a CopyRecord, it's already hardened
Not according to the structural type of CopyRecord,
export type CopyRecord<T extends Passable = any> = Record<string, T>;
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 marshaller threw without it.
That's a a bug in the caller that claimed something was a CopyRecord
without harden
-ing it.
This is actually the cost of adding the harden(...)
here: it masks such bugs.
/** | ||
* Transactions seen *ever* by the contract. | ||
* | ||
* Note that these are stored in IAVL and grow monotonically. At some point in the future we may want to prune them. |
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 wonder what "monotonic" is intended to communicate. Monotonic is a relationship between 2 variables. Is the dependent variable here time? A constant function is non-decreasing and hence monotonic.
* Note that these are stored in IAVL and grow monotonically. At some point in the future we may want to prune them. | |
* Note that like all durable stores, this SetStore is stored in IAVL. It grows without bound (though the amount of growth per incoming message to the contract is bounded). At some point in the future we may want to prune it. |
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 dependent variable here time?
Yes, or blockheight, what have you.
A constant function is non-decreasing and hence monotonic.
Sure, it could stay constant if it's never used.
The suggestion is more clear, I'll take it.
node: `fastUsdc.status`, | ||
owner: `the statuses of fast USDC transfers identified by their tx hashes`, | ||
node: `fastUsdc.txns`, | ||
owner: `the Ethereum transactions upon which Fast USDC is acting`, |
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.
these vstorage snapshots are nice for looking at an example or two, but I wonder about how to make it easier to figure out all possible values.
How about including the name of a TypedPattern and the Type in the doc
?
Maybe documentStorageSchema
should support the pattern?
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.
maybe sometime but out of scope rn, imo
* fullValue: NatValue; | ||
* }} SettlerTransferCtx | ||
*/ | ||
onFulfilled(_result, ctx) { | ||
const { txHash, sender, fullValue } = ctx; | ||
statusManager.forwarded(txHash, sender, fullValue); | ||
const { txHash, nfa, fullValue } = ctx; |
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.
ctx
is part of the durable storage of the relevant vow, right? So this property name change would be a breaking change if this code were deployed, right?
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.
yes if it were deployed
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.
When we get to upgrade testing, I hope we manage to test upgrading with pending vows.
@@ -10,7 +10,7 @@ import type { CctpTxEvidence } from '../../src/types.js'; | |||
|
|||
type Common = Awaited<ReturnType<typeof commonSetup>>; | |||
type TestContext = { | |||
statusNode: ERef<StorageNode>; | |||
transactionsNode: ERef<StorageNode>; |
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.
odd that we spell it out internally but not externally.
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.
txs
is embedded in a string that's already very long and distinct so the motivation was there.
I agree it's probably better to make them match. I'd rather make this txsNode
than have published.fastUsdc.transactions.0x19330d10D9Cc8751218eaf51E8885D058642E08A
@@ -42,7 +40,54 @@ | |||
"typescript": "~5.7.2" | |||
}, | |||
"resolutions": { | |||
"axios": "1.6.7" | |||
"axios": "1.6.7", | |||
"@agoric/cosmos": "portal:../golang/cosmos", |
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.
Did you use tooling to add all these resolutions?
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.
agoric-sdk/multichain-testing/README.md
Line 24 in 1af5f1d
(Note that the '@agoric/*' deps will come from the parent directory due to `yarn link --relative ../../.. --all`) |
85c1b26
to
1af5f1d
Compare
bce780b
to
72cc751
Compare
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.
LGTM hoping CI passes
> The example below illustrates the schema of the data published there. | ||
> | ||
> See also board marshalling conventions (_to appear_). | ||
|
||
[ | ||
[ | ||
'published.fastUsdc.status.0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702', | ||
'ADVANCING', |
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.
Oh, the evidence isn't stored in a way that it can be looked up for each update.
Ah I see, yea not worth changing that part.
I think that's covered by the unit tests
I guess that makes sense. Unit tests for exhaustiveness, and a boot test just to make sure the vstorage write works for any path. It was confusing seeing the different results between the two though so thanks for updating.
Do you think the A3P tests also need it?
I'm not sure the scope of the a3p tests, seems like they're not super important for fast-usdc because they're on a single chain, but the multichain-testing tests might want to have test cases for the status updates. There weren't any such existing test cases there before this PR though so I don't think it's in scope.
627a9ea
to
998db91
Compare
`sender` and `address` are ambiguous in FUC
- no need to support legacy Ethereum txs that do not include chain id in the tx envelope
998db91
to
ccb9e28
Compare
refs: #10598
Description
The dashboard will requires data for each transaction. In the course of designing that we explored how to design the storage node paths without creating too much cost in IAVL. (The data in HEAD carry a burden of magnitude O(k+v) for all network nodes (including non-validators) into perpetuity.)
Examining the trade-offs we arrived at:
Security Considerations
none
Scaling Considerations
One storage path per transaction, which persists until the deletion job is called.
One entry in a set to keep track of transactions to delete.
Documentation Considerations
Not developer facing
Testing Considerations
CI
Upgrade Considerations
not yet deployed