Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extension-chrome): ownership service #76

Closed
wants to merge 75 commits into from

Conversation

zhangyouxin
Copy link
Contributor

@zhangyouxin zhangyouxin commented Feb 13, 2023

usage:

// given backend, keystoreService, storage, notificationService

// probe task runs regularly in the background to guarantee that
// the lockInfos in storage is up to date with the CKB chain
// and make sure there are plenty of off-chain locks to use.

ProbeTask.getInstance().run();

const { fullOwnershipService, ruleBasedOwnershipService } = createOwnershipServices({
  backend,
  keystoreService,
  notificationService,
  locksManager: new LocksManager({ storage }),
});

fullOwnershipService.getOffChainLocks({});

@zhangyouxin
Copy link
Contributor Author

I reviewed the test cases related to full ownership, but to be honest, the completion of this PR is still too low

  • no paginated test case of getLiveCells
  • no matched test case of getOffChainLocks, only with a toHaveLength(1)
  • no non-empty test case of getOnCHainLocks
  • no non-empty test case of signTransaction
  • no non-empty test case of signData

these cases are probably the most important of this PR for a reviewer. As a reviewer, if there are some cases like this, it would be more friendly

describe('getLiveCells', async () => {
  it('should only the first page of cells will be fetched', () => {
    mockedBackend.pushCells([createCell({lock: key1}, createCell({lock: key2}))])
    probeTask.start();
    await sleep(100);
    expect(ownership.getLiveCells()).resolve.toMatchObject([
      createCell({lock: key1}, 
      createCell({lock: key2}))
    ])
  });

  it('should paginated when the cursor is non-empty', () => {
    const cells = [withLock1, withLock2, ...,  withLock15] 
    ...
    expect(ownership.getLiveCells()).resolve.toMatchObject(first10Cells);
    expect(ownership.getLiveCells({cursor: page2})).resolve.toMatchObject(last5Cells)
  })
})

added test cases mentioned above, and now the branch coverage is higher

@homura homura mentioned this pull request Feb 20, 2023
11 tasks
}
}

export class BackendProvider {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should remove it because config is loaded from ConfigService

Comment on lines +11 to +14
nodeUri: string;
indexer: Indexer;
rpc: RPC;
indexerRPC: IndexerRPC;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these properties are unnecessary, which make it hard to test and limits support for the other backend like the CKB Light Client in the future

indexerCursor?: string;
};
}) => Promise<Paginate<Cell>>;
getLiveCellFetcher: () => (outPoint: OutPoint) => Promise<Cell>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getLiveCell(outPoint: OutPoint): Promise<Cell> is enough

rpc: RPC;
indexerRPC: IndexerRPC;

hasHistory: (payload: { lock: Script }) => Promise<boolean>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be hasHistories(payload: { lock: Script }): Promise<boolean[]>
fetch histories without batching probably cause performance issue

},
};
}
export async function getOnchainLockProvider(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be getOnChainLockProvider

});
}

async getlockInfoByLock({ lock }: { lock: Script }): Promise<LockInfo | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be getLockInfoByLock

const lockInfo = await config.locksManager.getlockInfoByLock({ lock: payload.lock });
asserts.asserts(lockInfo, 'Lock not found when call signData with', payload);
const password = (await config.notificationService.requestSignData({ data: payload.data })).password;
const signature = await config.keystoreService.signMessage({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to avoid security risk, the signData must with some magick bytes, we can mark a TODO here

const keystoreService = createServicesFactory().get('keystoreService');
const storage = createServicesFactory().get('storage');
if (!keystoreService || !storage) return;
console.log('wallet already initialized, start probe task...');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use the logger instead of console.log

Comment on lines 25 to 26
const keystoreService = createServicesFactory().get('keystoreService');
const storage = createServicesFactory().get('storage');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor the keystoreSertvice and storage to params instead of createServiceFactory everywhere


export interface WalletMethods extends CallMap {
wallet_enable: Call<void, void>;
wallet_isEnabled: Call<void, boolean>;
wallet_getNetworkName: Call<void, string>;

wallet_fullOwnership_getUnusedLocks: Call<void, Script[]>;
wallet_fullOwnership_getOffChainLocks: Call<GetOffChainLocksPayload, Promise<Script[]>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promise is unnecessary in Call

@@ -24,15 +30,121 @@ addMethod('wallet_enable', async (_, { getRequesterAppInfo, resolveService }) =>
}

await configService.addWhitelistItem({ host: host, favicon: `${protocol}//${host}/favicon.ico` });
const servicesFactory = createServicesFactory();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use resolveService instead of createServicesFactory() everywhere

});

addMethod('wallet_fullOwnership_getOffChainLocks', async (payload) => {
console.log('====================================');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

====== is unnecessary, if you just want to make a different logger, please createLogger('special_module_name') to instead

const keystoreService = await servicesFactory.get('keystoreService');
const notificationService = await servicesFactory.get('notificationService');
const storage = await servicesFactory.get('storage');
console.log('storage', await storage.getItem('full' as never));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid using never as a parameter, it is not recommendded

@homura
Copy link
Contributor

homura commented Feb 21, 2023

This PR has many issues, and we should plan to reimplement this PR

Complex Asynchronous

Ownership need to handle many async, this PR uses Promise.all and for + Promise to implement async. When we need to control the concurrency of async, this would be a bad solution. For example, when we need to probe if the next 100 on-chain locks have cells or not, Promise.all may cause the server to reject too many connection requests

Not Enough Abstraction

The full-external full-internal rule-based are similar, and we don't need to treat them differently, we can just use compose instead of the cross these condition like getRuleBasedOffChainLocks

interface DerivedLocks {
  lockInfos: LockInfo[];
}

const isFullOwnership = (info) => info.parantPath === FULL_OWNERSHIP_PARENT_PATH;
const isRuleBasedOwnership = (info) => info.parentPath === RULE_BASED_OWNERSHIP_PARENT_PATH;
const isOnChain = (info) => info.change === 'external';
const ifOffChain = (info) => info.change === 'internal';

// an async db driver
const infos = await lockInfos.filter(compose(isFullOwnership, isOffChain));
...

Therefor, we will not merge this PR, but we will use this branch for writing e2e cases

zhangyouxin and others added 12 commits February 22, 2023 12:24
Signed-off-by: homura <homura.dev@gmail.com>
Signed-off-by: homura <homura.dev@gmail.com>
Signed-off-by: homura <homura.dev@gmail.com>
Signed-off-by: homura <homura.dev@gmail.com>
Signed-off-by: homura <homura.dev@gmail.com>
Signed-off-by: homura <homura.dev@gmail.com>
Signed-off-by: homura <homura.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants