Merge pull request #4554 from mempool/mononaut/unmineable-txs
Handle unmineable transactions in GBT implementations
This commit is contained in:
commit
aad288c0d9
3
backend/rust-gbt/index.d.ts
vendored
3
backend/rust-gbt/index.d.ts
vendored
@ -45,5 +45,6 @@ export class GbtResult {
|
|||||||
blockWeights: Array<number>
|
blockWeights: Array<number>
|
||||||
clusters: Array<Array<number>>
|
clusters: Array<Array<number>>
|
||||||
rates: Array<Array<number>>
|
rates: Array<Array<number>>
|
||||||
constructor(blocks: Array<Array<number>>, blockWeights: Array<number>, clusters: Array<Array<number>>, rates: Array<Array<number>>)
|
overflow: Array<number>
|
||||||
|
constructor(blocks: Array<Array<number>>, blockWeights: Array<number>, clusters: Array<Array<number>>, rates: Array<Array<number>>, overflow: Array<number>)
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat
|
|||||||
indexed_accelerations[acceleration.uid as usize] = Some(acceleration);
|
indexed_accelerations[acceleration.uid as usize] = Some(acceleration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Initializing working vecs with uid capacity for {}", max_uid + 1);
|
||||||
let mempool_len = mempool.len();
|
let mempool_len = mempool.len();
|
||||||
let mut audit_pool: AuditPool = Vec::with_capacity(max_uid + 1);
|
let mut audit_pool: AuditPool = Vec::with_capacity(max_uid + 1);
|
||||||
audit_pool.resize(max_uid + 1, None);
|
audit_pool.resize(max_uid + 1, None);
|
||||||
@ -127,74 +128,75 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat
|
|||||||
let next_from_stack = next_valid_from_stack(&mut mempool_stack, &audit_pool);
|
let next_from_stack = next_valid_from_stack(&mut mempool_stack, &audit_pool);
|
||||||
let next_from_queue = next_valid_from_queue(&mut modified, &audit_pool);
|
let next_from_queue = next_valid_from_queue(&mut modified, &audit_pool);
|
||||||
if next_from_stack.is_none() && next_from_queue.is_none() {
|
if next_from_stack.is_none() && next_from_queue.is_none() {
|
||||||
continue;
|
info!("No transactions left! {:#?} in overflow", overflow.len());
|
||||||
}
|
|
||||||
let (next_tx, from_stack) = match (next_from_stack, next_from_queue) {
|
|
||||||
(Some(stack_tx), Some(queue_tx)) => match queue_tx.cmp(stack_tx) {
|
|
||||||
std::cmp::Ordering::Less => (stack_tx, true),
|
|
||||||
_ => (queue_tx, false),
|
|
||||||
},
|
|
||||||
(Some(stack_tx), None) => (stack_tx, true),
|
|
||||||
(None, Some(queue_tx)) => (queue_tx, false),
|
|
||||||
(None, None) => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if from_stack {
|
|
||||||
mempool_stack.pop();
|
|
||||||
} else {
|
} else {
|
||||||
modified.pop();
|
let (next_tx, from_stack) = match (next_from_stack, next_from_queue) {
|
||||||
}
|
(Some(stack_tx), Some(queue_tx)) => match queue_tx.cmp(stack_tx) {
|
||||||
|
std::cmp::Ordering::Less => (stack_tx, true),
|
||||||
|
_ => (queue_tx, false),
|
||||||
|
},
|
||||||
|
(Some(stack_tx), None) => (stack_tx, true),
|
||||||
|
(None, Some(queue_tx)) => (queue_tx, false),
|
||||||
|
(None, None) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
if blocks.len() < (MAX_BLOCKS - 1)
|
if from_stack {
|
||||||
&& ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize())
|
mempool_stack.pop();
|
||||||
>= MAX_BLOCK_WEIGHT_UNITS)
|
} else {
|
||||||
|| (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS))
|
modified.pop();
|
||||||
{
|
|
||||||
// hold this package in an overflow list while we check for smaller options
|
|
||||||
overflow.push(next_tx.uid);
|
|
||||||
failures += 1;
|
|
||||||
} else {
|
|
||||||
let mut package: Vec<(u32, u32, usize)> = Vec::new();
|
|
||||||
let mut cluster: Vec<u32> = Vec::new();
|
|
||||||
let is_cluster: bool = !next_tx.ancestors.is_empty();
|
|
||||||
for ancestor_id in &next_tx.ancestors {
|
|
||||||
if let Some(Some(ancestor)) = audit_pool.get(*ancestor_id as usize) {
|
|
||||||
package.push((*ancestor_id, ancestor.order(), ancestor.ancestors.len()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
package.sort_unstable_by(|a, b| -> Ordering {
|
|
||||||
if a.2 != b.2 {
|
|
||||||
// order by ascending ancestor count
|
|
||||||
a.2.cmp(&b.2)
|
|
||||||
} else if a.1 != b.1 {
|
|
||||||
// tie-break by ascending partial txid
|
|
||||||
a.1.cmp(&b.1)
|
|
||||||
} else {
|
|
||||||
// tie-break partial txid collisions by ascending uid
|
|
||||||
a.0.cmp(&b.0)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
package.push((next_tx.uid, next_tx.order(), next_tx.ancestors.len()));
|
|
||||||
|
|
||||||
let cluster_rate = next_tx.cluster_rate();
|
|
||||||
|
|
||||||
for (txid, _, _) in &package {
|
|
||||||
cluster.push(*txid);
|
|
||||||
if let Some(Some(tx)) = audit_pool.get_mut(*txid as usize) {
|
|
||||||
tx.used = true;
|
|
||||||
tx.set_dirty_if_different(cluster_rate);
|
|
||||||
transactions.push(tx.uid);
|
|
||||||
block_weight += tx.weight;
|
|
||||||
block_sigops += tx.sigops;
|
|
||||||
}
|
|
||||||
update_descendants(*txid, &mut audit_pool, &mut modified, cluster_rate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_cluster {
|
if blocks.len() < (MAX_BLOCKS - 1)
|
||||||
clusters.push(cluster);
|
&& ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize())
|
||||||
}
|
>= MAX_BLOCK_WEIGHT_UNITS)
|
||||||
|
|| (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS))
|
||||||
|
{
|
||||||
|
// hold this package in an overflow list while we check for smaller options
|
||||||
|
overflow.push(next_tx.uid);
|
||||||
|
failures += 1;
|
||||||
|
} else {
|
||||||
|
let mut package: Vec<(u32, u32, usize)> = Vec::new();
|
||||||
|
let mut cluster: Vec<u32> = Vec::new();
|
||||||
|
let is_cluster: bool = !next_tx.ancestors.is_empty();
|
||||||
|
for ancestor_id in &next_tx.ancestors {
|
||||||
|
if let Some(Some(ancestor)) = audit_pool.get(*ancestor_id as usize) {
|
||||||
|
package.push((*ancestor_id, ancestor.order(), ancestor.ancestors.len()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
package.sort_unstable_by(|a, b| -> Ordering {
|
||||||
|
if a.2 != b.2 {
|
||||||
|
// order by ascending ancestor count
|
||||||
|
a.2.cmp(&b.2)
|
||||||
|
} else if a.1 != b.1 {
|
||||||
|
// tie-break by ascending partial txid
|
||||||
|
a.1.cmp(&b.1)
|
||||||
|
} else {
|
||||||
|
// tie-break partial txid collisions by ascending uid
|
||||||
|
a.0.cmp(&b.0)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
package.push((next_tx.uid, next_tx.order(), next_tx.ancestors.len()));
|
||||||
|
|
||||||
failures = 0;
|
let cluster_rate = next_tx.cluster_rate();
|
||||||
|
|
||||||
|
for (txid, _, _) in &package {
|
||||||
|
cluster.push(*txid);
|
||||||
|
if let Some(Some(tx)) = audit_pool.get_mut(*txid as usize) {
|
||||||
|
tx.used = true;
|
||||||
|
tx.set_dirty_if_different(cluster_rate);
|
||||||
|
transactions.push(tx.uid);
|
||||||
|
block_weight += tx.weight;
|
||||||
|
block_sigops += tx.sigops;
|
||||||
|
}
|
||||||
|
update_descendants(*txid, &mut audit_pool, &mut modified, cluster_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_cluster {
|
||||||
|
clusters.push(cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
failures = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this block is full
|
// this block is full
|
||||||
@ -203,10 +205,14 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat
|
|||||||
let queue_is_empty = mempool_stack.is_empty() && modified.is_empty();
|
let queue_is_empty = mempool_stack.is_empty() && modified.is_empty();
|
||||||
if (exceeded_package_tries || queue_is_empty) && blocks.len() < (MAX_BLOCKS - 1) {
|
if (exceeded_package_tries || queue_is_empty) && blocks.len() < (MAX_BLOCKS - 1) {
|
||||||
// finalize this block
|
// finalize this block
|
||||||
if !transactions.is_empty() {
|
if transactions.is_empty() {
|
||||||
blocks.push(transactions);
|
info!("trying to push an empty block! breaking loop! mempool {:#?} | modified {:#?} | overflow {:#?}", mempool_stack.len(), modified.len(), overflow.len());
|
||||||
block_weights.push(block_weight);
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blocks.push(transactions);
|
||||||
|
block_weights.push(block_weight);
|
||||||
|
|
||||||
// reset for the next block
|
// reset for the next block
|
||||||
transactions = Vec::with_capacity(initial_txes_per_block);
|
transactions = Vec::with_capacity(initial_txes_per_block);
|
||||||
block_weight = BLOCK_RESERVED_WEIGHT;
|
block_weight = BLOCK_RESERVED_WEIGHT;
|
||||||
@ -265,6 +271,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat
|
|||||||
block_weights,
|
block_weights,
|
||||||
clusters,
|
clusters,
|
||||||
rates,
|
rates,
|
||||||
|
overflow,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +133,7 @@ pub struct GbtResult {
|
|||||||
pub block_weights: Vec<u32>,
|
pub block_weights: Vec<u32>,
|
||||||
pub clusters: Vec<Vec<u32>>,
|
pub clusters: Vec<Vec<u32>>,
|
||||||
pub rates: Vec<Vec<f64>>, // Tuples not supported. u32 fits inside f64
|
pub rates: Vec<Vec<f64>>, // Tuples not supported. u32 fits inside f64
|
||||||
|
pub overflow: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All on another thread, this runs an arbitrary task in between
|
/// All on another thread, this runs an arbitrary task in between
|
||||||
|
@ -368,12 +368,15 @@ class MempoolBlocks {
|
|||||||
// run the block construction algorithm in a separate thread, and wait for a result
|
// run the block construction algorithm in a separate thread, and wait for a result
|
||||||
const rustGbt = saveResults ? this.rustGbtGenerator : new GbtGenerator();
|
const rustGbt = saveResults ? this.rustGbtGenerator : new GbtGenerator();
|
||||||
try {
|
try {
|
||||||
const { blocks, blockWeights, rates, clusters } = this.convertNapiResultTxids(
|
const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids(
|
||||||
await rustGbt.make(Object.values(newMempool) as RustThreadTransaction[], convertedAccelerations as RustThreadAcceleration[], this.nextUid),
|
await rustGbt.make(Object.values(newMempool) as RustThreadTransaction[], convertedAccelerations as RustThreadAcceleration[], this.nextUid),
|
||||||
);
|
);
|
||||||
if (saveResults) {
|
if (saveResults) {
|
||||||
this.rustInitialized = true;
|
this.rustInitialized = true;
|
||||||
}
|
}
|
||||||
|
const mempoolSize = Object.keys(newMempool).length;
|
||||||
|
const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length;
|
||||||
|
logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${mempoolSize} in the mempool, ${overflow.length} were unmineable`);
|
||||||
const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, saveResults);
|
const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, saveResults);
|
||||||
logger.debug(`RUST makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
|
logger.debug(`RUST makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
|
||||||
return processed;
|
return processed;
|
||||||
@ -424,7 +427,7 @@ class MempoolBlocks {
|
|||||||
|
|
||||||
// run the block construction algorithm in a separate thread, and wait for a result
|
// run the block construction algorithm in a separate thread, and wait for a result
|
||||||
try {
|
try {
|
||||||
const { blocks, blockWeights, rates, clusters } = this.convertNapiResultTxids(
|
const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids(
|
||||||
await this.rustGbtGenerator.update(
|
await this.rustGbtGenerator.update(
|
||||||
added as RustThreadTransaction[],
|
added as RustThreadTransaction[],
|
||||||
removedUids,
|
removedUids,
|
||||||
@ -432,9 +435,10 @@ class MempoolBlocks {
|
|||||||
this.nextUid,
|
this.nextUid,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0);
|
const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length;
|
||||||
|
logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${mempoolSize} in the mempool, ${overflow.length} were unmineable`);
|
||||||
if (mempoolSize !== resultMempoolSize) {
|
if (mempoolSize !== resultMempoolSize) {
|
||||||
throw new Error('GBT returned wrong number of transactions, cache is probably out of sync');
|
throw new Error('GBT returned wrong number of transactions , cache is probably out of sync');
|
||||||
} else {
|
} else {
|
||||||
const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, true);
|
const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, true);
|
||||||
this.removeUids(removedUids);
|
this.removeUids(removedUids);
|
||||||
@ -658,8 +662,8 @@ class MempoolBlocks {
|
|||||||
return { blocks: convertedBlocks, rates: convertedRates, clusters: convertedClusters } as { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }};
|
return { blocks: convertedBlocks, rates: convertedRates, clusters: convertedClusters } as { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }};
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertNapiResultTxids({ blocks, blockWeights, rates, clusters }: GbtResult)
|
private convertNapiResultTxids({ blocks, blockWeights, rates, clusters, overflow }: GbtResult)
|
||||||
: { blocks: string[][], blockWeights: number[], rates: [string, number][], clusters: string[][] } {
|
: { blocks: string[][], blockWeights: number[], rates: [string, number][], clusters: string[][], overflow: string[] } {
|
||||||
const convertedBlocks: string[][] = blocks.map(block => block.map(uid => {
|
const convertedBlocks: string[][] = blocks.map(block => block.map(uid => {
|
||||||
const txid = this.uidMap.get(uid);
|
const txid = this.uidMap.get(uid);
|
||||||
if (txid !== undefined) {
|
if (txid !== undefined) {
|
||||||
@ -677,7 +681,15 @@ class MempoolBlocks {
|
|||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
convertedClusters.push(cluster.map(uid => this.uidMap.get(uid)) as string[]);
|
convertedClusters.push(cluster.map(uid => this.uidMap.get(uid)) as string[]);
|
||||||
}
|
}
|
||||||
return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters };
|
const convertedOverflow: string[] = overflow.map(uid => {
|
||||||
|
const txid = this.uidMap.get(uid);
|
||||||
|
if (txid !== undefined) {
|
||||||
|
return txid;
|
||||||
|
} else {
|
||||||
|
throw new Error('GBT returned an unmineable transaction with unknown uid');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,10 +173,13 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
|
|||||||
// this block is full
|
// this block is full
|
||||||
const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000);
|
const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000);
|
||||||
const queueEmpty = top >= mempoolArray.length && modified.isEmpty();
|
const queueEmpty = top >= mempoolArray.length && modified.isEmpty();
|
||||||
|
|
||||||
if ((exceededPackageTries || queueEmpty) && blocks.length < 7) {
|
if ((exceededPackageTries || queueEmpty) && blocks.length < 7) {
|
||||||
// construct this block
|
// construct this block
|
||||||
if (transactions.length) {
|
if (transactions.length) {
|
||||||
blocks.push(transactions.map(t => t.uid));
|
blocks.push(transactions.map(t => t.uid));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// reset for the next block
|
// reset for the next block
|
||||||
transactions = [];
|
transactions = [];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user