Skip to content

Commit c4b7675

Browse files
author
Eduardo Leegwater Simões
committed
piecrust: one instance per contract function call
Changes the instance reclamation code to allow for exactly one module instance per contract function call. This changes the way in which the node processes the state, leading to diverging state roots on erroring calls. Simplifies the instance code by using an atomic integer to construct an atomically reference counted pointer, which then gets shared with the environment. This fixes some bad dereferences occuring under certain circumstances. Resolves: #325
1 parent bb2f3fc commit c4b7675

File tree

5 files changed

+126
-154
lines changed

5 files changed

+126
-154
lines changed

piecrust/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Changed
1515

16+
- Change to have one instance per contract function call [#325]
1617
- Upgrade `dusk-wasmtime` to version `17`
1718

1819
### Fixed
1920

21+
- Fix bad dereferences in caused by instance reclamation [#325]
2022
- Fix overflow in gas limit calculation in inter-contract call
2123

2224
## [0.15.0] - 2024-01-24
@@ -357,6 +359,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
357359
[#234]: https://github.com/dusk-network/piecrust/pull/234
358360

359361
<!-- ISSUES -->
362+
[#325]: https://github.com/dusk-network/piecrust/issues/325
360363
[#301]: https://github.com/dusk-network/piecrust/issues/313
361364
[#301]: https://github.com/dusk-network/piecrust/issues/301
362365
[#296]: https://github.com/dusk-network/piecrust/issues/296

piecrust/src/call_tree.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ use std::mem;
99

1010
use piecrust_uplink::ContractId;
1111

12+
use crate::instance::WrappedInstance;
13+
1214
/// An element of the call tree.
13-
#[derive(Debug, Clone, Copy)]
15+
#[derive(Clone)]
1416
pub struct CallTreeElem {
1517
pub contract_id: ContractId,
1618
pub limit: u64,
1719
pub spent: u64,
1820
pub mem_len: usize,
21+
pub instance: WrappedInstance,
1922
}
2023

2124
/// The tree of contract calls.
@@ -47,7 +50,7 @@ impl CallTree {
4750
pub(crate) fn move_up(&mut self, spent: u64) -> Option<CallTreeElem> {
4851
self.0.map(|inner| unsafe {
4952
(*inner).elem.spent = spent;
50-
let elem = (*inner).elem;
53+
let elem = (*inner).elem.clone();
5154

5255
let parent = (*inner).parent;
5356
if parent.is_none() {
@@ -63,7 +66,7 @@ impl CallTree {
6366
/// current element.
6467
pub(crate) fn move_up_prune(&mut self) -> Option<CallTreeElem> {
6568
self.0.map(|inner| unsafe {
66-
let elem = (*inner).elem;
69+
let elem = (*inner).elem.clone();
6770

6871
let parent = (*inner).parent;
6972
if let Some(parent) = parent {
@@ -98,7 +101,7 @@ impl CallTree {
98101
i += 1;
99102
}
100103

101-
current.map(|inner| unsafe { (*inner).elem })
104+
current.map(|inner| unsafe { (*inner).elem.clone() })
102105
}
103106

104107
/// Clears the call tree of all elements.

piecrust/src/imports.rs

+18-20
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,12 @@ pub(crate) fn hq(
138138
) -> WasmtimeResult<u32> {
139139
let env = fenv.data_mut();
140140

141-
let instance = env.self_instance();
141+
let mut instance = env.self_instance();
142142

143143
let name_len = name_len as usize;
144144

145-
check_ptr(instance, name_ofs, name_len)?;
146-
check_arg(instance, arg_len)?;
145+
check_ptr(&instance, name_ofs, name_len)?;
146+
check_arg(&instance, arg_len)?;
147147

148148
let name = instance.with_memory(|buf| {
149149
// performance: use a dedicated buffer here?
@@ -163,11 +163,11 @@ pub(crate) fn hd(
163163
) -> WasmtimeResult<u32> {
164164
let env = fenv.data_mut();
165165

166-
let instance = env.self_instance();
166+
let mut instance = env.self_instance();
167167

168168
let name_len = name_len as usize;
169169

170-
check_ptr(instance, name_ofs, name_len)?;
170+
check_ptr(&instance, name_ofs, name_len)?;
171171

172172
let name = instance.with_memory(|buf| {
173173
// performance: use a dedicated buffer here?
@@ -194,13 +194,13 @@ pub(crate) fn c(
194194
) -> WasmtimeResult<i32> {
195195
let env = fenv.data_mut();
196196

197-
let instance = env.self_instance();
197+
let mut instance = env.self_instance();
198198

199199
let name_len = name_len as usize;
200200

201-
check_ptr(instance, mod_id_ofs, CONTRACT_ID_BYTES)?;
202-
check_ptr(instance, name_ofs, name_len)?;
203-
check_arg(instance, arg_len)?;
201+
check_ptr(&instance, mod_id_ofs, CONTRACT_ID_BYTES)?;
202+
check_ptr(&instance, name_ofs, name_len)?;
203+
check_arg(&instance, arg_len)?;
204204

205205
let argbuf_ofs = instance.arg_buffer_offset();
206206

@@ -225,9 +225,7 @@ pub(crate) fn c(
225225
let callee_stack_element = env
226226
.push_callstack(mod_id, callee_limit)
227227
.expect("pushing to the callstack should succeed");
228-
let callee = env
229-
.instance(&callee_stack_element.contract_id)
230-
.expect("callee instance should exist");
228+
let mut callee = callee_stack_element.instance;
231229

232230
callee.snap().map_err(|err| Error::MemorySnapshotFailure {
233231
reason: None,
@@ -242,7 +240,7 @@ pub(crate) fn c(
242240
let ret_len = callee
243241
.call(name, arg.len() as u32, callee_limit)
244242
.map_err(Error::normalize)?;
245-
check_arg(callee, ret_len as u32)?;
243+
check_arg(&callee, ret_len as u32)?;
246244

247245
// copy back result
248246
callee.read_argument(&mut memory[argbuf_ofs..][..ret_len as usize]);
@@ -291,8 +289,8 @@ pub(crate) fn emit(
291289

292290
let topic_len = topic_len as usize;
293291

294-
check_ptr(instance, topic_ofs, topic_len)?;
295-
check_arg(instance, arg_len)?;
292+
check_ptr(&instance, topic_ofs, topic_len)?;
293+
check_arg(&instance, arg_len)?;
296294

297295
let data = instance.with_arg_buf(|buf| {
298296
let arg_len = arg_len as usize;
@@ -327,7 +325,7 @@ fn feed(mut fenv: Caller<Env>, arg_len: u32) -> WasmtimeResult<()> {
327325
let env = fenv.data_mut();
328326
let instance = env.self_instance();
329327

330-
check_arg(instance, arg_len)?;
328+
check_arg(&instance, arg_len)?;
331329

332330
let data = instance.with_arg_buf(|buf| {
333331
let arg_len = arg_len as usize;
@@ -342,7 +340,7 @@ fn hdebug(mut fenv: Caller<Env>, msg_len: u32) -> WasmtimeResult<()> {
342340
let env = fenv.data_mut();
343341
let instance = env.self_instance();
344342

345-
check_arg(instance, msg_len)?;
343+
check_arg(&instance, msg_len)?;
346344

347345
Ok(instance.with_arg_buf(|buf| {
348346
let slice = &buf[..msg_len as usize];
@@ -365,7 +363,7 @@ fn limit(fenv: Caller<Env>) -> u64 {
365363

366364
fn spent(fenv: Caller<Env>) -> u64 {
367365
let env = fenv.data();
368-
let instance = env.self_instance();
366+
let mut instance = env.self_instance();
369367

370368
let limit = env.limit();
371369
let remaining = instance.get_remaining_gas();
@@ -377,7 +375,7 @@ fn panic(fenv: Caller<Env>, arg_len: u32) -> WasmtimeResult<()> {
377375
let env = fenv.data();
378376
let instance = env.self_instance();
379377

380-
check_arg(instance, arg_len)?;
378+
check_arg(&instance, arg_len)?;
381379

382380
Ok(instance.with_arg_buf(|buf| {
383381
let slice = &buf[..arg_len as usize];
@@ -392,7 +390,7 @@ fn panic(fenv: Caller<Env>, arg_len: u32) -> WasmtimeResult<()> {
392390
}
393391

394392
fn owner(fenv: Caller<Env>, mod_id_ofs: usize) -> WasmtimeResult<i32> {
395-
check_ptr(fenv.data().self_instance(), mod_id_ofs, CONTRACT_ID_BYTES)?;
393+
check_ptr(&fenv.data().self_instance(), mod_id_ofs, CONTRACT_ID_BYTES)?;
396394

397395
let env = fenv.data();
398396

piecrust/src/instance.rs

+58-22
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
// Copyright (c) DUSK NETWORK. All rights reserved.
66

77
use std::io;
8+
use std::mem::MaybeUninit;
89
use std::ops::{Deref, DerefMut};
10+
use std::sync::atomic::{AtomicUsize, Ordering};
911

1012
use dusk_wasmtime::{Instance, Module, Mutability, Store, ValType};
1113
use piecrust_uplink::{ContractId, Event, ARGBUF_LEN};
@@ -16,16 +18,45 @@ use crate::session::Session;
1618
use crate::store::Memory;
1719
use crate::Error;
1820

19-
pub struct WrappedInstance {
20-
instance: Instance,
21-
arg_buf_ofs: usize,
22-
store: Store<Env>,
23-
memory: Memory,
21+
pub struct WrappedInstance(*mut WrappedInstanceInner);
22+
23+
impl Clone for WrappedInstance {
24+
fn clone(&self) -> Self {
25+
unsafe {
26+
(*self.0).count.fetch_add(1, Ordering::SeqCst);
27+
Self(self.0)
28+
}
29+
}
30+
}
31+
32+
impl Drop for WrappedInstance {
33+
fn drop(&mut self) {
34+
if self.count.fetch_sub(1, Ordering::SeqCst) == 1 {
35+
unsafe {
36+
let _ = Box::from_raw(self.0);
37+
}
38+
}
39+
}
40+
}
41+
42+
impl Deref for WrappedInstance {
43+
type Target = WrappedInstanceInner;
44+
45+
fn deref(&self) -> &Self::Target {
46+
unsafe { &*self.0 }
47+
}
48+
}
49+
50+
impl DerefMut for WrappedInstance {
51+
fn deref_mut(&mut self) -> &mut Self::Target {
52+
unsafe { &mut *self.0 }
53+
}
2454
}
2555

2656
pub(crate) struct Env {
2757
self_id: ContractId,
2858
session: Session,
59+
instance: MaybeUninit<WrappedInstance>,
2960
}
3061

3162
impl Deref for Env {
@@ -43,20 +74,8 @@ impl DerefMut for Env {
4374
}
4475

4576
impl Env {
46-
pub fn self_instance<'b>(&self) -> &'b mut WrappedInstance {
47-
let stack_element = self
48-
.session
49-
.nth_from_top(0)
50-
.expect("there should be at least one element in the call stack");
51-
self.instance(&stack_element.contract_id)
52-
.expect("instance should exist")
53-
}
54-
55-
pub fn instance<'b>(
56-
&self,
57-
contract_id: &ContractId,
58-
) -> Option<&'b mut WrappedInstance> {
59-
self.session.instance(contract_id)
77+
pub fn self_instance(&self) -> WrappedInstance {
78+
unsafe { self.instance.assume_init_ref().clone() }
6079
}
6180

6281
pub fn limit(&self) -> u64 {
@@ -94,6 +113,7 @@ impl WrappedInstance {
94113
let env = Env {
95114
self_id: contract_id,
96115
session,
116+
instance: MaybeUninit::uninit(),
97117
};
98118

99119
let module =
@@ -177,16 +197,32 @@ impl WrappedInstance {
177197
// A memory is no longer new after one instantiation
178198
memory.is_new = false;
179199

180-
let wrapped = WrappedInstance {
200+
let inner = WrappedInstanceInner {
181201
store,
182202
instance,
183203
arg_buf_ofs,
184204
memory,
205+
count: AtomicUsize::new(1),
185206
};
186207

187-
Ok(wrapped)
208+
let mut instance = WrappedInstance(Box::into_raw(Box::new(inner)));
209+
let instance_clone = instance.clone();
210+
211+
instance.store.data_mut().instance = MaybeUninit::new(instance_clone);
212+
213+
Ok(instance)
188214
}
215+
}
216+
217+
pub struct WrappedInstanceInner {
218+
instance: Instance,
219+
arg_buf_ofs: usize,
220+
store: Store<Env>,
221+
memory: Memory,
222+
count: AtomicUsize,
223+
}
189224

225+
impl WrappedInstanceInner {
190226
pub(crate) fn snap(&mut self) -> io::Result<()> {
191227
self.memory.snap()?;
192228
Ok(())
@@ -342,7 +378,7 @@ impl WrappedInstance {
342378
}
343379

344380
fn map_call_err(
345-
instance: &mut WrappedInstance,
381+
instance: &mut WrappedInstanceInner,
346382
err: dusk_wasmtime::Error,
347383
) -> Error {
348384
if instance.get_remaining_gas() == 0 {

0 commit comments

Comments
 (0)