diff --git a/backend/rust-gbt/index.d.ts b/backend/rust-gbt/index.d.ts index 2acdde672..68b56a8e1 100644 --- a/backend/rust-gbt/index.d.ts +++ b/backend/rust-gbt/index.d.ts @@ -5,6 +5,7 @@ export interface ThreadTransaction { uid: number + order: number fee: number weight: number sigops: number diff --git a/backend/rust-gbt/src/audit_transaction.rs b/backend/rust-gbt/src/audit_transaction.rs index c12911f0a..2df9e0b8c 100644 --- a/backend/rust-gbt/src/audit_transaction.rs +++ b/backend/rust-gbt/src/audit_transaction.rs @@ -12,6 +12,7 @@ use std::{ #[derive(Clone, Debug)] pub struct AuditTransaction { pub uid: u32, + order: u32, pub fee: u64, pub weight: u32, pub sigop_adjusted_vsize: u32, @@ -50,19 +51,24 @@ impl PartialEq for AuditTransaction { impl Eq for AuditTransaction {} #[inline] -pub fn partial_cmp_uid_score(a: (u32, f64), b: (u32, f64)) -> Option { +pub fn partial_cmp_uid_score(a: (u32, u32, f64), b: (u32, u32, f64)) -> Option { // If either score is NaN, this is false, // and partial_cmp will return None - if a.1 == b.1 { - Some(a.0.cmp(&b.0)) + if a.2 != b.2 { + a.2.partial_cmp(&b.2) + } else if a.1 != b.1 { + Some(b.1.cmp(&a.1)) } else { - a.1.partial_cmp(&b.1) + Some(a.0.cmp(&b.0)) } } impl PartialOrd for AuditTransaction { fn partial_cmp(&self, other: &Self) -> Option { - partial_cmp_uid_score((self.uid, self.score), (other.uid, other.score)) + partial_cmp_uid_score( + (self.uid, self.order, self.score), + (other.uid, self.order, other.score), + ) } } @@ -86,6 +92,7 @@ impl AuditTransaction { let sigop_adjusted_vsize = ((tx.weight + 3) / 4).max(tx.sigops * 5); Self { uid: tx.uid, + order: tx.order, fee: tx.fee as u64, weight: tx.weight, sigop_adjusted_vsize, @@ -113,6 +120,11 @@ impl AuditTransaction { self.score } + #[inline] + pub const fn order(&self) -> u32 { + self.order + } + #[inline] pub const fn ancestor_sigop_adjusted_vsize(&self) -> u32 { self.ancestor_sigop_adjusted_vsize diff --git a/backend/rust-gbt/src/gbt.rs b/backend/rust-gbt/src/gbt.rs index bab19ac89..f88c022c4 100644 --- a/backend/rust-gbt/src/gbt.rs +++ b/backend/rust-gbt/src/gbt.rs @@ -25,6 +25,7 @@ type ModifiedQueue = PriorityQueue; #[derive(Debug)] struct TxPriority { uid: u32, + order: u32, score: f64, } impl PartialEq for TxPriority { @@ -35,10 +36,12 @@ impl PartialEq for TxPriority { impl Eq for TxPriority {} impl PartialOrd for TxPriority { fn partial_cmp(&self, other: &Self) -> Option { - if self.score == other.score { - Some(self.uid.cmp(&other.uid)) - } else { + if self.score != other.score { self.score.partial_cmp(&other.score) + } else if self.order != other.order { + Some(other.order.cmp(&self.order)) + } else { + Some(self.uid.cmp(&other.uid)) } } } @@ -80,17 +83,17 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap) -> GbtResult { trace!("Post relative graph Audit Pool: {:#?}", audit_pool); info!("Sorting by descending ancestor score"); - let mut mempool_stack: Vec<(u32, f64)> = mempool_stack + let mut mempool_stack: Vec<(u32, u32, f64)> = mempool_stack .into_iter() .map(|txid| { let atx = audit_pool .get(&txid) .expect("All txids are from audit_pool"); - (txid, atx.score()) + (txid, atx.order(), atx.score()) }) .collect(); mempool_stack.sort_unstable_by(|a, b| partial_cmp_uid_score(*a, *b).expect("Not NaN")); - let mut mempool_stack: Vec = mempool_stack.into_iter().map(|(txid, _)| txid).collect(); + let mut mempool_stack: Vec = mempool_stack.into_iter().map(|(txid, _, _)| txid).collect(); info!("Building blocks by greedily choosing the highest feerate package"); info!("(i.e. the package rooted in the transaction with the best ancestor score)"); @@ -212,6 +215,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap) -> GbtResult { *overflowed, TxPriority { uid: *overflowed, + order: overflowed_tx.order(), score: overflowed_tx.score(), }, ); @@ -376,6 +380,7 @@ fn update_descendants( descendant.uid, TxPriority { uid: descendant.uid, + order: descendant.order(), score: descendant.score(), }, ); @@ -385,6 +390,7 @@ fn update_descendants( descendant.uid, TxPriority { uid: descendant.uid, + order: descendant.order(), score: descendant.score(), }, ); diff --git a/backend/rust-gbt/src/lib.rs b/backend/rust-gbt/src/lib.rs index bfd54ae58..08f7cb599 100644 --- a/backend/rust-gbt/src/lib.rs +++ b/backend/rust-gbt/src/lib.rs @@ -23,7 +23,7 @@ mod u32_hasher_types; use u32_hasher_types::{u32hashmap_with_capacity, U32HasherState}; -/// This is the initial capacity of the GbtGenerator struct's inner HashMap. +/// This is the initial capacity of the `GbtGenerator` struct's inner `HashMap`. /// /// Note: This doesn't *have* to be a power of 2. (uwu) const STARTING_CAPACITY: usize = 1_048_576; diff --git a/backend/rust-gbt/src/thread_transaction.rs b/backend/rust-gbt/src/thread_transaction.rs index 7bbefe648..ca81f5990 100644 --- a/backend/rust-gbt/src/thread_transaction.rs +++ b/backend/rust-gbt/src/thread_transaction.rs @@ -4,6 +4,7 @@ use napi_derive::napi; #[napi(object)] pub struct ThreadTransaction { pub uid: u32, + pub order: u32, pub fee: f64, pub weight: u32, pub sigops: u32, diff --git a/backend/src/__tests__/gbt/gbt-tests.ts b/backend/src/__tests__/gbt/gbt-tests.ts index 92c023aca..8619457ea 100644 --- a/backend/src/__tests__/gbt/gbt-tests.ts +++ b/backend/src/__tests__/gbt/gbt-tests.ts @@ -1,7 +1,6 @@ import fs from 'fs'; -import { GbtGenerator } from '../../../rust-gbt'; +import { GbtGenerator, ThreadTransaction } from '../../../rust-gbt'; import path from 'path'; -import { CompactThreadTransaction } from '../../mempool.interfaces'; const baseline = require('./test-data/target-template.json'); const testVector = require('./test-data/test-data-ids.json'); @@ -28,18 +27,20 @@ describe('Rust GBT', () => { }); }); -function mempoolFromArrayBuffer(buf: ArrayBuffer): CompactThreadTransaction[] { +function mempoolFromArrayBuffer(buf: ArrayBuffer): ThreadTransaction[] { const view = new DataView(buf); const count = view.getUint32(0, false); - const txs: CompactThreadTransaction[] = []; + const txs: ThreadTransaction[] = []; let offset = 4; for (let i = 0; i < count; i++) { - const tx: CompactThreadTransaction = { - uid: view.getUint32(offset, false), + const uid = view.getUint32(offset, false); + const tx: ThreadTransaction = { + uid, + order: txidToOrdering(vectorUidMap.get(uid) as string), fee: view.getFloat64(offset + 4, false), weight: view.getUint32(offset + 12, false), sigops: view.getUint32(offset + 16, false), - feePerVsize: view.getFloat64(offset + 20, false), + // feePerVsize: view.getFloat64(offset + 20, false), effectiveFeePerVsize: view.getFloat64(offset + 28, false), inputs: [], }; @@ -53,3 +54,7 @@ function mempoolFromArrayBuffer(buf: ArrayBuffer): CompactThreadTransaction[] { } return txs; } + +function txidToOrdering(txid: string): number { + return parseInt(txid.slice(56).match(/../g)?.reverse().join('') as string, 16); +} diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index c7d7b8b27..bc3f33806 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -89,6 +89,9 @@ class Mempool { if (this.mempoolCache[txid].sigops == null || this.mempoolCache[txid].effectiveFeePerVsize == null) { this.mempoolCache[txid] = transactionUtils.extendMempoolTransaction(this.mempoolCache[txid]); } + if (this.mempoolCache[txid].order == null) { + this.mempoolCache[txid].order = transactionUtils.txidToOrdering(txid); + } count++; } if (this.mempoolChangedCallback) { diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index 8523a938e..28861a997 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -76,6 +76,7 @@ class TransactionUtils { const adjustedFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1, (transaction.fee || 0) / adjustedVsize); const transactionExtended: MempoolTransactionExtended = Object.assign(transaction, { + order: this.txidToOrdering(transaction.txid), vsize: Math.round(transaction.weight / 4), adjustedVsize, sigops, @@ -154,6 +155,11 @@ class TransactionUtils { return sigops; } + + // returns the most significant 4 bytes of the txid as an integer + public txidToOrdering(txid: string): number { + return parseInt(txid.slice(56).match(/../g)?.reverse().join('') as string, 16); + } } export default new TransactionUtils(); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index efdd874bf..eaa0cd63f 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -93,6 +93,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction { } export interface MempoolTransactionExtended extends TransactionExtended { + order: number; sigops: number; adjustedVsize: number; adjustedFeePerVsize: number;