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

transaction vstorage #10633

Merged
merged 22 commits into from
Dec 17, 2024
Merged

transaction vstorage #10633

merged 22 commits into from
Dec 17, 2024

Conversation

turadg
Copy link
Member

@turadg turadg commented Dec 6, 2024

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:

  • store one node per transaction
    • push all changes through it
    • rely on indexer to demux
  • delete on demand
    • when we have an API for block-safe deletion it can delete in the operation
    • until then keep track of what to delete
    • a core-eval can be run to cull data whenever necessary

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

Copy link

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

Deploying agoric-sdk with  Cloudflare Pages  Cloudflare Pages

Latest commit: ccb9e28
Status: ✅  Deploy successful!
Preview URL: https://3d3a51d6.agoric-sdk.pages.dev
Branch Preview URL: https://10578-transaction-vstorage.agoric-sdk.pages.dev

View logs

@turadg turadg force-pushed the 10578-transaction-vstorage branch from 01ab575 to c6133c3 Compare December 7, 2024 01:39
@turadg turadg changed the title ansaction vstorage transaction vstorage Dec 7, 2024
mergify bot added a commit that referenced this pull request Dec 10, 2024
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
@turadg turadg force-pushed the 10578-transaction-vstorage branch 3 times, most recently from c7552eb to 5731f20 Compare December 13, 2024 02:20
@@ -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 }}`;
Copy link
Member

Choose a reason for hiding this comment

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

neat: string & { length: 40 }

@turadg turadg force-pushed the 10578-transaction-vstorage branch from 3607cf6 to e81fdcd Compare December 14, 2024 01:19
@turadg turadg force-pushed the 10578-transaction-vstorage branch from e81fdcd to 5351b38 Compare December 14, 2024 01:46
@turadg turadg marked this pull request as ready for review December 14, 2024 01:47
@turadg turadg requested a review from a team as a code owner December 14, 2024 01:47
@turadg turadg force-pushed the 10578-transaction-vstorage branch from 5351b38 to 4223710 Compare December 14, 2024 18:29
* @param {string} str
* @returns unknown
*/
export const parseWithBigint = str =>
Copy link
Member

@gibson042 gibson042 Dec 14, 2024

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' ] }
}
*/

Copy link
Member Author

@turadg turadg Dec 16, 2024

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".

Copy link
Member

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..

@turadg turadg force-pushed the 10578-transaction-vstorage branch from 07e5c5a to 33173f9 Compare December 16, 2024 20:44
@turadg turadg requested review from gibson042 and dckc December 16, 2024 20:45
@turadg turadg added the force:integration Force integration tests to run on PR label Dec 16, 2024
sequence: false,
});
const storage = makeFakeStorageKit(
'fun', // Fast USDC Node
Copy link
Contributor

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.

Copy link
Member Author

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.

const storage = makeFakeStorageKit(
'fun', // Fast USDC Node
);
storage.getPureData = path =>
Copy link
Contributor

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.

Copy link
Member Author

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',
Copy link
Contributor

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?

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 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

Copy link
Contributor

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

publishEvidence(txHash, evidence);
publishStatus(txHash, status);
. I think the 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?

Copy link
Member Author

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.

Copy link
Contributor

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.

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 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?

Copy link
Contributor

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.

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.

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.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
> 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?

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. concision
  2. the evidence uses txn
  3. to make clear this is an Ethereum-identified transaction

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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))),
Copy link
Member

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.

Copy link
Member Author

@turadg turadg Dec 16, 2024

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>;

Copy link
Member

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.

packages/fast-usdc/src/exos/status-manager.js Outdated Show resolved Hide resolved
/**
* 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.
Copy link
Member

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.

Suggested change
* 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.

Copy link
Member Author

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`,
Copy link
Member

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?

Copy link
Member Author

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;
Copy link
Member

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?

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 if it were deployed

Copy link
Member

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>;
Copy link
Member

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.

Copy link
Member Author

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",
Copy link
Member

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?

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 '@agoric/*' deps will come from the parent directory due to `yarn link --relative ../../.. --all`)

@turadg turadg force-pushed the 10578-transaction-vstorage branch from 85c1b26 to 1af5f1d Compare December 16, 2024 23:02
@turadg turadg force-pushed the 10578-transaction-vstorage branch from bce780b to 72cc751 Compare December 17, 2024 02:04
@turadg turadg requested a review from samsiegart December 17, 2024 02:27
Copy link
Contributor

@samsiegart samsiegart left a 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',
Copy link
Contributor

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.

@turadg turadg force-pushed the 10578-transaction-vstorage branch from 627a9ea to 998db91 Compare December 17, 2024 03:55
@turadg turadg added the automerge:rebase Automatically rebase updates, then merge label Dec 17, 2024
@turadg turadg force-pushed the 10578-transaction-vstorage branch from 998db91 to ccb9e28 Compare December 17, 2024 13:55
@mergify mergify bot merged commit a2813f6 into master Dec 17, 2024
81 checks passed
@mergify mergify bot deleted the 10578-transaction-vstorage branch December 17, 2024 14:47
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 force:integration Force integration tests to run on PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants