这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@wrwg
Copy link
Contributor

@wrwg wrwg commented Nov 11, 2025

Description

This adds a new function to the annotator which directly extracts table information from a value.

Also fixes an issue in type substitution, ensuring that sharing of struct info is preserved over substitution.

This remembers whether a FatType contains table, and only if so recurses into MoveValue to find the table infos. It avoids the construction of annotated value altogether.

Consider vector<T>: before this change, indexer would visit any value in the vector scanning for table handles even if its known that T doesn't has tables.

The information whether tables are in a type is computed during FatType construction and shared between cached structs, as well as cached in the annotator itself.


Note

Introduces a fast path to extract table info without full annotation, tracks/caches whether types contain tables, fixes generic struct substitution, and updates the indexer to use the new API.

  • Move Resource Viewer:
    • Adds collect_table_info(ty_tag, blob, infos) to extract table info without constructing AnnotatedMoveValue.
    • Caches whether a TypeTag/FatType contains tables and skips traversal when not needed.
    • Improves generic type substitution via a struct substitutor to preserve sharing/caching.
  • Fat Types:
    • Extends FatStructType with contains_tables and is_table(); propagates through subst and layouts.
    • Adds StructName and updates substitution APIs to accept a substitutor closure.
  • Core Types:
    • Adds TABLE_MODULE_ID and TABLE_STRUCT_NAME constants.
  • Indexer (storage/indexer/src/db_v2.rs):
    • Replaces recursive value parsing with calls to collect_table_info.
    • Adds helpers: collect_table_info_from_* and process_table_infos to persist TableInfo.
    • Removes old parse_move_value-based table discovery and related checks.

Written by Cursor Bugbot for commit 416e680. This will update automatically on new commits. Configure here.

Copy link
Contributor Author

wrwg commented Nov 11, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on December 7

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@wrwg wrwg requested a review from vgao1996 November 13, 2025 05:21
// Everything else cannot harbor tables.
(_, _) => Ok(()),
}
}
Copy link

Choose a reason for hiding this comment

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

Bug: Closures silently ignore captured data.

The collect_table_info_from_value function doesn't handle closures with captured values. When a MoveValue::Closure is encountered with FatType::Function, it falls through to the catch-all case and returns Ok(()) without recursively checking the captured values for tables. The old code explicitly iterated through closure_value.captured to find tables, but this logic is missing in the new implementation, causing tables in closure-captured values to be silently ignored.

Fix in Cursor Fix in Web

Copy link
Contributor

Choose a reason for hiding this comment

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

Cursor has a great point about closures (also discussed in 1:1). Say we capture a table handle in a closure, how do we process it? we need type info from that function, in theory, viewer has access to modules, can we just based on function's name get the module + type signature, based on mask get types of the function, then recurse to check if contains_tables?

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, cursor bug bot pretty good.

I thought about this, and the result is we did not do it before and we don't need to do now. The entire table scanning business is kind of optional.

AnnotedMoveValue also did not contain any info about tables for captured arguments. That is since info about captured args is build from the layout which does not contain struct names. The question we discussed in 1:1 is whether this is only on the top-level or transitively, but it is the later.

We could start looking up function info to also scan captured values, but that seems beyond this perf related PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair

@wrwg wrwg marked this pull request as draft November 13, 2025 07:56
@wrwg wrwg force-pushed the wrwg/table-check branch 13 times, most recently from 73c3223 to 9dde4fb Compare November 14, 2025 01:08
@wrwg wrwg marked this pull request as ready for review November 14, 2025 01:34
@wrwg wrwg requested a review from georgemitenkov November 14, 2025 01:35
@wrwg wrwg added the CICD:run-e2e-tests when this label is present github actions will run all land-blocking e2e tests from the PR label Nov 14, 2025
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

