Skip to content

Commit 8d24bb0

Browse files
authored
Merge pull request casper-network#5213 from mpapierski/casper-networkgh-5195-purge-global-state-entries
casper-networkGH-5195: Purge global state entries
2 parents 7dbc817 + 4248fce commit 8d24bb0

19 files changed

Lines changed: 496 additions & 501 deletions

File tree

Cargo.lock

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

executor/wasm-host/src/host.rs

Lines changed: 125 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ use casper_executor_wasm_common::{
88
ENTRY_POINT_PAYMENT_SELF_ONWARD,
99
},
1010
error::{
11-
CallError, TrapCode, HOST_ERROR_INVALID_DATA, HOST_ERROR_INVALID_INPUT,
12-
HOST_ERROR_MAX_MESSAGES_PER_BLOCK_EXCEEDED, HOST_ERROR_MESSAGE_TOPIC_FULL,
13-
HOST_ERROR_NOT_FOUND, HOST_ERROR_PAYLOAD_TOO_LONG, HOST_ERROR_SUCCESS,
14-
HOST_ERROR_TOO_MANY_TOPICS, HOST_ERROR_TOPIC_TOO_LONG,
11+
CallError, CALLEE_NOT_CALLABLE, CALLEE_SUCCEEDED, CALLEE_TRAPPED, HOST_ERROR_INVALID_DATA,
12+
HOST_ERROR_INVALID_INPUT, HOST_ERROR_MAX_MESSAGES_PER_BLOCK_EXCEEDED,
13+
HOST_ERROR_MESSAGE_TOPIC_FULL, HOST_ERROR_NOT_FOUND, HOST_ERROR_PAYLOAD_TOO_LONG,
14+
HOST_ERROR_SUCCESS, HOST_ERROR_TOO_MANY_TOPICS, HOST_ERROR_TOPIC_TOO_LONG,
1515
},
1616
flags::ReturnFlags,
1717
keyspace::{Keyspace, KeyspaceTag},
1818
};
1919
use casper_executor_wasm_interface::{
2020
executor::{ExecuteError, ExecuteRequestBuilder, ExecuteResult, ExecutionKind, Executor},
21-
u32_from_host_result, Caller, HostResult, VMError, VMResult,
21+
u32_from_host_result, Caller, VMError, VMResult,
2222
};
2323
use casper_storage::{
2424
global_state::GlobalStateReader,
@@ -129,7 +129,7 @@ pub fn casper_write<S: GlobalStateReader, E: Executor>(
129129
Ok(key_name) => key_name,
130130
Err(_) => {
131131
// TODO: Invalid key name encoding
132-
return Ok(HOST_ERROR_NOT_FOUND);
132+
return Ok(HOST_ERROR_INVALID_DATA);
133133
}
134134
};
135135

@@ -139,7 +139,7 @@ pub fn casper_write<S: GlobalStateReader, E: Executor>(
139139
let key_name = match std::str::from_utf8(&key_payload_bytes) {
140140
Ok(key_name) => key_name,
141141
Err(_) => {
142-
return Ok(1);
142+
return Ok(HOST_ERROR_INVALID_DATA);
143143
}
144144
};
145145

@@ -197,6 +197,104 @@ pub fn casper_write<S: GlobalStateReader, E: Executor>(
197197
Ok(HOST_ERROR_SUCCESS)
198198
}
199199

200+
/// Remove value under a key.
201+
///
202+
/// This produces a transformation of Prune to the global state. Keep in mind that technically the
203+
/// data is not removed from the global state as it still there, it's just not reachable anymore
204+
/// from the newly created tip.
205+
///
206+
/// The name for this host function is `remove` to keep it simple and consistent with read/write
207+
/// verbs, and also consistent with the rust stdlib vocabulary i.e. `V`
208+
pub fn casper_remove<S: GlobalStateReader, E: Executor>(
209+
mut caller: impl Caller<Context = Context<S, E>>,
210+
key_space: u64,
211+
key_ptr: u32,
212+
key_size: u32,
213+
) -> VMResult<u32> {
214+
let write_cost = caller.context().config.host_function_costs().remove;
215+
charge_host_function_call(
216+
&mut caller,
217+
&write_cost,
218+
[key_space as u32, key_ptr, key_size],
219+
)?;
220+
221+
let keyspace_tag = match KeyspaceTag::from_u64(key_space) {
222+
Some(keyspace_tag) => keyspace_tag,
223+
None => {
224+
// Unknown keyspace received, return error
225+
return Ok(HOST_ERROR_NOT_FOUND);
226+
}
227+
};
228+
229+
let key_payload_bytes = caller.memory_read(key_ptr, key_size.try_into().unwrap())?;
230+
231+
let keyspace = match keyspace_tag {
232+
KeyspaceTag::State => Keyspace::State,
233+
KeyspaceTag::Context => Keyspace::Context(&key_payload_bytes),
234+
KeyspaceTag::NamedKey => {
235+
let key_name = match std::str::from_utf8(&key_payload_bytes) {
236+
Ok(key_name) => key_name,
237+
Err(_) => {
238+
// TODO: Invalid key name encoding
239+
return Ok(HOST_ERROR_INVALID_DATA);
240+
}
241+
};
242+
243+
Keyspace::NamedKey(key_name)
244+
}
245+
KeyspaceTag::PaymentInfo => {
246+
let key_name = match std::str::from_utf8(&key_payload_bytes) {
247+
Ok(key_name) => key_name,
248+
Err(_) => {
249+
return Ok(HOST_ERROR_INVALID_DATA);
250+
}
251+
};
252+
253+
if !caller.has_export(key_name) {
254+
// Missing wasm export, unable to perform global state write
255+
return Ok(HOST_ERROR_NOT_FOUND);
256+
}
257+
258+
Keyspace::PaymentInfo(key_name)
259+
}
260+
};
261+
262+
let global_state_key = match keyspace_to_global_state_key(caller.context(), keyspace) {
263+
Some(global_state_key) => global_state_key,
264+
None => {
265+
// Unknown keyspace received, return error
266+
return Ok(HOST_ERROR_NOT_FOUND);
267+
}
268+
};
269+
270+
let global_state_read_result = caller.context_mut().tracking_copy.read(&global_state_key);
271+
match global_state_read_result {
272+
Ok(Some(_stored_value)) => {
273+
// Produce a prune transform only if value under a given key exists in the global state
274+
caller.context_mut().tracking_copy.prune(global_state_key);
275+
}
276+
Ok(None) => {
277+
// Entry does not exists, and we can't proceed with the prune operation
278+
return Ok(HOST_ERROR_NOT_FOUND);
279+
}
280+
Err(error) => {
281+
// To protect the network against potential non-determinism (i.e. one validator runs out
282+
// of space or just faces I/O issues that other validators may not have) we're simply
283+
// aborting the process, hoping that once the node goes back online issues are resolved
284+
// on the validator side. TODO: We should signal this to the contract
285+
// runtime somehow, and let validator nodes skip execution.
286+
error!(
287+
?error,
288+
?global_state_key,
289+
"Error while attempting a read before removing value; aborting"
290+
);
291+
panic!("Error while attempting a read before removing value; aborting key={global_state_key:?} error={error:?}")
292+
}
293+
}
294+
295+
Ok(HOST_ERROR_SUCCESS)
296+
}
297+
200298
pub fn casper_print<S: GlobalStateReader, E: Executor>(
201299
mut caller: impl Caller<Context = Context<S, E>>,
202300
message_ptr: u32,
@@ -220,7 +318,7 @@ pub fn casper_read<S: GlobalStateReader, E: Executor>(
220318
info_ptr: u32,
221319
cb_alloc: u32,
222320
alloc_ctx: u32,
223-
) -> Result<u32, VMError> {
321+
) -> VMResult<u32> {
224322
let read_cost = caller.context().config.host_function_costs().read;
225323
charge_host_function_call(
226324
&mut caller,
@@ -439,7 +537,7 @@ pub fn casper_create<S: GlobalStateReader + 'static, E: Executor + 'static>(
439537
seed_ptr: u32,
440538
seed_len: u32,
441539
result_ptr: u32,
442-
) -> VMResult<HostResult> {
540+
) -> VMResult<u32> {
443541
let create_cost = caller.context().config.host_function_costs().create;
444542
charge_host_function_call(
445543
&mut caller,
@@ -474,7 +572,7 @@ pub fn casper_create<S: GlobalStateReader + 'static, E: Executor + 'static>(
474572

475573
let seed = if seed_ptr != 0 {
476574
if seed_len != 32 {
477-
return Ok(Err(CallError::NotCallable));
575+
return Ok(CALLEE_NOT_CALLABLE);
478576
}
479577
let seed_bytes = caller.memory_read(seed_ptr, seed_len as usize)?;
480578
let seed_bytes: [u8; 32] = seed_bytes.try_into().unwrap(); // SAFETY: We checked for length.
@@ -494,7 +592,7 @@ pub fn casper_create<S: GlobalStateReader + 'static, E: Executor + 'static>(
494592
Ok(entry_point) => Some(entry_point),
495593
Err(utf8_error) => {
496594
error!(%utf8_error, "entry point name is not a valid utf-8 string; unable to call");
497-
return Ok(Err(CallError::NotCallable));
595+
return Ok(CALLEE_NOT_CALLABLE);
498596
}
499597
}
500598
}
@@ -589,9 +687,7 @@ pub fn casper_create<S: GlobalStateReader + 'static, E: Executor + 'static>(
589687
Ok(uref) => uref,
590688
Err(mint_error) => {
591689
error!(?mint_error, "Failed to create a purse");
592-
return Ok(Err(CallError::CalleeTrapped(
593-
TrapCode::UnreachableCodeReached,
594-
)));
690+
return Ok(CALLEE_TRAPPED);
595691
}
596692
};
597693

@@ -660,7 +756,7 @@ pub fn casper_create<S: GlobalStateReader + 'static, E: Executor + 'static>(
660756
caller.consume_gas(gas_usage.gas_spent())?;
661757

662758
if let Some(host_error) = host_error {
663-
return Ok(Err(host_error));
759+
return Ok(host_error.into_u32());
664760
}
665761

666762
caller
@@ -694,7 +790,7 @@ pub fn casper_create<S: GlobalStateReader + 'static, E: Executor + 'static>(
694790

695791
caller.memory_write(result_ptr, create_result_bytes)?;
696792

697-
Ok(Ok(()))
793+
Ok(CALLEE_SUCCEEDED)
698794
}
699795

700796
#[allow(clippy::too_many_arguments)]
@@ -709,7 +805,7 @@ pub fn casper_call<S: GlobalStateReader + 'static, E: Executor + 'static>(
709805
input_len: u32,
710806
cb_alloc: u32,
711807
cb_ctx: u32,
712-
) -> VMResult<HostResult> {
808+
) -> VMResult<u32> {
713809
let call_cost = caller.context().config.host_function_costs().call;
714810
charge_host_function_call(
715811
&mut caller,
@@ -748,7 +844,7 @@ pub fn casper_call<S: GlobalStateReader + 'static, E: Executor + 'static>(
748844
Ok(entry_point) => entry_point,
749845
Err(utf8_error) => {
750846
error!(%utf8_error, "entry point name is not a valid utf-8 string; unable to call");
751-
return Ok(Err(CallError::NotCallable));
847+
return Ok(CALLEE_NOT_CALLABLE);
752848
}
753849
}
754850
};
@@ -842,7 +938,7 @@ pub fn casper_call<S: GlobalStateReader + 'static, E: Executor + 'static>(
842938

843939
caller.consume_gas(gas_spent)?;
844940

845-
Ok(host_result)
941+
Ok(u32_from_host_result(host_result))
846942
}
847943

848944
pub fn casper_env_caller<S: GlobalStateReader, E: Executor>(
@@ -881,7 +977,7 @@ pub fn casper_env_caller<S: GlobalStateReader, E: Executor>(
881977
pub fn casper_env_transferred_value<S: GlobalStateReader, E: Executor>(
882978
mut caller: impl Caller<Context = Context<S, E>>,
883979
output: u32,
884-
) -> Result<(), VMError> {
980+
) -> VMResult<()> {
885981
let transferred_value_cost = caller
886982
.context()
887983
.config
@@ -1175,7 +1271,7 @@ pub fn casper_upgrade<S: GlobalStateReader + 'static, E: Executor>(
11751271
entry_point_size: u32,
11761272
input_ptr: u32,
11771273
input_size: u32,
1178-
) -> VMResult<HostResult> {
1274+
) -> VMResult<u32> {
11791275
let upgrade_cost = caller.context().config.host_function_costs().upgrade;
11801276
charge_host_function_call(
11811277
&mut caller,
@@ -1203,7 +1299,7 @@ pub fn casper_upgrade<S: GlobalStateReader + 'static, E: Executor>(
12031299
Ok(entry_point) => Some(entry_point),
12041300
Err(utf8_error) => {
12051301
error!(%utf8_error, "entry point name is not a valid utf-8 string; unable to call");
1206-
return Ok(Err(CallError::NotCallable));
1302+
return Ok(CALLEE_NOT_CALLABLE);
12071303
}
12081304
}
12091305
}
@@ -1224,7 +1320,7 @@ pub fn casper_upgrade<S: GlobalStateReader + 'static, E: Executor>(
12241320
let (smart_contract_addr, callee_addressable_entity_key) = match caller.context().callee {
12251321
Key::Account(_account_hash) => {
12261322
error!("Account upgrade is not possible");
1227-
return Ok(Err(CallError::NotCallable));
1323+
return Ok(CALLEE_NOT_CALLABLE);
12281324
}
12291325
addressable_entity_key @ Key::SmartContract(smart_contract_addr) => {
12301326
let smart_contract_key = addressable_entity_key;
@@ -1242,12 +1338,12 @@ pub fn casper_upgrade<S: GlobalStateReader + 'static, E: Executor>(
12421338
?smart_contract_key,
12431339
"Unable to find latest addressible entity hash for contract"
12441340
);
1245-
return Ok(Err(CallError::NotCallable));
1341+
return Ok(CALLEE_NOT_CALLABLE);
12461342
}
12471343
}
12481344
}
12491345
Ok(Some(other)) => panic!("should be smart contract but got {other:?}"),
1250-
Ok(None) => return Ok(Err(CallError::NotCallable)),
1346+
Ok(None) => return Ok(CALLEE_NOT_CALLABLE),
12511347
Err(error) => {
12521348
error!(
12531349
?error,
@@ -1270,7 +1366,7 @@ pub fn casper_upgrade<S: GlobalStateReader + 'static, E: Executor>(
12701366
Ok(Some(other_entity)) => {
12711367
panic!("Unexpected entity type: {other_entity:?}")
12721368
}
1273-
Ok(None) => return Ok(Err(CallError::NotCallable)),
1369+
Ok(None) => return Ok(CALLEE_NOT_CALLABLE),
12741370
Err(error) => {
12751371
panic!("Error while reading from storage; aborting key={callee_addressable_entity_key:?} error={error:?}")
12761372
}
@@ -1347,7 +1443,7 @@ pub fn casper_upgrade<S: GlobalStateReader + 'static, E: Executor>(
13471443
caller.consume_gas(gas_usage.gas_spent())?;
13481444

13491445
if let Some(host_error) = host_error {
1350-
return Ok(Err(host_error));
1446+
return Ok(host_error.into_u32());
13511447
}
13521448

13531449
caller
@@ -1369,12 +1465,12 @@ pub fn casper_upgrade<S: GlobalStateReader + 'static, E: Executor>(
13691465
?preparation_error,
13701466
"Wasm preparation error while performing upgrade"
13711467
);
1372-
return Ok(Err(CallError::NotCallable));
1468+
return Ok(CALLEE_NOT_CALLABLE);
13731469
}
13741470
}
13751471
}
13761472

1377-
Ok(Ok(()))
1473+
Ok(CALLEE_SUCCEEDED)
13781474
}
13791475

13801476
// TODO: Should this be blocktime instead of block_time? [Consistency]

executor/wasm/tests/integration.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ fn call_dummy_host_fn_by_name(
771771
HostFunctionCostsV2 {
772772
read: HostFunction::fixed(1),
773773
write: HostFunction::fixed(1),
774+
remove: HostFunction::fixed(1),
774775
copy_input: HostFunction::fixed(1),
775776
ret: HostFunction::fixed(1),
776777
create: HostFunction::fixed(1),
@@ -851,6 +852,7 @@ fn host_functions_consume_gas() {
851852
assert_consumes_gas("write");
852853
}
853854

855+
#[allow(dead_code)]
854856
fn write_n_bytes_at_limit(
855857
bytes_len: u64,
856858
gas_limit: u64,
@@ -864,6 +866,7 @@ fn write_n_bytes_at_limit(
864866
HostFunctionCostsV2 {
865867
read: HostFunction::fixed(0),
866868
write: HostFunction::fixed(0),
869+
remove: HostFunction::fixed(0),
867870
copy_input: HostFunction::fixed(0),
868871
ret: HostFunction::fixed(0),
869872
create: HostFunction::fixed(0),

executor/wasmer-backend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ wasmer = { version = "5.0.4", default-features = false, features = [
1818
wasmer-compiler-singlepass = "5.0.4"
1919
wasmer-middlewares = "5.0.4"
2020
wasmer-types = "5.0.4"
21+
tracing = "0.1.41"
2122

2223
[dev-dependencies]
2324
wat = "1.227.1"

0 commit comments

Comments
 (0)