Skip to content

Commit cfd529d

Browse files
committed
start implementing a custom Externals
The MockExternal is unfortunately to rudimentary for what is necessary out of the debugger here. In particular one thing is that we want to be able to manage `Storage` separately from the lifetime of externals, and another thing is that its `generate_data_id` for example is too simplistic and does not necessarily reflect as closely what one would expect from a real system.
1 parent 718eca2 commit cfd529d

File tree

3 files changed

+253
-22
lines changed

3 files changed

+253
-22
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ <h2>
2323
<p>
2424
The debugging experience will differ between browsers. For instance Chromium supports DWARF
2525
debug info. Contracts built from Rust source will embed such debug info into the
26-
<code>.wasm</code> file, for example, so long as debug info is enabled. This would then allow
26+
<code>.wasm</code> file, as long as debug info is enabled. This would then allow
2727
debugging Rust code and not the underlying WebAssembly!
2828
</p>
2929

loader.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import init, { b64encode, list_methods, prepare_contract, Logic, Context, init_panic_hook } from "./pkg/neardebug.js";
1+
import init, { b64encode, list_methods, prepare_contract, Logic, Context, Store, init_panic_hook } from "./pkg/neardebug.js";
22

33
(function(window, document) {
44
async function run(method_name) {
55
const contract = window.contract;
66
const memory = new WebAssembly.Memory({ initial: 1024, maximum: 2048 });
77
contract.memory = memory;
88
const context = new Context().input_str(document.querySelector("#input").value);
9-
const logic = new Logic(context, memory);
9+
const logic = new Logic(context, memory, contract.store);
1010
contract.logic = logic;
1111

1212
const import_object = { env: {} };
@@ -100,7 +100,7 @@ import init, { b64encode, list_methods, prepare_contract, Logic, Context, init_p
100100
bls12381_pairing_check: (value_len, value_ptr) /* -> [u64] */ => { console.log("TODO bls12381_pairing_check"); },
101101
bls12381_p1_decompress: (value_len, value_ptr, register_id) /* -> [u64] */ => { console.log("TODO bls(12381_p1_decompress"); },
102102
bls12381_p2_decompress: (value_len, value_ptr, register_id) /* -> [u64] */ => { console.log("TODO bls12381_p2_decompress"); },
103-
sandbox_debug_log: (len, ptr) /* -> [] */ => { console.log("TODO samdbox_debug_log"); },
103+
sandbox_debug_log: (len, ptr) /* -> [] */ => { console.log("TODO sandbox_debug_log"); },
104104
sleep_nanos: (duration) /* -> [] */ => { console.log("TODO sleep_nanos"); },
105105
};
106106
window.contract.instance = await WebAssembly.instantiate(window.contract.module, import_object);
@@ -109,6 +109,8 @@ import init, { b64encode, list_methods, prepare_contract, Logic, Context, init_p
109109

110110
async function load(contract_data) {
111111
delete contract.instance;
112+
delete contract.memory;
113+
delete contract.logic;
112114
if (contract_data === undefined) {
113115
delete contract.module;
114116
return;
@@ -142,9 +144,9 @@ import init, { b64encode, list_methods, prepare_contract, Logic, Context, init_p
142144
async function on_load() {
143145
await init();
144146
init_panic_hook();
145-
window.contract = {};
146-
window.contract.registers = [];
147-
window.contract.storage = {};
147+
window.contract = {
148+
store: new Store(),
149+
};
148150
const form = document.querySelector('#contract_form');
149151
form.addEventListener('submit', async (e) => {
150152
e.preventDefault();

src/near_vm_runner/mod.rs

Lines changed: 244 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,254 @@ pub mod logic;
33
pub mod profile;
44

55
use std::str::FromStr as _;
6+
use std::sync::MutexGuard;
67

78
use js_sys::ArrayBuffer;
9+
use logic::mocks::mock_external::MockedValuePtr;
810
pub use logic::with_ext_cost_counter;
9-
use logic::{gas_counter, ExecutionResultState, External, GasCounter, MemSlice, VMContext};
11+
use logic::{
12+
gas_counter, ExecutionResultState, External, GasCounter, MemSlice, VMContext, VMLogicError,
13+
ValuePtr,
14+
};
1015
use logic::{mocks::mock_external, types::PromiseIndex};
1116
use near_parameters::vm::Config;
1217
pub use near_primitives_core::code::ContractCode;
13-
use near_primitives_core::types::{AccountId, EpochHeight, Gas, StorageUsage};
18+
use near_primitives_core::types::{AccountId, Balance, EpochHeight, Gas, StorageUsage};
1419
pub use profile::ProfileDataV3;
1520
use serde::Serialize as _;
21+
use std::result::Result as SResult;
1622
use wasm_bindgen::prelude::*;
1723

24+
fn js_serializer() -> serde_wasm_bindgen::Serializer {
25+
serde_wasm_bindgen::Serializer::new()
26+
.serialize_missing_as_null(true)
27+
.serialize_large_number_types_as_bigints(true)
28+
.serialize_bytes_as_arrays(false)
29+
}
30+
31+
#[wasm_bindgen]
32+
#[derive(Clone)]
33+
pub struct Store(std::sync::Arc<std::sync::Mutex<std::collections::HashMap<Vec<u8>, Vec<u8>>>>);
34+
35+
#[wasm_bindgen]
36+
impl Store {
37+
#[wasm_bindgen(constructor)]
38+
pub fn new() -> Self {
39+
Self(Default::default())
40+
}
41+
42+
fn guard(&self) -> MutexGuard<std::collections::HashMap<Vec<u8>, Vec<u8>>> {
43+
self.0.lock().unwrap_or_else(|e| e.into_inner())
44+
}
45+
46+
pub fn to_value(&self) -> Result<JsValue> {
47+
self.guard().serialize(&js_serializer()).map_err(Into::into)
48+
}
49+
50+
pub fn set(&self, key: &[u8], value: &[u8]) {
51+
self.guard().insert(key.to_vec(), value.to_vec());
52+
}
53+
54+
pub fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
55+
self.guard().get(key).cloned()
56+
}
57+
58+
pub fn remove(&self, key: &[u8]) {
59+
self.guard().remove(key);
60+
}
61+
62+
pub fn remove_subtree(&self, prefix: &[u8]) {
63+
self.guard().retain(|key, _| !key.starts_with(prefix));
64+
}
65+
66+
pub fn has_key(&self, key: &[u8]) -> bool {
67+
self.guard().contains_key(key)
68+
}
69+
}
70+
71+
#[wasm_bindgen]
72+
pub struct DebugExternal {
73+
store: Store,
74+
}
75+
76+
#[wasm_bindgen]
77+
impl DebugExternal {
78+
#[wasm_bindgen(constructor)]
79+
pub fn new(store: &Store) -> Self {
80+
Self {
81+
store: store.clone(),
82+
}
83+
}
84+
}
85+
86+
impl External for DebugExternal {
87+
fn storage_set(&mut self, key: &[u8], value: &[u8]) -> SResult<(), VMLogicError> {
88+
self.store.set(key, value);
89+
Ok(())
90+
}
91+
92+
fn storage_get<'a>(
93+
&'a self,
94+
key: &[u8],
95+
_: near_parameters::vm::StorageGetMode,
96+
) -> SResult<Option<Box<dyn logic::ValuePtr + 'a>>, VMLogicError> {
97+
let v = self.store.get(key);
98+
Ok(v.map(|v| Box::new(MockedValuePtr::new(&v)) as Box<_>))
99+
}
100+
101+
fn storage_remove(&mut self, key: &[u8]) -> SResult<(), VMLogicError> {
102+
self.store.remove(key);
103+
Ok(())
104+
}
105+
106+
fn storage_remove_subtree(&mut self, prefix: &[u8]) -> SResult<(), VMLogicError> {
107+
self.store.remove_subtree(prefix);
108+
Ok(())
109+
}
110+
111+
fn storage_has_key(
112+
&mut self,
113+
key: &[u8],
114+
_: near_parameters::vm::StorageGetMode,
115+
) -> SResult<bool, VMLogicError> {
116+
Ok(self.store.has_key(key))
117+
}
118+
119+
fn generate_data_id(&mut self) -> near_primitives_core::hash::CryptoHash {
120+
todo!()
121+
}
122+
123+
fn get_trie_nodes_count(&self) -> logic::TrieNodesCount {
124+
logic::TrieNodesCount { db_reads: 0, mem_reads: 0 }
125+
}
126+
127+
fn get_recorded_storage_size(&self) -> usize {
128+
0
129+
}
130+
131+
fn validator_stake(&self, account_id: &AccountId) -> SResult<Option<Balance>, VMLogicError> {
132+
todo!()
133+
}
134+
135+
fn validator_total_stake(&self) -> SResult<Balance, VMLogicError> {
136+
todo!()
137+
}
138+
139+
fn create_action_receipt(
140+
&mut self,
141+
receipt_indices: Vec<logic::types::ReceiptIndex>,
142+
receiver_id: AccountId,
143+
) -> SResult<logic::types::ReceiptIndex, logic::VMLogicError> {
144+
todo!()
145+
}
146+
147+
fn create_promise_yield_receipt(
148+
&mut self,
149+
receiver_id: AccountId,
150+
) -> SResult<
151+
(
152+
logic::types::ReceiptIndex,
153+
near_primitives_core::hash::CryptoHash,
154+
),
155+
logic::VMLogicError,
156+
> {
157+
todo!()
158+
}
159+
160+
fn submit_promise_resume_data(
161+
&mut self,
162+
data_id: near_primitives_core::hash::CryptoHash,
163+
data: Vec<u8>,
164+
) -> SResult<bool, logic::VMLogicError> {
165+
todo!()
166+
}
167+
168+
fn append_action_create_account(
169+
&mut self,
170+
receipt_index: logic::types::ReceiptIndex,
171+
) -> SResult<(), logic::VMLogicError> {
172+
todo!()
173+
}
174+
175+
fn append_action_deploy_contract(
176+
&mut self,
177+
receipt_index: logic::types::ReceiptIndex,
178+
code: Vec<u8>,
179+
) -> SResult<(), logic::VMLogicError> {
180+
todo!()
181+
}
182+
183+
fn append_action_function_call_weight(
184+
&mut self,
185+
receipt_index: logic::types::ReceiptIndex,
186+
method_name: Vec<u8>,
187+
args: Vec<u8>,
188+
attached_deposit: Balance,
189+
prepaid_gas: Gas,
190+
gas_weight: near_primitives_core::types::GasWeight,
191+
) -> SResult<(), logic::VMLogicError> {
192+
todo!()
193+
}
194+
195+
fn append_action_transfer(
196+
&mut self,
197+
receipt_index: logic::types::ReceiptIndex,
198+
deposit: Balance,
199+
) -> SResult<(), logic::VMLogicError> {
200+
todo!()
201+
}
202+
203+
fn append_action_stake(
204+
&mut self,
205+
receipt_index: logic::types::ReceiptIndex,
206+
stake: Balance,
207+
public_key: near_crypto::PublicKey,
208+
) {
209+
todo!()
210+
}
211+
212+
fn append_action_add_key_with_full_access(
213+
&mut self,
214+
receipt_index: logic::types::ReceiptIndex,
215+
public_key: near_crypto::PublicKey,
216+
nonce: near_primitives_core::types::Nonce,
217+
) {
218+
todo!()
219+
}
220+
221+
fn append_action_add_key_with_function_call(
222+
&mut self,
223+
receipt_index: logic::types::ReceiptIndex,
224+
public_key: near_crypto::PublicKey,
225+
nonce: near_primitives_core::types::Nonce,
226+
allowance: Option<Balance>,
227+
receiver_id: AccountId,
228+
method_names: Vec<Vec<u8>>,
229+
) -> SResult<(), logic::VMLogicError> {
230+
todo!()
231+
}
232+
233+
fn append_action_delete_key(
234+
&mut self,
235+
receipt_index: logic::types::ReceiptIndex,
236+
public_key: near_crypto::PublicKey,
237+
) {
238+
todo!()
239+
}
240+
241+
fn append_action_delete_account(
242+
&mut self,
243+
receipt_index: logic::types::ReceiptIndex,
244+
beneficiary_id: AccountId,
245+
) -> SResult<(), logic::VMLogicError> {
246+
todo!()
247+
}
248+
249+
fn get_receipt_receiver(&self, receipt_index: logic::types::ReceiptIndex) -> &AccountId {
250+
todo!()
251+
}
252+
}
253+
18254
#[wasm_bindgen]
19255
pub struct Context(VMContext);
20256

@@ -60,7 +296,7 @@ type Result<T> = std::result::Result<T, JsError>;
60296
#[wasm_bindgen]
61297
impl Logic {
62298
#[wasm_bindgen(constructor)]
63-
pub fn new(context: Context, memory: js_sys::WebAssembly::Memory) -> Self {
299+
pub fn new(context: Context, memory: js_sys::WebAssembly::Memory, store: &Store) -> Self {
64300
let max_gas_burnt = u64::max_value();
65301
let prepaid_gas = u64::max_value();
66302
let is_view = false;
@@ -75,10 +311,10 @@ impl Logic {
75311
);
76312
let result_state =
77313
ExecutionResultState::new(&context.0, gas_counter, config.wasm_config.clone());
78-
let mock_ext = Box::new(mock_external::MockedExternal::new());
314+
let ext = Box::new(DebugExternal::new(store));
79315
Self {
80316
logic: logic::VMLogic::new(
81-
mock_ext,
317+
ext,
82318
context.0,
83319
config.fees.clone(),
84320
result_state,
@@ -87,17 +323,10 @@ impl Logic {
87323
}
88324
}
89325

90-
fn js_serializer(&self) -> serde_wasm_bindgen::Serializer {
91-
serde_wasm_bindgen::Serializer::new()
92-
.serialize_missing_as_null(true)
93-
.serialize_large_number_types_as_bigints(true)
94-
.serialize_bytes_as_arrays(false)
95-
}
96-
97326
pub fn context(&self) -> Result<JsValue> {
98327
self.logic
99328
.context
100-
.serialize(&self.js_serializer())
329+
.serialize(&js_serializer())
101330
.map_err(Into::into)
102331
}
103332

@@ -106,12 +335,12 @@ impl Logic {
106335
.result_state
107336
.clone()
108337
.compute_outcome()
109-
.serialize(&self.js_serializer())
338+
.serialize(&js_serializer())
110339
.map_err(Into::into)
111340
}
112341

113342
pub fn registers(&mut self) -> Result<JsValue> {
114-
let s = self.js_serializer();
343+
let s = js_serializer();
115344
self.logic.registers().serialize(&s).map_err(Into::into)
116345
}
117346

0 commit comments

Comments
 (0)