169 lines
4.1 KiB
TypeScript
169 lines
4.1 KiB
TypeScript
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;
|
|
}
|
|
}
|