Skip to content

Commit

Permalink
fix: Refactor fill inputs to a new function. Use more clear variable …
Browse files Browse the repository at this point in the history
…name.
  • Loading branch information
yanguoyu committed Jun 19, 2023
1 parent cf25d37 commit 5e11fdf
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 39 deletions.
67 changes: 36 additions & 31 deletions packages/neuron-wallet/src/services/tx/transaction-persistor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ export class TransactionPersistor {
// After the tx is fetched:
// 1. If the tx is not persisted before fetching, output = live, input = dead
// 2. If the tx is already persisted before fetching, output = live, input = dead
private static saveWithFetch = async (transaction: Transaction, lockArgsSet?: Set<string> ): Promise<TransactionEntity> => {
private static saveWithFetch = async (
transaction: Transaction,
lockArgsSetNeedsDetail?: Set<string>
): Promise<TransactionEntity> => {
const connection = getConnection()
const txEntity: TransactionEntity | undefined = await connection
.getRepository(TransactionEntity)
Expand Down Expand Up @@ -192,15 +195,15 @@ export class TransactionPersistor {
return txEntity
}

return TransactionPersistor.create(transaction, OutputStatus.Live, OutputStatus.Dead, lockArgsSet)
return TransactionPersistor.create(transaction, OutputStatus.Live, OutputStatus.Dead, lockArgsSetNeedsDetail)
}

// only create, check exist before this
public static create = async (
transaction: Transaction,
outputStatus: OutputStatus,
inputStatus: OutputStatus,
lockArgsSet?: Set<string>,
lockArgsSetNeedsDetail?: Set<string>
): Promise<TransactionEntity> => {
const connection = getConnection()
const tx = new TransactionEntity()
Expand Down Expand Up @@ -300,31 +303,29 @@ export class TransactionPersistor {
}
return output
})
let currentWalletInputs: InputEntity[] = inputs
let currentWalletOutputs: OutputEntity[] = outputs
let willSaveDetailInputs: InputEntity[] = inputs
let willSaveDetailOutputs: OutputEntity[] = outputs
let txLocks: TxLockEntity[] = []
if (lockArgsSet?.size) {
currentWalletInputs = inputs.filter(v => this.isCurrentWalletCell(v, lockArgsSet))
currentWalletOutputs = outputs.filter(v => this.isCurrentWalletCell(v, lockArgsSet))
txLocks = [...new Set(
[
...inputs.filter(v => v.lockHash && !this.isCurrentWalletCell(v, lockArgsSet))
.map(v => v.lockHash!),
...outputs.filter(v => !this.isCurrentWalletCell(v, lockArgsSet))
.map(v => v.lockHash),
]
)].map(v => TxLockEntity.fromObject({ txHash: tx.hash, lockHash: v}))
if (lockArgsSetNeedsDetail?.size) {
willSaveDetailInputs = inputs.filter(v => this.shouldSaveDetail(v, lockArgsSetNeedsDetail))
willSaveDetailOutputs = outputs.filter(v => this.shouldSaveDetail(v, lockArgsSetNeedsDetail))
txLocks = [
...new Set([
...inputs.filter(v => v.lockHash && !this.shouldSaveDetail(v, lockArgsSetNeedsDetail)).map(v => v.lockHash!),
...outputs.filter(v => !this.shouldSaveDetail(v, lockArgsSetNeedsDetail)).map(v => v.lockHash),
]),
].map(v => TxLockEntity.fromObject({ txHash: tx.hash, lockHash: v }))
}

const chunk = 100
const queryRunner = connection.createQueryRunner()
await TransactionPersistor.waitUntilTransactionFinished(queryRunner)
await queryRunner.startTransaction()
try {
await queryRunner.manager.save(tx)
await queryRunner.manager.save(currentWalletInputs, { chunk })
await queryRunner.manager.save(willSaveDetailInputs, { chunk })
await queryRunner.manager.save(previousOutputs, { chunk })
await queryRunner.manager.save(currentWalletOutputs, { chunk })
await queryRunner.manager.save(willSaveDetailOutputs, { chunk })
await queryRunner.manager.save(txLocks, { chunk })
await queryRunner.commitTransaction()
} catch (err) {
Expand Down Expand Up @@ -356,26 +357,29 @@ export class TransactionPersistor {
public static convertTransactionAndSave = async (
transaction: Transaction,
saveType: TxSaveType,
lockArgsSet?: Set<string>,
lockArgsSetNeedsDetail?: Set<string>
): Promise<TransactionEntity> => {
const tx: Transaction = transaction

let txEntity: TransactionEntity
if (saveType === TxSaveType.Sent) {
txEntity = await TransactionPersistor.saveWithSent(tx)
} else if (saveType === TxSaveType.Fetch) {
txEntity = await TransactionPersistor.saveWithFetch(tx, lockArgsSet)
txEntity = await TransactionPersistor.saveWithFetch(tx, lockArgsSetNeedsDetail)
} else {
throw new Error('Error TxSaveType!')
}
return txEntity
}

public static saveFetchTx = async (transaction: Transaction, lockArgsSet?: Set<string>): Promise<TransactionEntity> => {
public static saveFetchTx = async (
transaction: Transaction,
lockArgsSetNeedsDetail?: Set<string>
): Promise<TransactionEntity> => {
const txEntity: TransactionEntity = await TransactionPersistor.convertTransactionAndSave(
transaction,
TxSaveType.Fetch,
lockArgsSet
lockArgsSetNeedsDetail
)
return txEntity
}
Expand All @@ -389,14 +393,15 @@ export class TransactionPersistor {
return txEntity
}

private static isCurrentWalletCell(cell: InputEntity | OutputEntity, lockArgsSet: Set<string>) {
return (cell.lockArgs && (
lockArgsSet.has(cell.lockArgs)
|| (
cell.lockArgs.length === CHEQUE_ARGS_LENGTH
&& [cell.lockArgs.slice(0, DEFAULT_ARGS_LENGTH), `0x${cell.lockArgs.slice(DEFAULT_ARGS_LENGTH)}`].some(v => lockArgsSet.has(v))
)
))
private static shouldSaveDetail(cell: InputEntity | OutputEntity, lockArgsSetNeedsDetail: Set<string>) {
return (
cell.lockArgs &&
(lockArgsSetNeedsDetail.has(cell.lockArgs) ||
(cell.lockArgs.length === CHEQUE_ARGS_LENGTH &&
[cell.lockArgs.slice(0, DEFAULT_ARGS_LENGTH), `0x${cell.lockArgs.slice(DEFAULT_ARGS_LENGTH)}`].some(v =>
lockArgsSetNeedsDetail.has(v)
)))
)
}
}

Expand Down
23 changes: 15 additions & 8 deletions packages/neuron-wallet/src/services/tx/transaction-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import exportTransactions from '../../utils/export-history'
import RpcService from '../rpc-service'
import NetworksService from '../networks'
import Script from '../../models/chain/script'
import Input from '../../models/chain/input'

export interface TransactionsByAddressesParam {
pageNo: number
Expand Down Expand Up @@ -96,7 +97,7 @@ export class TransactionsService {
SELECT input.transactionHash FROM input WHERE input.lockArgs in (select publicKeyInBlake160 from hd_public_key_info where walletId = @1)
)
`,
[lockHashToSearch, params.walletID ]
[lockHashToSearch, params.walletID]
)
.then<string[]>((txs: { transactionHash: string }[]) => txs.map(tx => tx.transactionHash))
} else if (type === SearchType.TxHash) {
Expand Down Expand Up @@ -497,7 +498,15 @@ export class TransactionsService {
return undefined
}
const tx = txInDB.toModel()
const inputTxHashes = txWithStatus.transaction.inputs.map(v => v.previousOutput?.txHash).filter((v): v is string => !!v)
tx.inputs = await this.fillInputFields(txWithStatus.transaction.inputs)
tx.outputs = txWithStatus.transaction.outputs
return tx
}

private static async fillInputFields(inputs: Input[]) {
const inputTxHashes = inputs.map(v => v.previousOutput?.txHash).filter((v): v is string => !!v)
if (!inputTxHashes.length) return inputs
const url: string = NetworksService.getInstance().getCurrent().remote
const ckb = new CKB(url)
const inputTxs = await ckb.rpc
.createBatchRequest<'getTransaction', string[], CKBComponents.TransactionWithStatus[]>(
Expand All @@ -508,19 +517,17 @@ export class TransactionsService {
inputTxs.forEach((v, idx) => {
inputTxMap.set(inputTxHashes[idx], v.transaction)
})
txWithStatus.transaction.inputs.forEach(v => {
if (!v.previousOutput?.txHash) return
return inputs.map(v => {
if (!v.previousOutput?.txHash) return v
const output = inputTxMap.get(v.previousOutput.txHash)?.outputs?.[+v.previousOutput.index]
if (!output) return
if (!output) return v
v.setCapacity(output.capacity)
v.setLock(Script.fromSDK(output.lock))
if (output.type) {
v.setType(Script.fromSDK(output.type))
}
return v
})
tx.inputs = txWithStatus.transaction.inputs
tx.outputs = txWithStatus.transaction.outputs
return tx
}

public static blake160sOfTx(tx: Transaction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import HdPublicKeyInfo from '../../../src/database/chain/entities/hd-public-key-
import TransactionPersistor, { TxSaveType } from '../../../src/services/tx/transaction-persistor'
import SystemScriptInfo from '../../../src/models/system-script-info'
import { scriptToAddress } from '@nervosnetwork/ckb-sdk-utils'
import Input from '../../../src/models/chain/input'
import OutPoint from '../../../src/models/chain/out-point'

jest.mock('../../../src/models/asset-account-info', () => {
const originalModule = jest.requireActual('../../../src/models/asset-account-info').default
Expand Down Expand Up @@ -561,4 +563,93 @@ describe('Test TransactionService', () => {
})
})
})

describe('fillInputFields', () => {
it('inputs is empty', async () => {
const inputs: Input[] = []
//@ts-ignore private-method
const actual = await TransactionService.fillInputFields(inputs)
expect(actual).toBe(inputs)
})
it('inputs without txHash', async () => {
const inputs = [
Input.fromObject({
previousOutput: null
})
]
//@ts-ignore private-method
const actual = await TransactionService.fillInputFields(inputs)
expect(actual).toBe(inputs)
})
it('can not get output', async () => {
const inputs = [
Input.fromObject({
previousOutput: new OutPoint(`0x${'0'.repeat(64)}`, '0x0')
})
]
ckbRpcExecMock.mockResolvedValueOnce([])
//@ts-ignore private-method
const actual = await TransactionService.fillInputFields(inputs)
expect(actual).toStrictEqual(inputs)
})
it('success fill input fields without type', async () => {
const inputs = [
Input.fromObject({
previousOutput: new OutPoint(`0x${'0'.repeat(64)}`, '0x0')
})
]
const transactionWithStatus = {
transaction: {
outputs: [
{
capacity: '0x1000',
lock: {
codeHash: `0x${'0'.repeat(64)}`,
hashType: 'data',
args: '0x0'
},
}
]
}
}
ckbRpcExecMock.mockResolvedValueOnce([transactionWithStatus])
//@ts-ignore private-method
const actual = await TransactionService.fillInputFields(inputs)
expect(actual[0].capacity).toBe('4096')
expect(actual[0].lock?.toSDK()).toStrictEqual(transactionWithStatus.transaction.outputs[0].lock)
expect(actual[0].type).toBeUndefined()
})
it('success fill input fields with type', async () => {
const inputs = [
Input.fromObject({
previousOutput: new OutPoint(`0x${'0'.repeat(64)}`, '0x0')
})
]
const transactionWithStatus = {
transaction: {
outputs: [
{
capacity: '0x1000',
lock: {
codeHash: `0x${'0'.repeat(64)}`,
hashType: 'data',
args: '0x0'
},
type: {
codeHash: `0x${'1'.repeat(64)}`,
hashType: 'data',
args: '0x1'
}
}
]
}
}
ckbRpcExecMock.mockResolvedValueOnce([transactionWithStatus])
//@ts-ignore private-method
const actual = await TransactionService.fillInputFields(inputs)
expect(actual[0].capacity).toBe('4096')
expect(actual[0].lock?.toSDK()).toStrictEqual(transactionWithStatus.transaction.outputs[0].lock)
expect(actual[0].type?.toSDK()).toStrictEqual(transactionWithStatus.transaction.outputs[0].type)
})
})
})

0 comments on commit 5e11fdf

Please sign in to comment.