diff --git a/packages/io/__tests__/listener.ts b/packages/io/__tests__/listener.ts index a81acdae..38f39331 100644 --- a/packages/io/__tests__/listener.ts +++ b/packages/io/__tests__/listener.ts @@ -53,8 +53,7 @@ describe('test Listener', () => { getCurrentEpoch: () => Promise.resolve(mockEpoch), getBlock: () => Promise.resolve(mockBlock), getAllLiveCellsWithWitness: function ( - _lockScript: Script, - _typeScript?: Script, + _scripts: Partial>, ): Promise<(CKBComponents.IndexerCell & { witness: string })[]> { return Promise.resolve([]) }, diff --git a/packages/io/src/chain-source/nervos.ts b/packages/io/src/chain-source/nervos.ts index ba059285..ff0772fc 100644 --- a/packages/io/src/chain-source/nervos.ts +++ b/packages/io/src/chain-source/nervos.ts @@ -5,7 +5,10 @@ import type { CKBComponents } from '@ckb-lumos/rpc/lib/types/api' export class NervosChainSource implements ChainSource { #rpc: RPC - constructor(rpcUrl: string, private _getCellsLimit: number = 1000) { + constructor( + rpcUrl: string, + private _getCellsLimit: number = 1000, + ) { this.#rpc = new RPC(rpcUrl) } @@ -21,8 +24,7 @@ export class NervosChainSource implements ChainSource { } getAllLiveCellsWithWitness = async ( - lockScript: Script, - typeScript?: Script | undefined, + scripts: Partial>, startBlock?: BI, endBlock?: BI, ) => { @@ -32,13 +34,36 @@ export class NervosChainSource implements ChainSource { endBlock = endBlock ?? BI.from(await this.getTipBlockNumber()) let lastCursor = '' + let scriptFilterOption: CKBComponents.GetCellsSearchKey | undefined = undefined + if (scripts.lock && scripts.type) { + scriptFilterOption = { + script: scripts.lock, + scriptType: 'lock', + filter: { + script: scripts.type, + }, + withData: true, + } + } else if (scripts.lock) { + scriptFilterOption = { + script: scripts.lock, + scriptType: 'lock', + withData: true, + } + } else if (scripts.type) { + scriptFilterOption = { + script: scripts.type, + scriptType: 'type', + withData: true, + } + } + if (!scriptFilterOption) throw new Error('One of lock script or type script is required at least') do { const cells = await this.#rpc.getCells( { - script: lockScript, - scriptType: 'lock', + ...scriptFilterOption, filter: { - script: typeScript, + ...scriptFilterOption?.filter, blockRange: [startBlock.toHexString(), endBlock.toHexString()], }, }, diff --git a/packages/io/src/types/index.ts b/packages/io/src/types/index.ts index 2c60d075..f8efa2a9 100644 --- a/packages/io/src/types/index.ts +++ b/packages/io/src/types/index.ts @@ -44,8 +44,7 @@ export interface ChainSource { getCurrentEpoch: () => Promise getBlock: (blockNumber: string) => Promise getAllLiveCellsWithWitness: ( - lockScript: Script, - typeScript?: Script, + scripts: Partial>, ) => Promise<(CKBComponents.IndexerCell & { witness: CKBComponents.Witness })[]> } diff --git a/packages/models/__tests__/binding-resource/manager.ts b/packages/models/__tests__/binding-resource/manager.ts index 0681b570..5cfd5b7c 100644 --- a/packages/models/__tests__/binding-resource/manager.ts +++ b/packages/models/__tests__/binding-resource/manager.ts @@ -74,8 +74,7 @@ const mockSource: ChainSource = { throw new Error('Function not implemented.') }, getAllLiveCellsWithWitness: function ( - _lockScript: Script, - _typeScript?: Script, + _scripts: Partial>, ): Promise<(CKBComponents.IndexerCell & { witness: string })[]> { return Promise.resolve([]) }, @@ -99,8 +98,7 @@ describe('Test resource binding', () => { throw new Error('Function not implemented.') }, getAllLiveCellsWithWitness: function ( - _lockScript: Script, - _typeScript?: Script, + _scripts: Partial>, ): Promise<(CKBComponents.IndexerCell & { witness: string })[]> { const witness = '0x' const cell: Cell = { @@ -181,7 +179,7 @@ describe('Test resource binding', () => { }, }, }) - expect(manager.registry.get(TypeScriptHash)?.get(LockScriptHash)).toEqual( + expect(manager.registry.getRegistry(LockScriptHash, TypeScriptHash)).toEqual( new Map([ [ 'local://store', @@ -202,7 +200,7 @@ describe('Test resource binding', () => { ]), ) await setTimeout(2000) - expect(manager.registry.get(TypeScriptHash)?.get(LockScriptHash)).toEqual( + expect(manager.registry.getRegistry(LockScriptHash, TypeScriptHash)).toEqual( new Map([ [ 'local://store', @@ -222,7 +220,7 @@ describe('Test resource binding', () => { ], ]), ) - expect(manager.registryReverse.get(ref.uri)).toEqual([TypeScriptHash, LockScriptHash]) + expect(manager.registry.getRegistryByUri(ref.uri)).toEqual([LockScriptHash, TypeScriptHash]) expect(mockXAdd).toBeCalledTimes(2) }) }) @@ -259,8 +257,8 @@ describe('Test resource binding', () => { }, }, }) - expect(manager.registry.get(TypeScriptHash)?.get(LockScriptHash)).toEqual(new Map()) - expect(manager.registryReverse.get(ref.uri)).toBeUndefined() + expect(manager.registry.getRegistry(LockScriptHash, TypeScriptHash)).toEqual(new Map()) + expect(manager.registry.getRegistryByUri(ref.uri)).toBeUndefined() }) it('will to nothing when no registry', () => { @@ -347,8 +345,7 @@ describe('Test resource binding', () => { return Promise.resolve(mockBlock) }, getAllLiveCellsWithWitness: function ( - _lockScript: Script, - _typeScript?: Script, + _scripts: Partial>, ): Promise<(CKBComponents.IndexerCell & { witness: string })[]> { return Promise.resolve([]) }, @@ -400,7 +397,12 @@ describe('Test resource binding', () => { jest.spyOn(CellChangeBuffer.prototype, 'popAll').mockImplementationOnce(() => [[change]]) jest.spyOn(CellChangeBuffer.prototype, 'hasReadyStore').mockImplementationOnce(() => true) - manager.register(cell.cellOutput.lock, cell.cellOutput.type, registry.uri, 'normal') + manager.register({ + lockScript: cell.cellOutput.lock, + typeScript: cell.cellOutput.type, + uri: registry.uri, + pattern: 'normal', + }) listener = manager.listen() await setTimeout(2000) expect(mockXAdd).toBeCalledTimes(2) @@ -422,8 +424,7 @@ describe('Test resource binding', () => { throw new Error('Function not implemented.') }, getAllLiveCellsWithWitness: function ( - _lockScript: Script, - _typeScript?: Script, + _scripts: Partial>, ): Promise<(CKBComponents.IndexerCell & { witness: string })[]> { return Promise.resolve([]) }, @@ -845,10 +846,10 @@ describe('Test resource binding', () => { expect(manager.lastBlock?.header.number).toEqual(mockBlock0.header.number) mockSource.getTipHeader = () => Promise.resolve(mockBlock.header) mockSource.getBlock = () => Promise.resolve(mockBlock) - manager.register(outputA.lock, outputA.type, ref1.uri, 'normal') - manager.register(outputE.lock, outputE.type, ref1.uri, 'normal') - manager.register(outputD.lock, outputD.type, ref2.uri, 'normal') - manager.register(outputC.lock, outputC.type, ref2.uri, 'normal') + manager.register({ lockScript: outputA.lock, typeScript: outputA.type, uri: ref1.uri, pattern: 'normal' }) + manager.register({ lockScript: outputE.lock, typeScript: outputE.type, uri: ref1.uri, pattern: 'normal' }) + manager.register({ lockScript: outputD.lock, typeScript: outputD.type, uri: ref2.uri, pattern: 'normal' }) + manager.register({ lockScript: outputC.lock, typeScript: outputC.type, uri: ref2.uri, pattern: 'normal' }) await setTimeout(2000) expect(mockXAdd).toBeCalledTimes(2) diff --git a/packages/models/src/resource-binding/manager.ts b/packages/models/src/resource-binding/manager.ts index d76128f1..ef3096e0 100644 --- a/packages/models/src/resource-binding/manager.ts +++ b/packages/models/src/resource-binding/manager.ts @@ -8,6 +8,7 @@ import { ResourceBindingRegistry, ResourceBindingManagerMessage, CellChange, + RegisterMessage, } from './types' import { outPointToOutPointString } from './utils' import { Listener } from '@ckb-js/kuai-io' @@ -18,16 +19,97 @@ import { CellChangeBuffer } from './cell-change-buffer' type Registry = Map +class ScriptRegistry { + #singleRegistry: Map = new Map() + #unionRegistry: Map> = new Map() + #registryReverse: Map = new Map() + + getRegistry(lockHash?: LockScriptHash, typeHash?: TypeScriptHash) { + if (lockHash && typeHash) { + return this.#unionRegistry.get(lockHash)?.get(typeHash) + } + if (lockHash) { + return this.#singleRegistry.get(lockHash) + } + if (typeHash) { + return this.#singleRegistry.get(typeHash) + } + } + + getRegistryByUri(uri: ActorURI) { + return this.#registryReverse.get(uri) + } + + setActorRegistry( + uri: ActorURI, + register: ResourceBindingRegistry, + lockHash?: LockScriptHash, + typeHash?: TypeScriptHash, + ) { + if (lockHash && typeHash) { + if (!this.#unionRegistry.has(lockHash)) { + this.#unionRegistry.set(lockHash, new Map([[typeHash, new Map([[uri, register]])]])) + } else if (!this.#unionRegistry.get(lockHash)?.get(typeHash)) { + this.#unionRegistry.get(lockHash)?.set(typeHash, new Map([[uri, register]])) + } else { + this.#unionRegistry.get(lockHash)?.get(typeHash)?.set(uri, register) + } + this.#registryReverse.set(uri, [lockHash, typeHash]) + return + } + const hash = lockHash ?? typeHash + if (hash) { + if (this.#singleRegistry.has(hash)) { + this.#singleRegistry.get(hash)?.set(uri, register) + } else { + this.#singleRegistry.set(hash, new Map([[uri, register]])) + } + return + } + } + + removeRegistry(lockHash?: LockScriptHash, typeHash?: TypeScriptHash) { + if (lockHash && typeHash) { + return this.#unionRegistry.get(lockHash)?.delete(typeHash) + } + if (lockHash) { + return this.#singleRegistry.delete(lockHash) + } + if (typeHash) { + return this.#singleRegistry.delete(typeHash) + } + } + + removeActorURI(uri: ActorURI) { + if (!this.#registryReverse.has(uri)) return + const [lockHash, typeHash] = this.#registryReverse.get(uri)! + this.#registryReverse.delete(uri) + if (lockHash && typeHash) { + this.#unionRegistry.get(lockHash)?.get(typeHash)?.delete(uri) + return + } + if (lockHash) { + this.#singleRegistry.get(lockHash)?.delete(uri) + return + } + if (typeHash) { + this.#singleRegistry.get(typeHash)?.delete(uri) + return + } + } +} @ActorProvider({ ref: { name: 'manager' }, autoBind: true }) export class Manager extends Actor> { - #registry: Map> = new Map() + #registry: ScriptRegistry = new ScriptRegistry() #registryOutPoint: Map = new Map() - #registryReverse: Map = new Map() #lastBlock: Block | undefined = undefined #tipBlockNumber = BI.from(0) #buffer: CellChangeBuffer = new CellChangeBuffer() - constructor(private _listener: Listener
, private _dataSource: ChainSource) { + constructor( + private _listener: Listener
, + private _dataSource: ChainSource, + ) { super() } @@ -163,9 +245,9 @@ export class Manager extends Actor() ;[...newOutputs.entries()].forEach(([outPoint, cellChangeData]) => { const { lock, type } = cellChangeData[0].cellOutput - const lockHash = utils.computeScriptHash(lock) - const typeHash = type ? utils.computeScriptHash(type) : 'null' - const registries = this.#registry.get(typeHash)?.get(lockHash) + const lockHash = lock ? utils.computeScriptHash(lock) : undefined + const typeHash = type ? utils.computeScriptHash(type) : undefined + const registries = this.#registry.getRegistry(lockHash, typeHash) // regsitry is empty if (!registries) return @@ -189,8 +271,8 @@ export class Manager extends Actor>): void => { @@ -220,14 +299,14 @@ export class Manager extends Actor() - } + async register(registerInfo: RegisterMessage['register']) { + const { lockScript, typeScript, uri, pattern } = registerInfo + const lockHash = lockScript ? utils.computeScriptHash(lockScript) : undefined + const typeHash = typeScript ? utils.computeScriptHash(typeScript) : undefined const registry: ResourceBindingRegistry = { uri, pattern, status: 'registered' } - registries.set(uri, registry) - this.#registry.get(typeHash)?.set(lockHash, registries) - this.#registryReverse.set(uri, [typeHash, lockHash]) - await this.initiateStore(registry, lock, type) - } - - revoke(uri: ActorURI) { - const registry = this.#registryReverse.get(uri) - if (registry) { - const [type, lock] = registry - this.#registry.get(type)?.get(lock)?.delete(uri) - this.#registryReverse.delete(uri) - } + this.#registry.setActorRegistry(uri, registry, lockHash, typeHash) + await this.initiateStore(registry, lockScript, typeScript) } listen(pollingInterval = 1000): { subscription: Subscription; updator: NodeJS.Timer } { @@ -277,7 +339,7 @@ export class Manager extends Actor>> { + get registry(): ScriptRegistry { return this.#registry } @@ -285,10 +347,6 @@ export class Manager extends Actor { - return this.#registryReverse - } - get lastBlock(): Block | undefined { return this.#lastBlock } diff --git a/packages/models/src/resource-binding/types.ts b/packages/models/src/resource-binding/types.ts index 2f685e9b..b925f7a2 100644 --- a/packages/models/src/resource-binding/types.ts +++ b/packages/models/src/resource-binding/types.ts @@ -1,7 +1,7 @@ import { Cell, Hash, Input, Script } from '@ckb-lumos/base' import { ActorURI } from '../actor' -export type TypeScriptHash = Hash | 'null' +export type TypeScriptHash = Hash export type LockScriptHash = Hash export type RegistryStatus = 'registered' | 'initiated' export type CellChangeData = [cell: Cell, witness: string] @@ -14,11 +14,11 @@ export interface ResourceBindingRegistry { status?: RegistryStatus } -interface RegisterMessage { +export interface RegisterMessage { type: 'register' register: { - typeScript: Script - lockScript: Script + typeScript?: Script + lockScript?: Script } & ResourceBindingRegistry } diff --git a/packages/models/src/store/store.ts b/packages/models/src/store/store.ts index 4f1734a8..f825225a 100644 --- a/packages/models/src/store/store.ts +++ b/packages/models/src/store/store.ts @@ -131,11 +131,11 @@ export class Store< } protected registerResourceBinding() { - if (!this.#lock) return - - const lockHash = computeScriptHash(this.#lock) + const lockHash = this.#lock ? computeScriptHash(this.#lock) : undefined + const typeHash = this.#type ? computeScriptHash(this.#type) : undefined + if (!lockHash && !typeHash) return this.call('local://resource', { - pattern: lockHash, + pattern: `${lockHash ?? ''}/${typeHash ?? ''}`, value: { type: 'register', register: {