AnnotatedMoveValue::Bytes(_) => {},
fn process_table_infos(&mut self, infos: Vec<(StructTag, MoveStruct)>) -> Result<()> {
for (tag, val) in infos {
if tag.type_args.len() == 2
Copy link
Contributor

Choose a reason for hiding this comment

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

should we assert that this is a table tag? The infos carry no table info so it might be better to enforce the invariant on how they are populated here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems overkill to me. The job of the collect table info stuff is to collect table types only. We cannot check all such invariants for perf reasons.

for (tag, val) in infos {
if tag.type_args.len() == 2
&& let MoveStruct::Runtime(vals) = &val
&& let Some(MoveValue::Address(handle)) = vals.first()
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need value in the first place? seems like we use types only

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 handle is used in save_table_info, so we need it.

let layout = (&fat_ty).try_into().map_err(into_vm_status)?;
let move_value = MoveValue::simple_deserialize(blob, &layout)?;
let mut limit = Limiter::default();
self.collect_table_info_from_value(&fat_ty, move_value, &mut limit, infos)
Copy link
Contributor

Choose a reason for hiding this comment

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

asked in indexer file as well, but why pass structs out?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seemed to me cleaner than just a pair of types, though I agree it has some overhead. But in the current flamegraph, youu don't see the annotator at all anymore, so I guess its OK.

Ok(())
}
},
(FatType::Runtime(tys), MoveValue::Struct(MoveStruct::Runtime(vals))) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this case? we fall into struct/struct above, and then if table, add, if contains, recurse, if not -> return early, this seems unreachable even?

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 FatType::Runtime can be theoretically constructed to represent a struct in a captured argument. It is not captured above, though I'm not sure it can appear in reality here. But for completeness of covering variants here better to include.

This adds a new function to the annotator whether a given type is or transitively contains tables. Based on that, the code in `db_v2.rs` avoids creating an annotated value if its known to contain no tables.

Consider `vector<T>`, before this change, indexer would visit any value in the vector scanning for table handles even if its known that `T` doesn't has tables.

The information whether tables are in a type is computed during FatType construction and shared between cached structs, as well as cached in the annotator itself.

One step further then this PR goes, would be move table handle scanning into the annotator and avoid to construct annotated values in the first place, but this here might already have significant effects.
let move_value = MoveValue::simple_deserialize(blob, &layout)?;
let mut limit = Limiter::default();
self.collect_table_info_from_value(&fat_ty, move_value, &mut limit, infos)
}
Copy link

Choose a reason for hiding this comment

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

Bug: Split Limiter Bypasses Resource Control

The collect_table_info function creates two separate Limiter::default() instances at lines 695 and 700, resetting resource tracking between type resolution and value collection. This allows the function to potentially exceed intended resource limits since each limiter starts fresh instead of accumulating charges across both operations.

Fix in Cursor Fix in Web

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

✅ Forge suite compat success on 7685f835f97083580c626fd78a05f91f177302c1 ==> 416e68041273850ea34bc5dd02d6dcf6f6ceb315

