Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ members = [
"third_party/move/documentation/framework-book/builder",
"third_party/move/extensions/move-table-extension",
"third_party/move/mono-move/alloc",
"third_party/move/mono-move/aptos-vm-v2",
"third_party/move/mono-move/core",
"third_party/move/mono-move/global-context",
"third_party/move/mono-move/loader",
Expand Down Expand Up @@ -901,6 +902,7 @@ aptos-position-natives = { path = "aptos-move/framework/position-natives" }
aptos-table-natives = { path = "aptos-move/framework/table-natives" }
legacy-move-compiler = { path = "third_party/move/move-compiler-v2/legacy-move-compiler" }
mono-move-alloc = { path = "third_party/move/mono-move/alloc" }
mono-move-aptos-vm-v2 = { path = "third_party/move/mono-move/aptos-vm-v2" }
mono-move-core = { path = "third_party/move/mono-move/core" }
mono-move-global-context = { path = "third_party/move/mono-move/global-context" }
mono-move-loader = { path = "third_party/move/mono-move/loader" }
Expand Down
65 changes: 65 additions & 0 deletions third_party/move/mono-move/aptos-vm-v2/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# aptos-vm-v2

Reimplementation of the AptosVM transaction-execution layer on the MonoMove VM.
Executes real Aptos user transactions single-threaded: prologue → payload →
epilogue → write-set publication, producing a `VMOutput`.

Proof-of-concept scope: entry functions only. Scripts, multisig, module
publishing, keyless/account-abstraction auth, orderless transactions, mempool
validation, and view functions are `TODO(completeness)` placeholders. Gas is a
placeholder budget (MonoMove's uncalibrated block costs against the
transaction's gas limit); IO/storage fees and refunds are `TODO(metering)`.

## Interface

- In: `SignedTransaction` (signature verification is the caller's job) + a
`StateView`. `TODO(completeness)`: take Block-STM's per-transaction view
traits (`ExecutorView`/`ResourceGroupView`) instead of raw `StateView`.
- Out: `(VMStatus, VMOutput)` — the same contract the legacy AptosVM produces.
- `AptosVMv2BlockExecutor` implements `VMBlockExecutor` with a sequential loop
and owns the `GlobalContext` (code cache) across blocks.

## Modules

| Module | Purpose |
|---|---|
| `lib.rs` | `AptosVMv2` entry points and the transaction lifecycle driver |
| `providers.rs` | `StateView` → MonoMove `ModuleProvider`/`ResourceProvider`; BCS→flat materialization, resource-group split, table items |
| `session.rs` | Native registry, parameter classification, argument placement, running one function in the shared interpreter context |
| `metadata.rs` | The slice of transaction metadata the prologue needs |
| `validation.rs` | Versioned prologue/epilogue calls into `0x1::transaction_validation` |
| `gas.rs` | Placeholder gas policy |
| `publish.rs` | Minimal write-set drain: CoW'd entry = write, group merge-back, deterministic ordering |
| `convert.rs` | Event finalization and `VMChangeSet`/`VMOutput` assembly |
| `errors.rs` | MonoMove outcomes → `VMStatus`, prologue aborts → discard statuses |
| `types.rs` | `TypeTag` ↔ `InternedType` conversions |
| `block_executor.rs` | Sequential `VMBlockExecutor` impl over an output-overlay view |

## Key design decisions

- **One session, checkpoints.** No per-stage change sets, no respawned
sessions, no view overlays: prologue, payload, and epilogue run in one
interpreter context, a failed payload rolls the heap and read-write set back
to the post-prologue checkpoint, and writes are drained exactly once at the
end.
- **Only the current feature path.** The versioned prologue/epilogue is the
only validation path; legacy variants and old gas versions are intentionally
not ported.
- **Gas amounts diverge from the legacy VM** by design until MonoMove's
schedule is calibrated; differential tests compare outputs with only the
fee-embedding slots masked.
- `publish.rs` is a stopgap consumer of the runtime's read-write-set drain
API; the real publication (modification detection, storage metadata,
refunds) is a separate workstream that replaces it from inside the runtime.

## Testing

```bash
cargo test -p mono-move-aptos-vm-v2
```

`tests/e2e.rs` builds genesis state with `FakeExecutor` (dev-dependency), runs
the same transaction on the legacy VM and this crate, and compares status,
write sets, and events, masking only the gas-fee slots. Cases: a successful
p2p transfer, and an insufficient-balance abort (payload rollback + failure
epilogue).
3 changes: 3 additions & 0 deletions third_party/move/mono-move/aptos-vm-v2/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# CLAUDE.md

See [AGENTS.md](AGENTS.md) for crate documentation, architecture, and commands.
37 changes: 37 additions & 0 deletions third_party/move/mono-move/aptos-vm-v2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "mono-move-aptos-vm-v2"
description = "The AptosVM transaction-execution layer reimplemented on the MonoMove VM."
version = "0.1.0"

# Workspace inherited keys
authors = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
publish = { workspace = true }
repository = { workspace = true }
rust-version = { workspace = true }

[dependencies]
anyhow = { workspace = true }
aptos-block-executor = { workspace = true }
aptos-framework = { workspace = true }
aptos-types = { workspace = true }
aptos-vm = { workspace = true }
aptos-vm-environment = { workspace = true }
aptos-vm-types = { workspace = true }
bcs = { workspace = true }
bytes = { workspace = true }
mono-move-core = { workspace = true }
mono-move-global-context = { workspace = true }
mono-move-loader = { workspace = true }
mono-move-natives = { workspace = true }
mono-move-runtime = { workspace = true }
move-binary-format = { workspace = true }
move-bytecode-verifier = { workspace = true }
move-core-types = { workspace = true }
serde = { workspace = true }

[dev-dependencies]
aptos-cached-packages = { workspace = true }
aptos-language-e2e-tests = { workspace = true }
108 changes: 108 additions & 0 deletions third_party/move/mono-move/aptos-vm-v2/src/block_executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) Aptos Foundation
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE

//! Sequential block execution: runs each user transaction in order against an
//! overlay of the outputs before it.
//
// TODO(completeness): Block-STM integration (parallel execution) is a separate
// workstream; this wrapper is the single-threaded stand-in.

use crate::AptosVMv2;
use aptos_block_executor::txn_provider::TxnProvider;
use aptos_types::{
block_executor::{
config::BlockExecutorConfigFromOnchain,
transaction_slice_metadata::TransactionSliceMetadata,
},
state_store::{
state_key::StateKey, state_storage_usage::StateStorageUsage, state_value::StateValue,
StateView, StateViewResult, TStateView,
},
transaction::{
signature_verified_transaction::SignatureVerifiedTransaction, AuxiliaryInfo, BlockOutput,
Transaction, TransactionOutput, TransactionStatus,
},
write_set::TransactionWrite,
};
use aptos_vm::VMBlockExecutor;
use aptos_vm_types::output::VMOutput;
use move_core_types::vm_status::{StatusCode, VMStatus};
use std::collections::HashMap;

/// `VMBlockExecutor` implementation backed by MonoMove, executing sequentially.
pub struct AptosVMv2BlockExecutor {
vm: AptosVMv2,
}

impl VMBlockExecutor for AptosVMv2BlockExecutor {
fn new() -> Self {
Self {
vm: AptosVMv2::new(),
}
}

fn execute_block(
&self,
txn_provider: &aptos_block_executor::txn_provider::default::DefaultTxnProvider<
SignatureVerifiedTransaction,
AuxiliaryInfo,
>,
state_view: &(impl StateView + Sync),
_onchain_config: BlockExecutorConfigFromOnchain,
_transaction_slice_metadata: TransactionSliceMetadata,
) -> Result<BlockOutput<SignatureVerifiedTransaction, TransactionOutput>, VMStatus> {
let mut overlay = OverlayView {
base: state_view,
overlay: HashMap::new(),
};
let mut outputs = Vec::with_capacity(txn_provider.num_txns());
for txn in txn_provider.get_txns() {
let output = match txn.expect_valid() {
Transaction::UserTransaction(signed_txn) => {
let (_status, output) = self.vm.execute_user_transaction(&overlay, signed_txn);
output
},
// TODO(completeness): block metadata, epilogue, state
// checkpoint, and validator transactions.
_ => VMOutput::empty_with_status(TransactionStatus::Discard(
StatusCode::FEATURE_UNDER_GATING,
)),
};
let output = output
.try_materialize_into_transaction_output(&overlay)
.map_err(|e| {
VMStatus::error(
StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
Some(format!("materialization failed: {e:?}")),
)
})?;
for (key, op) in output.write_set().write_op_iter() {
overlay.overlay.insert(key.clone(), op.as_state_value());
}
outputs.push(output);
}
Ok(BlockOutput::new(outputs, None))
}
}

/// A state view layering the block's prior outputs over the base view.
struct OverlayView<'a, S> {
base: &'a S,
overlay: HashMap<StateKey, Option<StateValue>>,
}

impl<S: StateView> TStateView for OverlayView<'_, S> {
type Key = StateKey;

fn get_state_value(&self, state_key: &StateKey) -> StateViewResult<Option<StateValue>> {
match self.overlay.get(state_key) {
Some(value) => Ok(value.clone()),
None => self.base.get_state_value(state_key),
}
}

fn get_usage(&self) -> StateViewResult<StateStorageUsage> {
// TODO(correctness): account for the overlay's items and bytes.
self.base.get_usage()
}
}
72 changes: 72 additions & 0 deletions third_party/move/mono-move/aptos-vm-v2/src/convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Aptos Foundation
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE

//! Assembles the transaction's `VMOutput`: events out of the event store,
//! writes out of publication, and the final status.

use crate::types::type_tag_of;
use anyhow::{anyhow, Result};
use aptos_types::{
contract_event::ContractEvent, event::EventKey, fee_statement::FeeStatement,
state_store::state_key::StateKey, transaction::TransactionStatus,
};
use aptos_vm_types::{
abstract_write_op::AbstractResourceWriteOp, change_set::VMChangeSet,
module_write_set::ModuleWriteSet, output::VMOutput,
};
use mono_move_core::native::NativeExtensions;
use mono_move_global_context::ExecutionGuard;
use mono_move_natives::{EventKind, EventStore};
use mono_move_runtime::serialize;
use std::collections::BTreeMap;

/// Serializes the events recorded in the event store, in emission order.
///
/// Must run while the interpreter's heap is alive: each entry's value is
/// serialized straight out of VM memory.
pub(crate) fn finalize_events(
extensions: &NativeExtensions,
guard: &ExecutionGuard<'_>,
) -> Result<Vec<ContractEvent>> {
let store = extensions
.get_mut::<EventStore>()
.map_err(|e| anyhow!("event store unavailable: {e:?}"))?;
store
.entries()
.iter()
.map(|entry| {
let type_tag = type_tag_of(entry.msg_ty)?;
// SAFETY: the heap is live for the duration of output assembly.
let blob = unsafe { serialize(guard, entry.msg_data.as_ptr(), entry.msg_ty) }
.map_err(|e| anyhow!("event value failed to serialize: {e}"))?;
Ok(match &entry.kind {
EventKind::V2 => ContractEvent::new_v2(type_tag, blob)?,
EventKind::V1 {
guid,
sequence_number,
} => {
let key: EventKey = bcs::from_bytes(guid)?;
ContractEvent::new_v1(key, *sequence_number, type_tag, blob)?
},
})
})
.collect()
}

/// Builds the final `VMOutput` from published writes, events, and status.
pub(crate) fn into_vm_output(
writes: BTreeMap<StateKey, AbstractResourceWriteOp>,
events: Vec<ContractEvent>,
fee_statement: FeeStatement,
status: TransactionStatus,
) -> VMOutput {
let change_set = VMChangeSet::new(
writes,
events.into_iter().map(|event| (event, None)).collect(),
BTreeMap::new(),
BTreeMap::new(),
BTreeMap::new(),
);
// TODO(completeness): module publishing writes.
VMOutput::new(change_set, ModuleWriteSet::empty(), fee_statement, status)
}
Loading
Loading