diff --git a/README.md b/README.md index 109a65d..468766a 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,16 @@ yarn - API_PORT: port number, default is: `56666` - CRUST_WS_ADDRESS: crust's websocket address, default is: `ws://localhost:9944` +- CRUST_REGISTRY_WS_ADDRESS: crust's registry chain address, default is: `ws://localhost:9944` ```shell -yarn debug {API_PORT} {CRUST_WS_ADDRESS} +yarn debug {API_PORT} {CRUST_WS_ADDRESS} {CRUST_REGISTRY_WS_ADDRESS} ``` #### 3. Build and start with original node ```shell -yarn build && yarn start {API_PORT} {CRUST_WS_ADDRESS} +yarn build && yarn start {API_PORT} {CRUST_WS_ADDRESS} {CRUST_REGISTRY_WS_ADDRESS} ``` ### Build from docker diff --git a/docs/api.md b/docs/api.md index d3fdbca..cdfd228 100644 --- a/docs/api.md +++ b/docs/api.md @@ -91,6 +91,74 @@ Response: } ``` +#### Get verifier verification results + +Request: + +```shell +curl 'http://localhost:56666/api/v1/verifier/verificationResults?address=cTL7WyybsDCmpKZguj2tRxnxycfDJd8k7ZzkSCXrV7yYoA6ra&pubKey=2e94eec8e31f1f7bed684ead40716294a0c40e3d505a43d4f3ebd17052d12bcdd5aafa9a59a471f19a16037a15d9e499ceda6d5e50085eab13f55213cdf8d1dd' +``` + +Response: + +```json +[ + { + "payload": { + "code": "0x32396365663466663535343961313737396434353664653363346263623133343433643432393438666237323663356462316232343131643939396131623462", + "who": "0x069686d23c8e0170553dddca0c36a659c6fc39fa0d5148f1ba1cc95ec4d4c414", + "pubkey": "0x3265393465656338653331663166376265643638346561643430373136323934613063343065336435303561343364346633656264313730353264313262636464356161666139613539613437316631396131363033376131356439653439396365646136643565353030383565616231336635353231336364663864316464", + "public": { + "sr25519": "0x2ec91af63632573a5b051376cdeb79730261e696117e68c67aa298d519f0c77c" + } + }, + "signature": { + "sr25519": "0x7cf8b9162c2368d7dad200bf509f07268e107e1667b58e5d614988c46c091b2b43b9a361c835186a4213c41d358dcfa69e18bbe4a4e6baf93e31a8618f919a80" + } + }, + { + "payload": { + "code": "0x32396365663466663535343961313737396434353664653363346263623133343433643432393438666237323663356462316232343131643939396131623462", + "who": "0x069686d23c8e0170553dddca0c36a659c6fc39fa0d5148f1ba1cc95ec4d4c414", + "pubkey": "0x3265393465656338653331663166376265643638346561643430373136323934613063343065336435303561343364346633656264313730353264313262636464356161666139613539613437316631396131363033376131356439653439396365646136643565353030383565616231336635353231336364663864316464", + "public": { + "sr25519": "0x328846691dd2401b2a62b123daea0e6f626cb4919dc560797645d26e3273a57a" + } + }, + "signature": { + "sr25519": "0x18665c99af1b209a1dec311b26041905749cef46f00a168fb60be8c9035d174c8b6feb5475ce2cb2351508d667e61347fc74bd461fbd90fcf3be9268e6ce4383" + } + }, + { + "payload": { + "code": "0x32396365663466663535343961313737396434353664653363346263623133343433643432393438666237323663356462316232343131643939396131623462", + "who": "0x069686d23c8e0170553dddca0c36a659c6fc39fa0d5148f1ba1cc95ec4d4c414", + "pubkey": "0x3265393465656338653331663166376265643638346561643430373136323934613063343065336435303561343364346633656264313730353264313262636464356161666139613539613437316631396131363033376131356439653439396365646136643565353030383565616231336635353231336364663864316464", + "public": { + "sr25519": "0x328846691dd2401b2a62b123daea0e6f626cb4919dc560797645d26e3273a57a" + } + }, + "signature": { + "sr25519": "0x1c2fcfc839a34ac77af76c573316cc98e2be726a1ae60402886940d1905d397e20e11828f75cba733bf874de1218baa474203b57d8b85a37004a421605f4d980" + } + }, + { + "payload": { + "code": "0x32396365663466663535343961313737396434353664653363346263623133343433643432393438666237323663356462316232343131643939396131623462", + "who": "0x069686d23c8e0170553dddca0c36a659c6fc39fa0d5148f1ba1cc95ec4d4c414", + "pubkey": "0x3265393465656338653331663166376265643638346561643430373136323934613063343065336435303561343364346633656264313730353264313262636464356161666139613539613437316631396131363033376131356439653439396365646136643565353030383565616231336635353231336364663864316464", + "public": { + "sr25519": "0x2ec91af63632573a5b051376cdeb79730261e696117e68c67aa298d519f0c77c" + } + }, + "signature": { + "sr25519": "0x9a2d4d37945412b671779eac17643da769278e487396889430f95fad83f2a5577f64530eea66e9c9ede5a3fbbbcb33111022ffa6f9fa7632d70e1a8f54b3b88e" + } + } +] + +``` + #### Get sWorker work report Request: @@ -166,6 +234,34 @@ Response: } ``` +#### Register with decentralized auth chain as sWorker + +Request: + +```shell +curl --request POST 'http://localhost:56666/api/v1/swork/registerWithDeauthChain' \ +--header 'Content-Type: application/json' \ +--header 'password: 123456' \ +--data-raw '{ + "who" : "0xa6efa374700f8640b777bc92c77d34447c5588d7eb7c4ec984323c7db0983009", + "code" : "0x34633130383435666561376230353433646264333965656236626434376434623735383131663565356435313666353164623263373564343439353961633761", + "pubkey" : "0x3061376637646531626635303538636161366239363661346435303738326139663261656137393536313735303234373734373136343264636433623132313363386562643663303162633737333062613432353563373938316463656437626432343464633735323063353936303534663031323361383833346333313139", + "pubkeys" : ["0x328846691dd2401b2a62b123daea0e6f626cb4919dc560797645d26e3273a57a", "0x2ec91af63632573a5b051376cdeb79730261e696117e68c67aa298d519f0c77c"], + "sig" : "1f9ddc0ec3cb13c6b779924e78ebf4036c6c063a691649a18955f871feeac36a3204ee0c88a26249cfb9702ccb69e4e3d8de7720f1cdf185fbc24351a8c4e548", + "sigs" : ["0x08c8b9c428add1c886d3c7485b36c2b3eea61c74a651e162ec2c53af7f97b67422a0b70d4b4e2eba35a6c01ccdd95a40527f9e9670ef8f1a109a43648479218a", + "0x1831dd101ed60ce5193a5b9ad95cc4792b735490f36398c4e2536ca7c9d88945bb40636f2313a0906b24f9e46dd2ba4c18c8a0fd772caa1b878af9398deb0a89"] + "backup" : "{\"address\":\"5FqazaU79hjpEMiWTWZx81VjsYFst15eBuSBKdQLgQibD7CX\",\"encoded\":\"0xc81537c9442bd1d3f4985531293d88f6d2a960969a88b1cf8413e7c9ec1d5f4955adf91d2d687d8493b70ef457532d505b9cee7a3d2b726a554242b75fb9bec7d4beab74da4bf65260e1d6f7a6b44af4505bf35aaae4cf95b1059ba0f03f1d63c5b7c3ccbacd6bd80577de71f35d0c4976b6e43fe0e1583530e773dfab3ab46c92ce3fa2168673ba52678407a3ef619b5e14155706d43bd329a5e72d36\",\"encoding\":{\"content\":[\"pkcs8\",\"sr25519\"],\"type\":\"xsalsa20-poly1305\",\"version\":\"2\"},\"meta\":{\"name\":\"Yang1\",\"tags\":[],\"whenCreated\":1580628430860}}" +}' +``` + +Response: + +```json +{ + "status": "success", +} +``` + #### Send sWorker work report Request: @@ -203,6 +299,28 @@ Response: } ``` +#### Verifier request verification + +Request: + +```shell +curl --request POST 'http://localhost:56666/api/v1/verifier/requestVerification' \ +--header 'Content-Type: application/json' \ +--header 'password: 123456' \ +--data-raw '{ + "evidence": "{\"account\":\"a6efa374700f8640b777bc92c77d34447c5588d7eb7c4ec984323c7db0983009\",\"quote\":\"030002000000000006000b00939a7233f79c4ca9940a0db3957f0607b4476fd35d720061cbb9960de7e399d8000000000203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000030000000000000029cef4ff5549a1779d456de3c4bcb13443d42948fb726c5db1b2411d999a1b4b000000000000000000000\",\"sig\":\"b07f9c1bfc48d6888360db296a09de276d90c63d468dcdf178b02e4dc4efcdfc4e683ffc7dfb70db1a9f547b65c5cac9c916ff7b8971064db27601d27e69c39b\"}", + "backup": "{\"address\":\"5FqazaU79hjpEMiWTWZx81VjsYFst15eBuSBKdQLgQibD7CX\",\"encoded\":\"0xc81537c9442bd1d3f4985531293d88f6d2a960969a88b1cf8413e7c9ec1d5f4955adf91d2d687d8493b70ef457532d505b9cee7a3d2b726a554242b75fb9bec7d4beab74da4bf65260e1d6f7a6b44af4505bf35aaae4cf95b1059ba0f03f1d63c5b7c3ccbacd6bd80577de71f35d0c4976b6e43fe0e1583530e773dfab3ab46c92ce3fa2168673ba52678407a3ef619b5e14155706d43bd329a5e72d36\",\"encoding\":{\"content\":[\"pkcs8\",\"sr25519\"],\"type\":\"xsalsa20-poly1305\",\"version\":\"2\"},\"meta\":{\"name\":\"Yang1\",\"tags\":[],\"whenCreated\":1580628430860}}" + }' +``` + +Response: + +```json +{ + "status": "success", +} +``` + ## Errors All the errors will be global catched with return `status code = 400` 😂 diff --git a/package.json b/package.json index 2016e19..1e89995 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "fix": "gts fix" }, "dependencies": { - "@crustio/type-definitions": "1.1.0", - "@polkadot/api": "4.2.2-4", + "@crustio/type-definitions": "1.3.0", + "@polkadot/api": "6.0.5", "body-parser": "^1.19.0", "connect-timeout": "^1.9.0", "express": "^4.17.1", diff --git a/src/index.ts b/src/index.ts index 466ff72..6e1702c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,10 +56,19 @@ app.get('/api/v1/swork/workreport', services.swork.workReport); app.get('/api/v1/swork/code', services.swork.code); app.get('/api/v1/swork/identity', services.swork.identity); app.get('/api/v1/market/file', services.market.file); +app.get( + '/api/v1/verifier/verificationResults', + services.verifier.verificationResults +); // Post routes app.post('/api/v1/swork/identity', services.swork.register); +app.post('/api/v1/swork/registerWithDeauthChain', services.swork.registerWithDeauthChain); app.post('/api/v1/swork/workreport', services.swork.reportWorks); +app.post( + '/api/v1/verifier/requestVerification', + services.verifier.requestVerification +); // Error handler app.use(errorHandler); diff --git a/src/services/chain.ts b/src/services/chain.ts index c12f846..170c183 100644 --- a/src/services/chain.ts +++ b/src/services/chain.ts @@ -20,7 +20,7 @@ export async function blockHash(api: ApiPromise, bn: number) { export async function health(api: ApiPromise) { logger.info('⛓ [chain]: Query system health'); - const h = await api.rpc.system.health(); + const h = (await api.rpc.system.health()) as any; const ch: CrustHealth = { isSyncing: h.isSyncing.isTrue, peers: h.peers.toNumber(), @@ -29,9 +29,9 @@ export async function health(api: ApiPromise) { // HEALTH PATCH: This is for the poor syncing process if (!ch.isSyncing) { - const h_before = await header(api); + const h_before = (await header(api)) as any; await sleep(3000); - const h_after = await header(api); + const h_after = (await header(api)) as any; if (h_before.number.toNumber() + 1 < h_after.number.toNumber()) { ch.isSyncing = true; } diff --git a/src/services/index.ts b/src/services/index.ts index 8b25009..9f86be4 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -2,10 +2,16 @@ import {Request, Response, NextFunction} from 'express'; import {ApiPromise, WsProvider} from '@polkadot/api'; import {typesBundleForPolkadot} from '@crustio/type-definitions'; import {blockHash, header, health} from './chain'; -import {register, reportWorks, workReport, code, identity} from './swork'; +import {register, reportWorks, workReport, code, identity, registerWithDeauthChain, } from './swork'; import {file} from './market'; -import {loadKeyringPair, resHandler, withApiReady} from './util'; +import { + loadKeyringPair, + resHandler, + withApiReady, + withRegistrationChainApiReady, +} from './util'; import {logger} from '../log'; +import {requestVerification, verificationResults} from './verifier'; // TODO: Better result export interface TxRes { @@ -39,7 +45,7 @@ export const getApi = (): ApiPromise => { export const chain = { header: (_: Request, res: Response, next: NextFunction) => { withApiReady(async (api: ApiPromise) => { - const h = await header(api); + const h = (await header(api)) as any; res.json({ number: h.number, hash: h.hash, @@ -65,6 +71,12 @@ export const swork = { await resHandler(register(api, krp, req), res); }, next); }, + registerWithDeauthChain: (req: Request, res: Response, next: NextFunction) => { + withApiReady(async (api: ApiPromise) => { + const krp = loadKeyringPair(req); + await resHandler(registerWithDeauthChain(api, krp, req), res); + }, next); + }, reportWorks: (req: Request, res: Response, next: NextFunction) => { withApiReady(async (api: ApiPromise) => { const krp = loadKeyringPair(req); @@ -96,6 +108,26 @@ export const market = { }, }; +export const verifier = { + verificationResults: (req: Request, res: Response, next: NextFunction) => { + withRegistrationChainApiReady(async (api: ApiPromise) => { + res.json( + await verificationResults( + api, + String(req.query['address']), + String(req.query['pubKey']) + ) + ); + }, next); + }, + requestVerification: (req: Request, res: Response, next: NextFunction) => { + withRegistrationChainApiReady(async (api: ApiPromise) => { + const krp = loadKeyringPair(req); + await resHandler(requestVerification(api, krp, req), res); + }, next); + }, +}; + function newApiPromise(): ApiPromise { return new ApiPromise({ provider: new WsProvider(process.argv[3] || 'ws://localhost:9944'), diff --git a/src/services/registrationChainApi.ts b/src/services/registrationChainApi.ts new file mode 100644 index 0000000..36c1786 --- /dev/null +++ b/src/services/registrationChainApi.ts @@ -0,0 +1,41 @@ +/* eslint-disable node/no-extraneous-import */ +import {ApiPromise, WsProvider} from '@polkadot/api'; +import {logger} from '@polkadot/util'; +const l = logger('registration-chain-api'); + +const types = { + Public: 'MultiSigner', + RegisterPayload: { + code: 'Vec', + who: 'AccountId', + pubkey: 'Vec', + public: 'Public', + }, + RegisterPayloadWithSignature: { + payload: 'RegisterPayload', + signature: 'MultiSignature', + }, + WrapSignature: { + signature: 'MultiSignature', + }, + WrapPublic: { + public: 'Public', + }, +}; + +export const registrationChainApi: ApiPromise = new ApiPromise({ + provider: new WsProvider(process.argv[4] || 'ws://localhost:9944'), + types, +}); + +registrationChainApi.on('connected', () => { + l.log( + `Registration chain API has been connected to the endpoint: ${process.argv[4]}` + ); +}); + +registrationChainApi.on('disconnected', (): void => { + l.error('Registration chain API has been disconnected from the endpoint'); + // eslint-disable-next-line no-process-exit + process.exit(1); +}); diff --git a/src/services/swork.ts b/src/services/swork.ts index 9edfc36..f59d806 100644 --- a/src/services/swork.ts +++ b/src/services/swork.ts @@ -23,7 +23,25 @@ export async function register( '0x' + req.body['sig'] ); - return handleSworkTxWithLock(async () => sendTx(tx, krp)); + return handleSworkTxWithLock(async () => sendTx(api, tx, krp)); +} + +export async function registerWithDeauthChain( + api: ApiPromise, + krp: KeyringPair, + req: Request +) { + logger.info(`⚙️ [swork]: Call register With decentralized auth chain with ${JSON.stringify(req.body)}`); + const tx = api.tx.swork.registerWithDeauthChain( + req.body['who'], + req.body['code'], + req.body['pubkeys'], + req.body['sigs'], + req.body['pubkey'], + req.body['sig'] + ); + + return handleSworkTxWithLock(async () => sendTx(api, tx, krp)); } export async function reportWorks( @@ -57,7 +75,7 @@ export async function reportWorks( ); let txRes = queryToObj( - await handleSworkTxWithLock(async () => sendTx(tx, krp)) + await handleSworkTxWithLock(async () => sendTx(api, tx, krp)) ); // Double confirm of tx status diff --git a/src/services/util.ts b/src/services/util.ts index 199f913..336bcd0 100644 --- a/src/services/util.ts +++ b/src/services/util.ts @@ -8,6 +8,8 @@ import {SubmittableExtrinsic} from '@polkadot/api/promise/types'; import {timeout} from 'promise-timeout'; import {TxRes, getApi} from './index'; import {logger} from '../log'; +import {ApiPromise, WsProvider} from '@polkadot/api'; +import {registrationChainApi} from './registrationChainApi'; const txLocker = {swork: false}; /** @@ -24,7 +26,11 @@ export function loadKeyringPair(req: Request): KeyringPair { return krp; } -export async function sendTx(tx: SubmittableExtrinsic, krp: KeyringPair) { +export async function sendTx( + api: ApiPromise, + tx: SubmittableExtrinsic, + krp: KeyringPair +) { return new Promise((resolve, reject) => { tx.signAndSend(krp, ({events = [], status}) => { logger.info( @@ -40,7 +46,9 @@ export async function sendTx(tx: SubmittableExtrinsic, krp: KeyringPair) { if (status.isInBlock) { events.forEach(({event: {data, method, section}}) => { if (section === 'system' && method === 'ExtrinsicFailed') { - const [dispatchError] = data as unknown as ITuple<[DispatchError]>; + const [dispatchError] = (data as unknown) as ITuple< + [DispatchError] + >; const result: TxRes = { status: 'failed', message: dispatchError.type, @@ -48,11 +56,11 @@ export async function sendTx(tx: SubmittableExtrinsic, krp: KeyringPair) { // Can get detail error info if (dispatchError.isModule) { const mod = dispatchError.asModule; - const error = getApi().registry.findMetaError( + const error = api.registry.findMetaError( new Uint8Array([mod.index.toNumber(), mod.error.toNumber()]) ); result.message = `${error.section}.${error.name}`; - result.details = error.documentation.join(''); + result.details = error.docs.join(''); } logger.info( @@ -98,6 +106,28 @@ export async function withApiReady(fn: Function, next: NextFunction) { } } +export async function withRegistrationChainApiReady( + fn: Function, + next: NextFunction +) { + const _api = await registrationChainApi.isReadyOrError; + if (!_api || !_api.isConnected) { + next( + new Error( + '⚠️ Registration Chain is offline, please connect a running chain.' + ) + ); + return; + } + try { + const matureApi = await _api.isReady; + await fn(matureApi); + next(); + } catch (err) { + next(err); + } +} + export async function resHandler(req: Promise, res: Response) { const txRes: any = await req; if (txRes && 'success' === txRes.status) { diff --git a/src/services/verifier.ts b/src/services/verifier.ts new file mode 100644 index 0000000..91c6058 --- /dev/null +++ b/src/services/verifier.ts @@ -0,0 +1,65 @@ +/* eslint-disable node/no-extraneous-import */ +import {ApiPromise, Keyring} from '@polkadot/api'; +import {logger} from '../log'; +import {KeyringPair} from '@polkadot/keyring/types'; +import {handleSworkTxWithLock, queryToObj, sendTx} from './util'; +import {Request} from 'express'; +import {u8aToHex, stringToU8a} from '@polkadot/util'; +import _ from 'lodash'; + +/** + * Queries + */ +export async function verificationResults( + api: ApiPromise, + addr: string, + pubKey: string +) { + logger.info( + `⚙️ [swork]: Query verification results with ${addr} pubKey ${pubKey}` + ); + + const chainResult = queryToObj( + await api.query.verifier.verificationResults(addr, '0x' + pubKey) + ); + + if (_.isEmpty(chainResult)) { + throw Error('Unable to get verification results'); + } else { + const kr = new Keyring({ + type: 'sr25519', + }); + + const result = chainResult.map((e: any) => { + return { + payload: { + ...e.payload, + who: u8aToHex(kr.decodeAddress(e.payload.who)), + }, + signature: e.signature, + }; + }); + return result; + } +} + +/** + * Send extrinsics + */ +export async function requestVerification( + api: ApiPromise, + krp: KeyringPair, + req: Request +) { + logger.info( + `⚙️ [swork]: Call verifier requestVerification with ${JSON.stringify( + req.body + )}` + ); + + const evidence = u8aToHex(stringToU8a(req.body['evidence'])); + + const tx = api.tx.verifier.requestVerification(evidence); + + return handleSworkTxWithLock(async () => sendTx(api, tx, krp)); +}