Resolve "WIP: UTXO Selection and Storage"
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import { Transaction } from './transaction.js';
|
||||
|
||||
import { binToHex } from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Sort transactions by the order in which they should be broadcast.
|
||||
*/
|
||||
export function sortTransactions(
|
||||
transactions: Array<Transaction>,
|
||||
): Array<Transaction> {
|
||||
const transactionMap = transactions.reduce(
|
||||
(map, transaction) => {
|
||||
const txHash = transaction.toHash().toHex();
|
||||
map[txHash] = transaction;
|
||||
return map;
|
||||
},
|
||||
{} as Record<string, Transaction>,
|
||||
);
|
||||
|
||||
const visited = new Set<string>();
|
||||
const order: Transaction[] = [];
|
||||
|
||||
const visit = (txid: string) => {
|
||||
if (visited.has(txid)) return;
|
||||
|
||||
const tx = transactionMap[txid];
|
||||
if (!tx) return;
|
||||
|
||||
visited.add(txid);
|
||||
|
||||
// First, visit all transactions that this transaction depends on
|
||||
for (const input of tx.getInputs()) {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash,
|
||||
);
|
||||
visit(outpointTransactionHashHex);
|
||||
}
|
||||
|
||||
order.push(tx);
|
||||
};
|
||||
|
||||
// Start the topological sort from each transaction
|
||||
for (const tx of Object.values(transactionMap)) {
|
||||
visit(tx.toHash().toHex());
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
export class ExtMap<T> extends Map<PropertyKey, T> {
|
||||
/**
|
||||
* Creates a new ExtMap from an array using a key selector
|
||||
*/
|
||||
static fromArray<T, K extends PropertyKey>(
|
||||
array: readonly T[],
|
||||
keySelector: (item: T) => K,
|
||||
): ExtMap<T> {
|
||||
const collection = new ExtMap<T>();
|
||||
array.forEach((item) => {
|
||||
const key = keySelector(item);
|
||||
collection.set(key, item);
|
||||
});
|
||||
return collection;
|
||||
}
|
||||
|
||||
static fromObject<T>(object: { [key: PropertyKey]: T }): ExtMap<T> {
|
||||
const collection = new ExtMap<T>();
|
||||
for (let [key, value] of Object.entries(object)) {
|
||||
collection.set(key, value);
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens an array of ExtMaps into a single ExtMap
|
||||
* Last collection's values take precedence for duplicate keys
|
||||
*/
|
||||
static flatten<T>(collections: ExtMap<T>[]): ExtMap<T> {
|
||||
const flattened = new ExtMap<T>();
|
||||
|
||||
collections.forEach((collection) => {
|
||||
for (const [key, value] of collection.entries()) {
|
||||
flattened.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return flattened;
|
||||
}
|
||||
|
||||
getOrFail(key: string): T {
|
||||
const value = this.get(key);
|
||||
|
||||
if (!value) {
|
||||
throw new Error(`Failed to find key "${key}"`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ExtMap with filtered entries
|
||||
*/
|
||||
filter(predicate: (value: T, key: PropertyKey) => boolean): ExtMap<T> {
|
||||
const filtered = new ExtMap<T>();
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (predicate(value, key)) {
|
||||
filtered.set(key, value);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ExtMap with mapped values
|
||||
*/
|
||||
map<U>(mapper: (value: T, key: PropertyKey) => U): ExtMap<U> {
|
||||
const mapped = new ExtMap<U>();
|
||||
for (const [key, value] of this.entries()) {
|
||||
mapped.set(key, mapper(value, key));
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ExtMap with entries sorted by the given comparator
|
||||
* @param comparator - Function to determine the sort order
|
||||
* @returns A new ExtMap with sorted entries
|
||||
*/
|
||||
sort(comparator: (a: T, b: T) => number): ExtMap<T> {
|
||||
const entries = Array.from(this.entries());
|
||||
const sorted = new ExtMap<T>();
|
||||
|
||||
entries
|
||||
.sort(([, valueA], [, valueB]) => comparator(valueA, valueB))
|
||||
.forEach(([key, value]) => {
|
||||
sorted.set(key, value);
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the collection to a plain object
|
||||
*/
|
||||
toObject(): Record<PropertyKey, T> {
|
||||
return Object.fromEntries(this.entries());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the collection to an array of values
|
||||
*/
|
||||
toArray(): T[] {
|
||||
return Array.from(this.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the collection to a Set
|
||||
* @param callback - Optional function to transform keys before adding to Set
|
||||
* @returns A Set containing either the keys or transformed values
|
||||
*/
|
||||
toSet<U = PropertyKey>(callback?: (key: PropertyKey, value: T) => U): Set<U> {
|
||||
const set = new Set<U>();
|
||||
for (const [key, value] of this.entries()) {
|
||||
set.add(callback ? callback(key, value) : (key as U));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user