Forge report malformed: Expecting property name enclosed in double quotes: line 20 column 1 (char 542)
'{\n  "metrics": [\n    {\n      "test_name": "compatibility::simple-validator-upgrade::liveness-check",\n      "metric": "submitted_txn",\n      "value": 429940.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::liveness-check",\n      "metric": "expired_txn",\n      "value": 0.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::liveness-check",\n      "metric": "avg_tps",\n      "value": 13085.098520910018\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::liveness-check",\n[2025-11-14T18:55:52Z INFO  aptos_forge::report] Test Ok\n      "metric": "avg_latency",\n      "value": 2639.546462297065\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::liveness-check",\n      "metric": "p50_latency",\n      "value": 2700.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::liveness-check",\n      "metric": "p90_latency",\n      "value": 3300.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::liveness-check",\n      "metric": "p99_latency",\n      "value": 3400.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::single-validator-upgrade",\n      "metric": "submitted_txn",\n      "value": 180060.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::single-validator-upgrade",\n      "metric": "expired_txn",\n      "value": 0.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::single-validator-upgrade",\n      "metric": "avg_tps",\n      "value": 5133.421899769676\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::single-validator-upgrade",\n      "metric": "avg_latency",\n      "value": 6679.804143063423\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::single-validator-upgrade",\n      "metric": "p50_latency",\n      "value": 7400.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::single-validator-upgrade",\n      "metric": "p90_latency",\n      "value": 7600.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::single-validator-upgrade",\n      "metric": "p99_latency",\n      "value": 7600.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::half-validator-upgrade",\n      "metric": "submitted_txn",\n      "value": 177340.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::half-validator-upgrade",\n      "metric": "expired_txn",\n      "value": 0.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::half-validator-upgrade",\n      "metric": "avg_tps",\n      "value": 4995.350109166902\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::half-validator-upgrade",\n      "metric": "avg_latency",\n      "value": 6816.781825871208\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::half-validator-upgrade",\n      "metric": "p50_latency",\n      "value": 7600.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::half-validator-upgrade",\n      "metric": "p90_latency",\n      "value": 7700.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::half-validator-upgrade",\n      "metric": "p99_latency",\n      "value": 7800.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::rest-validator-upgrade",\n      "metric": "submitted_txn",\n      "value": 271460.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::rest-validator-upgrade",\n      "metric": "expired_txn",\n      "value": 0.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::rest-validator-upgrade",\n      "metric": "avg_tps",\n      "value": 8165.9530702159045\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::rest-validator-upgrade",\n      "metric": "avg_latency",\n      "value": 4156.868205260444\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::rest-validator-upgrade",\n      "metric": "p50_latency",\n      "value": 4500.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::rest-validator-upgrade",\n      "metric": "p90_latency",\n      "value": 4800.0\n    },\n    {\n      "test_name": "compatibility::simple-validator-upgrade::rest-validator-upgrade",\n      "metric": "p99_latency",\n      "value": 5000.0\n    }\n  ],\n  "text": "Compatibility test results for 7685f835f97083580c626fd78a05f91f177302c1 ==> 416e68041273850ea34bc5dd02d6dcf6f6ceb315 (PR)\\n1. Check liveness of validators at old version: 7685f835f97083580c626fd78a05f91f177302c1\\ncompatibility::simple-validator-upgrade::liveness-check : committed: 13085.10 txn/s, latency: 2639.55 ms, (p50: 2700 ms, p70: 3000, p90: 3300 ms, p99: 3400 ms), latency samples: 429940\\n2. Upgrading first Validator to new version: 416e68041273850ea34bc5dd02d6dcf6f6ceb315\\ncompatibility::simple-validator-upgrade::single-validator-upgrade : committed: 5133.42 txn/s, latency: 6679.80 ms, (p50: 7400 ms, p70: 7500, p90: 7600 ms, p99: 7600 ms), latency samples: 180060\\n3. Upgrading rest of first batch to new version: 416e68041273850ea34bc5dd02d6dcf6f6ceb315\\ncompatibility::simple-validator-upgrade::half-validator-upgrade : committed: 4995.35 txn/s, latency: 6816.78 ms, (p50: 7600 ms, p70: 7700, p90: 7700 ms, p99: 7800 ms), latency samples: 177340\\n4. upgrading second batch to new version: 416e68041273850ea34bc5dd02d6dcf6f6ceb315\\ncompatibility::simple-validator-upgrade::rest-validator-upgrade : committed: 8165.95 txn/s, latency: 4156.87 ms, (p50: 4500 ms, p70: 4600, p90: 4800 ms, p99: 5000 ms), latency samples: 271460\\n5. check swarm health\\nCompatibility test for 7685f835f97083580c626fd78a05f91f177302c1 ==> 416e68041273850ea34bc5dd02d6dcf6f6ceb315 passed\\nTest Ok"\n}'
Trailing Log Lines:
2. Upgrading first Validator to new version: 416e68041273850ea34bc5dd02d6dcf6f6ceb315
compatibility::simple-validator-upgrade::single-validator-upgrade : committed: 5133.42 txn/s, latency: 6679.80 ms, (p50: 7400 ms, p70: 7500, p90: 7600 ms, p99: 7600 ms), latency samples: 180060
3. Upgrading rest of first batch to new version: 416e68041273850ea34bc5dd02d6dcf6f6ceb315
compatibility::simple-validator-upgrade::half-validator-upgrade : committed: 4995.35 txn/s, latency: 6816.78 ms, (p50: 7600 ms, p70: 7700, p90: 7700 ms, p99: 7800 ms), latency samples: 177340
4. upgrading second batch to new version: 416e68041273850ea34bc5dd02d6dcf6f6ceb315
compatibility::simple-validator-upgrade::rest-validator-upgrade : committed: 8165.95 txn/s, latency: 4156.87 ms, (p50: 4500 ms, p70: 4600, p90: 4800 ms, p99: 5000 ms), latency samples: 271460
5. check swarm health
Compatibility test for 7685f835f97083580c626fd78a05f91f177302c1 ==> 416e68041273850ea34bc5dd02d6dcf6f6ceb315 passed
Test Ok

