Removed Transaction Graph as Primitive
Signed-off-by: James Zuccon <zuccon@gmail.com>
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user