Skip to content

Commit 6bdc941

Browse files
authored
Rust Test VM for integration tests (#184)
* Introduce sub crate for test vm * Fundamental VM type * Implementation of state manipulation: actor set / get, checkpoint, rollback * Tests of basic functionality Co-authored-by: zenground0 <[email protected]>
1 parent 1ef7650 commit 6bdc941

File tree

5 files changed

+144
-1
lines changed

5 files changed

+144
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ devnet = []
3636
[workspace]
3737
members = [
3838
"actors/*",
39+
"test_vm",
3940
]
4041

4142
## Uncomment when working locally on ref-fvm and this repo simultaneously.

actors/runtime/src/test_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ lazy_static! {
6565
const IPLD_RAW: u64 = 0x55;
6666

6767
/// Returns an identity CID for bz.
68-
fn make_builtin(bz: &[u8]) -> Cid {
68+
pub fn make_builtin(bz: &[u8]) -> Cid {
6969
Cid::new_v1(IPLD_RAW, Multihash::wrap(0, bz).expect("name too long"))
7070
}
7171

test_vm/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "test_vm"
3+
description = "Reference vm for integration testing builtin actors"
4+
version = "8.0.0-alpha.1"
5+
license = "MIT OR Apache-2.0"
6+
authors = ["Protocol Labs", "Filecoin Core Devs"]
7+
edition = "2021"
8+
keywords = ["filecoin", "web3", "wasm"]
9+
10+
[lib]
11+
12+
[dependencies]
13+
fil_actors_runtime = { version = "8.0.0-alpha.1", path = "../actors/runtime" }
14+
fvm_shared = { version = "0.2.2", default-features = false }
15+
fvm_ipld_hamt = "0.2.0"
16+
num-traits = "0.2.14"
17+
num-derive = "0.3.3"
18+
log = "0.4.14"
19+
indexmap = { version = "1.8.0", features = ["serde-1"] }
20+
cid = { version = "0.8.3", default-features = false, features = ["serde-codec"] }
21+
serde = { version = "1.0.136", features = ["derive"] }
22+
thiserror = "1.0.30"
23+
anyhow = "1.0.56"
24+
[dev-dependencies]
25+
fil_actors_runtime = { version = "8.0.0-alpha.1", path = "../actors/runtime", features = ["test_utils", "sector-default"] }
26+
27+

test_vm/src/lib.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use cid::Cid;
2+
use fvm_ipld_hamt::{BytesKey, Hamt, Sha256};
3+
use fvm_shared::address::Address;
4+
use fvm_shared::bigint::bigint_ser;
5+
use fvm_shared::blockstore::MemoryBlockstore;
6+
use fvm_shared::econ::TokenAmount;
7+
use fvm_shared::encoding::tuple::*;
8+
use std::error::Error;
9+
use std::fmt;
10+
11+
pub struct VM<'bs> {
12+
store: &'bs MemoryBlockstore,
13+
state_root: Cid,
14+
actors_dirty: bool,
15+
actors: Hamt<&'bs MemoryBlockstore, Actor, BytesKey>,
16+
}
17+
18+
impl<'bs> VM<'bs> {
19+
pub fn new(store: &'bs MemoryBlockstore) -> VM<'bs> {
20+
let mut actors = Hamt::<&'bs MemoryBlockstore, Actor, BytesKey, Sha256>::new(store);
21+
VM { store, state_root: actors.flush().unwrap(), actors_dirty: false, actors }
22+
}
23+
24+
pub fn get_actor(&self, addr: &Address) -> Option<&Actor> {
25+
self.actors.get(&addr.to_bytes()).unwrap()
26+
}
27+
28+
// blindly overwrite the actor at this address whether it previously existed or not
29+
pub fn set_actor(&mut self, key: &Address, a: Actor) {
30+
let _ = self.actors.set(key.to_bytes().into(), a).unwrap();
31+
}
32+
33+
pub fn checkpoint(&mut self) -> Cid {
34+
self.state_root = self.actors.flush().unwrap();
35+
self.actors_dirty = false;
36+
self.state_root
37+
}
38+
39+
pub fn rollback(&mut self, root: &Cid) {
40+
self.actors =
41+
Hamt::<&'bs MemoryBlockstore, Actor, BytesKey, Sha256>::load(root, self.store).unwrap();
42+
self.state_root = *root;
43+
self.actors_dirty = false;
44+
}
45+
}
46+
47+
#[derive(Serialize_tuple, Deserialize_tuple, Clone, PartialEq, Debug)]
48+
pub struct Actor {
49+
pub code: Cid, // Might want to mock this out to avoid dealing with the annoying bundler
50+
pub head: Cid,
51+
pub call_seq_num: u64,
52+
#[serde(with = "bigint_ser")]
53+
pub balance: TokenAmount,
54+
}
55+
56+
pub fn actor(code: Cid, head: Cid, seq: u64, bal: TokenAmount) -> Actor {
57+
Actor { code, head, call_seq_num: seq, balance: bal }
58+
}
59+
60+
#[derive(Debug)]
61+
pub struct TestVMError {
62+
msg: String,
63+
}
64+
65+
impl fmt::Display for TestVMError {
66+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67+
write!(f, "{}", self.msg)
68+
}
69+
}
70+
71+
impl Error for TestVMError {
72+
fn description(&self) -> &str {
73+
&self.msg
74+
}
75+
}
76+
77+
impl From<fvm_ipld_hamt::Error> for TestVMError {
78+
fn from(h_err: fvm_ipld_hamt::Error) -> Self {
79+
vm_err(h_err.to_string().as_str())
80+
}
81+
}
82+
83+
pub fn vm_err(msg: &str) -> TestVMError {
84+
TestVMError { msg: msg.to_string() }
85+
}

test_vm/tests/test_vm_test.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use fil_actors_runtime::test_utils::{make_builtin, ACCOUNT_ACTOR_CODE_ID, PAYCH_ACTOR_CODE_ID};
2+
use fvm_shared::address::Address;
3+
use fvm_shared::blockstore::MemoryBlockstore;
4+
use fvm_shared::econ::TokenAmount;
5+
use test_vm::{actor, VM};
6+
7+
#[test]
8+
fn state_control() {
9+
let store = MemoryBlockstore::new();
10+
let mut v = VM::new(&store);
11+
let addr1 = Address::new_id(1000);
12+
let addr2 = Address::new_id(2222);
13+
14+
// set actor
15+
let a1 = actor(*ACCOUNT_ACTOR_CODE_ID, make_builtin(b"a1-head"), 42, TokenAmount::from(10u8));
16+
v.set_actor(&addr1, a1.clone());
17+
let out = v.get_actor(&addr1).unwrap();
18+
assert_eq!(out, &a1);
19+
let check = v.checkpoint();
20+
21+
let a2 = actor(*PAYCH_ACTOR_CODE_ID, make_builtin(b"a2-head"), 88, TokenAmount::from(1u8));
22+
v.set_actor(&addr2, a2.clone());
23+
assert_eq!(v.get_actor(&addr2).unwrap(), &a2);
24+
// rollback removes a2 but not a1
25+
v.rollback(&check);
26+
27+
// a2 is gone
28+
assert_eq!(None, v.get_actor(&addr2));
29+
assert_eq!(v.get_actor(&addr1).unwrap(), &a1);
30+
}

0 commit comments

Comments
 (0)