Skip to content

Commit 16fa2a4

Browse files
committed
feat: impl sync by indexer RPCs
1 parent 71a4d61 commit 16fa2a4

File tree

3 files changed

+199
-1
lines changed

3 files changed

+199
-1
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import Core from '@nervosnetwork/ckb-sdk-core'
2+
3+
import { NetworkWithID } from 'services/networks'
4+
import { networkSwitchSubject } from 'services/sync/renderer-params'
5+
6+
let core: Core
7+
networkSwitchSubject.subscribe((network: NetworkWithID | undefined) => {
8+
if (network) {
9+
core = new Core(network.remote)
10+
}
11+
})
12+
13+
export default class IndexerRPC {
14+
public deindexLockHash = async (lockHash: string) => {
15+
return core.rpc.deindexLockHash(lockHash)
16+
}
17+
18+
public indexLockHash = async (lockHash: string, indexFrom = '0') => {
19+
return core.rpc.indexLockHash(lockHash, indexFrom)
20+
}
21+
22+
public getTransactionByLockHash = async (
23+
lockHash: string,
24+
page: string,
25+
per: string,
26+
reverseOrder: boolean = false
27+
) => {
28+
const result = await core.rpc.getTransactionsByLockHash(lockHash, page, per, reverseOrder)
29+
return result
30+
}
31+
32+
public getLockHashIndexStates = async () => {
33+
return core.rpc.getLockHashIndexStates()
34+
}
35+
36+
public getLiveCellsByLockHash = async (
37+
lockHash: string,
38+
page: string,
39+
per: string,
40+
reverseOrder: boolean = false
41+
) => {
42+
const result = await core.rpc.getLiveCellsByLockHash(lockHash, page, per, reverseOrder)
43+
return result
44+
}
45+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import Utils from 'services/sync/utils'
2+
import logger from 'utils/logger'
3+
import GetBlocks from 'services/sync/get-blocks'
4+
import { Transaction } from 'types/cell-types'
5+
import TypeConvert from 'types/type-convert'
6+
import BlockNumber from 'services/sync/block-number'
7+
8+
import IndexerRPC from './indexer-rpc'
9+
import TransactionPersistor from '../tx/transaction-persistor'
10+
11+
export default class Queue {
12+
private lockHashes: string[]
13+
private indexerRPC: IndexerRPC
14+
private getBlocksService: GetBlocks
15+
private per = 50
16+
private interval = 5000
17+
private blockNumberService: BlockNumber
18+
19+
private stopped = false
20+
private indexed = false
21+
22+
private inProcess = false
23+
24+
constructor(lockHashes: string[]) {
25+
this.lockHashes = lockHashes
26+
this.indexerRPC = new IndexerRPC()
27+
this.getBlocksService = new GetBlocks()
28+
this.blockNumberService = new BlockNumber()
29+
}
30+
31+
public setLockHashes = (lockHashes: string[]): void => {
32+
this.lockHashes = lockHashes
33+
this.indexed = false
34+
}
35+
36+
/* eslint no-await-in-loop: "off" */
37+
/* eslint no-restricted-syntax: "off" */
38+
public start = async () => {
39+
while (!this.stopped) {
40+
try {
41+
this.inProcess = true
42+
const { lockHashes } = this
43+
if (!this.indexed) {
44+
await this.indexLockHashes(lockHashes)
45+
this.indexed = true
46+
}
47+
const currentBlockNumber = await this.getCurrentBlockNumber(lockHashes)
48+
for (const lockHash of lockHashes) {
49+
await this.pipeline(lockHash, 'createdBy')
50+
}
51+
for (const lockHash of lockHashes) {
52+
await this.pipeline(lockHash, 'consumedBy')
53+
}
54+
if (currentBlockNumber) {
55+
await this.blockNumberService.updateCurrent(currentBlockNumber)
56+
}
57+
await this.yield(this.interval)
58+
} catch (err) {
59+
logger.error('sync indexer error:', err)
60+
} finally {
61+
await this.yield()
62+
this.inProcess = false
63+
}
64+
}
65+
}
66+
67+
public getCurrentBlockNumber = async (lockHashes: string[]) => {
68+
// get lock hash indexer status
69+
const lockHashIndexStates = await this.indexerRPC.getLockHashIndexStates()
70+
const blockNumbers = lockHashIndexStates
71+
.filter(state => lockHashes.includes(state.lockHash))
72+
.map(state => state.blockNumber)
73+
const uniqueBlockNumbers = [...new Set(blockNumbers)]
74+
const blockNumbersBigInt = uniqueBlockNumbers.map(num => BigInt(num))
75+
const minBlockNumber = blockNumbersBigInt.sort()[0]
76+
return minBlockNumber
77+
}
78+
79+
public indexLockHashes = async (lockHashes: string[]) => {
80+
await Utils.mapSeries(lockHashes, async (lockHash: string) => {
81+
await this.indexerRPC.indexLockHash(lockHash)
82+
})
83+
}
84+
85+
// type: 'createdBy' | 'consumedBy'
86+
public pipeline = async (lockHash: string, type: string) => {
87+
let page = 0
88+
let stopped = false
89+
while (!stopped) {
90+
const txs = await this.indexerRPC.getTransactionByLockHash(lockHash, page.toString(), this.per.toString())
91+
if (txs.length < this.per) {
92+
stopped = true
93+
}
94+
for (const tx of txs) {
95+
let txPoint: CKBComponents.TransactionPoint | null = null
96+
if (type === 'createdBy') {
97+
txPoint = tx.createdBy
98+
} else if (type === 'consumedBy') {
99+
txPoint = tx.consumedBy
100+
}
101+
if (txPoint) {
102+
const transactionWithStatus = await this.getBlocksService.getTransaction(txPoint.txHash)
103+
const ckbTransaction: CKBComponents.Transaction = transactionWithStatus.transaction
104+
const transaction: Transaction = TypeConvert.toTransaction(ckbTransaction)
105+
// tx timestamp / blockNumber / blockHash
106+
const { blockHash } = transactionWithStatus.txStatus
107+
if (blockHash) {
108+
const blockHeader = await this.getBlocksService.getHeader(blockHash)
109+
transaction.blockHash = blockHash
110+
transaction.blockNumber = blockHeader.number
111+
transaction.timestamp = blockHeader.timestamp
112+
}
113+
await TransactionPersistor.saveFetchTx(transaction)
114+
}
115+
}
116+
page += 1
117+
}
118+
}
119+
120+
public stop = () => {
121+
this.stopped = true
122+
}
123+
124+
public waitForDrained = async (timeout: number = 5000) => {
125+
const startAt: number = +new Date()
126+
while (this.inProcess) {
127+
const now: number = +new Date()
128+
if (now - startAt > timeout) {
129+
return
130+
}
131+
await this.yield(50)
132+
}
133+
}
134+
135+
public stopAndWait = async () => {
136+
this.stop()
137+
await this.waitForDrained()
138+
}
139+
140+
private yield = async (millisecond: number = 1) => {
141+
await Utils.sleep(millisecond)
142+
}
143+
}

packages/neuron-wallet/src/services/sync/get-blocks.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Core from '@nervosnetwork/ckb-sdk-core'
22

3-
import { Block } from 'types/cell-types'
3+
import { Block, BlockHeader } from 'types/cell-types'
44
import TypeConvert from 'types/type-convert'
55
import { NetworkWithID } from 'services/networks'
66
import CheckAndSave from './check-and-save'
@@ -55,6 +55,16 @@ export default class GetBlocks {
5555
return block
5656
}
5757

58+
public getTransaction = async (hash: string): Promise<CKBComponents.TransactionWithStatus> => {
59+
const tx = await core.rpc.getTransaction(hash)
60+
return tx
61+
}
62+
63+
public getHeader = async (hash: string): Promise<BlockHeader> => {
64+
const result = await core.rpc.getHeader(hash)
65+
return TypeConvert.toBlockHeader(result)
66+
}
67+
5868
public static getBlockByNumber = async (num: string): Promise<Block> => {
5969
const block = await core.rpc.getBlockByNumber(num)
6070
return TypeConvert.toBlock(block)

0 commit comments

Comments
 (0)