Skip to content

Commit ca6ce3c

Browse files
authored
Merge pull request #2975 from ljedrz/feat/replace_vm_locks_with_channel
[Feat] Ensure sequential processing of potential Ledger writes
2 parents ce07a2c + c4f3d7e commit ca6ce3c

File tree

11 files changed

+323
-60
lines changed

11 files changed

+323
-60
lines changed

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.

ledger/src/advance.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ use anyhow::Context;
1919

2020
impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
2121
/// Returns a candidate for the next block in the ledger, using a committed subdag and its transmissions.
22+
///
23+
/// # Panics
24+
/// This function panics if called from an async context.
2225
pub fn prepare_advance_to_next_quorum_block<R: Rng + CryptoRng>(
2326
&self,
2427
subdag: Subdag<N>,
@@ -51,6 +54,9 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
5154
}
5255

5356
/// Returns a candidate for the next block in the ledger.
57+
///
58+
/// # Panics
59+
/// This function panics if called from an async context.
5460
pub fn prepare_advance_to_next_beacon_block<R: Rng + CryptoRng>(
5561
&self,
5662
private_key: &PrivateKey<N>,
@@ -92,6 +98,9 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
9298
}
9399

94100
/// Adds the given block as the next block in the ledger.
101+
///
102+
/// # Panics
103+
/// This function panics if called from an async context.
95104
pub fn advance_to_next_block(&self, block: &Block<N>) -> Result<()> {
96105
// Acquire the write lock on the current block.
97106
let mut current_block = self.current_block.write();
@@ -212,6 +221,9 @@ where
212221

213222
impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
214223
/// Constructs a block template for the next block in the ledger.
224+
///
225+
/// # Panics
226+
/// This function panics if called from an async context.
215227
#[allow(clippy::type_complexity)]
216228
fn construct_block_template<R: Rng + CryptoRng>(
217229
&self,

ledger/src/check_next_block.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ use anyhow::{Context, bail};
2121

2222
impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
2323
/// Checks the given block is valid next block.
24+
///
25+
/// # Panics
26+
/// This function panics if called from an async context.
2427
pub fn check_next_block<R: CryptoRng + Rng>(&self, block: &Block<N>, rng: &mut R) -> Result<()> {
2528
let height = block.height();
2629
let latest_block = self.latest_block();

ledger/src/test_helpers/chain_builder.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ impl<N: Network> TestChainBuilder<N> {
194194
}
195195

196196
/// Create multiple blocks, with additional parameters.
197+
///
198+
/// # Panics
199+
/// This function panics if called from an async context.
197200
pub fn generate_blocks_with_opts(
198201
&mut self,
199202
num_blocks: usize,

synthesizer/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ optional = true
148148
workspace = true
149149
features = [ "preserve_order" ]
150150

151+
[dependencies.tokio]
152+
version = "1"
153+
features = [ "sync" ]
154+
151155
[dependencies.tracing]
152156
workspace = true
153157

synthesizer/src/vm/finalize.rs

Lines changed: 86 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
2626
/// Returns the confirmed transactions, aborted transaction IDs,
2727
/// and finalize operations from pre-ratify and post-ratify.
2828
///
29-
/// Note: This method is used to create a new block (including the genesis block).
29+
/// # Note
30+
/// This method is used to create a new block (including the genesis block).
3031
/// - If `coinbase_reward = None`, then the `ratifications` will not be modified.
3132
/// - If `coinbase_reward = Some(coinbase_reward)`, then the method will append a
3233
/// `Ratify::BlockReward(block_reward)` and `Ratify::PuzzleReward(puzzle_reward)`
3334
/// to the front of the `ratifications` list.
35+
///
36+
/// # Panics
37+
/// This function panics if called from an async context.
3438
#[inline]
3539
#[allow(clippy::too_many_arguments)]
3640
pub fn speculate<'a, R: Rng + CryptoRng>(
@@ -67,8 +71,8 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
6771
time_since_last_block,
6872
coinbase_reward,
6973
candidate_ratifications,
70-
candidate_solutions,
71-
verified_transactions.into_iter(),
74+
candidate_solutions.clone(),
75+
verified_transactions.into_iter().cloned().collect(),
7276
)?;
7377

7478
// Get the aborted transaction ids.
@@ -103,6 +107,9 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
103107
/// This function also ensure that the given transactions are well-formed and unique.
104108
///
105109
/// Returns the finalize operations from pre-ratify and post-ratify.
110+
///
111+
/// # Panics
112+
/// This function panics if called from an async context.
106113
#[inline]
107114
pub fn check_speculate<R: Rng + CryptoRng>(
108115
&self,
@@ -138,8 +145,8 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
138145
time_since_last_block,
139146
None,
140147
candidate_ratifications,
141-
solutions,
142-
candidate_transactions.iter(),
148+
solutions.clone(),
149+
candidate_transactions,
143150
)?;
144151

145152
// Ensure the ratifications after speculation match.
@@ -196,29 +203,66 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
196203
/// Returns the ratifications, confirmed transactions, aborted transactions,
197204
/// and finalize operations from pre-ratify and post-ratify.
198205
///
199-
/// Note: This method is used by `VM::speculate` and `VM::check_speculate`.
206+
/// # Note
207+
/// This method is used by `VM::speculate` and `VM::check_speculate`.
200208
/// - If `coinbase_reward = None`, then the `ratifications` will not be modified.
201209
/// - If `coinbase_reward = Some(coinbase_reward)`, then the method will append a
202210
/// `Ratify::BlockReward(block_reward)` and `Ratify::PuzzleReward(puzzle_reward)`
203211
/// to the front of the `ratifications` list.
204-
fn atomic_speculate<'a>(
212+
///
213+
/// # Panics
214+
/// This function panics if called from an async context.
215+
fn atomic_speculate(
205216
&self,
206217
state: FinalizeGlobalState,
207218
time_since_last_block: i64,
208219
coinbase_reward: Option<u64>,
209220
ratifications: Vec<Ratify<N>>,
210-
solutions: &Solutions<N>,
211-
transactions: impl ExactSizeIterator<Item = &'a Transaction<N>>,
221+
solutions: Solutions<N>,
222+
transactions: Vec<Transaction<N>>,
223+
) -> Result<(
224+
Ratifications<N>,
225+
Vec<ConfirmedTransaction<N>>,
226+
Vec<(Transaction<N>, String)>,
227+
Vec<FinalizeOperation<N>>,
228+
)> {
229+
let sequential_op = SequentialOperation::AtomicSpeculate(
230+
state,
231+
time_since_last_block,
232+
coinbase_reward,
233+
ratifications,
234+
solutions,
235+
transactions,
236+
);
237+
let Some(SequentialOperationResult::AtomicSpeculate(ret)) = self.run_sequential_operation(sequential_op) else {
238+
bail!("Already shutting down");
239+
};
240+
241+
ret
242+
}
243+
244+
/// Internal function called when invoking [`Self::atomic_speculate`].
245+
///
246+
/// # Note
247+
/// This function must only be called from the sequential operation thread.
248+
///
249+
/// # Panics
250+
/// This function panics if not called from the sequential operation thread.
251+
pub(crate) fn atomic_speculate_inner(
252+
&self,
253+
state: FinalizeGlobalState,
254+
time_since_last_block: i64,
255+
coinbase_reward: Option<u64>,
256+
ratifications: Vec<Ratify<N>>,
257+
solutions: Solutions<N>,
258+
transactions: Vec<Transaction<N>>,
212259
) -> Result<(
213260
Ratifications<N>,
214261
Vec<ConfirmedTransaction<N>>,
215262
Vec<(Transaction<N>, String)>,
216263
Vec<FinalizeOperation<N>>,
217264
)> {
218-
// Acquire the atomic lock, which is needed to ensure this function is not called concurrently
219-
// with other `atomic_finalize!` macro calls, which will cause a `bail!` to be triggered erroneously.
220-
// Note: This lock must be held for the entire scope of the call to `atomic_finalize!`.
221-
let _atomic_lock = self.atomic_lock.lock();
265+
self.ensure_sequential_processing();
222266

223267
let timer = timer!("VM::atomic_speculate");
224268

@@ -319,7 +363,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
319363

320364
// Determine if the transaction should be aborted.
321365
if let Some(reason) = self.should_abort_transaction(
322-
transaction,
366+
&transaction,
323367
&transition_ids,
324368
&input_ids,
325369
&output_ids,
@@ -336,7 +380,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
336380
// Process the transaction in an isolated atomic batch.
337381
// - If the transaction succeeds, the finalize operations are stored.
338382
// - If the transaction fails, the atomic batch is aborted and no finalize operations are stored.
339-
let outcome = match transaction {
383+
let outcome = match &transaction {
340384
// The finalize operation here involves appending the 'stack',
341385
// and adding the program to the finalize tree.
342386
Transaction::Deploy(_, _, program_owner, deployment, fee) => {
@@ -535,7 +579,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
535579
let post_ratifications = reward_ratifications.iter().chain(post_ratifications);
536580

537581
// Process the post-ratifications.
538-
match Self::atomic_post_ratify::<false>(&self.puzzle, store, state, post_ratifications, solutions) {
582+
match Self::atomic_post_ratify::<false>(&self.puzzle, store, state, post_ratifications, &solutions) {
539583
// Store the finalize operations from the post-ratify.
540584
Ok(operations) => ratified_finalize_operations.extend(operations),
541585
// Note: This will abort the entire atomic batch.
@@ -570,10 +614,9 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
570614
solutions: &Solutions<N>,
571615
transactions: &Transactions<N>,
572616
) -> Result<Vec<FinalizeOperation<N>>> {
573-
// Acquire the atomic lock, which is needed to ensure this function is not called concurrently
574-
// with other `atomic_finalize!` macro calls, which will cause a `bail!` to be triggered erroneously.
575-
// Note: This lock must be held for the entire scope of the call to `atomic_finalize!`.
576-
let _atomic_lock = self.atomic_lock.lock();
617+
// The tests may run this method ad-hoc, outside of the context of add_next_block.
618+
#[cfg(not(test))]
619+
self.ensure_sequential_processing();
577620

578621
let timer = timer!("VM::atomic_finalize");
579622

@@ -1848,8 +1891,8 @@ finalize transfer_public:
18481891
CurrentNetwork::BLOCK_TIME as i64,
18491892
None,
18501893
vec![],
1851-
&None.into(),
1852-
[deployment_transaction].iter(),
1894+
None.into(),
1895+
vec![deployment_transaction],
18531896
)
18541897
.unwrap();
18551898
assert_eq!(candidate_transactions.len(), 1);
@@ -1937,15 +1980,15 @@ finalize transfer_public:
19371980
vm.check_transaction(&bond_validator_transaction, None, rng).unwrap();
19381981

19391982
// Speculate on the transactions.
1940-
let transactions = [bond_validator_transaction.clone()];
1983+
let transactions = vec![bond_validator_transaction.clone()];
19411984
let (_, confirmed_transactions, _, _) = vm
19421985
.atomic_speculate(
19431986
sample_finalize_state(1),
19441987
CurrentNetwork::BLOCK_TIME as i64,
19451988
None,
19461989
vec![],
1947-
&None.into(),
1948-
transactions.iter(),
1990+
None.into(),
1991+
transactions,
19491992
)
19501993
.unwrap();
19511994

@@ -2063,15 +2106,15 @@ finalize transfer_public:
20632106
vm.check_transaction(&bond_validator_transaction, None, rng).unwrap();
20642107

20652108
// Speculate on the transactions.
2066-
let transactions = [bond_validator_transaction.clone()];
2109+
let transactions = vec![bond_validator_transaction.clone()];
20672110
let (_, confirmed_transactions, _, _) = vm
20682111
.atomic_speculate(
20692112
sample_finalize_state(1),
20702113
CurrentNetwork::BLOCK_TIME as i64,
20712114
None,
20722115
vec![],
2073-
&None.into(),
2074-
transactions.iter(),
2116+
None.into(),
2117+
transactions,
20752118
)
20762119
.unwrap();
20772120

@@ -2083,15 +2126,15 @@ finalize transfer_public:
20832126
);
20842127

20852128
// Speculate on the transactions.
2086-
let transactions = [bond_validator_transaction.clone()];
2129+
let transactions = vec![bond_validator_transaction.clone()];
20872130
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
20882131
.atomic_speculate(
20892132
sample_finalize_state(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V3).unwrap()),
20902133
CurrentNetwork::BLOCK_TIME as i64,
20912134
None,
20922135
vec![],
2093-
&None.into(),
2094-
transactions.iter(),
2136+
None.into(),
2137+
transactions,
20952138
)
20962139
.unwrap();
20972140

@@ -2194,15 +2237,15 @@ finalize transfer_public:
21942237
// Transfer_10 -> Balance = 30 - 10 = 20
21952238
// Transfer_20 -> Balance = 20 - 20 = 0
21962239
{
2197-
let transactions = [mint_10.clone(), transfer_10.clone(), transfer_20.clone()];
2240+
let transactions = vec![mint_10.clone(), transfer_10.clone(), transfer_20.clone()];
21982241
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
21992242
.atomic_speculate(
22002243
sample_finalize_state(1),
22012244
CurrentNetwork::BLOCK_TIME as i64,
22022245
None,
22032246
vec![],
2204-
&None.into(),
2205-
transactions.iter(),
2247+
None.into(),
2248+
transactions,
22062249
)
22072250
.unwrap();
22082251

@@ -2222,15 +2265,15 @@ finalize transfer_public:
22222265
// Mint_20 -> Balance = 10 + 20 = 30
22232266
// Transfer_30 -> Balance = 30 - 30 = 0
22242267
{
2225-
let transactions = [transfer_20.clone(), mint_10.clone(), mint_20.clone(), transfer_30.clone()];
2268+
let transactions = vec![transfer_20.clone(), mint_10.clone(), mint_20.clone(), transfer_30.clone()];
22262269
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
22272270
.atomic_speculate(
22282271
sample_finalize_state(1),
22292272
CurrentNetwork::BLOCK_TIME as i64,
22302273
None,
22312274
vec![],
2232-
&None.into(),
2233-
transactions.iter(),
2275+
None.into(),
2276+
transactions,
22342277
)
22352278
.unwrap();
22362279

@@ -2250,15 +2293,15 @@ finalize transfer_public:
22502293
// Transfer_20 -> Balance = 20 - 20 = 0
22512294
// Transfer_10 -> Balance = 0 - 10 = -10 (should be rejected)
22522295
{
2253-
let transactions = [transfer_20.clone(), transfer_10.clone()];
2296+
let transactions = vec![transfer_20.clone(), transfer_10.clone()];
22542297
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
22552298
.atomic_speculate(
22562299
sample_finalize_state(1),
22572300
CurrentNetwork::BLOCK_TIME as i64,
22582301
None,
22592302
vec![],
2260-
&None.into(),
2261-
transactions.iter(),
2303+
None.into(),
2304+
transactions,
22622305
)
22632306
.unwrap();
22642307

@@ -2282,15 +2325,15 @@ finalize transfer_public:
22822325
// Transfer_20 -> Balance = 10 - 20 = -10 (should be rejected)
22832326
// Transfer_10 -> Balance = 10 - 10 = 0
22842327
{
2285-
let transactions = [mint_20.clone(), transfer_30.clone(), transfer_20.clone(), transfer_10.clone()];
2328+
let transactions = vec![mint_20.clone(), transfer_30.clone(), transfer_20.clone(), transfer_10.clone()];
22862329
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
22872330
.atomic_speculate(
22882331
sample_finalize_state(1),
22892332
CurrentNetwork::BLOCK_TIME as i64,
22902333
None,
22912334
vec![],
2292-
&None.into(),
2293-
transactions.iter(),
2335+
None.into(),
2336+
transactions,
22942337
)
22952338
.unwrap();
22962339

synthesizer/src/vm/helpers/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ mod macros;
2525

2626
mod rewards;
2727
pub use rewards::*;
28+
29+
mod sequential_op;
30+
pub use sequential_op::*;

0 commit comments

Comments
 (0)