=== BEGIN JUNIT ===
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="forge" tests="1" failures="0" errors="0" uuid="6529124e-9565-4c32-9245-78c60664b628">
    <testsuite name="compat" tests="1" disabled="0" errors="0" failures="0">
        <testcase name="compatibility::simple-validator-upgrade">
        </testcase>
    </testsuite>
</testsuites>
=== END JUNIT ===
[2025-11-14T18:55:52Z INFO  aptos_forge::backend::k8s::cluster_helper] Deleting namespace forge-compat-pr-18088: Some(NamespaceStatus { conditions: None, phase: Some("Terminating") })
[2025-11-14T18:55:52Z INFO  aptos_forge::backend::k8s::cluster_helper] aptos-node resources for Forge removed in namespace: forge-compat-pr-18088

test result: ok. 1 passed; 0 soft failed; 0 hard failed; 0 filtered out

Debugging output:
NAME                                         READY   STATUS      RESTARTS   AGE
aptos-node-0-validator-0                     1/1     Running     0          4m10s
aptos-node-1-validator-0                     1/1     Running     0          5m38s
aptos-node-2-validator-0                     1/1     Running     0          2m24s
aptos-node-3-validator-0                     1/1     Running     0          95s
forge-testnet-deployer-6rmrp                 0/1     Completed   0          9m43s
genesis-aptos-genesis-eforge5c8e6d65-tsp68   0/1     Completed   0          9m7s

@github-actions
Copy link
Contributor

✅ Forge suite realistic_env_max_load success on 416e68041273850ea34bc5dd02d6dcf6f6ceb315

two traffics test: inner traffic : committed: 13585.14 txn/s, submitted: 13585.30 txn/s, expired: 0.16 txn/s, latency: 2773.39 ms, (p50: 2700 ms, p70: 2900, p90: 3000 ms, p99: 3600 ms), latency samples: 5060640
two traffics test : committed: 100.01 txn/s, latency: 733.68 ms, (p50: 700 ms, p70: 800, p90: 900 ms, p99: 1100 ms), latency samples: 1860
Latency breakdown for phase 0: ["MempoolToBlockCreation: max: 2.244, avg: 2.142", "ConsensusProposalToOrdered: max: 0.169, avg: 0.167", "ConsensusOrderedToCommit: max: 0.083, avg: 0.072", "ConsensusProposalToCommit: max: 0.248, avg: 0.239"]
Max non-epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.92s no progress at version 20864 (avg 0.07s) [limit 15].
Max epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.44s no progress at version 5774154 (avg 0.34s) [limit 16].
Test Ok

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD:run-e2e-tests when this label is present github actions will run all land-blocking e2e tests from the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants