Skip to content

Commit c2d1b0b

Browse files
vgao1996claude
andcommitted
[mono-move] Add aptos-vm-v2 transaction layer
Reimplements the AptosVM transaction-execution layer on the MonoMove VM: prologue -> entry-function payload -> epilogue -> write-set publication, producing a VMOutput, executed single-threaded. One MonoMove session with checkpoints replaces the legacy VM's staged sessions and change-set squashing: a failed payload rolls the heap and read-write set back to the post-prologue state, and writes (including resource-group merge-back) are drained exactly once at the end. Scope is deliberately minimal: only the versioned validation path, entry functions only, placeholder gas. Scripts, multisig, publishing, keyless/AA, orderless, IO/storage fees, and Block-STM integration are inline TODOs; write-set publication is a stopgap behind the runtime's new drain API so the real publication workstream can replace it. Differential tests against the legacy VM on FakeExecutor genesis: a p2p transfer (byte-equal writes except the two fee-embedding slots), an insufficient-balance abort (rollback + failure epilogue, matching abort codes), and a two-transaction sequential block through the VMBlockExecutor wrapper. Runtime additions: ResourceReadWriteSet::entries() drain API and InterpreterContext accessors (read_write_set, reset_gas, gas_balance, load_function) so a driver can run several functions in one transaction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 75222b2 commit c2d1b0b

20 files changed

Lines changed: 2252 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ members = [
210210
"third_party/move/documentation/framework-book/builder",
211211
"third_party/move/extensions/move-table-extension",
212212
"third_party/move/mono-move/alloc",
213+
"third_party/move/mono-move/aptos-vm-v2",
213214
"third_party/move/mono-move/core",
214215
"third_party/move/mono-move/global-context",
215216
"third_party/move/mono-move/loader",
@@ -901,6 +902,7 @@ aptos-position-natives = { path = "aptos-move/framework/position-natives" }
901902
aptos-table-natives = { path = "aptos-move/framework/table-natives" }
902903
legacy-move-compiler = { path = "third_party/move/move-compiler-v2/legacy-move-compiler" }
903904
mono-move-alloc = { path = "third_party/move/mono-move/alloc" }
905+
mono-move-aptos-vm-v2 = { path = "third_party/move/mono-move/aptos-vm-v2" }
904906
mono-move-core = { path = "third_party/move/mono-move/core" }
905907
mono-move-global-context = { path = "third_party/move/mono-move/global-context" }
906908
mono-move-loader = { path = "third_party/move/mono-move/loader" }
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# aptos-vm-v2
2+
3+
Reimplementation of the AptosVM transaction-execution layer on the MonoMove VM.
4+
Executes real Aptos user transactions single-threaded: prologue → payload →
5+
epilogue → write-set publication, producing a `VMOutput`.
6+
7+
Proof-of-concept scope: entry functions only. Scripts, multisig, module
8+
publishing, keyless/account-abstraction auth, orderless transactions, mempool
9+
validation, and view functions are `TODO(completeness)` placeholders. Gas is a
10+
placeholder budget (MonoMove's uncalibrated block costs against the
11+
transaction's gas limit); IO/storage fees and refunds are `TODO(metering)`.
12+
13+
## Interface
14+
15+
- In: `SignedTransaction` (signature verification is the caller's job) + a
16+
`StateView`. `TODO(completeness)`: take Block-STM's per-transaction view
17+
traits (`ExecutorView`/`ResourceGroupView`) instead of raw `StateView`.
18+
- Out: `(VMStatus, VMOutput)` — the same contract the legacy AptosVM produces.
19+
- `AptosVMv2BlockExecutor` implements `VMBlockExecutor` with a sequential loop
20+
and owns the `GlobalContext` (code cache) across blocks.
21+
22+
## Modules
23+
24+
| Module | Purpose |
25+
|---|---|
26+
| `lib.rs` | `AptosVMv2` entry points and the transaction lifecycle driver |
27+
| `providers.rs` | `StateView` → MonoMove `ModuleProvider`/`ResourceProvider`; BCS→flat materialization, resource-group split, table items |
28+
| `session.rs` | Native registry, parameter classification, argument placement, running one function in the shared interpreter context |
29+
| `metadata.rs` | The slice of transaction metadata the prologue needs |
30+
| `validation.rs` | Versioned prologue/epilogue calls into `0x1::transaction_validation` |
31+
| `gas.rs` | Placeholder gas policy |
32+
| `publish.rs` | Minimal write-set drain: CoW'd entry = write, group merge-back, deterministic ordering |
33+
| `convert.rs` | Event finalization and `VMChangeSet`/`VMOutput` assembly |
34+
| `errors.rs` | MonoMove outcomes → `VMStatus`, prologue aborts → discard statuses |
35+
| `types.rs` | `TypeTag``InternedType` conversions |
36+
| `block_executor.rs` | Sequential `VMBlockExecutor` impl over an output-overlay view |
37+
38+
## Key design decisions
39+
40+
- **One session, checkpoints.** No per-stage change sets, no respawned
41+
sessions, no view overlays: prologue, payload, and epilogue run in one
42+
interpreter context, a failed payload rolls the heap and read-write set back
43+
to the post-prologue checkpoint, and writes are drained exactly once at the
44+
end.
45+
- **Only the current feature path.** The versioned prologue/epilogue is the
46+
only validation path; legacy variants and old gas versions are intentionally
47+
not ported.
48+
- **Gas amounts diverge from the legacy VM** by design until MonoMove's
49+
schedule is calibrated; differential tests compare outputs with only the
50+
fee-embedding slots masked.
51+
- `publish.rs` is a stopgap consumer of the runtime's read-write-set drain
52+
API; the real publication (modification detection, storage metadata,
53+
refunds) is a separate workstream that replaces it from inside the runtime.
54+
55+
## Testing
56+
57+
```bash
58+
cargo test -p mono-move-aptos-vm-v2
59+
```
60+
61+
`tests/e2e.rs` builds genesis state with `FakeExecutor` (dev-dependency), runs
62+
the same transaction on the legacy VM and this crate, and compares status,
63+
write sets, and events, masking only the gas-fee slots. Cases: a successful
64+
p2p transfer, and an insufficient-balance abort (payload rollback + failure
65+
epilogue).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# CLAUDE.md
2+
3+
See [AGENTS.md](AGENTS.md) for crate documentation, architecture, and commands.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[package]
2+
name = "mono-move-aptos-vm-v2"
3+
description = "The AptosVM transaction-execution layer reimplemented on the MonoMove VM."
4+
version = "0.1.0"
5+
6+
# Workspace inherited keys
7+
authors = { workspace = true }
8+
edition = { workspace = true }
9+
homepage = { workspace = true }
10+
license = { workspace = true }
11+
publish = { workspace = true }
12+
repository = { workspace = true }
13+
rust-version = { workspace = true }
14+
15+
[dependencies]
16+
anyhow = { workspace = true }
17+
aptos-block-executor = { workspace = true }
18+
aptos-framework = { workspace = true }
19+
aptos-types = { workspace = true }
20+
aptos-vm = { workspace = true }
21+
aptos-vm-environment = { workspace = true }
22+
aptos-vm-types = { workspace = true }
23+
bcs = { workspace = true }
24+
bytes = { workspace = true }
25+
mono-move-core = { workspace = true }
26+
mono-move-global-context = { workspace = true }
27+
mono-move-loader = { workspace = true }
28+
mono-move-natives = { workspace = true }
29+
mono-move-runtime = { workspace = true }
30+
move-binary-format = { workspace = true }
31+
move-bytecode-verifier = { workspace = true }
32+
move-core-types = { workspace = true }
33+
serde = { workspace = true }
34+
35+
[dev-dependencies]
36+
aptos-cached-packages = { workspace = true }
37+
aptos-language-e2e-tests = { workspace = true }
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright (c) Aptos Foundation
2+
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE
3+
4+
//! Sequential block execution: runs each user transaction in order against an
5+
//! overlay of the outputs before it.
6+
//
7+
// TODO(completeness): Block-STM integration (parallel execution) is a separate
8+
// workstream; this wrapper is the single-threaded stand-in.
9+
10+
use crate::AptosVMv2;
11+
use aptos_block_executor::txn_provider::TxnProvider;
12+
use aptos_types::{
13+
block_executor::{
14+
config::BlockExecutorConfigFromOnchain,
15+
transaction_slice_metadata::TransactionSliceMetadata,
16+
},
17+
state_store::{
18+
state_key::StateKey, state_storage_usage::StateStorageUsage, state_value::StateValue,
19+
StateView, StateViewResult, TStateView,
20+
},
21+
transaction::{
22+
signature_verified_transaction::SignatureVerifiedTransaction, AuxiliaryInfo, BlockOutput,
23+
Transaction, TransactionOutput, TransactionStatus,
24+
},
25+
write_set::TransactionWrite,
26+
};
27+
use aptos_vm::VMBlockExecutor;
28+
use aptos_vm_types::output::VMOutput;
29+
use move_core_types::vm_status::{StatusCode, VMStatus};
30+
use std::collections::HashMap;
31+
32+
/// `VMBlockExecutor` implementation backed by MonoMove, executing sequentially.
33+
pub struct AptosVMv2BlockExecutor {
34+
vm: AptosVMv2,
35+
}
36+
37+
impl VMBlockExecutor for AptosVMv2BlockExecutor {
38+
fn new() -> Self {
39+
Self {
40+
vm: AptosVMv2::new(),
41+
}
42+
}
43+
44+
fn execute_block(
45+
&self,
46+
txn_provider: &aptos_block_executor::txn_provider::default::DefaultTxnProvider<
47+
SignatureVerifiedTransaction,
48+
AuxiliaryInfo,
49+
>,
50+
state_view: &(impl StateView + Sync),
51+
_onchain_config: BlockExecutorConfigFromOnchain,
52+
_transaction_slice_metadata: TransactionSliceMetadata,
53+
) -> Result<BlockOutput<SignatureVerifiedTransaction, TransactionOutput>, VMStatus> {
54+
let mut overlay = OverlayView {
55+
base: state_view,
56+
overlay: HashMap::new(),
57+
};
58+
let mut outputs = Vec::with_capacity(txn_provider.num_txns());
59+
for txn in txn_provider.get_txns() {
60+
let output = match txn.expect_valid() {
61+
Transaction::UserTransaction(signed_txn) => {
62+
let (_status, output) = self.vm.execute_user_transaction(&overlay, signed_txn);
63+
output
64+
},
65+
// TODO(completeness): block metadata, epilogue, state
66+
// checkpoint, and validator transactions.
67+
_ => VMOutput::empty_with_status(TransactionStatus::Discard(
68+
StatusCode::FEATURE_UNDER_GATING,
69+
)),
70+
};
71+
let output = output
72+
.try_materialize_into_transaction_output(&overlay)
73+
.map_err(|e| {
74+
VMStatus::error(
75+
StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
76+
Some(format!("materialization failed: {e:?}")),
77+
)
78+
})?;
79+
for (key, op) in output.write_set().write_op_iter() {
80+
overlay.overlay.insert(key.clone(), op.as_state_value());
81+
}
82+
outputs.push(output);
83+
}
84+
Ok(BlockOutput::new(outputs, None))
85+
}
86+
}
87+
88+
/// A state view layering the block's prior outputs over the base view.
89+
struct OverlayView<'a, S> {
90+
base: &'a S,
91+
overlay: HashMap<StateKey, Option<StateValue>>,
92+
}
93+
94+
impl<S: StateView> TStateView for OverlayView<'_, S> {
95+
type Key = StateKey;
96+
97+
fn get_state_value(&self, state_key: &StateKey) -> StateViewResult<Option<StateValue>> {
98+
match self.overlay.get(state_key) {
99+
Some(value) => Ok(value.clone()),
100+
None => self.base.get_state_value(state_key),
101+
}
102+
}
103+
104+
fn get_usage(&self) -> StateViewResult<StateStorageUsage> {
105+
// TODO(correctness): account for the overlay's items and bytes.
106+
self.base.get_usage()
107+
}
108+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Aptos Foundation
2+
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE
3+
4+
//! Assembles the transaction's `VMOutput`: events out of the event store,
5+
//! writes out of publication, and the final status.
6+
7+
use crate::types::type_tag_of;
8+
use anyhow::{anyhow, Result};
9+
use aptos_types::{
10+
contract_event::ContractEvent, event::EventKey, fee_statement::FeeStatement,
11+
state_store::state_key::StateKey, transaction::TransactionStatus,
12+
};
13+
use aptos_vm_types::{
14+
abstract_write_op::AbstractResourceWriteOp, change_set::VMChangeSet,
15+
module_write_set::ModuleWriteSet, output::VMOutput,
16+
};
17+
use mono_move_core::native::NativeExtensions;
18+
use mono_move_global_context::ExecutionGuard;
19+
use mono_move_natives::{EventKind, EventStore};
20+
use mono_move_runtime::serialize;
21+
use std::collections::BTreeMap;
22+
23+
/// Serializes the events recorded in the event store, in emission order.
24+
///
25+
/// Must run while the interpreter's heap is alive: each entry's value is
26+
/// serialized straight out of VM memory.
27+
pub(crate) fn finalize_events(
28+
extensions: &NativeExtensions,
29+
guard: &ExecutionGuard<'_>,
30+
) -> Result<Vec<ContractEvent>> {
31+
let store = extensions
32+
.get_mut::<EventStore>()
33+
.map_err(|e| anyhow!("event store unavailable: {e:?}"))?;
34+
store
35+
.entries()
36+
.iter()
37+
.map(|entry| {
38+
let type_tag = type_tag_of(entry.msg_ty)?;
39+
// SAFETY: the heap is live for the duration of output assembly.
40+
let blob = unsafe { serialize(guard, entry.msg_data.as_ptr(), entry.msg_ty) }
41+
.map_err(|e| anyhow!("event value failed to serialize: {e}"))?;
42+
Ok(match &entry.kind {
43+
EventKind::V2 => ContractEvent::new_v2(type_tag, blob)?,
44+
EventKind::V1 {
45+
guid,
46+
sequence_number,
47+
} => {
48+
let key: EventKey = bcs::from_bytes(guid)?;
49+
ContractEvent::new_v1(key, *sequence_number, type_tag, blob)?
50+
},
51+
})
52+
})
53+
.collect()
54+
}
55+
56+
/// Builds the final `VMOutput` from published writes, events, and status.
57+
pub(crate) fn into_vm_output(
58+
writes: BTreeMap<StateKey, AbstractResourceWriteOp>,
59+
events: Vec<ContractEvent>,
60+
fee_statement: FeeStatement,
61+
status: TransactionStatus,
62+
) -> VMOutput {
63+
let change_set = VMChangeSet::new(
64+
writes,
65+
events.into_iter().map(|event| (event, None)).collect(),
66+
BTreeMap::new(),
67+
BTreeMap::new(),
68+
BTreeMap::new(),
69+
);
70+
// TODO(completeness): module publishing writes.
71+
VMOutput::new(change_set, ModuleWriteSet::empty(), fee_statement, status)
72+
}

0 commit comments

Comments
 (0)