When upgrading our codebase on top of substrate's polkadot-v0.9.42
, we discovered it is no longer possible to make nested request to fn execute_in_transaction(call)
, from within the call
.
Given in polkadot-v0.9.42
:
fn execute_in_transaction<F: FnOnce(&Self) -> #crate_::TransactionOutcome<R>, R>(
&self,
call: F,
) -> R where Self: Sized {
self.start_transaction();
*std::cell::RefCell::borrow_mut(&self.commit_on_success) = false;
let res = call(self);
*std::cell::RefCell::borrow_mut(&self.commit_on_success) = true;
self.commit_or_rollback(std::matches!(res, #crate_::TransactionOutcome::Commit(_)));
res.into_inner()
}
fn start_transaction(&self) {
if !*std::cell::RefCell::borrow(&self.commit_on_success) {
return
}
#crate_::OverlayedChanges::start_transaction(
&mut std::cell::RefCell::borrow_mut(&self.changes)
);
if let Some(recorder) = &self.recorder {
#crate_::ProofRecorder::<Block>::start_transaction(&recorder);
}
}
We get:
execute_in_transaction {
start_transaction
commit_on_success = false
execute_in_transaction {
start_transaction <- returns, no transaction created in overlay
res = ...
commit_on_success = true
commit_or_rollback(res) <- closes the outer transaction
}
commit_on_success = true
commit_or_rollback(res) <- fails on `NoOpenTransaction`
}
Q1: Are these changes intentional to disallow such nested behaviour? Or is this something that might be supported in the future ?
The problem:
In Mangata, transaction included into block N
is executed in block N+1
because of
shuffling(VER prevention) mechanism. Instead of pushing extrinsics into the current block, we also build the next one, apply the extrinsics there, get the list of valid ones, and rollback the entire new block changes.
eg.
authorship.propose {
...
apply_previous_block_extrinsics
valid_txs = execute_in_transaction {
init_new_block
txs = []
for tx in tx_pool {
execute_in_transaction {
apply_extrinsic_with_context(tx)
} is OK ? txs.push(tx)
}
TransactionOutcome::Rollback(txs)
}
create_enqueue_txs_inherent(valid_txs)
continue building the block as usual...
}
Without modifying the impl_runtime_apis
, our initial idea was to instantiate another runtime_api()
and apply changes to both of them until the next block validation step. We would use the duplicate runtime api to verify a new block with trxs and just discard the changes.
Such solution would have some performance implications having to apply changes twice.
Ideally, we would imagine creating a disposable snapshot of the current runtime to validate next block execution against.
Q2: Are there currently any other substrate apis we could leverage to achieve our goal?