Skip to content

Commit

Permalink
feat: complete paymaster cell signing & sending transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
ahonn committed Mar 25, 2024
1 parent b67ed7b commit 025ec27
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ CKB_RPC_URL=https://testnet.ckb.dev/rpc
CKB_INDEXER_URL=https://testnet.ckb.dev/indexer

PAYMASTER_PRIVATE_KEY=
PAYMASTER_CELL_CAPACITY=220
PAYMASTER_CELL_CAPACITY=255
PAYMASTER_CELL_PRESET_COUNT=5000

UNLOCKER_CELL_BATCH_SIZE=100
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
"@nervosnetwork/ckb-sdk-utils": "^0.109.1",
"@rgbpp-sdk/btc": "0.0.0-snap-20240324134200",
"@rgbpp-sdk/ckb": "0.0.0-snap-20240324134200",
"@rgbpp-sdk/btc": "0.0.0-snap-20240325120223",
"@rgbpp-sdk/ckb": "0.0.0-snap-20240325120223",
"@sentry/node": "^7.102.1",
"@sentry/profiling-node": "^7.102.1",
"awilix": "^10.0.1",
Expand Down
18 changes: 9 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 33 additions & 15 deletions src/services/paymaster.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Cell } from '@ckb-lumos/lumos';
import { Cradle } from '../container';
import { Job, Queue, Worker } from 'bullmq';
import { Queue, Worker } from 'bullmq';
import { AppendPaymasterCellAndSignTxParams, IndexerCell, appendPaymasterCellAndSignCkbTx } from '@rgbpp-sdk/ckb';
import { hd, config, BI } from '@ckb-lumos/lumos';
import * as Sentry from '@sentry/node';

