Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move transaction verification into atomic_speculate #2388

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 2 additions & 12 deletions ledger/src/check_next_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

use super::*;

use rand::{rngs::StdRng, SeedableRng};

impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
/// Checks the given block is valid next block.
pub fn check_next_block<R: CryptoRng + Rng>(&self, block: &Block<N>, rng: &mut R) -> Result<()> {
Expand All @@ -38,14 +36,6 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
}
}

// Ensure each transaction is well-formed and unique.
let transactions = block.transactions();
let rngs = (0..transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::<Vec<_>>();
cfg_iter!(transactions).zip(rngs).try_for_each(|(transaction, mut rng)| {
self.check_transaction_basic(transaction, transaction.to_rejected_id()?, &mut rng)
.map_err(|e| anyhow!("Invalid transaction found in the transactions list: {e}"))
})?;

// TODO (howardwu): Remove this after moving the total supply into credits.aleo.
{
// // Retrieve the latest total supply.
Expand Down Expand Up @@ -78,9 +68,9 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
block.previous_hash(),
)?;

// Ensure speculation over the unconfirmed transactions is correct.
// Ensure speculation over the unconfirmed transactions is correct and ensure each transaction is well-formed and unique.
let ratified_finalize_operations =
self.vm.check_speculate(state, block.ratifications(), block.solutions(), block.transactions())?;
self.vm.check_speculate(state, block.ratifications(), block.solutions(), block.transactions(), rng)?;

// Retrieve the committee lookback.
let committee_lookback = {
Expand Down
151 changes: 85 additions & 66 deletions synthesizer/src/vm/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,63 +41,23 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
) -> Result<(Ratifications<N>, Transactions<N>, Vec<N::TransactionID>, Vec<FinalizeOperation<N>>)> {
let timer = timer!("VM::speculate");

// Collect the candidate transactions into a vector.
let candidate_transactions: Vec<_> = candidate_transactions.collect::<Vec<_>>();
let candidate_transaction_ids: Vec<_> = candidate_transactions.iter().map(|tx| tx.id()).collect();

// If the transactions are not part of the genesis block, ensure each transaction is well-formed and unique. Abort any transactions that are not.
let (verified_transactions, verification_aborted_transactions) =
match self.block_store().find_block_height_from_state_root(self.block_store().current_state_root())? {
// If the current state root does not exist in the block store, then the genesis block has not been introduced yet.
None => (candidate_transactions, vec![]),
// Verify transactions for all non-genesis cases.
_ => {
let rngs =
(0..candidate_transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::<Vec<_>>();
// Verify the transactions and collect the error message if there is one.
cfg_into_iter!(candidate_transactions).zip(rngs).partition_map(|(transaction, mut rng)| {
// Abort the transaction if it is a fee transaction.
if transaction.is_fee() {
return Either::Right((
transaction,
"Fee transactions are not allowed in speculate".to_string(),
));
}
// Verify the transaction.
match self.check_transaction(transaction, None, &mut rng) {
Ok(_) => Either::Left(transaction),
Err(e) => Either::Right((transaction, e.to_string())),
}
})
}
};

// Performs a **dry-run** over the list of ratifications, solutions, and transactions.
let (ratifications, confirmed_transactions, speculation_aborted_transactions, ratified_finalize_operations) =
self.atomic_speculate(
let (ratifications, confirmed_transactions, aborted_transactions, ratified_finalize_operations) = self
.atomic_speculate(
state,
coinbase_reward,
candidate_ratifications,
candidate_solutions,
verified_transactions.into_iter(),
candidate_transactions,
rng,
)?;

// Get the aborted transaction ids.
let verification_aborted_transaction_ids = verification_aborted_transactions.iter().map(|(tx, e)| (tx.id(), e));
let speculation_aborted_transaction_ids = speculation_aborted_transactions.iter().map(|(tx, e)| (tx.id(), e));
let unordered_aborted_transaction_ids: IndexMap<N::TransactionID, &String> =
verification_aborted_transaction_ids.chain(speculation_aborted_transaction_ids).collect();

// Filter and order the aborted transaction ids according to candidate_transactions
let aborted_transaction_ids: Vec<_> = candidate_transaction_ids
.into_iter()
.filter_map(|tx_id| {
unordered_aborted_transaction_ids.get(&tx_id).map(|error| {
warn!("Speculation safely aborted a transaction - {error} ({tx_id})");
tx_id
})
})
.collect();
// Convert the aborted transactions into aborted transaction IDs.
let mut aborted_transaction_ids = Vec::with_capacity(aborted_transactions.len());
for (tx, error) in aborted_transactions {
warn!("Speculation safely aborted a transaction - {error} ({})", tx.id());
aborted_transaction_ids.push(tx.id());
}

finish!(timer, "Finished dry-run of the transactions");

Expand All @@ -114,12 +74,13 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
///
/// Returns the finalize operations from pre-ratify and post-ratify.
#[inline]
pub fn check_speculate(
pub fn check_speculate<R: Rng + CryptoRng>(
&self,
state: FinalizeGlobalState,
ratifications: &Ratifications<N>,
solutions: &Solutions<N>,
transactions: &Transactions<N>,
rng: &mut R,
) -> Result<Vec<FinalizeOperation<N>>> {
let timer = timer!("VM::check_speculate");

Expand All @@ -131,7 +92,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {

// Performs a **dry-run** over the list of ratifications, solutions, and transactions.
let (speculate_ratifications, confirmed_transactions, aborted_transactions, ratified_finalize_operations) =
self.atomic_speculate(state, None, candidate_ratifications, solutions, candidate_transactions.iter())?;
self.atomic_speculate(state, None, candidate_ratifications, solutions, candidate_transactions.iter(), rng)?;

// Ensure the ratifications after speculation match.
if ratifications != &speculate_ratifications {
Expand Down Expand Up @@ -192,19 +153,51 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
/// - If `coinbase_reward = Some(coinbase_reward)`, then the method will append a
/// `Ratify::BlockReward(block_reward)` and `Ratify::PuzzleReward(puzzle_reward)`
/// to the front of the `ratifications` list.
fn atomic_speculate<'a>(
fn atomic_speculate<'a, R: Rng + CryptoRng>(
&self,
state: FinalizeGlobalState,
coinbase_reward: Option<u64>,
ratifications: Vec<Ratify<N>>,
solutions: &Solutions<N>,
transactions: impl ExactSizeIterator<Item = &'a Transaction<N>>,
rng: &mut R,
) -> Result<(
Ratifications<N>,
Vec<ConfirmedTransaction<N>>,
Vec<(Transaction<N>, String)>,
Vec<FinalizeOperation<N>>,
)> {
// Collect the candidate transactions into a vector.
let candidate_transactions: Vec<_> = transactions.collect::<Vec<_>>();
let candidate_transaction_ids: Vec<_> = candidate_transactions.iter().map(|tx| tx.id()).collect();

// If the transactions are not part of the genesis block, ensure each transaction is well-formed and unique. Abort any transactions that are not.
let (verified_transactions, verification_aborted_transactions) =
match self.block_store().find_block_height_from_state_root(self.block_store().current_state_root())? {
// If the current state root does not exist in the block store, then the genesis block has not been introduced yet.
None => (candidate_transactions, vec![]),
// Verify transactions for all non-genesis cases.
_ => {
let rngs =
(0..candidate_transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::<Vec<_>>();
// Verify the transactions and collect the error message if there is one.
cfg_into_iter!(candidate_transactions).zip(rngs).partition_map(|(transaction, mut rng)| {
// Abort the transaction if it is a fee transaction.
if transaction.is_fee() {
return Either::Right((
transaction.clone(),
"Fee transactions are not allowed in speculate".to_string(),
));
}
// Verify the transaction.
match self.check_transaction(transaction, None, &mut rng) {
Ok(_) => Either::Left(transaction),
Err(e) => Either::Right((transaction.clone(), e.to_string())),
}
})
}
};

// Acquire the atomic lock, which is needed to ensure this function is not called concurrently
// with other `atomic_finalize!` macro calls, which will cause a `bail!` to be triggered erroneously.
// Note: This lock must be held for the entire scope of the call to `atomic_finalize!`.
Expand All @@ -215,7 +208,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
// Retrieve the number of solutions.
let num_solutions = solutions.len();
// Retrieve the number of transactions.
let num_transactions = transactions.len();
let num_transactions = verified_transactions.len();

// Perform the finalize operation on the preset finalize mode.
atomic_finalize!(self.finalize_store(), FinalizeMode::DryRun, {
Expand Down Expand Up @@ -287,8 +280,8 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
// Initialize the list of created transition public keys.
let mut tpks: IndexSet<Group<N>> = IndexSet::new();

// Finalize the transactions.
'outer: for transaction in transactions {
// Finalize the verified transactions.
'outer: for transaction in verified_transactions {
// Ensure the number of confirmed transactions does not exceed the maximum.
// Upon reaching the maximum number of confirmed transactions, all remaining transactions are aborted.
if confirmed.len() >= Self::MAXIMUM_CONFIRMED_TRANSACTIONS {
Expand Down Expand Up @@ -541,11 +534,26 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
return Err("Failed to construct the ratifications after speculation".to_string());
};

/* Construct the aborted transactions */

// Combined the aborted transactions from verification and speculation.
let mut unordered_aborted_transactions: IndexMap<_, _> = verification_aborted_transactions
.into_iter()
.chain(aborted)
.map(|(tx, error)| (tx.id(), (tx, error)))
.collect();

// Filter and order the aborted transaction ids according to candidate_transactions
let aborted_transactions: Vec<_> = candidate_transaction_ids
.into_iter()
.filter_map(|tx_id| unordered_aborted_transactions.remove(&tx_id))
.collect();

finish!(timer);

// On return, 'atomic_finalize!' will abort the batch, and return the ratifications,
// confirmed & aborted transactions, and finalize operations from pre-ratify and post-ratify.
Ok((ratifications, confirmed, aborted, ratified_finalize_operations))
Ok((ratifications, confirmed, aborted_transactions, ratified_finalize_operations))
})
}

Expand Down Expand Up @@ -1473,7 +1481,14 @@ finalize transfer_public:

// Ensure the dry run of the redeployment will cause a reject transaction to be created.
let (_, candidate_transactions, aborted_transaction_ids, _) = vm
.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), [deployment_transaction].iter())
.atomic_speculate(
sample_finalize_state(1),
None,
vec![],
&None.into(),
[deployment_transaction].iter(),
rng,
)
.unwrap();
assert_eq!(candidate_transactions.len(), 1);
assert!(matches!(candidate_transactions[0], ConfirmedTransaction::RejectedDeploy(..)));
Expand Down Expand Up @@ -1575,8 +1590,9 @@ finalize transfer_public:
// Transfer_20 -> Balance = 20 - 20 = 0
{
let transactions = [mint_10.clone(), transfer_10.clone(), transfer_20.clone()];
let (_, confirmed_transactions, aborted_transaction_ids, _) =
vm.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter()).unwrap();
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter(), rng)
.unwrap();

// Assert that all the transactions are accepted.
assert_eq!(confirmed_transactions.len(), 3);
Expand All @@ -1595,8 +1611,9 @@ finalize transfer_public:
// Transfer_30 -> Balance = 30 - 30 = 0
{
let transactions = [transfer_20.clone(), mint_10.clone(), mint_20.clone(), transfer_30.clone()];
let (_, confirmed_transactions, aborted_transaction_ids, _) =
vm.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter()).unwrap();
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter(), rng)
.unwrap();

// Assert that all the transactions are accepted.
assert_eq!(confirmed_transactions.len(), 4);
Expand All @@ -1615,8 +1632,9 @@ finalize transfer_public:
// Transfer_10 -> Balance = 0 - 10 = -10 (should be rejected)
{
let transactions = [transfer_20.clone(), transfer_10.clone()];
let (_, confirmed_transactions, aborted_transaction_ids, _) =
vm.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter()).unwrap();
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter(), rng)
.unwrap();

// Assert that the accepted and rejected transactions are correct.
assert_eq!(confirmed_transactions.len(), 2);
Expand All @@ -1639,8 +1657,9 @@ finalize transfer_public:
// Transfer_10 -> Balance = 10 - 10 = 0
{
let transactions = [mint_20.clone(), transfer_30.clone(), transfer_20.clone(), transfer_10.clone()];
let (_, confirmed_transactions, aborted_transaction_ids, _) =
vm.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter()).unwrap();
let (_, confirmed_transactions, aborted_transaction_ids, _) = vm
.atomic_speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter(), rng)
.unwrap();

// Assert that the accepted and rejected transactions are correct.
assert_eq!(confirmed_transactions.len(), 4);
Expand Down