@ckb-ccc/core
Version:
Core of CCC - CKBer's Codebase
220 lines (193 loc) • 6.14 kB
text/typescript
import { Cell, CellLike, OutPoint, OutPointLike } from "../../ckb/index.js";
import { hexFrom, HexLike } from "../../hex/index.js";
import { Num, numFrom, NumLike } from "../../num/index.js";
import { ClientCollectableSearchKeyLike } from "../clientTypes.advanced.js";
import {
ClientBlock,
ClientBlockHeader,
ClientBlockHeaderLike,
ClientBlockLike,
ClientTransactionResponse,
ClientTransactionResponseLike,
} from "../clientTypes.js";
import { ClientCache } from "./cache.js";
import { CellRecord, filterCell, MapLru } from "./memory.advanced.js";
export class ClientCacheMemory extends ClientCache {
/**
* OutPoint => [isLive, Cell | OutPoint]
*/
private readonly cells: MapLru<string, CellRecord>;
/**
* TX Hash => Transaction Response
*/
private readonly knownTransactions: MapLru<string, ClientTransactionResponse>;
/**
* Block Number => Block Hash
*/
private readonly knownBlockHashes: MapLru<Num, string>;
/**
* Block Hash => Block Header / Full Block
*/
private readonly knownBlocks: MapLru<
string,
Pick<ClientBlock, "header"> | ClientBlock
>;
constructor(
private readonly maxCells = 512,
private readonly maxTxs = 256,
private readonly maxBlocks = 128,
) {
super();
this.cells = new MapLru<string, CellRecord>(this.maxCells);
this.knownTransactions = new MapLru<string, ClientTransactionResponse>(
this.maxTxs,
);
this.knownBlockHashes = new MapLru<Num, string>(this.maxBlocks);
this.knownBlocks = new MapLru<
string,
Pick<ClientBlock, "header"> | ClientBlock
>(this.maxBlocks);
}
async markUsableNoCache(
...cellLikes: (CellLike | CellLike[])[]
): Promise<void> {
cellLikes.flat().forEach((cellLike) => {
const cell = Cell.from(cellLike).clone();
const outPointStr = hexFrom(cell.outPoint.toBytes());
this.cells.set(outPointStr, [true, cell]);
});
}
async markUnusable(
...outPointLikes: (OutPointLike | OutPointLike[])[]
): Promise<void> {
outPointLikes.flat().forEach((outPointLike) => {
const outPoint = OutPoint.from(outPointLike);
const outPointStr = hexFrom(outPoint.toBytes());
const existed = this.cells.get(outPointStr);
if (existed) {
existed[0] = false;
return;
}
this.cells.set(outPointStr, [false, { outPoint }]);
});
}
async clear(): Promise<void> {
this.cells.clear();
this.knownTransactions.clear();
}
async *findCells(
keyLike: ClientCollectableSearchKeyLike,
): AsyncGenerator<Cell> {
for (const [key, [isLive, cell]] of this.cells.entries()) {
if (!isLive) {
continue;
}
if (!filterCell(keyLike, cell)) {
continue;
}
this.cells.get(key);
yield cell.clone();
}
}
async isUnusable(outPointLike: OutPointLike): Promise<boolean> {
const outPoint = OutPoint.from(outPointLike);
return !(this.cells.get(hexFrom(outPoint.toBytes()))?.[0] ?? true);
}
async recordCells(...cells: (CellLike | CellLike[])[]): Promise<void> {
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]);
});
}
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 recordTransactionResponses(
...transactions: (
| ClientTransactionResponseLike
| ClientTransactionResponseLike[]
)[]
): Promise<void> {
transactions.flat().map((txLike) => {
const tx = ClientTransactionResponse.from(txLike);
this.knownTransactions.set(tx.transaction.hash(), tx);
});
}
async getTransactionResponse(
txHashLike: HexLike,
): Promise<ClientTransactionResponse | undefined> {
const txHash = hexFrom(txHashLike);
return this.knownTransactions.get(txHash)?.clone();
}
async recordHeaders(
...headers: (ClientBlockHeaderLike | ClientBlockHeaderLike[])[]
): Promise<void> {
headers.flat().map((headerLike) => {
const header = ClientBlockHeader.from(headerLike);
this.knownBlockHashes.set(header.number, header.hash);
const existed = this.knownBlocks.get(header.hash);
if (existed) {
return;
}
this.knownBlocks.set(header.hash, { header });
});
}
async getHeaderByHash(
hashLike: HexLike,
): Promise<ClientBlockHeader | undefined> {
const hash = hexFrom(hashLike);
const block = this.knownBlocks.get(hash);
if (block) {
this.knownBlockHashes.get(block.header.number); // For LRU
}
return block?.header;
}
async getHeaderByNumber(
numberLike: NumLike,
): Promise<ClientBlockHeader | undefined> {
const number = numFrom(numberLike);
const hash = this.knownBlockHashes.get(number);
if (!hash) {
return;
}
return this.getHeaderByHash(hash);
}
async recordBlocks(
...blocks: (ClientBlockLike | ClientBlockLike[])[]
): Promise<void> {
blocks.flat().map((blockLike) => {
const block = ClientBlock.from(blockLike);
this.knownBlockHashes.set(block.header.number, block.header.hash);
this.knownBlocks.set(block.header.hash, block);
});
}
async getBlockByHash(hashLike: HexLike): Promise<ClientBlock | undefined> {
const hash = hexFrom(hashLike);
const block = this.knownBlocks.get(hash);
if (block) {
this.knownBlockHashes.get(block.header.number); // For LRU
if ("transactions" in block) {
return block;
}
}
return;
}
async getBlockByNumber(
numberLike: NumLike,
): Promise<ClientBlock | undefined> {
const number = numFrom(numberLike);
const hash = this.knownBlockHashes.get(number);
if (!hash) {
return;
}
return this.getBlockByHash(hash);
}
}