Skip to content

Commit

Permalink
refactor(core): ClientCacheMemory
Browse files Browse the repository at this point in the history
  • Loading branch information
Hanssen0 committed Aug 31, 2024
1 parent 7c92783 commit 52156f9
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 67 deletions.
5 changes: 5 additions & 0 deletions .changeset/silent-nails-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ckb-ccc/core": patch
---

feat(core): ClientCache.revertTransaction
96 changes: 83 additions & 13 deletions packages/core/src/client/cache/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,93 @@ import {
import { HexLike } from "../../hex/index.js";
import { ClientCollectableSearchKeyLike } from "../clientTypes.advanced.js";

export interface ClientCache {
markUsable(...cellLikes: (CellLike | CellLike[])[]): Promise<void>;
markUnusable(
export abstract class ClientCache {
abstract markUsable(...cellLikes: (CellLike | CellLike[])[]): Promise<void>;
abstract markUnusable(
...outPointLike: (OutPointLike | OutPointLike[])[]
): Promise<void>;
markTransactions(
async markTransactions(
...transactionLike: (TransactionLike | TransactionLike[])[]
): Promise<void>;
): Promise<void> {
await Promise.all([
this.recordTransactions(...transactionLike),
...transactionLike.flat().map((transactionLike) => {
const tx = Transaction.from(transactionLike);
const txHash = tx.hash();

return Promise.all([
...tx.inputs.map((i) => this.markUnusable(i.previousOutput)),
...tx.outputs.map((o, i) =>
this.markUsable({
cellOutput: o,
outputData: tx.outputsData[i],
outPoint: {
txHash,
index: i,
},
}),
),
]);
}),
]);
}
async revertTransactions(
...transactionLike: (TransactionLike | TransactionLike[])[]
): Promise<void> {
await Promise.all([
this.recordTransactions(...transactionLike),
...transactionLike.flat().map((transactionLike) => {
const tx = Transaction.from(transactionLike);
const txHash = tx.hash();

isUnusable(outPointLike: OutPointLike): Promise<boolean>;
return Promise.all([
...tx.inputs.map(async (i) => {
const cell = await this.getCell(i.previousOutput);
if (cell) {
return this.markUsable(cell);
}
}),
...tx.outputs.map((_, i) =>
this.markUnusable({
txHash,
index: i,
}),
),
]);
}),
]);
}
abstract findCells(
filter: ClientCollectableSearchKeyLike,
): AsyncGenerator<Cell>;
/**
* Get a known cell by out point
* @param _outPoint
*/
abstract getCell(_outPoint: OutPointLike): Promise<Cell | undefined>;
abstract isUnusable(outPointLike: OutPointLike): Promise<boolean>;

recordTransactions(
...transactions: (TransactionLike | TransactionLike[])[]
): Promise<void>;
getTransaction(txHash: HexLike): Promise<Transaction | undefined>;
/**
* Record known transactions
* Implement this method to enable transactions query caching
* @param _transactions
*/
async recordTransactions(
..._transactions: (TransactionLike | TransactionLike[])[]
): Promise<void> {}
/**
* Get a known transaction by hash
* Implement this method to enable transactions query caching
* @param _txHash
*/
async getTransaction(_txHash: HexLike): Promise<Transaction | undefined> {
return;
}

recordCells(...cells: (CellLike | CellLike[])[]): Promise<void>;
getCell(outPoint: OutPointLike): Promise<Cell | undefined>;
findCells(filter: ClientCollectableSearchKeyLike): AsyncGenerator<Cell>;
/**
* Record known cells
* Implement this method to enable cells query caching
* @param _cells
*/
async recordCells(..._cells: (CellLike | CellLike[])[]): Promise<void> {}
}
110 changes: 56 additions & 54 deletions packages/core/src/client/cache/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,32 @@ import { ClientCollectableSearchKeyLike } from "../clientTypes.advanced.js";
import { ClientCache } from "./cache.js";
import { filterCell } from "./memory.advanced.js";

export class ClientCacheMemory implements ClientCache {
private readonly unusableOutPoints: OutPoint[] = [];
private readonly usableCells: Cell[] = [];
private readonly knownTransactions: Transaction[] = [];
private readonly knownCells: Cell[] = [];
export class ClientCacheMemory extends ClientCache {
/**
* OutPoint => [isLive, Cell | OutPoint]
*/
private readonly cells: Map<
string,
| [
false,
Pick<Cell, "outPoint"> &
Partial<Pick<Cell, "cellOutput" | "outputData">>,
]
| [true, Cell]
| [undefined, Cell]
> = new Map();

/**
* TX Hash => Transaction
*/
private readonly knownTransactions: Map<string, Transaction> = new Map();

async markUsable(...cellLikes: (CellLike | CellLike[])[]): Promise<void> {
cellLikes.flat().forEach((cellLike) => {
const cell = Cell.from(cellLike).clone();
this.usableCells.push(cell);
this.knownCells.push(cell);
const outPointStr = hexFrom(cell.outPoint.toBytes());

const index = this.unusableOutPoints.findIndex((o) =>
cell.outPoint.eq(o),
);
if (index !== -1) {
this.unusableOutPoints.splice(index, 1);
}
this.cells.set(outPointStr, [true, cell]);
});
}

Expand All @@ -37,74 +45,68 @@ export class ClientCacheMemory implements ClientCache {
): Promise<void> {
outPointLikes.flat().forEach((outPointLike) => {
const outPoint = OutPoint.from(outPointLike);
this.unusableOutPoints.push(outPoint.clone());
const outPointStr = hexFrom(outPoint.toBytes());

const index = this.usableCells.findIndex((c) => c.outPoint.eq(outPoint));
if (index !== -1) {
this.usableCells.splice(index, 1);
const existed = this.cells.get(outPointStr);
if (existed) {
existed[0] = false;
return;
}
this.cells.set(outPointStr, [false, { outPoint }]);
});
}

async markTransactions(
...transactionLike: (TransactionLike | TransactionLike[])[]
): Promise<void> {
await Promise.all(
transactionLike.flat().map(async (transactionLike) => {
const tx = Transaction.from(transactionLike);
const txHash = tx.hash();

await Promise.all(
tx.inputs.map((i) => this.markUnusable(i.previousOutput)),
);
await Promise.all(
tx.outputs.map((o, i) =>
this.markUsable({
cellOutput: o,
outputData: tx.outputsData[i],
outPoint: {
txHash,
index: i,
},
}),
),
);
}),
);
}

async *findCells(
keyLike: ClientCollectableSearchKeyLike,
): AsyncGenerator<Cell> {
for (const cell of this.usableCells) {
for (const [isLive, cell] of this.cells.values()) {
if (!isLive) {
continue;
}
if (!filterCell(keyLike, cell)) {
continue;
}

yield cell;
yield cell.clone();
}
}
async getCell(outPointLike: OutPointLike): Promise<Cell | undefined> {
const outPoint = OutPoint.from(outPointLike);

const cell = this.cells.get(hexFrom(outPoint.toBytes()))?.[1];
if (cell && cell.cellOutput && cell.outputData) {
return Cell.from((cell as Cell).clone());
}
}

async isUnusable(outPointLike: OutPointLike): Promise<boolean> {
const outPoint = OutPoint.from(outPointLike);
return this.unusableOutPoints.find((o) => o.eq(outPoint)) !== undefined;

return !(this.cells.get(hexFrom(outPoint.toBytes()))?.[0] ?? true);
}

async recordTransactions(
...transactions: (TransactionLike | TransactionLike[])[]
): Promise<void> {
this.knownTransactions.push(...transactions.flat().map(Transaction.from));
transactions.flat().map((txLike) => {
const tx = Transaction.from(txLike);
this.knownTransactions.set(tx.hash(), tx);
});
}
async getTransaction(txHashLike: HexLike): Promise<Transaction | undefined> {
const txHash = hexFrom(txHashLike);
return this.knownTransactions.find((tx) => tx.hash() === txHash);
return this.knownTransactions.get(txHash)?.clone();
}

async recordCells(...cells: (CellLike | CellLike[])[]): Promise<void> {
this.knownCells.push(...cells.flat().map(Cell.from));
}
async getCell(outPointLike: OutPointLike): Promise<Cell | undefined> {
const outPoint = OutPoint.from(outPointLike);
return this.knownCells.find((cell) => cell.outPoint.eq(outPoint));
cells.flat().map((cellLike) => {
const cell = Cell.from(cellLike);
const outPointStr = hexFrom(cell.outPoint.toBytes());

if (this.cells.get(outPointStr)) {
return;
}
this.cells.set(outPointStr, [undefined, cell]);
});
}
}

0 comments on commit 52156f9

Please sign in to comment.