From fd524cd61ecd20b2f09791f43d341322a0e14988 Mon Sep 17 00:00:00 2001 From: neeboo Date: Sun, 4 Aug 2019 03:49:36 +0800 Subject: [PATCH] break(hdnode):remake hdnode --- docs/{.gitkeep => README.md} | 0 package.json | 6 +- packages/harmony-account/src/hdnode.ts | 218 ++++++++++++++---- packages/harmony-core/src/blockchain.ts | 4 +- packages/harmony-core/src/util.ts | 101 +++++++- .../harmony-network/src/rpcMethod/builder.ts | 4 +- packages/harmony-network/src/rpcMethod/rpc.ts | 2 +- scripts/docs.ts | 14 +- scripts/typedoc/batch.js | 30 +++ 9 files changed, 320 insertions(+), 59 deletions(-) rename docs/{.gitkeep => README.md} (100%) create mode 100644 scripts/typedoc/batch.js diff --git a/docs/.gitkeep b/docs/README.md similarity index 100% rename from docs/.gitkeep rename to docs/README.md diff --git a/package.json b/package.json index 2913528..13633ad 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "dist": "yarn packages:bundler && yarn bundle:webpack", "packages:cleanUnexpected": "gulp cleanUnexpected", "packages:clean": "gulp cleanServer && yarn packages:cleanUnexpected", - "packages:cleanDocs": "gulp cleanDocs", "packages:bundler": "yarn packages:clean && yarn build:ts && ts-node -P scripts/tsconfig.json scripts/bundle.ts", "schema": "ts-node -P scripts/tsconfig.json scripts/typings/schema.ts core", "test": "cross-env TEST_ENV=unit jest -c scripts/jest/jest.config.js --rootDir=.", @@ -28,7 +27,10 @@ "test:e2e": "cross-env TEST_ENV=e2e jest -c ./scripts/jest/jest.e2e.config.js --runInBand --verbose --collectCoverage=false", "docs:vue": "ts-node -P scripts/tsconfig.json scripts/docs.ts vuepress", "docs:gitbook": "ts-node -P scripts/tsconfig.json scripts/docs.ts gitbook", - "docs:build": "ts-node -P scripts/tsconfig.json scripts/docs.ts docusaurus,vuepress,gitbook,bitbucket,default", + "docs:docusaurus": "ts-node -P scripts/tsconfig.json scripts/docs.ts docusaurus", + "docs:bitbucket": "ts-node -P scripts/tsconfig.json scripts/docs.ts bitbucket", + "docs:default": "ts-node -P scripts/tsconfig.json scripts/docs.ts default", + "docs:clean": "gulp cleanDocs", "docs:bundler": "yarn packages:cleanDocs && yarn docs:build", "release": "yarn bootstrap && yarn bundle && lerna publish --exact", "format": "prettier --write '**/*.{ts,tsx,js}' --config .prettierrc", diff --git a/packages/harmony-account/src/hdnode.ts b/packages/harmony-account/src/hdnode.ts index 4d6d9d8..2103782 100644 --- a/packages/harmony-account/src/hdnode.ts +++ b/packages/harmony-account/src/hdnode.ts @@ -1,19 +1,47 @@ +import {bip39, hdkey, getAddress, BN, Signature} from '@harmony-js/crypto'; import { - bip39, - hdkey, - encryptPhrase, - decryptPhrase, - EncryptOptions, -} from '@harmony-js/crypto'; -import { HDPath } from '@harmony-js/utils'; + HDPath, + // defineReadOnly, + isHttp, + isWs, + ChainID, + ChainType, + Unit, + isHex, + numberToHex, +} from '@harmony-js/utils'; +import {Messenger, HttpProvider, WSProvider} from '@harmony-js/network'; +import { + Transaction, + TxStatus, + RLPSign, + TransasctionReceipt, +} from '@harmony-js/transaction'; +import {Account} from './account'; -export class HDNode extends hdkey { - static new() { - return new HDNode(bip39.generateMnemonic(), 0); - } - static add(phrase: string, index: number) { - return new HDNode(phrase, index); - } +interface WalletsInterfaces { + [key: string]: Account; +} + +interface Web3TxPrams { + id?: string; + from?: string; + to?: string; + nonce?: number | string; + gasLimit?: BN | number | string; + gasPrice?: BN | number | string; + shardID?: number | string; + toShardID?: number | string; + data?: string; + value?: BN; + chainId?: number; + rawTransaction?: string; + unsignedRawTransaction?: string; + signature?: Signature | string; + receipt?: TransasctionReceipt; +} + +export class HDNode { static isValidMnemonic(phrase: string): boolean { if (phrase.trim().split(/\s+/g).length < 12) { return false; @@ -23,60 +51,148 @@ export class HDNode extends hdkey { static generateMnemonic(): string { return bip39.generateMnemonic(); } + public provider: HttpProvider | WSProvider; + private messenger: Messenger; + private hdwallet: hdkey | undefined; private path: string; - private mnemonic?: string; - private entropy?: string; - private childKey?: hdkey; + private index: number; + private addressCount: number; + private addresses: string[]; + private wallets: WalletsInterfaces; - constructor(menmonic?: string, index: number = 0) { - super(); + constructor( + provider: string | HttpProvider | WSProvider = 'http://localhost:9500', + menmonic?: string, + index: number = 0, + addressCount: number = 1, + chainType: ChainType = ChainType.Harmony, + chainId: ChainID = ChainID.Default, + ) { + this.provider = this.setProvider(provider); + this.messenger = new Messenger(this.provider, chainType, chainId); + this.hdwallet = undefined; + this.addresses = []; + this.wallets = {}; this.path = HDPath; - this.mnemonic = menmonic; - this.entropy = this.mnemonic ? this.getEntropy(this.mnemonic) : undefined; - this.childKey = this.entropy - ? this.getChildKey(this.entropy, index) - : undefined; + this.index = index; + this.addressCount = addressCount; + this.getHdWallet(menmonic || HDNode.generateMnemonic()); } - getEntropy(mnemonic: string) { - return bip39.mnemonicToEntropy(mnemonic); + normalizePrivateKeys(mnemonic: string | string[]) { + if (Array.isArray(mnemonic)) { + return mnemonic; + } else if (mnemonic && !mnemonic.includes(' ')) { + return [mnemonic]; + } else { + return false; + } } - getChildKey(entropy: string, index: number) { - const master = HDNode.fromMasterSeed(Buffer.from(entropy, 'hex')); - return master.derive(`${this.path}${index}`); + + setProvider(provider: string | HttpProvider | WSProvider) { + if (isHttp(provider) && typeof provider === 'string') { + return new HttpProvider(provider); + } else if (provider instanceof HttpProvider) { + return provider; + } else if (isWs(provider) && typeof provider === 'string') { + return new WSProvider(provider); + } else if (provider instanceof WSProvider) { + return provider; + } else { + throw new Error('provider is not recognized'); + } } - async lock(password: string, options: EncryptOptions) { - if (this.mnemonic && HDNode.isValidMnemonic(this.mnemonic)) { - try { - this.mnemonic = await encryptPhrase(this.mnemonic, password, options); - } catch (error) { - throw error; + getHdWallet(mnemonic: string) { + if (!HDNode.isValidMnemonic(mnemonic)) { + throw new Error('Mnemonic invalid or undefined'); + } + this.hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(mnemonic)); + + for (let i = this.index; i < this.index + this.addressCount; i++) { + if (!this.hdwallet) { + throw new Error('hdwallet is not found'); } - } else { - throw new Error('mnemonic is not valid'); + const childKey = this.hdwallet.derive(`${this.path}${i}`); + const prv = childKey.privateKey.toString('hex'); + const account = new Account(prv); + const addr = account.checksumAddress; + this.addresses.push(addr); + this.wallets[addr] = account; } } + send( + ...args: [string, (string | any[] | undefined)?, (string | undefined)?] + ) { + this.messenger.send.apply(this.messenger, args); + } - async unlock(password: string) { - if (this.mnemonic) { - try { - this.mnemonic = await decryptPhrase( - JSON.parse(this.mnemonic), - password, - ); - } catch (error) { - throw error; + sendAsync( + ...args: [string, (string | any[] | undefined)?, (string | undefined)?] + ) { + this.send(...args); + } + // tslint:disable-next-line: ban-types + getAccounts(cb?: Function) { + if (cb) { + cb(null, this.addresses); + } + return this.addresses; + } + // tslint:disable-next-line: ban-types + getPrivateKey(address: string, cb?: Function) { + if (!cb) { + if (!this.wallets[address]) { + throw new Error('Account not found'); + } else { + return this.wallets[address].privateKey; } + } + if (!this.wallets[address]) { + return cb('Account not found'); } else { - throw new Error('mnemonic is not valid'); + cb(null, this.wallets[address].privateKey); } } + signTransaction(txParams: any | Web3TxPrams, cb: Function) { + const from: string = getAddress(txParams.from).checksum; + const to: string = getAddress(txParams.to).checksum; + const gasLimit = isHex(txParams.gasLimit) + ? txParams.gasLimit + : new Unit(txParams.gasLimit).asWei().toWei(); + const gasPrice = isHex(txParams.gasPrice) + ? txParams.gasPrice + : new Unit(txParams.gasPrice).asWei().toWei(); + const value = isHex(txParams.value) + ? txParams.value + : numberToHex(txParams.value); + const nonce = isHex(txParams.nonce) + ? txParams.nonce + : numberToHex(txParams.nonce); + const prv = this.wallets[from].privateKey; - get _privateKey() { - return this.childKey ? this.childKey.privateKey.toString('hex') : ''; + const tx = new Transaction( + {...txParams, from, to, gasLimit, gasPrice, value, nonce}, + this.messenger, + TxStatus.INTIALIZED, + ); + tx.getRLPUnsigned(); + if (prv) { + const rawTransaction = RLPSign(tx, prv)[1]; + if (cb) { + cb(null, rawTransaction); + } + return rawTransaction; + } + } + getAddress(idx?: number) { + if (!idx) { + return this.addresses[0]; + } else { + return this.addresses[idx]; + } } - get _publicKey() { - return this.childKey ? this.childKey.publicKey.toString('hex') : ''; + getAddresses() { + return this.addresses; } } diff --git a/packages/harmony-core/src/blockchain.ts b/packages/harmony-core/src/blockchain.ts index 14e9b3c..1123e42 100644 --- a/packages/harmony-core/src/blockchain.ts +++ b/packages/harmony-core/src/blockchain.ts @@ -235,9 +235,9 @@ class Blockchain { return this.getRpcResult(result); } - async getProtocalVersion() { + async getProtocolVersion() { const result = await this.messenger.send( - RPCMethod.ProtocalVersion, + RPCMethod.ProtocolVersion, [], this.messenger.chainPrefix, ); diff --git a/packages/harmony-core/src/util.ts b/packages/harmony-core/src/util.ts index 6975cee..870d37e 100644 --- a/packages/harmony-core/src/util.ts +++ b/packages/harmony-core/src/util.ts @@ -1,7 +1,106 @@ -import { ChainType, ChainID } from '@harmony-js/utils'; +import {ChainType, ChainID, defaultConfig} from '@harmony-js/utils'; +import {Harmony} from './harmony'; export interface HarmonyConfig { chainUrl: string; chainType: ChainType; chainId: ChainID; } + +// tslint:disable-next-line: variable-name +export function createWeb3(_web3: any) { + const url: string = _web3.currentProvider.url; + const harmony = new Harmony(url, { + chainId: defaultConfig.Default.Chain_ID, + chainType: defaultConfig.Default.Chain_Type, + chainUrl: defaultConfig.Default.Chain_URL, + }); + + _web3.setProvider(harmony.messenger.provider); + _web3.messenger = harmony.messenger; + _web3.eth.getRpcResult = harmony.blockchain.getRpcResult; + + // map blockchain to eth + const {blockchain} = harmony; + _web3.eth.getBlockNumber = () => blockchain.getBlockByNumber; + _web3.eth.getBalance = (address: string, blockNumber?: string) => + blockchain.getBalance({address, blockNumber}); + _web3.eth.getBlockByHash = (blockHash: string, returnObject?: boolean) => + blockchain.getBlockByHash({blockHash, returnObject}); + _web3.eth.getBlockByNumber = (blockNumber: string, returnObject?: boolean) => + blockchain.getBlockByNumber({blockNumber, returnObject}); + _web3.eth.getBlockTransactionCountByHash = (blockHash: string) => + blockchain.getBlockTransactionCountByHash({blockHash}); + _web3.eth.getBlockTransactionCountByNumber = (blockNumber: string) => + blockchain.getBlockTransactionCountByNumber({blockNumber}); + _web3.eth.getTransactionByBlockHashAndIndex = ( + blockHash: string, + index: string, + ) => blockchain.getTransactionByBlockHashAndIndex({blockHash, index}); + _web3.eth.getTransactionByBlockNumberAndIndex = ( + blockNumber: string, + index: string, + ) => blockchain.getTransactionByBlockNumberAndIndex({blockNumber, index}); + _web3.eth.getTransactionByHash = (txnHash: string) => + blockchain.getTransactionByHash({txnHash}); + _web3.eth.getTransactionReceipt = (txnHash: string) => + blockchain.getTransactionReceipt({txnHash}); + _web3.eth.getCode = (address: string, blockNumber?: string) => + blockchain.getCode({address, blockNumber}); + _web3.eth.net_peerCount = () => blockchain.net_peerCount(); + _web3.eth.net_version = () => blockchain.net_version(); + _web3.eth.getProtocolVersion = () => blockchain.getProtocolVersion(); + _web3.eth.getStorageAt = ( + address: string, + position: string, + blockNumber: string | undefined, + ) => blockchain.getStorageAt({address, position, blockNumber}); + _web3.eth.getTransactionCount = ( + address: string, + blockNumber: string | undefined, + ) => blockchain.getTransactionCount({address, blockNumber}); + _web3.eth.estimateGas = (to: string, data: string) => + blockchain.estimateGas({to, data}); + _web3.eth.gasPrice = () => blockchain.gasPrice(); + _web3.eth.call = (payload: any, blockNumber: string | undefined) => + blockchain.call({payload, blockNumber}); + _web3.eth.newPendingTransactions = () => blockchain.newPendingTransactions(); + _web3.eth.newBlockHeaders = () => blockchain.newBlockHeaders(); + _web3.eth.syncing = () => blockchain.syncing(); + _web3.eth.logs = (options: any) => blockchain.logs(options); + + // map subscribe to _web3 + _web3.eth.subscribe = harmony.messenger.subscribe; + + // map accounts to _web3 + _web3.accounts = harmony.wallet.accounts; + _web3.eth.accounts.create = harmony.wallet.createAccount; + _web3.eth.accounts.privateKeyToAccount = harmony.wallet.addByPrivateKey; + _web3.eth.accounts.encrypt = async (privateKey: string, password: string) => { + const newAcc = new harmony.Modules.Account(privateKey, harmony.messenger); + const result = await newAcc.toFile(password); + return result; + }; + + _web3.eth.accounts.decrypt = async ( + keystoreJsonV3: any, + password: string, + ) => { + const newAcc = new harmony.Modules.Account(); + const result = await newAcc.fromFile( + JSON.stringify(keystoreJsonV3), + password, + ); + return result; + }; + + _web3.eth.accounts.signTransaction = harmony.wallet.signTransaction; + + // map transaction to web3 + _web3.eth.recoverTransaction = harmony.transactions.recover; + + // map contract to web3 + _web3.eth.Contract = harmony.contracts.createContract; + + _web3.utils = {..._web3.utils, ...harmony.utils, ...harmony.crypto}; +} diff --git a/packages/harmony-network/src/rpcMethod/builder.ts b/packages/harmony-network/src/rpcMethod/builder.ts index 0d65066..14ccd31 100644 --- a/packages/harmony-network/src/rpcMethod/builder.ts +++ b/packages/harmony-network/src/rpcMethod/builder.ts @@ -29,7 +29,9 @@ class JsonRpc { params: string | undefined | any[], ): RPCRequestPayload => { // FIXME: error to be done by shared/errors - if (!method) throw new Error('jsonrpc method should be specified!'); + if (!method) { + throw new Error('jsonrpc method should be specified!'); + } // advance message ID this.messageId += 1; diff --git a/packages/harmony-network/src/rpcMethod/rpc.ts b/packages/harmony-network/src/rpcMethod/rpc.ts index 8686329..a5fb114 100644 --- a/packages/harmony-network/src/rpcMethod/rpc.ts +++ b/packages/harmony-network/src/rpcMethod/rpc.ts @@ -63,7 +63,7 @@ export const enum RPCMethod { // 30. net_version NetVersion = 'net_version', // 31. hmy_protocolVersion - ProtocalVersion = 'hmy_protocolVersion', + ProtocolVersion = 'hmy_protocolVersion', } export const enum RPCErrorCode { diff --git a/scripts/docs.ts b/scripts/docs.ts index d2d63bc..97c5b74 100644 --- a/scripts/docs.ts +++ b/scripts/docs.ts @@ -5,6 +5,7 @@ import spawn from 'cross-spawn'; // tslint:disable-next-line: no-var-requires const runner = require.resolve('./typedoc/runner'); +const batch = require.resolve('./typedoc/batch'); const options = {stdio: 'inherit'}; const outputs = process.argv.slice(2)[0].split(','); @@ -51,7 +52,18 @@ async function docs() { } } -docs(); +// docs(); + +async function batchDocs() { + await preProcessFunc(preProcessProjects); + // const srcArry = []; + // for (const pkg of projects) { + // srcArry.push(pkg.src); + + // } +} + +batchDocs(); // async function docs() { // await preProcessFunc(preProcessProjects); diff --git a/scripts/typedoc/batch.js b/scripts/typedoc/batch.js new file mode 100644 index 0000000..1161783 --- /dev/null +++ b/scripts/typedoc/batch.js @@ -0,0 +1,30 @@ +const path = require('path'); +// tslint:disable-next-line: no-implicit-dependencies no-var-requires +const {Application} = require('typedoc'); +// tslint:disable-next-line: no-implicit-dependencies no-var-requires +const arg = require('arg'); + +const args = arg({ + '--pkgPath': String, + '-p': '--pkgPath', + '--pkgSrc': String, + '-s': '--pkgSrc', + '--target': String, + '-t': '--target', +}); + +const pkgSrc = args['--pkgSrc']; +const pkgPath = args['--pkgPath']; +const target = args['--target']; + +const app = new Application({ + mode: 'file', + tsconfig: `tsconfig.json`, + theme: target === 'default' ? 'default' : 'markdown', + plugin: path.resolve('node_modules/typedoc-plugin-markdown'), + platform: target, +}); + +const files = [...app.expandInputFiles([pkgSrc])]; + +console.log(files);