fix(rpc): Avoid selecting duplicate transactions in block templates (#6006)

* Removes candidate_tx from candidate_txs

- Creates a new WeightedIndex instead of updating the weights

- Logs tx hashes and unpaid_actions in getblocktemplate

* Applies suggestions from PR review.
This commit is contained in:
Arya 2023-01-22 21:48:45 -05:00 committed by GitHub
parent 002782b5b7
commit 53b446668e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 17 deletions

View File

@ -568,6 +568,14 @@ where
let next_block_height =
(chain_tip_and_local_time.tip_height + 1).expect("tip is far below Height::MAX");
tracing::debug!(
mempool_tx_hashes = ?mempool_txs
.iter()
.map(|tx| tx.transaction.id.mined_id())
.collect::<Vec<_>>(),
"selecting transactions for the template from the mempool"
);
// Randomly select some mempool transactions.
//
// TODO: sort these transactions to match zcashd's order, to make testing easier.
@ -580,6 +588,14 @@ where
)
.await;
tracing::debug!(
selected_mempool_tx_hashes = ?mempool_txs
.iter()
.map(|tx| tx.transaction.id.mined_id())
.collect::<Vec<_>>(),
"selected transactions for the template from the mempool"
);
// - After this point, the template only depends on the previously fetched data.
let response = GetBlockTemplate::new(

View File

@ -244,6 +244,14 @@ impl GetBlockTemplate {
.map(ToString::to_string)
.collect();
tracing::debug!(
selected_txs = ?mempool_txs
.iter()
.map(|tx| (tx.transaction.id.mined_id(), tx.unpaid_actions))
.collect::<Vec<_>>(),
"creating template ... "
);
GetBlockTemplate {
capabilities,

View File

@ -53,7 +53,7 @@ pub async fn select_mempool_transactions(
fake_coinbase_transaction(network, next_block_height, miner_address, like_zcashd);
// Setup the transaction lists.
let (conventional_fee_txs, low_fee_txs): (Vec<_>, Vec<_>) = mempool_txs
let (mut conventional_fee_txs, mut low_fee_txs): (Vec<_>, Vec<_>) = mempool_txs
.into_iter()
.partition(VerifiedUnminedTx::pays_conventional_fee);
@ -74,7 +74,7 @@ pub async fn select_mempool_transactions(
while let Some(tx_weights) = conventional_fee_tx_weights {
conventional_fee_tx_weights = checked_add_transaction_weighted_random(
&conventional_fee_txs,
&mut conventional_fee_txs,
tx_weights,
&mut selected_txs,
&mut remaining_block_bytes,
@ -90,7 +90,7 @@ pub async fn select_mempool_transactions(
while let Some(tx_weights) = low_fee_tx_weights {
low_fee_tx_weights = checked_add_transaction_weighted_random(
&low_fee_txs,
&mut low_fee_txs,
tx_weights,
&mut selected_txs,
&mut remaining_block_bytes,
@ -160,7 +160,7 @@ fn setup_fee_weighted_index(transactions: &[VerifiedUnminedTx]) -> Option<Weight
/// Returns the updated transaction weights.
/// If all transactions have been chosen, returns `None`.
fn checked_add_transaction_weighted_random(
candidate_txs: &[VerifiedUnminedTx],
candidate_txs: &mut Vec<VerifiedUnminedTx>,
tx_weights: WeightedIndex<f32>,
selected_txs: &mut Vec<VerifiedUnminedTx>,
remaining_block_bytes: &mut usize,
@ -201,20 +201,14 @@ fn checked_add_transaction_weighted_random(
/// If some transactions have not yet been chosen, returns the weighted index and the transaction.
/// Otherwise, just returns the transaction.
fn choose_transaction_weighted_random(
candidate_txs: &[VerifiedUnminedTx],
mut weighted_index: WeightedIndex<f32>,
candidate_txs: &mut Vec<VerifiedUnminedTx>,
weighted_index: WeightedIndex<f32>,
) -> (Option<WeightedIndex<f32>>, VerifiedUnminedTx) {
let candidate_position = weighted_index.sample(&mut thread_rng());
let candidate_tx = candidate_txs[candidate_position].clone();
let candidate_tx = candidate_txs.swap_remove(candidate_position);
// Only pick each transaction once, by setting picked transaction weights to zero
if weighted_index
.update_weights(&[(candidate_position, &0.0)])
.is_err()
{
// All weights are zero, so each transaction has either been selected or rejected
(None, candidate_tx)
} else {
(Some(weighted_index), candidate_tx)
}
// We have to regenerate this index each time we choose a transaction, due to floating-point sum inaccuracies.
// If we don't, some chosen transactions can end up with a tiny non-zero weight, leading to duplicates.
// <https://github.com/rust-random/rand/blob/4bde8a0adb517ec956fcec91665922f6360f974b/src/distributions/weighted_index.rs#L173-L183>
(setup_fee_weighted_index(candidate_txs), candidate_tx)
}