interface IPaymaster {
getNextCellJob(token: string): Promise<Job<Cell> | null>;
getNextCell(token: string): Promise<IndexerCell | null>;
refillCellQueue(): Promise<number>;
appendCellAndSignTx(
txid: string,
Expand Down Expand Up @@ -47,6 +47,7 @@ export default class Paymaster implements IPaymaster {
});
this.worker = new Worker(PAYMASTER_CELL_QUEUE_NAME, undefined, {
connection: cradle.redis,
lockDuration: 60_000,
removeOnComplete: { count: 0 },
});
this.cellCapacity = this.cradle.env.PAYMASTER_CELL_CAPACITY;
Expand All @@ -72,11 +73,11 @@ export default class Paymaster implements IPaymaster {
}

/**
* Get the next paymaster cell job from the queue
* Get the next paymaster cell from the queue
* will refill the queue if the count is less than the threshold
* @param token - the token to get the next job, using btc txid by default
*/
public async getNextCellJob(token: string) {
public async getNextCell(token: string) {
// avoid the refilling to be triggered multiple times
if (!this.refilling) {
const count = await this.queue.getWaitingCount();
Expand All @@ -93,8 +94,31 @@ export default class Paymaster implements IPaymaster {
this.refilling = false;
}
}
const job = await this.worker.getNextJob(token);
return job;

let cell: IndexerCell | null = null;
while (!cell) {
const job = await this.worker.getNextJob(token);
if (!job) {
break;
}

const data = job.data;
const liveCell = await this.cradle.ckbRpc.getLiveCell(data.outPoint!, false);

Check failure on line 106 in src/services/paymaster.ts

View workflow job for this annotation

GitHub Actions / test

test/services/paymaster.test.ts > Paymaster > getNextCellJob: should return the next job when queue has sufficient jobs

TypeError: Cannot destructure property 'txHash' of 'outPoint' as it is undefined. ❯ Array.toOutPoint node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/paramsFormatter.ts:54:13 ❯ map node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:78:44 ❯ Method.getPayload node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:75:25 ❯ CKBRPC.getLiveCell node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:44:26 ❯ Paymaster.getNextCell src/services/paymaster.ts:106:49 ❯ test/services/paymaster.test.ts:45:17

Check failure on line 106 in src/services/paymaster.ts

View workflow job for this annotation

GitHub Actions / test

test/services/paymaster.test.ts > Paymaster > getNextCellJob: should trigger refill when queue has fewer jobs than threshold

TypeError: Cannot destructure property 'txHash' of 'outPoint' as it is undefined. ❯ Array.toOutPoint node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/paramsFormatter.ts:54:13 ❯ map node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:78:44 ❯ Method.getPayload node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:75:25 ❯ CKBRPC.getLiveCell node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:44:26 ❯ Paymaster.getNextCell src/services/paymaster.ts:106:49 ❯ test/services/paymaster.test.ts:58:17

Check failure on line 106 in src/services/paymaster.ts

View workflow job for this annotation

GitHub Actions / test

test/services/paymaster.test.ts > Paymaster > getNextCellJob: should return a job when queue is empty and refill is successful

TypeError: Cannot destructure property 'txHash' of 'outPoint' as it is undefined. ❯ Array.toOutPoint node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/paramsFormatter.ts:54:13 ❯ map node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:78:44 ❯ Method.getPayload node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:75:25 ❯ CKBRPC.getLiveCell node_modules/.pnpm/@[email protected]/node_modules/@ckb-lumos/rpc/src/method.ts:44:26 ❯ Paymaster.getNextCell src/services/paymaster.ts:106:49 ❯ test/services/paymaster.test.ts:70:17
if (!liveCell || liveCell.status !== 'live') {
job.remove();
continue;
}

cell = {
output: data.cellOutput,
outPoint: data.outPoint!,
outputData: data.data,
blockNumber: data.blockNumber!,
txIndex: data.txIndex!,
};
}

return cell;
}

/**
Expand Down Expand Up @@ -144,14 +168,7 @@ export default class Paymaster implements IPaymaster {
params: Pick<AppendPaymasterCellAndSignTxParams, 'ckbRawTx' | 'sumInputsCapacity'>,
) {
const { ckbRawTx, sumInputsCapacity } = params;
const { data: cell } = await this.getNextCellJob(token);
const paymasterCell: IndexerCell = {
output: cell.cellOutput,
outPoint: cell.outPoint!,
outputData: cell.data,
blockNumber: cell.blockNumber!,
txIndex: cell.txIndex!,
};
const paymasterCell = await this.getNextCell(token);
this.cradle.logger.debug(`[Paymaster] Get paymaster cell: ${JSON.stringify(paymasterCell)}`);

const signedTx = await appendPaymasterCellAndSignCkbTx({
Expand All @@ -161,6 +178,7 @@ export default class Paymaster implements IPaymaster {
secp256k1PrivateKey: this.privateKey,
isMainnet: this.cradle.env.NETWORK === 'mainnet',
});
this.cradle.logger.debug(`[Paymaster] Signed transaction: ${JSON.stringify(signedTx)}`);
return signedTx;
}

Expand All @@ -178,7 +196,7 @@ export default class Paymaster implements IPaymaster {
const id = `${outPoint.txHash}:${outPoint.index}`;
const job = await this.queue.getJob(id);
if (job) {
await job.moveToCompleted(null, token);
await job.moveToCompleted(null, token, false);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/services/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ export default class TransactionManager implements ITransactionManager {

await this.waitForTranscationConfirmed(txHash);
// mark the paymaster cell as spent to avoid double spending
if (ckbVirtualResult.needPaymasterCell) {
await this.cradle.paymaster.makePaymasterCellAsSpent(txid, signedTx!);
}
// if (ckbVirtualResult.needPaymasterCell) {
// await this.cradle.paymaster.makePaymasterCellAsSpent(txid, signedTx!);
// }
return txHash;
} catch (err) {
this.cradle.logger.debug(err);
Expand Down
10 changes: 5 additions & 5 deletions test/services/paymaster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('Paymaster', () => {
vi.spyOn(paymaster['queue'], 'getWaitingCount').mockResolvedValue(2);
vi.spyOn(paymaster, 'refillCellQueue');

await paymaster.getNextCellJob('token');
await paymaster.getNextCell('token');
expect(paymaster.refillCellQueue).not.toHaveBeenCalled();
});

Expand All @@ -42,7 +42,7 @@ describe('Paymaster', () => {
);
vi.spyOn(paymaster, 'refillCellQueue');

const job = await paymaster.getNextCellJob('token');
const job = await paymaster.getNextCell('token');
expect(job).toBeInstanceOf(Job);
expect(paymaster.refillCellQueue).not.toHaveBeenCalled();
expect(paymaster['refilling']).toBeFalsy();
Expand All @@ -55,7 +55,7 @@ describe('Paymaster', () => {
new Job(paymaster['queue'], 'test-job', {}) as Job<Cell>,
);

const job = await paymaster.getNextCellJob('token');
const job = await paymaster.getNextCell('token');
expect(job).toBeInstanceOf(Job);
expect(paymaster.refillCellQueue).toHaveBeenCalled();
});
Expand All @@ -67,7 +67,7 @@ describe('Paymaster', () => {
new Job(paymaster['queue'], 'refilled-job', {}) as Job<Cell>,
);

const job = await paymaster.getNextCellJob('token');
const job = await paymaster.getNextCell('token');
expect(job).toBeInstanceOf(Job);
expect(job?.name).toBe('refilled-job');
expect(paymaster.refillCellQueue).toHaveBeenCalled();
Expand All @@ -78,7 +78,7 @@ describe('Paymaster', () => {
vi.spyOn(paymaster, 'refillCellQueue').mockRejectedValue(new Error('Refill failed'));
vi.spyOn(paymaster['worker'], 'getNextJob');

await expect(paymaster.getNextCellJob('token')).rejects.toThrow('Refill failed');
await expect(paymaster.getNextCell('token')).rejects.toThrow('Refill failed');
expect(paymaster.refillCellQueue).toHaveBeenCalled();
expect(paymaster['worker'].getNextJob).not.toHaveBeenCalled();
});
Expand Down

0 comments on commit 025ec27

Please sign in to comment.