Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit c14ff62

Browse files
authored
sp-api: Support nested transactions (#14447)
* sp-api: Support nested transactions Adds support for nested transactions in `sp-api` by using `execute_in_transaction`. This was working until a recent refactor, but this was actually not intended. However, supporting nested transactions is a worthwhile feature to have. So, this pr "brings it back" and adds a test to ensure it will not break. * Make clippy happy * Assert that the runtime api type is not unwind safe * Count number of transactions
1 parent 444bc4f commit c14ff62

File tree

5 files changed

+109
-42
lines changed

5 files changed

+109
-42
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

primitives/api/proc-macro/src/impl_runtime_apis.rs

+46-39
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> {
229229
#crate_::std_enabled! {
230230
pub struct RuntimeApiImpl<Block: #crate_::BlockT, C: #crate_::CallApiAt<Block> + 'static> {
231231
call: &'static C,
232-
commit_on_success: std::cell::RefCell<bool>,
232+
transaction_depth: std::cell::RefCell<u16>,
233233
changes: std::cell::RefCell<#crate_::OverlayedChanges>,
234234
storage_transaction_cache: std::cell::RefCell<
235235
#crate_::StorageTransactionCache<Block, C::StateBackend>
@@ -248,11 +248,15 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> {
248248
) -> R where Self: Sized {
249249
self.start_transaction();
250250

251-
*std::cell::RefCell::borrow_mut(&self.commit_on_success) = false;
251+
*std::cell::RefCell::borrow_mut(&self.transaction_depth) += 1;
252252
let res = call(self);
253-
*std::cell::RefCell::borrow_mut(&self.commit_on_success) = true;
253+
std::cell::RefCell::borrow_mut(&self.transaction_depth)
254+
.checked_sub(1)
255+
.expect("Transactions are opened and closed together; qed");
254256

255-
self.commit_or_rollback(std::matches!(res, #crate_::TransactionOutcome::Commit(_)));
257+
self.commit_or_rollback_transaction(
258+
std::matches!(res, #crate_::TransactionOutcome::Commit(_))
259+
);
256260

257261
res.into_inner()
258262
}
@@ -332,7 +336,7 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> {
332336
) -> #crate_::ApiRef<'a, Self::RuntimeApi> {
333337
RuntimeApiImpl {
334338
call: unsafe { std::mem::transmute(call) },
335-
commit_on_success: true.into(),
339+
transaction_depth: 0.into(),
336340
changes: std::default::Default::default(),
337341
recorder: std::default::Default::default(),
338342
storage_transaction_cache: std::default::Default::default(),
@@ -341,52 +345,47 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> {
341345
}
342346

343347
impl<Block: #crate_::BlockT, C: #crate_::CallApiAt<Block>> RuntimeApiImpl<Block, C> {
344-
fn commit_or_rollback(&self, commit: bool) {
348+
fn commit_or_rollback_transaction(&self, commit: bool) {
345349
let proof = "\
346350
We only close a transaction when we opened one ourself.
347351
Other parts of the runtime that make use of transactions (state-machine)
348352
also balance their transactions. The runtime cannot close client initiated
349353
transactions; qed";
350-
if *std::cell::RefCell::borrow(&self.commit_on_success) {
351-
let res = if commit {
352-
let res = if let Some(recorder) = &self.recorder {
353-
#crate_::ProofRecorder::<Block>::commit_transaction(&recorder)
354-
} else {
355-
Ok(())
356-
};
357354

358-
let res2 = #crate_::OverlayedChanges::commit_transaction(
359-
&mut std::cell::RefCell::borrow_mut(&self.changes)
360-
);
361-
362-
// Will panic on an `Err` below, however we should call commit
363-
// on the recorder and the changes together.
364-
std::result::Result::and(res, std::result::Result::map_err(res2, drop))
355+
let res = if commit {
356+
let res = if let Some(recorder) = &self.recorder {
357+
#crate_::ProofRecorder::<Block>::commit_transaction(&recorder)
365358
} else {
366-
let res = if let Some(recorder) = &self.recorder {
367-
#crate_::ProofRecorder::<Block>::rollback_transaction(&recorder)
368-
} else {
369-
Ok(())
370-
};
359+
Ok(())
360+
};
371361

372-
let res2 = #crate_::OverlayedChanges::rollback_transaction(
373-
&mut std::cell::RefCell::borrow_mut(&self.changes)
374-
);
362+
let res2 = #crate_::OverlayedChanges::commit_transaction(
363+
&mut std::cell::RefCell::borrow_mut(&self.changes)
364+
);
375365

376-
// Will panic on an `Err` below, however we should call commit
377-
// on the recorder and the changes together.
378-
std::result::Result::and(res, std::result::Result::map_err(res2, drop))
366+
// Will panic on an `Err` below, however we should call commit
367+
// on the recorder and the changes together.
368+
std::result::Result::and(res, std::result::Result::map_err(res2, drop))
369+
} else {
370+
let res = if let Some(recorder) = &self.recorder {
371+
#crate_::ProofRecorder::<Block>::rollback_transaction(&recorder)
372+
} else {
373+
Ok(())
379374
};
380375

381-
std::result::Result::expect(res, proof);
382-
}
376+
let res2 = #crate_::OverlayedChanges::rollback_transaction(
377+
&mut std::cell::RefCell::borrow_mut(&self.changes)
378+
);
379+
380+
// Will panic on an `Err` below, however we should call commit
381+
// on the recorder and the changes together.
382+
std::result::Result::and(res, std::result::Result::map_err(res2, drop))
383+
};
384+
385+
std::result::Result::expect(res, proof);
383386
}
384387

385388
fn start_transaction(&self) {
386-
if !*std::cell::RefCell::borrow(&self.commit_on_success) {
387-
return
388-
}
389-
390389
#crate_::OverlayedChanges::start_transaction(
391390
&mut std::cell::RefCell::borrow_mut(&self.changes)
392391
);
@@ -486,7 +485,13 @@ impl<'a> ApiRuntimeImplToApiRuntimeApiImpl<'a> {
486485
params: std::vec::Vec<u8>,
487486
fn_name: &dyn Fn(#crate_::RuntimeVersion) -> &'static str,
488487
) -> std::result::Result<std::vec::Vec<u8>, #crate_::ApiError> {
489-
self.start_transaction();
488+
// If we are not already in a transaction, we should create a new transaction
489+
// and then commit/roll it back at the end!
490+
let transaction_depth = *std::cell::RefCell::borrow(&self.transaction_depth);
491+
492+
if transaction_depth == 0 {
493+
self.start_transaction();
494+
}
490495

491496
let res = (|| {
492497
let version = #crate_::CallApiAt::<__SrApiBlock__>::runtime_version_at(
@@ -510,7 +515,9 @@ impl<'a> ApiRuntimeImplToApiRuntimeApiImpl<'a> {
510515
)
511516
})();
512517

513-
self.commit_or_rollback(std::result::Result::is_ok(&res));
518+
if transaction_depth == 0 {
519+
self.commit_or_rollback_transaction(std::result::Result::is_ok(&res));
520+
}
514521

515522
res
516523
}

primitives/api/test/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ criterion = "0.4.0"
3030
futures = "0.3.21"
3131
log = "0.4.17"
3232
sp-core = { version = "21.0.0", path = "../../core" }
33+
static_assertions = "1.1.0"
3334

3435
[[bench]]
3536
name = "bench"

primitives/api/test/tests/runtime_calls.rs

+51-3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@
1515
// See the License for the specific language governing permissions and
1616
// limitations under the License.
1717

18-
use sp_api::{Core, ProvideRuntimeApi};
19-
use sp_runtime::traits::{HashFor, Header as HeaderT};
18+
use std::panic::UnwindSafe;
19+
20+
use sp_api::{ApiExt, Core, ProvideRuntimeApi};
21+
use sp_runtime::{
22+
traits::{HashFor, Header as HeaderT},
23+
TransactionOutcome,
24+
};
2025
use sp_state_machine::{
2126
create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionStrategy,
2227
};
2328
use substrate_test_runtime_client::{
2429
prelude::*,
2530
runtime::{Block, Header, TestAPI, Transfer},
26-
DefaultTestClientBuilderExt, TestClientBuilder,
31+
DefaultTestClientBuilderExt, TestClient, TestClientBuilder,
2732
};
2833

2934
use codec::Encode;
@@ -187,3 +192,46 @@ fn disable_logging_works() {
187192
assert!(output.contains("Logging from native works"));
188193
}
189194
}
195+
196+
// Certain logic like the transaction handling is not unwind safe.
197+
//
198+
// Ensure that the type is not unwind safe!
199+
static_assertions::assert_not_impl_any!(<TestClient as ProvideRuntimeApi<_>>::Api: UnwindSafe);
200+
201+
#[test]
202+
fn ensure_transactional_works() {
203+
const KEY: &[u8] = b"test";
204+
205+
let client = TestClientBuilder::new().build();
206+
let best_hash = client.chain_info().best_hash;
207+
208+
let runtime_api = client.runtime_api();
209+
runtime_api.execute_in_transaction(|api| {
210+
api.write_key_value(best_hash, KEY.to_vec(), vec![1, 2, 3], false).unwrap();
211+
212+
api.execute_in_transaction(|api| {
213+
api.write_key_value(best_hash, KEY.to_vec(), vec![1, 2, 3, 4], false).unwrap();
214+
215+
TransactionOutcome::Commit(())
216+
});
217+
218+
TransactionOutcome::Commit(())
219+
});
220+
221+
let changes = runtime_api
222+
.into_storage_changes(&client.state_at(best_hash).unwrap(), best_hash)
223+
.unwrap();
224+
assert_eq!(changes.main_storage_changes[0].1, Some(vec![1, 2, 3, 4]));
225+
226+
let runtime_api = client.runtime_api();
227+
runtime_api.execute_in_transaction(|api| {
228+
assert!(api.write_key_value(best_hash, KEY.to_vec(), vec![1, 2, 3], true).is_err());
229+
230+
TransactionOutcome::Commit(())
231+
});
232+
233+
let changes = runtime_api
234+
.into_storage_changes(&client.state_at(best_hash).unwrap(), best_hash)
235+
.unwrap();
236+
assert_eq!(changes.main_storage_changes[0].1, Some(vec![1, 2, 3]));
237+
}

test-utils/runtime/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ decl_runtime_apis! {
217217
fn do_trace_log();
218218
/// Verify the given signature, public & message bundle.
219219
fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec<u8>) -> bool;
220+
/// Write the given `value` under the given `key` into the storage and then optional panic.
221+
fn write_key_value(key: Vec<u8>, value: Vec<u8>, panic: bool);
220222
}
221223
}
222224

@@ -606,6 +608,14 @@ impl_runtime_apis! {
606608
fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec<u8>) -> bool {
607609
sp_io::crypto::ed25519_verify(&sig, &message, &public)
608610
}
611+
612+
fn write_key_value(key: Vec<u8>, value: Vec<u8>, panic: bool) {
613+
sp_io::storage::set(&key, &value);
614+
615+
if panic {
616+
panic!("I'm just following my master");
617+
}
618+
}
609619
}
610620

611621
impl sp_consensus_aura::AuraApi<Block, AuraId> for Runtime {

0 commit comments

Comments
 (0)