diff --git a/.changeset/light-vans-arrive.md b/.changeset/light-vans-arrive.md new file mode 100644 index 00000000..7e3e7171 --- /dev/null +++ b/.changeset/light-vans-arrive.md @@ -0,0 +1,5 @@ +--- +"@ckb-ccc/core": minor +--- + +feat(core): more rpc calls cache diff --git a/packages/core/src/client/cache/cache.ts b/packages/core/src/client/cache/cache.ts index e3aaebfb..2f33a2ee 100644 --- a/packages/core/src/client/cache/cache.ts +++ b/packages/core/src/client/cache/cache.ts @@ -6,10 +6,31 @@ import { TransactionLike, } from "../../ckb/index.js"; import { HexLike } from "../../hex/index.js"; +import { NumLike } from "../../num/index.js"; import { ClientCollectableSearchKeyLike } from "../clientTypes.advanced.js"; +import { + ClientBlock, + ClientBlockHeader, + ClientBlockHeaderLike, + ClientBlockLike, + ClientTransactionResponse, + ClientTransactionResponseLike, +} from "../clientTypes.js"; +/** + * @public + * The ClientCache class is mainly designed for chained transactions. + * Consumed & Created cells are "marked" so they can be correctly handled when composing transactions. + * It also act as cache for rpc requests to reduce cost, but this is optional. + */ export abstract class ClientCache { - abstract markUsable(...cellLikes: (CellLike | CellLike[])[]): Promise; + abstract markUsableNoCache( + ...cellLikes: (CellLike | CellLike[])[] + ): Promise; + async markUsable(...cellLikes: (CellLike | CellLike[])[]): Promise { + await this.recordCells(...cellLikes); + return this.markUsableNoCache(...cellLikes); + } abstract markUnusable( ...outPointLike: (OutPointLike | OutPointLike[])[] ): Promise; @@ -17,7 +38,12 @@ export abstract class ClientCache { ...transactionLike: (TransactionLike | TransactionLike[])[] ): Promise { await Promise.all([ - this.recordTransactions(...transactionLike), + this.recordTransactionResponses( + transactionLike.flat().map((transaction) => ({ + transaction: transaction, + status: "sent", + })), + ), ...transactionLike.flat().map((transactionLike) => { const tx = Transaction.from(transactionLike); const txHash = tx.hash(); @@ -42,34 +68,121 @@ export abstract class ClientCache { abstract findCells( filter: ClientCollectableSearchKeyLike, ): AsyncGenerator; + abstract isUnusable(outPointLike: OutPointLike): Promise; + + // ====== + // Following methods are for requests caching and optional. + // ====== + + /** + * Record known cells + * Implement this method to enable cells query caching + * @param _cells + */ + async recordCells(..._cells: (CellLike | CellLike[])[]): Promise {} /** * Get a known cell by out point + * Implement this method to enable cells query caching * @param _outPoint */ - abstract getCell(_outPoint: OutPointLike): Promise; - abstract isUnusable(outPointLike: OutPointLike): Promise; + async getCell(_outPoint: OutPointLike): Promise { + return; + } /** - * Record known transactions + * Record known transaction responses. * Implement this method to enable transactions query caching * @param _transactions */ - async recordTransactions( - ..._transactions: (TransactionLike | TransactionLike[])[] + async recordTransactionResponses( + ..._transactions: ( + | ClientTransactionResponseLike + | ClientTransactionResponseLike[] + )[] ): Promise {} /** - * Get a known transaction by hash + * Get a known transaction response by hash * Implement this method to enable transactions query caching * @param _txHash */ - async getTransaction(_txHash: HexLike): Promise { + async getTransactionResponse( + _txHash: HexLike, + ): Promise { + return; + } + /** + * Record known transactions. + * @param transactions + */ + async recordTransactions( + ...transactions: (TransactionLike | TransactionLike[])[] + ): Promise { + return this.recordTransactionResponses( + transactions.flat().map((transaction) => ({ + transaction, + status: "unknown", + })), + ); + } + /** + * Get a known transaction by hash + * @param txHash + */ + async getTransaction(txHash: HexLike): Promise { + return (await this.getTransactionResponse(txHash))?.transaction; + } + + /** + * Record known block headers. + * Implement this method to enable block headers query caching + * @param _headers + */ + async recordHeaders( + ..._headers: (ClientBlockHeaderLike | ClientBlockHeaderLike[])[] + ): Promise {} + /** + * Get a known block header by hash + * Implement this method to enable block headers query caching + * @param _hash + */ + async getHeaderByHash( + _hash: HexLike, + ): Promise { + return; + } + /** + * Get a known block header by number + * Implement this method to enable block headers query caching + * @param _number + */ + async getHeaderByNumber( + _number: NumLike, + ): Promise { return; } /** - * Record known cells - * Implement this method to enable cells query caching - * @param _cells + * Record known blocks. + * Implement this method to enable blocks query caching + * @param _blocks */ - async recordCells(..._cells: (CellLike | CellLike[])[]): Promise {} + async recordBlocks( + ..._blocks: (ClientBlockLike | ClientBlockLike[])[] + ): Promise {} + /** + * Get a known block header by hash + * Implement this method to enable block headers query caching + * @param _hash + */ + async getBlockByHash(_hash: HexLike): Promise { + return; + } + /** + * Get a known block header by number + * Implement this method to enable block headers query caching + * @param _number + */ + async getBlockByNumber(_number: NumLike): Promise { + return; + } } diff --git a/packages/core/src/client/cache/memory.advanced.ts b/packages/core/src/client/cache/memory.advanced.ts index 7b300bd1..30b8841a 100644 --- a/packages/core/src/client/cache/memory.advanced.ts +++ b/packages/core/src/client/cache/memory.advanced.ts @@ -149,7 +149,12 @@ export class MapLru extends Map { super(); } - access(key: K) { + get(key: K) { + const val = super.get(key); + if (val === undefined) { + return; + } + const index = this.lru.indexOf(key); if (index !== -1) { this.lru.splice(index, 1); @@ -159,15 +164,27 @@ export class MapLru extends Map { this.delete(this.lru[0]); this.lru.shift(); } - } - get(key: K) { - this.access(key); - return super.get(key); + return val; } set(key: K, value: V) { - this.access(key); - return super.set(key, value); + this.get(key); + + super.set(key, value); + return this; + } + + delete(key: K): boolean { + if (!super.delete(key)) { + return false; + } + + const index = this.lru.indexOf(key); + if (index !== -1) { + this.lru.splice(index, 1); + } + + return true; } } diff --git a/packages/core/src/client/cache/memory.ts b/packages/core/src/client/cache/memory.ts index 63f61f36..c6f30b51 100644 --- a/packages/core/src/client/cache/memory.ts +++ b/packages/core/src/client/cache/memory.ts @@ -1,13 +1,15 @@ -import { - Cell, - CellLike, - OutPoint, - OutPointLike, - Transaction, - TransactionLike, -} from "../../ckb/index.js"; +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"; @@ -18,21 +20,44 @@ export class ClientCacheMemory extends ClientCache { private readonly cells: MapLru; /** - * TX Hash => Transaction + * TX Hash => Transaction Response + */ + private readonly knownTransactions: MapLru; + + /** + * Block Number => Block Hash + */ + private readonly knownBlockHashes: MapLru; + + /** + * Block Hash => Block Header / Full Block */ - private readonly knownTransactions: MapLru; + private readonly knownBlocks: MapLru< + string, + Pick | ClientBlock + >; constructor( private readonly maxCells = 512, private readonly maxTxs = 256, + private readonly maxBlocks = 128, ) { super(); this.cells = new MapLru(this.maxCells); - this.knownTransactions = new MapLru(this.maxTxs); + this.knownTransactions = new MapLru( + this.maxTxs, + ); + this.knownBlockHashes = new MapLru(this.maxBlocks); + this.knownBlocks = new MapLru< + string, + Pick | ClientBlock + >(this.maxBlocks); } - async markUsable(...cellLikes: (CellLike | CellLike[])[]): Promise { + async markUsableNoCache( + ...cellLikes: (CellLike | CellLike[])[] + ): Promise { cellLikes.flat().forEach((cellLike) => { const cell = Cell.from(cellLike).clone(); const outPointStr = hexFrom(cell.outPoint.toBytes()); @@ -73,10 +98,28 @@ export class ClientCacheMemory extends ClientCache { continue; } - this.cells.access(key); + this.cells.get(key); yield cell.clone(); } } + + async isUnusable(outPointLike: OutPointLike): Promise { + const outPoint = OutPoint.from(outPointLike); + + return !(this.cells.get(hexFrom(outPoint.toBytes()))?.[0] ?? true); + } + + async recordCells(...cells: (CellLike | CellLike[])[]): Promise { + 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 { const outPoint = OutPoint.from(outPointLike); @@ -86,34 +129,91 @@ export class ClientCacheMemory extends ClientCache { } } - async isUnusable(outPointLike: OutPointLike): Promise { - const outPoint = OutPoint.from(outPointLike); - - return !(this.cells.get(hexFrom(outPoint.toBytes()))?.[0] ?? true); - } - - async recordTransactions( - ...transactions: (TransactionLike | TransactionLike[])[] + async recordTransactionResponses( + ...transactions: ( + | ClientTransactionResponseLike + | ClientTransactionResponseLike[] + )[] ): Promise { transactions.flat().map((txLike) => { - const tx = Transaction.from(txLike); - this.knownTransactions.set(tx.hash(), tx); + const tx = ClientTransactionResponse.from(txLike); + this.knownTransactions.set(tx.transaction.hash(), tx); }); } - async getTransaction(txHashLike: HexLike): Promise { + async getTransactionResponse( + txHashLike: HexLike, + ): Promise { const txHash = hexFrom(txHashLike); return this.knownTransactions.get(txHash)?.clone(); } - async recordCells(...cells: (CellLike | CellLike[])[]): Promise { - cells.flat().map((cellLike) => { - const cell = Cell.from(cellLike); - const outPointStr = hexFrom(cell.outPoint.toBytes()); + async recordHeaders( + ...headers: (ClientBlockHeaderLike | ClientBlockHeaderLike[])[] + ): Promise { + headers.flat().map((headerLike) => { + const header = ClientBlockHeader.from(headerLike); - if (this.cells.get(outPointStr)) { + this.knownBlockHashes.set(header.number, header.hash); + + const existed = this.knownBlocks.get(header.hash); + if (existed) { return; } - this.cells.set(outPointStr, [undefined, cell]); + this.knownBlocks.set(header.hash, { header }); }); } + async getHeaderByHash( + hashLike: HexLike, + ): Promise { + 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 { + const number = numFrom(numberLike); + + const hash = this.knownBlockHashes.get(number); + if (!hash) { + return; + } + return this.getHeaderByHash(hash); + } + + async recordBlocks( + ...blocks: (ClientBlockLike | ClientBlockLike[])[] + ): Promise { + 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 { + 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 { + const number = numFrom(numberLike); + + const hash = this.knownBlockHashes.get(number); + if (!hash) { + return; + } + return this.getBlockByHash(hash); + } } diff --git a/packages/core/src/client/client.ts b/packages/core/src/client/client.ts index a4dd7e5a..7ead8516 100644 --- a/packages/core/src/client/client.ts +++ b/packages/core/src/client/client.ts @@ -14,6 +14,7 @@ import { reduceAsync, sleep } from "../utils/index.js"; import { ClientCache } from "./cache/index.js"; import { ClientCacheMemory } from "./cache/memory.js"; import { + CONFIRMED_BLOCK_TIME, ClientCollectableSearchKeyLike, DEFAULT_MAX_FEE_RATE, DEFAULT_MIN_FEE_RATE, @@ -37,6 +38,10 @@ import { ScriptInfo, } from "./clientTypes.js"; +function hasHeaderConfirmed(header: ClientBlockHeader): boolean { + return numFrom(Date.now()) - header.timestamp >= CONFIRMED_BLOCK_TIME; +} + /** * @public */ @@ -74,24 +79,94 @@ export abstract class Client { abstract getTip(): Promise; abstract getTipHeader(verbosity?: number | null): Promise; - abstract getBlockByNumber( + abstract getBlockByNumberNoCache( blockNumber: NumLike, verbosity?: number | null, withCycles?: boolean | null, ): Promise; - abstract getBlockByHash( + abstract getBlockByHashNoCache( blockHash: HexLike, verbosity?: number | null, withCycles?: boolean | null, ): Promise; - abstract getHeaderByNumber( + abstract getHeaderByNumberNoCache( blockNumber: NumLike, verbosity?: number | null, ): Promise; - abstract getHeaderByHash( + abstract getHeaderByHashNoCache( blockHash: HexLike, verbosity?: number | null, ): Promise; + async getBlockByNumber( + blockNumber: NumLike, + verbosity?: number | null, + withCycles?: boolean | null, + ): Promise { + const block = await this.cache.getBlockByNumber(blockNumber); + if (block) { + return block; + } + + const res = await this.getBlockByNumberNoCache( + blockNumber, + verbosity, + withCycles, + ); + if (res && hasHeaderConfirmed(res.header)) { + await this.cache.recordBlocks(res); + } + return res; + } + async getBlockByHash( + blockHash: HexLike, + verbosity?: number | null, + withCycles?: boolean | null, + ): Promise { + const block = await this.cache.getBlockByHash(blockHash); + if (block) { + return block; + } + + const res = await this.getBlockByHashNoCache( + blockHash, + verbosity, + withCycles, + ); + if (res && hasHeaderConfirmed(res.header)) { + await this.cache.recordBlocks(res); + } + return res; + } + async getHeaderByNumber( + blockNumber: NumLike, + verbosity?: number | null, + ): Promise { + const header = await this.cache.getHeaderByNumber(blockNumber); + if (header) { + return header; + } + + const res = await this.getHeaderByNumberNoCache(blockNumber, verbosity); + if (res && hasHeaderConfirmed(res)) { + await this.cache.recordHeaders(res); + } + return res; + } + async getHeaderByHash( + blockHash: HexLike, + verbosity?: number | null, + ): Promise { + const header = await this.cache.getHeaderByHash(blockHash); + if (header) { + return header; + } + + const res = await this.getHeaderByHashNoCache(blockHash, verbosity); + if (res && hasHeaderConfirmed(res)) { + await this.cache.recordHeaders(res); + } + return res; + } abstract estimateCycles(transaction: TransactionLike): Promise; abstract sendTransactionDry( @@ -115,7 +190,7 @@ export abstract class Client { return cached; } - const transaction = await this.getTransactionNoCache(outPoint.txHash); + const transaction = await this.getTransaction(outPoint.txHash); if (!transaction) { return; } @@ -132,6 +207,30 @@ export abstract class Client { return cell; } + async getCellWithHeader( + outPointLike: OutPointLike, + ): Promise<{ cell: Cell; header?: ClientBlockHeader } | undefined> { + const outPoint = OutPoint.from(outPointLike); + + const res = await this.getTransactionWithHeader(outPoint.txHash); + if (!res) { + return; + } + const { transaction, header } = res; + + const output = transaction.transaction.getOutput(outPoint.index); + if (!output) { + return; + } + + const cell = Cell.from({ + outPoint, + ...output, + }); + await this.cache.recordCells(cell); + return { cell, header }; + } + abstract getCellLiveNoCache( outPointLike: OutPointLike, withData?: boolean | null, @@ -504,9 +603,7 @@ export abstract class Client { const txHash = await this.sendTransactionNoCache(tx, validator); - await this.cache.recordTransactions(tx); await this.cache.markTransactions(tx); - return txHash; } @@ -515,25 +612,48 @@ export abstract class Client { ): Promise { const txHash = hexFrom(txHashLike); const res = await this.getTransactionNoCache(txHash); - if (res?.transaction) { + if (res) { + await this.cache.recordTransactionResponses(res); return res; } - const tx = await this.cache.getTransaction(txHash); - if (!tx) { - return; + return this.cache.getTransactionResponse(txHash); + } + + /** + * This method gets specified transaction with its block header (if existed). + * This is mainly for caching because we need the header to test if we can safely trust the cached tx status. + * @param txHashLike + */ + async getTransactionWithHeader( + txHashLike: HexLike, + ): Promise< + | { transaction: ClientTransactionResponse; header?: ClientBlockHeader } + | undefined + > { + const txHash = hexFrom(txHashLike); + const tx = await this.cache.getTransactionResponse(txHash); + if (tx?.blockHash) { + const header = await this.getHeaderByHash(tx.blockHash); + if (header && hasHeaderConfirmed(header)) { + return { + transaction: tx, + header, + }; + } } + const res = await this.getTransactionNoCache(txHash); if (!res) { - return { - transaction: tx, - status: "sent", - }; + return; } + await this.cache.recordTransactionResponses(res); return { - ...res, - transaction: tx, + transaction: res, + header: res.blockHash + ? await this.getHeaderByHash(res.blockHash) + : undefined, }; } diff --git a/packages/core/src/client/clientTypes.advanced.ts b/packages/core/src/client/clientTypes.advanced.ts index c8f59a5b..f59c6cdd 100644 --- a/packages/core/src/client/clientTypes.advanced.ts +++ b/packages/core/src/client/clientTypes.advanced.ts @@ -2,8 +2,9 @@ import { ScriptLike } from "../ckb/index.js"; import { HexLike } from "../hex/index.js"; import { Num, numFrom, NumLike } from "../num/index.js"; -export const DEFAULT_MAX_FEE_RATE = 10000000; -export const DEFAULT_MIN_FEE_RATE = 1000; +export const CONFIRMED_BLOCK_TIME = numFrom(1000 * 10 * 50); // 50 blocks * 10s +export const DEFAULT_MAX_FEE_RATE = numFrom(10000000); +export const DEFAULT_MIN_FEE_RATE = numFrom(1000); export function clientSearchKeyRangeFrom([a, b]: [NumLike, NumLike]): [ Num, diff --git a/packages/core/src/client/clientTypes.ts b/packages/core/src/client/clientTypes.ts index d35f7a9d..28e925e9 100644 --- a/packages/core/src/client/clientTypes.ts +++ b/packages/core/src/client/clientTypes.ts @@ -3,6 +3,7 @@ import { CellDep, CellDepLike, Epoch, + EpochLike, HashType, HashTypeLike, OutPoint, @@ -10,6 +11,8 @@ import { Script, ScriptLike, Transaction, + TransactionLike, + epochFrom, hashTypeFrom, } from "../ckb/index.js"; import { Hex, HexLike, hexFrom } from "../hex/index.js"; @@ -67,6 +70,10 @@ export class CellDepInfo { ) {} static from(cellDepInfoLike: CellDepInfoLike): CellDepInfo { + if (cellDepInfoLike instanceof CellDepInfo) { + return cellDepInfoLike; + } + return new CellDepInfo( CellDep.from(cellDepInfoLike.cellDep), apply(Script.from, cellDepInfoLike.type), @@ -94,6 +101,10 @@ export class ScriptInfo { ) {} static from(scriptInfoLike: ScriptInfoLike): ScriptInfo { + if (scriptInfoLike instanceof ScriptInfo) { + return scriptInfoLike; + } + return new ScriptInfo( hexFrom(scriptInfoLike.codeHash), hashTypeFrom(scriptInfoLike.hashType), @@ -120,15 +131,59 @@ export type TransactionStatus = /** * @public */ -export type ClientTransactionResponse = { - transaction: Transaction; +export type ClientTransactionResponseLike = { + transaction: TransactionLike; status: TransactionStatus; - cycles?: Num; - blockHash?: Hex; - blockNumber?: Num; - txIndex?: Num; + cycles?: NumLike; + blockHash?: HexLike; + blockNumber?: NumLike; + txIndex?: NumLike; reason?: string; }; +/** + * @public + */ +export class ClientTransactionResponse { + constructor( + public transaction: Transaction, + public status: TransactionStatus, + public cycles?: Num, + public blockHash?: Hex, + public blockNumber?: Num, + public txIndex?: Num, + public reason?: string, + ) {} + + static from( + responseLike: ClientTransactionResponseLike, + ): ClientTransactionResponse { + if (responseLike instanceof ClientTransactionResponse) { + return responseLike; + } + + return new ClientTransactionResponse( + Transaction.from(responseLike.transaction), + responseLike.status, + apply(numFrom, responseLike.cycles), + apply(hexFrom, responseLike.blockHash), + apply(numFrom, responseLike.blockNumber), + apply(numFrom, responseLike.txIndex), + responseLike.reason, + ); + } + + clone() { + return new ClientTransactionResponse( + this.transaction.clone(), + this.status, + this.cycles, + this.blockHash, + this.blockNumber, + this.txIndex, + this.reason, + ); + } +} /** * @public @@ -154,6 +209,10 @@ export class ClientIndexerSearchKeyFilter { static from( filterLike: ClientIndexerSearchKeyFilterLike, ): ClientIndexerSearchKeyFilter { + if (filterLike instanceof ClientIndexerSearchKeyFilter) { + return filterLike; + } + return new ClientIndexerSearchKeyFilter( apply(Script.from, filterLike.script), apply(clientSearchKeyRangeFrom, filterLike.scriptLenRange), @@ -186,6 +245,10 @@ export class ClientIndexerSearchKey { ) {} static from(keyLike: ClientIndexerSearchKeyLike): ClientIndexerSearchKey { + if (keyLike instanceof ClientIndexerSearchKey) { + return keyLike; + } + return new ClientIndexerSearchKey( Script.from(keyLike.script), keyLike.scriptType, @@ -230,6 +293,10 @@ export class ClientIndexerSearchKeyTransaction { static from( keyLike: ClientIndexerSearchKeyTransactionLike, ): ClientIndexerSearchKeyTransaction { + if (keyLike instanceof ClientIndexerSearchKeyTransaction) { + return keyLike; + } + return new ClientIndexerSearchKeyTransaction( Script.from(keyLike.script), keyLike.scriptType, @@ -273,58 +340,167 @@ export type ClientFindTransactionsGroupedResponse = { /** * @public */ -export type ClientBlockHeader = { - compactTarget: Num; +export type ClientBlockHeaderLike = { + compactTarget: NumLike; dao: { /** * C_i: the total issuance up to and including block i. */ - c: Num; + c: NumLike; /** * AR_i: the current accumulated rate at block i. * AR_j / AR_i reflects the CKByte amount if one deposit 1 CKB to Nervos DAO at block i, and withdraw at block j. */ - ar: Num; + ar: NumLike; /** * S_i: the total unissued secondary issuance up to and including block i, * including unclaimed Nervos DAO compensation and treasury funds. */ - s: Num; + s: NumLike; /** * U_i: the total occupied capacities currently in the blockchain up to and including block i. * Occupied capacity is the sum of capacities used to store all cells. */ - u: Num; + u: NumLike; }; - epoch: Epoch; - extraHash: Hex; - hash: Hex; - nonce: Num; - number: Num; - parentHash: Hex; - proposalsHash: Hex; - timestamp: Num; - transactionsRoot: Hex; - version: Num; + epoch: EpochLike; + extraHash: HexLike; + hash: HexLike; + nonce: NumLike; + number: NumLike; + parentHash: HexLike; + proposalsHash: HexLike; + timestamp: NumLike; + transactionsRoot: HexLike; + version: NumLike; }; +/** + * @public + */ +export class ClientBlockHeader { + constructor( + public compactTarget: Num, + public dao: { + /** + * C_i: the total issuance up to and including block i. + */ + c: Num; + /** + * AR_i: the current accumulated rate at block i. + * AR_j / AR_i reflects the CKByte amount if one deposit 1 CKB to Nervos DAO at block i, and withdraw at block j. + */ + ar: Num; + /** + * S_i: the total unissued secondary issuance up to and including block i, + * including unclaimed Nervos DAO compensation and treasury funds. + */ + s: Num; + /** + * U_i: the total occupied capacities currently in the blockchain up to and including block i. + * Occupied capacity is the sum of capacities used to store all cells. + */ + u: Num; + }, + public epoch: Epoch, + public extraHash: Hex, + public hash: Hex, + public nonce: Num, + public number: Num, + public parentHash: Hex, + public proposalsHash: Hex, + public timestamp: Num, + public transactionsRoot: Hex, + public version: Num, + ) {} + + static from(headerLike: ClientBlockHeaderLike): ClientBlockHeader { + if (headerLike instanceof ClientBlockHeader) { + return headerLike; + } + + return new ClientBlockHeader( + numFrom(headerLike.compactTarget), + { + c: numFrom(headerLike.dao.c), + ar: numFrom(headerLike.dao.ar), + s: numFrom(headerLike.dao.s), + u: numFrom(headerLike.dao.u), + }, + epochFrom(headerLike.epoch), + hexFrom(headerLike.extraHash), + hexFrom(headerLike.hash), + numFrom(headerLike.nonce), + numFrom(headerLike.number), + hexFrom(headerLike.parentHash), + hexFrom(headerLike.proposalsHash), + numFrom(headerLike.timestamp), + hexFrom(headerLike.transactionsRoot), + numFrom(headerLike.version), + ); + } +} /** * @public */ -export type ClientBlockUncle = { - header: ClientBlockHeader; - proposals: Hex[]; +export type ClientBlockUncleLike = { + header: ClientBlockHeaderLike; + proposals: HexLike[]; }; +/** + * @public + */ +export class ClientBlockUncle { + constructor( + public header: ClientBlockHeader, + public proposals: Hex[], + ) {} + + static from(uncleLike: ClientBlockUncleLike): ClientBlockUncle { + if (uncleLike instanceof ClientBlockUncle) { + return uncleLike; + } + + return new ClientBlockUncle( + ClientBlockHeader.from(uncleLike.header), + uncleLike.proposals.map(hexFrom), + ); + } +} /** * @public */ -export type ClientBlock = { - header: ClientBlockHeader; - proposals: Hex[]; - transactions: Transaction[]; - uncles: ClientBlockUncle[]; +export type ClientBlockLike = { + header: ClientBlockHeaderLike; + proposals: HexLike[]; + transactions: TransactionLike[]; + uncles: ClientBlockUncleLike[]; }; +/** + * @public + */ +export class ClientBlock { + constructor( + public header: ClientBlockHeader, + public proposals: Hex[], + public transactions: Transaction[], + public uncles: ClientBlockUncle[], + ) {} + + static from(blockLike: ClientBlockLike): ClientBlock { + if (blockLike instanceof ClientBlock) { + return blockLike; + } + + return new ClientBlock( + ClientBlockHeader.from(blockLike.header), + blockLike.proposals.map(hexFrom), + blockLike.transactions.map(Transaction.from), + blockLike.uncles.map(ClientBlockUncle.from), + ); + } +} export interface ErrorClientBaseLike { message?: string; diff --git a/packages/core/src/client/jsonRpc/client.ts b/packages/core/src/client/jsonRpc/client.ts index 0761e722..a30e8651 100644 --- a/packages/core/src/client/jsonRpc/client.ts +++ b/packages/core/src/client/jsonRpc/client.ts @@ -169,11 +169,11 @@ export abstract class ClientJsonRpc extends Client { * @param withCycles - whether the return cycles of block transactions. (Optional, default false.) * @returns Block */ - getBlockByNumber = this.buildSender( + getBlockByNumberNoCache = this.buildSender( "get_block_by_number", [(v: NumLike) => numToHex(numFrom(v))], (b: JsonRpcBlock) => apply(JsonRpcTransformers.blockTo, b), - ) as Client["getBlockByNumber"]; + ) as Client["getBlockByNumberNoCache"]; /** * Get block by block hash @@ -183,9 +183,11 @@ export abstract class ClientJsonRpc extends Client { * @param withCycles - whether the return cycles of block transactions. (Optional, default false.) * @returns Block */ - getBlockByHash = this.buildSender("get_block", [hexFrom], (b: JsonRpcBlock) => - apply(JsonRpcTransformers.blockTo, b), - ) as Client["getBlockByHash"]; + getBlockByHashNoCache = this.buildSender( + "get_block", + [hexFrom], + (b: JsonRpcBlock) => apply(JsonRpcTransformers.blockTo, b), + ) as Client["getBlockByHashNoCache"]; /** * Get header by block number @@ -194,11 +196,11 @@ export abstract class ClientJsonRpc extends Client { * @param verbosity - result format which allows 0 and 1. (Optional, the default is 1.) * @returns BlockHeader */ - getHeaderByNumber = this.buildSender( + getHeaderByNumberNoCache = this.buildSender( "get_header_by_number", [(v: NumLike) => numToHex(numFrom(v))], (b: JsonRpcBlockHeader) => apply(JsonRpcTransformers.blockHeaderTo, b), - ) as Client["getHeaderByNumber"]; + ) as Client["getHeaderByNumberNoCache"]; /** * Get header by block hash @@ -207,11 +209,11 @@ export abstract class ClientJsonRpc extends Client { * @param verbosity - result format which allows 0 and 1. (Optional, the default is 1.) * @returns BlockHeader */ - getHeaderByHash = this.buildSender( + getHeaderByHashNoCache = this.buildSender( "get_header", [hexFrom], (b: JsonRpcBlockHeader) => apply(JsonRpcTransformers.blockHeaderTo, b), - ) as Client["getHeaderByHash"]; + ) as Client["getHeaderByHashNoCache"]; /** * Estimate cycles of a transaction. diff --git a/packages/core/src/client/jsonRpc/transformers.ts b/packages/core/src/client/jsonRpc/transformers.ts index 543d71b0..4f46bc7a 100644 --- a/packages/core/src/client/jsonRpc/transformers.ts +++ b/packages/core/src/client/jsonRpc/transformers.ts @@ -192,7 +192,7 @@ export class JsonRpcTransformers { return; } - return { + return ClientTransactionResponse.from({ transaction: JsonRpcTransformers.transactionTo(transaction), status, cycles: apply(numFrom, cycles), @@ -200,7 +200,7 @@ export class JsonRpcTransformers { blockNumber: apply(numFrom, block_number), txIndex: apply(numFrom, tx_index), reason, - }; + }); } static blockHeaderTo(header: JsonRpcBlockHeader): ClientBlockHeader { const dao = bytesFrom(header.dao);