Removed Transaction Graph as Primitive

Signed-off-by: James Zuccon <zuccon@gmail.com>
This commit is contained in:
James Zuccon
2024-09-12 21:45:27 +10:00
parent 68758140a8
commit 2c9812200d
@@ -1,160 +0,0 @@
import { Transaction } from './transaction.js';
import { binToHex, Output } from '@bitauth/libauth';
export interface UTXO extends Output {
outpointTransactionHash: string;
outpointIndex: number;
}
export class TransactionGraph {
private transactions: Map<string, Transaction>;
constructor() {
this.transactions = new Map();
}
addTransaction(tx: Transaction) {
this.transactions.set(tx.toHashHex(), tx);
}
removeTransaction(txHash: string) {
this.transactions.delete(txHash);
}
removeDependentTransactions(txHash: string): string[] {
const removedTxids: string[] = [];
const toRemove: Set<string> = new Set([txHash]);
const findDependents = (id: string) => {
for (const [otherTxid, tx] of this.transactions) {
if (
tx
.getInputs()
.some((input) => binToHex(input.outpointTransactionHash) === id)
) {
toRemove.add(otherTxid);
findDependents(otherTxid);
}
}
};
findDependents(txHash);
for (const id of toRemove) {
if (this.transactions.has(id)) {
this.transactions.delete(id);
removedTxids.push(id);
}
}
return removedTxids;
}
getTransaction(txHash: string): Transaction | undefined {
return this.transactions.get(txHash);
}
getAllTransactions(): Transaction[] {
return Array.from(this.transactions.values());
}
deriveUtxoState(): UTXO[] {
const utxoMap = new Map<string, UTXO>();
// First, add all outputs as potential UTXOs
for (const tx of this.transactions.values()) {
tx.getOutputs().forEach((output, outpointIndex) => {
const txHashHex = tx.toHashHex();
const utxo: UTXO = {
outpointTransactionHash: txHashHex,
outpointIndex,
valueSatoshis: output.valueSatoshis,
lockingBytecode: output.lockingBytecode,
};
utxoMap.set(`${txHashHex}:${outpointIndex}`, utxo);
});
}
// Then, remove all spent outputs
for (const tx of this.transactions.values()) {
tx.getInputs().forEach((input) => {
const outpointTransactionHashHex = binToHex(
input.outpointTransactionHash
);
utxoMap.delete(`${outpointTransactionHashHex}:${input.outpointIndex}`);
});
}
return Array.from(utxoMap.values());
}
isTransactionValid(tx: Transaction): boolean {
const currentUtxos = this.deriveUtxoState();
const utxoMap = new Map(
currentUtxos.map((utxo) => [
`${utxo.outpointTransactionHash}:${utxo.outpointIndex}`,
utxo,
])
);
return tx.getInputs().every((input) => {
const outpointTransactionHashHex = binToHex(
input.outpointTransactionHash
);
return utxoMap.has(
`${outpointTransactionHashHex}:${input.outpointIndex}`
);
});
}
getDependentTransactions(txHash: string): Transaction[] {
return this.getAllTransactions().filter((tx) => {
tx.getInputs().some((input) => {
const outpointTransactionHashHex = binToHex(
input.outpointTransactionHash
);
return outpointTransactionHashHex === txHash;
});
});
}
getBroadcastOrder(): Transaction[] {
const visited = new Set<string>();
const order: Transaction[] = [];
const visit = (txid: string) => {
if (visited.has(txid)) return;
const tx = this.transactions.get(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 this.transactions.values()) {
visit(tx.toHashHex());
}
return order;
}
getNextTransactionToBroadcast(): Transaction | undefined {
// This is pretty inefficient in the sense we recompile the graph each time.
// But I'm not sure if that would really have much real-world performance impact.
return this.getBroadcastOrder()[0] ?? undefined;
}
}