diff --git a/README.md b/README.md index 94ec191..6679923 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,41 @@ With **Giantjs**, you get: ### Install ``` -$ npm install -g gcdk +$ git clone https://github.com/GiantPay/giantjs.git gcdk; cd gcdk +$ npm install +``` + +### Build project + +``` +$ npm run build ``` ### Quick Usage -For a default set of contracts and tests, run the following within an empty project directory: +Compile your contracts, deploy those contracts to the network. + +``` +node dist/giant create contract {ContractName} +node dist/giant deploy {ContractName} +``` + +### Tests + +Run their associated unit tests. + +``` +$ npm test +``` + +### Autocompile mode + +For autocompile mode open new terminal window. ``` -$ giantjs init +$ npm run watch ``` -From there, you can run `giantjs compile`, `giantjs migrate` and `giantjs test` to compile your contracts, deploy those contracts to the network, and run their associated unit tests. **Giantjs** comes bundled with a local development blockchain server that launches automatically when you invoke the commands above. diff --git a/config.json b/config.json new file mode 100644 index 0000000..d1ac1ae --- /dev/null +++ b/config.json @@ -0,0 +1,18 @@ +{ + "network": "development", + "mining": true, + "blockTime": 500, + "maxBlockSize": 5, + "feePrice": 10, + "debug": false, + "caller": { + "privateKey": "YULqUkbYnFT6Fw6YS9Es1H94THRe4BrujuXNgtqT52UMgivrqq7K", + "publicKey": "GUuf1RCuFmLAbyNFT5WifEpZTnLYk2rtVd", + "premine": 20000 + }, + "owner": { + "privateKey": "YUsc83YuzURjo5eqezyCKUQXAMXets1Ko6edm9paexd7wD8DYhrU", + "publicKey": "GPLkrYE3GdXDoZMz4zhxyBmTiF1N3AQvpH", + "premine": 2010 + } +} diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index e493523..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,30 +0,0 @@ -const gulp = require('gulp') -const babel = require('gulp-babel') -const watch = require('gulp-watch') -// const eslint = require('gulp-eslint') - -// gulp.task('eslint', () => -// gulp.src(['src/**']) -// .pipe(eslint()) -// .pipe(eslint.format()) -// .pipe(eslint.failAfterError()) -// ) - -gulp.task('watch', () => - gulp.src('src/**') - .pipe(watch('src/**', { - verbose: true - })) - .pipe(babel({ - presets: ['env'] - })) - .pipe(gulp.dest('dist')) -) - -gulp.task('default', () => - gulp.src('src/**') - .pipe(babel({ - presets: ['env'] - })) - .pipe(gulp.dest('dist')) -) diff --git a/package.json b/package.json index dfdacb6..0f6aedd 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "author": "GiantCodeDev ", "license": "MIT", "scripts": { - "build": "NODE_ENV=production babel src --out-dir dist", + "build": "NODE_ENV=production babel src --out-dir dist; node dist/giant init", + "watch": "babel src -d dist -w", "prepublishOnly": "npm run build", "test": "NODE_ENV=test mocha --require babel-core/register ./test/**/*.test.js", "coverage": "istanbul cover _mocha -- --recursive" @@ -18,6 +19,7 @@ "preferGlobal": true, "bin": "./src/giantjs.js", "dependencies": { + "async": "^2.6.1", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", "babel-plugin-remove-comments": "^2.0.0", @@ -45,7 +47,9 @@ "require-from-string": "^2.0.2" }, "devDependencies": { + "async_hooks": "^1.0.0", "babel-core": "^6.26.3", + "babel-preset-es2016": "^6.24.1", "chai": "^4.1.2", "coveralls": "^3.0.2", "gulp": "^3.9.1", @@ -55,6 +59,7 @@ "istanbul": "^0.4.5", "mocha": "^5.2.0", "proxyquire": "^2.0.1", - "sinon": "^6.1.5" + "sinon": "^6.1.5", + "uglify-js": "^3.4.9" } } diff --git a/src/babel/babel-plugin-contract-code-reflection/index.js b/src/babel/babel-plugin-contract-code-reflection/index.js index 788ab71..0c364b7 100644 --- a/src/babel/babel-plugin-contract-code-reflection/index.js +++ b/src/babel/babel-plugin-contract-code-reflection/index.js @@ -1,3 +1,4 @@ +import giantConfig from '../../config' import logger from '../../logger' const getMetadata = (path) => { @@ -21,9 +22,10 @@ export default (babel) => { metadata.className = declaration.get('id').node.name metadata.methods = [] + metadata.deployFee = 0 declaration.traverse({ - ClassMethod (path) { + ClassMethod(path) { metadata.methods.push({ name: path.get('key').node.name, params: path.get('params').map((param) => param.node.name), diff --git a/src/babel/babel-plugin-contract-fee/index.js b/src/babel/babel-plugin-contract-fee/index.js new file mode 100644 index 0000000..0186236 --- /dev/null +++ b/src/babel/babel-plugin-contract-fee/index.js @@ -0,0 +1,233 @@ +import logger from '../../logger' +import giantConfig from "../../config"; + +let pfeVars = { + Program: {count: 0, max: 1, fee: 10, required: true}, + StringLiteral: {count: 0, max: 1000, fee: 4, required: true}, + ExpressionStatement: {count: 0, max: 1000, fee: 4, required: true}, + Identifier: {count: 0, max: 1000, fee: 4, required: true}, + MemberExpression: {count: 0, max: 100, fee: 4, required: true}, + ObjectProperty: {count: 0, max: 1000, fee: 4, required: true}, + ObjectExpression: {count: 0, max: 100, fee: 4, required: true}, + CallExpression: {count: 0, max: 100, fee: 4, required: true}, + VariableDeclarator: {count: 0, max: 100, fee: 4, required: true}, + VariableDeclaration: {count: 0, max: 100, fee: 4, required: true}, + BinaryExpression: {count: 0, max: 100, fee: 4, required: true}, + UpdateExpression: {count: 0, max: 100, fee: 4, required: true}, + LogicalExpression: {count: 0, max: 100, fee: 4, required: true}, + AssignmentExpression: {count: 0, max: 100, fee: 4, required: true}, + IfStatement: {count: 0, max: 100, fee: 4, required: true}, + BlockStatement: {count: 0, max: 100, fee: 4, required: true}, + ForStatement: {count: 0, max: 100, fee: 4, required: true}, + FunctionDeclaration: {count: 0, max: 100, fee: 4, required: true}, + ReturnStatement: {count: 0, max: 100, fee: 4, required: true}, + FunctionExpression: {count: 0, max: 100, fee: 4, required: true}, + ConditionalExpression: {count: 0, max: 100, fee: 4, required: true}, + UnaryExpression: {count: 0, max: 100, fee: 4, required: true}, + NewExpression: {count: 0, max: 100, fee: 4, required: true}, + ThrowStatement: {count: 0, max: 100, fee: 4, required: true}, + ThisExpression: {count: 0, max: 100, fee: 4, required: true}, + SequenceExpression: {count: 0, max: 100, fee: 4, required: false}, + ArrayExpression: {count: 0, max: 100, fee: 4, required: true}, + ForStatement: {count: 0, max: 10, fee: 20, required: false}, + ForInStatement: {count: 0, max: 10, fee: 20, required: false}, + WhileStatement: {count: 0, max: 10, fee: 20, required: false}, + DoWhileStatement: {count: 0, max: 10, fee: 20, required: false}, + WhitePaper: { + getNodeOwner: {count: 0, max: 10, fee: 20, required: false}, + getNodeOwnerBalance: {count: 0, max: 10, fee: 30, required: false}, + getCaller: {count: 0, max: 10, fee: 40, required: false}, + getCallerBalance: {count: 0, max: 10, fee: 50, required: false}, + }, +}, pfeVarsCount = (type, whitePaper) => { + if (type) { + pfeVars.hasOwnProperty(type) ? pfeVars[type].count++ : pfeVars[type] = {count: 1} + } else if (whitePaper) { + pfeVars.WhitePaper[whitePaper].count++ + } +} + + +/** + * @returns ast and pfe functions of the giant contract code + * + */ +export default ({template: template}) => { + let pfeCall = (pfeVars) => { + let str = '{' + for (let i in pfeVars) { + str += i + `: {count: ${pfeVars[i]['count']}, fee: ${pfeVars[i]['fee']}},\n` + } + str += `WhitePaper: {` + for (let i in pfeVars.WhitePaper) { + str += i + `: {count: ${pfeVars.WhitePaper[i]['count']}, fee: ${pfeVars.WhitePaper[i]['fee']}},\n` + } + str += '}}' + return template(`var pfeVars = ${str}`, { + sourceType: 'module' + })() + }, injectionPath + + return { + visitor: { + Program: (path) => { + pfeVarsCount(path.type, false) + path.traverse({ + StringLiteral: (path) => { + /** + * its possible use like White Paper marker + * + * logger.info("Visiting StringLiteral : " + path.node.value) + * + {giantjs} info : Visiting StringLiteral : buyCoin + {giantjs} info : Visiting StringLiteral : sendCoin + {giantjs} info : Visiting StringLiteral : getBalance + * + */ + pfeVarsCount(path.type, false) + if (pfeVars.WhitePaper.hasOwnProperty(path.node.value)) { + pfeVarsCount(false, path.node.value) + } + }, + ExpressionStatement: (path) => { + pfeVarsCount(path.type, false) + }, + FunctionDeclaration: (path) => { + pfeVarsCount(path.type, false) + if (path.node.id.name == '_inherits') { + /** + * Some place for injection pfeVars + */ + injectionPath = path + } + }, + Identifier: (path) => { + pfeVarsCount(path.type, false) + /** + * its possible use like White Paper marker + * + * logger.info("Visiting Identifier : " + path.node.name) + * + {giantjs} info : Visiting Identifier : getBalance + {giantjs} info : Visiting Identifier : address + {giantjs} info : Visiting Identifier : balances + {giantjs} info : Visiting Identifier : get + {giantjs} info : Visiting Identifier : address + {giantjs} info : Visiting Identifier : MetaCoin + * + */ + }, + MemberExpression: (path) => { + pfeVarsCount(path.type, false) + }, + ObjectProperty: (path) => { + pfeVarsCount(path.type, false) + }, + ObjectExpression: (path) => { + pfeVarsCount(path.type, false) + }, + CallExpression: (path) => { + pfeVarsCount(path.type, false) + }, + VariableDeclarator: (path) => { + pfeVarsCount(path.type, false) + }, + VariableDeclaration: (path) => { + pfeVarsCount(path.type, false) + }, + BinaryExpression: (path) => { + pfeVarsCount(path.type, false) + }, + UpdateExpression: (path) => { + pfeVarsCount(path.type, false) + }, + LogicalExpression: (path) => { + pfeVarsCount(path.type, false) + }, + AssignmentExpression: (path) => { + pfeVarsCount(path.type, false) + }, + IfStatement: (path) => { + pfeVarsCount(path.type, false) + }, + BlockStatement: (path) => { + pfeVarsCount(path.type, false) + }, + ForStatement: (path) => { + pfeVarsCount(path.type, false) + }, + ReturnStatement: (path) => { + pfeVarsCount(path.type, false) + }, + FunctionExpression: (path) => { + pfeVarsCount(path.type, false) + }, + ConditionalExpression: (path) => { + pfeVarsCount(path.type, false) + }, + UnaryExpression: (path) => { + pfeVarsCount(path.type, false) + }, + NewExpression: (path) => { + pfeVarsCount(path.type, false) + }, + ThrowStatement: (path) => { + pfeVarsCount(path.type, false) + }, + ThisExpression: (path) => { + pfeVarsCount(path.type, false) + }, + SequenceExpression: (path) => { + pfeVarsCount(path.type, false) + }, + ArrayExpression: (path) => { + pfeVarsCount(path.type, false) + }, + ForStatement: (path) => { + pfeVarsCount(path.type, false) + }, + ForInStatement: (path) => { + pfeVarsCount(path.type, false) + }, + WhileStatement: (path) => { + pfeVarsCount(path.type, false) + }, + DoWhileStatement: (path) => { + pfeVarsCount(path.type, false) + }, + }) + injectionPath.insertAfter(pfeCall(pfeVars)) + } + }, post(state) { + + /** + * validator logic + * + * */ + let foundErrors = [] + logger.warn(`Count fee debug ${giantConfig.debug}`) + for (let k in pfeVars) { + if (pfeVars[k].required && !pfeVars[k].count) { + foundErrors.push('not found ' + k) + } + if (pfeVars[k].count > pfeVars[k].max) { + foundErrors.push(k + ' ' + + pfeVars[k].count + + ' times payment, expect max ' + + pfeVars[k].max) + } else { + if (giantConfig.debug) { + logger.info(k + ' ' + pfeVars[k].count + ' times payment') + } + } + } + + if (!foundErrors.length) { + logger.warn('Succeseful! Contract code transpiled.') + } else { + logger.error('Some errors found', foundErrors) + } + + } + } +} diff --git a/src/babel/babel-plugin-contract-validator/index.js b/src/babel/babel-plugin-contract-validator/index.js index 535cb01..af434af 100644 --- a/src/babel/babel-plugin-contract-validator/index.js +++ b/src/babel/babel-plugin-contract-validator/index.js @@ -1,4 +1,17 @@ import logger from '../../logger' +import giantConfig from "../../config"; + +let validatorVars = { + importDeclaration: {count: 0, max: 20, fee: 2}, + exportDefaultDeclaration: {count: 0, max: 1}, + superClassExtend: {count: 0, max: 1}, + classDeclaration: {count: 0, max: 1}, + classMethodDeclaration: {count: 0, max: 20}, + constructorDeclaration: {count: 0, max: 1}, + constructorThisDeclaration: {count: 0, max: 100}, + superDeclaration: {count: 0, max: 1}, + functionDeclaration: {count: 0, max: 100} +} /** * Rules for the validation of a contract: @@ -7,40 +20,122 @@ import logger from '../../logger' * 3) class must be exported by default * 4) the superclass must be imported from the module (check that the module is either GiantContract or the address of the contract) * - * @returns validator of the giant contract code + * @returns ast validator info of the giant contract code */ export default () => { return { visitor: { Program: (path) => { - let foundExportDefaultDeclaration = false - path.traverse({ + ImportDeclaration: (subPath) => { + /** + * validation ImportDeclaration + * import from GiantContract, GiantBlockChain + * + * */ + if (subPath.get('source').get('value').node.indexOf('GiantContract') + || subPath.get('source').get('value').node.indexOf('GiantBlockChain')) { + validatorVars.importDeclaration.count++ + } + }, ExportDefaultDeclaration: (subPath) => { - foundExportDefaultDeclaration = true + /** + * validation ExportDefaultDeclaration + * + * */ + validatorVars.exportDefaultDeclaration.count++ subPath.stop() } }) - - if (!foundExportDefaultDeclaration) { - throw path.buildCodeFrameError('No default export detected') - } }, - ExportDefaultDeclaration: (path) => { - const declaration = path.get('declaration') - - if (!declaration.isClassDeclaration()) { - throw path.buildCodeFrameError('By default, not a class is exported') + ClassDeclaration: (path) => { + /** + * validation ClassDeclaration + * + * */ + validatorVars.classDeclaration.count++ + /** + * validation superClassExtend + * + * */ + if(path.get('superClass').get('name').node=='Contract'){ + validatorVars.superClassExtend.count++ } - - const className = declaration.get('id').name - const superClass = declaration.node.superClass - - if (!superClass) { - throw path.buildCodeFrameError('The class of the contract must be the heir of the Contract') + path.traverse({ + ClassMethod(subPath) { + /** + * validation ClassMethod + * + * */ + let node = subPath.get('kind').node + if (node == 'constructor') { + /** + * validation Constructor + * + * */ + validatorVars.constructorDeclaration.count++ + subPath.traverse({ + CallExpression(subSubPath) { + if (subSubPath.get('callee').get('type').node == 'Super') { + /** + * validation Super + * + * */ + validatorVars.superDeclaration.count++ + } + }, ThisExpression(subSubPath) { + /** + * validation ThisExpression + * + * */ + validatorVars.constructorThisDeclaration.count++ + } + }) + } else { + /** + * validation ClassMethodDeclaration + * + * */ + validatorVars.classMethodDeclaration.count++ + } + } + }) + }, + FunctionDeclaration: (path) => { + /** + * validation FunctionDeclaration + * + * */ + validatorVars.functionDeclaration.count++ + } + }, post(state) { + /** + * validator logic + * + * */ + let foundErrors = [] + logger.warn(`Validate contract debug ${giantConfig.debug}`) + for (let k in validatorVars) { + if (!validatorVars[k].count) { + foundErrors.push('not found ' + k) + } else { + if (validatorVars[k].count > validatorVars[k].max) { + foundErrors.push(k + ' ' + + validatorVars[k].count + + ' times, expect max ' + + validatorVars[k].max) + } else { + if(giantConfig.debug){ + logger.info('found ' + k + ' ' + validatorVars[k].count + ' times') + } + } } - - // TODO need to verify that the class is in the correct inheritance hierarchy + } + if (!foundErrors.length) { + logger.warn('Contract ' + state.opts.basename + ' is valid') + } else { + logger.error('Some errors found', foundErrors) + throw path.buildCodeFrameError('Contract ' + state.opts.basename + ' is not valid') } } } diff --git a/src/compile/GiantContract.js b/src/compile/GiantContract.js index 527d8c0..8bfb3cc 100644 --- a/src/compile/GiantContract.js +++ b/src/compile/GiantContract.js @@ -1,32 +1,250 @@ +import {transformFileSync, transform} from 'babel-core' +import ContractValidator from '../babel/babel-plugin-contract-validator' +import ContractFee from '../babel/babel-plugin-contract-fee' +import ContractCodeReflection from '../babel/babel-plugin-contract-code-reflection' import fs from 'fs' -import {transformFileSync} from 'babel-core' - +import UglifyJS from 'uglify-js' import GiantPath from '../path' -import ContractValidator from '../babel/babel-plugin-contract-validator' +import figlet from 'figlet' +import logger from '../logger' +import giantConfig from "../config"; export default class GiantContract { - constructor (name) { + constructor(name) { + this.valid = null + this.compiled = null this.name = name + this.code = {} + this.code.es5 = null + this.code.es6 = null + this.code.es5pfe = null + this.code.runTime = null + this.pfeVars = null this.fileName = GiantPath.getContractFile(name) this.targetFileName = GiantPath.getTargetContractFile(name) + this.targetFileNameRunTime = GiantPath.getTargetContractFileRunTime(name) + this.pfeAmount = 0 + } + + /** + * WP some methods List + * + * getCallerAddress - White Paper method + */ + + getCallerAddress() { + if (giantConfig.debug) { + logger.warn(`Called method GiantContract.getCallerAddress, return ${giantConfig.caller.publicKey}`) + } + return giantConfig.caller.publicKey + } + + /** + * getCallerPremine - White Paper method + */ + + getCallerPremine() { + if (giantConfig.debug) { + logger.warn(`Called method GiantContract.getCallerPremine, return ${giantConfig.caller.premine}`) + } + return giantConfig.caller.premine } - compile () { - // TODO need to transform & compress code for production ready network - // build & validate contract - const {ast, code} = transformFileSync(this.fileName, { + /** + * getOwnerAddress - White Paper method + */ + + getOwnerAddress() { + if (giantConfig.debug) { + logger.warn(`Called method GiantContract.getOwnerAddress, return ${giantConfig.owner.publicKey}`) + } + + return giantConfig.owner.publicKey + } + + /** + * getOwnerPremine - White Paper method + */ + + getOwnerPremine() { + if (giantConfig.debug) { + logger.warn(`Called method GiantContract.getOwnerPremine, return ${giantConfig.owner.premine}`) + } + return giantConfig.owner.premine + } + + compile() { + let self = this + + if (this.valid) { + let self = this + + fs.writeFileSync(this.targetFileNameRunTime, this.code.runTime.code, 'utf-8') + + this.compiled = true + + figlet('runtime', function (err, data) { + if (err) { + logger.error('Something went wrong...'); + console.dir(err); + return; + } + + console.log(data) + + logger.warn(`Succeseful! Contract ${self.name} was compiled ${GiantPath.getTargetContractFileRunTime(self.name)}`) + + console.log('') + logger.info(`FULL CONTRACT AMOUNT (someContract.metadata.deployFee) : ${self.pfeAmount} GIC \n`) + }) + } + } + + + validate() { + let self = this + + /** + * TODO : pfeDesc - fn for init payment proccess + */ + let pfeDesc = '\nfunction pfe(pfeVars){ ' + this.name + '.pfeVars = pfeVars; console.log(pfeVars)}' + + /** + * this.code.es6 - the original, readable contract code + */ + + this.code.es6 = fs.readFileSync(this.fileName, 'utf8') + + fs.writeFileSync(this.targetFileName, this.code.es6) + + let {code} = transformFileSync(this.fileName) + + /** + * this.code.es5 - the es5 transpiled code + */ + + this.code.es5 = code + + let result = transform(this.code.es5, { + 'plugins': [ContractFee] + }) + + /** + * this.code.es5pfe - the es5 contract code and pfe + */ + + this.code.es5pfe = result.code + pfeDesc + + /** + * this.code.runTime - final, runtime version + */ + + this.code.runTime = UglifyJS.minify(this.code.es5pfe) + + this.mountModule((ContractClass) => { + let contractObject = new ContractClass.default("A") + + this.pfeVars = contractObject.getPfe() + + let giantConfigDebug = giantConfig.debug + + for (let i in this.pfeVars) { + if (i == 'WhitePaper') { + logger.warn(`Found WhitePaper Declarations`) + + console.log(this.pfeVars['WhitePaper']) + + if (typeof this.pfeVars['WhitePaper'].count == 'undefined') { + logger.error(`WhitePaper count undefined`) + } + } + + if (this.pfeVars[i].count) { + this.pfeAmount += this.pfeVars[i].count * this.pfeVars[i].fee + + if (giantConfig.debug) { + console.log(`Declaration ${i} counted`) + } + } + } + + giantConfig.debug = giantConfigDebug + + logger.warn(`Contract full pfe amount ${this.pfeAmount}`) + + /** + * call other wp methods + */ + console.log(`!!! wp method !!!`) + console.log(`Owner Info ${contractObject.getNodeOwner()}`) + console.log(`Caller Info ${contractObject.getCallerAddress()} `) + console.log(`Caller premine balance ${contractObject.getCallerBalance()} `) + + }) + + transformFileSync(this.fileName, { 'plugins': [ContractValidator] }) - // TODO need to optimize ast before saving - fs.writeFileSync(this.targetFileName, code) + this.valid = true + + figlet('valid es6', function (err, data) { + if (err) { + logger.error('Something went wrong...'); + console.dir(err); + return; + } + console.log(data) + logger.warn(`Contract ${self.name} is valid ${GiantPath.getContractFile(self.name)}`) + }) + } + + mountModule(cb) { + const m = require('module'), moduleName = `Deploy` + + var res = require('vm').runInThisContext(m.wrap(this.code.runTime.code))(exports, require, module, __filename, __dirname) + + logger.info(`Mount module ${moduleName}`) + + cb(module.exports) + } + + getMetadata() { + const result = transform(this.code.es6, { + plugins: [ + [ContractCodeReflection] + ], + ast: true, + comments: false, + code: false + }) + this.metadata = result.ast.metadata + return this.metadata + } + + multiplePayment(mockCaller, contractAddressArr, billAmount, cb) { + logger.warn(`Multiple payment : ${contractAddressArr.length} wallets. Sending ${billAmount} GIC for each : `) - this.ast = ast - this.code = code + for (let i in contractAddressArr) { + if (typeof contractAddressArr[i] != 'undefined') { + //logger.warn(`${contractAddressArr[i].publicKey} balance before tx ${contractAddressArr[i].premine}.`) + const from = this.getCallerAddress() + const to = contractAddressArr[i].publicKey + /** + * call contractAddressArr txs type transfer + */ + mockCaller.sendFrom(from, to, billAmount) + } + } + cb({'status': true}) } - getCode () { - return this.code + getCode() { + if (this.code) { + return this.code + } else { + logger.warn(`Contract code ${this.name} not compiled.`) + } } } diff --git a/src/compile/GiantModule.js b/src/compile/GiantModule.js deleted file mode 100644 index dcedfed..0000000 --- a/src/compile/GiantModule.js +++ /dev/null @@ -1,10 +0,0 @@ -export default class GiantModule { - - constructor (code) { - - } - - compile() { - - } -} \ No newline at end of file diff --git a/src/config/index.js b/src/config/index.js index f7d5d59..e240355 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,6 +1,6 @@ import fs from 'fs' import path from 'path' -const giantConfig = require(path.relative(__dirname, `${process.cwd()}/giantjs-config`)) +const giantConfig = require(path.relative(__dirname, `${process.cwd()}/config.json`)) export default giantConfig diff --git a/src/create/index.js b/src/create/index.js index f17a966..857360a 100644 --- a/src/create/index.js +++ b/src/create/index.js @@ -17,10 +17,35 @@ const createContract = (name) => { fs.writeFile( path.resolve(contracts, name + '.js'), '\'use strict\'\n\n' + - 'import Contract from \'GiantContract\'\n\n' + + 'import Contract from \'../../dist/compile/GiantContract\'\n\n' + 'export default class ' + name + ' extends Contract {\n' + '\tconstructor() {\n' + '\t\tsuper()\n' + + '\t\tthis.balances = []\n' + + '\t}\n' + + '\n' + + '\tgetNodeOwner() {\n' + + '\t\tthis.owner = this.getOwnerAddress()\n' + + '\t\treturn this.owner\n' + + '\t}\n' + + '\n' + + '\tgetNodeOwnerBalance() {\n' + + '\t\tthis.ownerBalance = this.getOwnerPremine()\n' + + '\t\treturn this.ownerBalance\n' + + '\t}\n' + + '\n' + + '\tgetCaller() {\n' + + '\t\tthis.caller = this.getCallerAddress()\n' + + '\t\treturn this.caller\n' + + '\t}\n' + + '\n' + + '\tgetCallerBalance() {\n' + + '\t\tthis.callerBalance = this.getCallerPremine()\n' + + '\t\treturn this.callerBalance\n' + + '\t}\n' + + '\n' + + '\tgetPfe() {\n' + + '\t\treturn pfeVars\n' + '\t}\n' + '}', (err) => { diff --git a/src/deploy/index.js b/src/deploy/index.js index 4160846..d3c5c86 100644 --- a/src/deploy/index.js +++ b/src/deploy/index.js @@ -1,32 +1,104 @@ +import giantConfig from '../config' import GiantNode from '../network/GiantNode' import GiantContract from '../compile/GiantContract' +import Hash from '../network/development/Hash' +import Chain from '../network/development/Chain' import logger from '../logger' - export default (name, cmd) => { const giantNode = new GiantNode({ - network: 'development', + network: giantConfig.network, clean: cmd.clean, - mining: true + mining: giantConfig.mining }) giantNode.on('ready', () => { - const giantContract = new GiantContract(name) - - giantContract.compile() - - // TODO it's necessary to take from the parameters - const accounts = giantNode.getAccounts() - - giantNode.deployContract(accounts[0], giantContract.getCode()) - .then((contract) => { - logger.info(contract) - }) - .catch((err) => { - logger.error(err) - }) - .finally(() => { - giantNode.stop() - }) + giantNode.checkChain(tipHeight => { + if (tipHeight) { + const giantContract = new GiantContract(name) + + giantContract.validate() + + if (giantContract.valid) { + try { + logger.debug('Compile Contract') + giantContract.compile() + } + catch (error) { + logger.error('Contract compilation error') + + if (error instanceof TypeError) { + logger.warn('TypeError') + } + else if (error instanceof RangeError) { + logger.warn('RangeError, loops') + } + console.error(error); + } + } + + const accounts = giantNode.getAccounts() + + giantNode.getLastHashes((prevBlockHash, prevTxId) => { + let options = {} + + const contractAddress = '0x' + Hash.sha256(prevBlockHash + giantContract.code) + + let wp = giantContract.pfeVars.WhitePaper + + let md = giantContract.getMetadata() + + for (let i in md.methods) { + if (wp.hasOwnProperty(md.methods[i].name)) { + logger.info(`Insert ${md.methods[i].name} fee ${wp[md.methods[i].name].fee} GIC in metadata`) + md.methods[i].params.push(wp[md.methods[i].name]) + } + } + + logger.info(`The contract metadata`) + + console.log(md) + + options.metadata = md + + options.metadata.tipHeight = tipHeight + + options.metadata.contractAddress = options.contractAddress = contractAddress + + options.contractCode = giantContract.code + + options.contractName = giantContract.name + + options.prevBlockHash = prevBlockHash + + options.prevTxId = prevTxId + + options.metadata.deployFee = giantContract.pfeAmount + + logger.info(`Syntax and wp amount : ${options.metadata.deployFee} GIC \n`) + + options.from = accounts[0] + + giantNode.deployContract(options) + .then((wallets) => { + console.log(wallets) + + logger.info(`Your account : ${accounts[0]}`) + + logger.info(`Your contract : ${giantContract.name} was deployed`) + }) + .catch((err) => { + logger.error(err) + }) + .finally(() => { + setTimeout(() => { + giantNode.stop() + }, 2000) + }) + }) + } else { + giantNode.chainFirstBlock() + } + }) }) } diff --git a/src/exec/index.js b/src/exec/index.js index 376d4c5..2cefefa 100644 --- a/src/exec/index.js +++ b/src/exec/index.js @@ -1,3 +1,58 @@ -export default () => { +import GiantNode from '../network/GiantNode' +import GiantContract from '../compile/GiantContract' +import logger from "../logger"; +import MockClient from '../network/development/MockClient' -} \ No newline at end of file +export default (address, method, args) => { + + const options = { + network: 'development', + mining: false + } + const giantNode = new GiantNode(options) + + giantNode.on('ready', () => { + giantNode.checkContractAddress(address, contractAddress => { + if (contractAddress) { + //applly methods call self.db.validateBlockData + giantNode.getLastHashes((prevBlockHash, prevTxId) => { + giantNode.initContract(contractAddress, metadata => { + logger.info(`Set pfe info for methods in metadata`) + + if (typeof giantNode.contracts[metadata.className] != 'undefined') { + + let contract = giantNode.contracts[metadata.className] + + contract.metadata = giantNode.whitePaperFee(metadata) + + logger.info(`Class ${metadata.className} metadata`) + + console.log(contract.metadata) + + const options = { + 'contractAddress': contractAddress, + 'contractName': metadata.className, + 'method': method, + 'args': args, + 'prevBlockHash': prevBlockHash, + 'prevTxId': prevTxId + } + + giantNode.initMethod(options) //args obj {'a': 1, 'b': 1} + /*const billAmount = 20 + const contractAddressArr = giantNode.getAccounts() + const mockCaller = giantNode.getCaller() + //console.log(contractAddressArr) + + giantNode.contracts[metadata.className].multiplePayment(mockCaller, contractAddressArr, billAmount, (result) => { + logger.info(`MultiplePayment status ${result.status}`) + })*/ + } else { + logger.info(`Contract ${contractAddress} not found`) + } + }) + }) + } + }) + }) +} diff --git a/src/giant.js b/src/giant.js index c8b9fce..6e530e9 100755 --- a/src/giant.js +++ b/src/giant.js @@ -31,13 +31,13 @@ program program .command('deploy ') - .description('Deploy smart contract by name') + .description('Deploy the smart contract by name') .option('-c, --clean', 'Clean development network data') .action(deploy); program - .command('exec ') - .description('') + .command('exec ') + .description('Call for a smart contract method using name or hash of the contract') .action(exec); program diff --git a/src/init/index.js b/src/init/index.js index 82b7b56..4b8ee2d 100644 --- a/src/init/index.js +++ b/src/init/index.js @@ -1,5 +1,7 @@ import path from 'path' import fs from 'fs' +import figlet from 'figlet' +import logger from "../logger" export default () => { const currentDir = process.cwd() @@ -7,33 +9,80 @@ export default () => { // TODO must be executed in an empty folder const contracts = path.resolve(currentDir, './contracts') + fs.stat(contracts, (err, stats) => { if (err) { - fs.mkdir(contracts) + fs.mkdir(contracts, function (error) { + if (!error) { + console.log('new folder ./contracts') + } else { + console.log(error) + } + }) + } else { + console.log('found folder ./contracts') } }) const migrations = path.resolve(currentDir, './migrations') fs.stat(migrations, (err, stats) => { if (err) { - fs.mkdir(migrations) + fs.mkdir(migrations, function (error) { + if (!error) { + console.log('new folder ./migrations') + } else { + console.log(error) + } + }) + } else { + console.log('found folder ./migrations') } }) const networks = path.resolve(currentDir, './network') fs.stat(networks, (err, stats) => { if (err) { - fs.mkdir(networks) + fs.mkdir(networks, function (error) { + if (!error) { + console.log('new folder ./network') + } else { + console.log(error) + } + }) + } else { + console.log('found folder ./network') } }) const test = path.resolve(currentDir, './test') fs.stat(test, (err, stats) => { if (err) { - fs.mkdir(test) + fs.mkdir(test, function (error) { + if (!error) { + console.log('new folder ./test') + } else { + console.log(error) + } + }) + } else { + console.log('found folder ./test') } }) const config = path.resolve(currentDir, './giant.js') - fs.writeFile(config, 'module.exports = {}') + fs.writeFile(config, 'module.exports = {}', function (err) { + if (err) { + console.log(err) + } else { + figlet('Project is ready', function (error, data) { + if (error) { + logger.error('Something went wrong...') + console.dir(error) + return + } + console.log(data) + console.log('Success! Created folders ./contracts ./migrations ./network ./test and config ./giant.js') + }) + } + }) } \ No newline at end of file diff --git a/src/network/GiantNode.js b/src/network/GiantNode.js index d4096e0..9ceb939 100644 --- a/src/network/GiantNode.js +++ b/src/network/GiantNode.js @@ -1,15 +1,22 @@ import MockClient from './development/MockClient' - +import logger from '../logger' import EventEmitter from 'events' +import vm from 'vm' +import fs from 'fs' +import path from 'path' +import giantConfig from '../config' /** * Encapsulates all methods of working with the Giant network */ export default class GiantNode extends EventEmitter { - constructor (options) { + constructor(options) { super() - + this.options = options + this.vm = vm + this.contractCalls = new Map() + this.contracts = [] const self = this // TODO need to set up network parameters from giantjs-config.js // TODO need to set up current network settings from console arguments @@ -25,27 +32,354 @@ export default class GiantNode extends EventEmitter { }) } - getAccounts () { + getCaller() { + return this._client + } + + getAccounts() { return this._client.getAccounts() } - getBalance () { + getBalance() { return this._client.getBalance() } - sendFrom (from, to, amount) { + checkChain(cb) { + this._client.getDB().getMetadata() + .then((metadata) => { + if (typeof metadata.tip == 'undefined') { + cb(false) + } else { + cb(metadata.tipHeight) + } + }) + } + + chainFirstBlock() { + let firstTx = this._client.wallet.premine() + let memPool = this._client.getDB().getMemPool() + memPool.transactions.push(firstTx) + } + + sendFrom(from, to, amount) { return this._client.sendFrom(from, to, amount) } - deployContract (from, code) { - return this._client.deployContract(from, code) + deployContract(options) { + return this._client.deployContract(options) + } + + /** + * getLastContractReceipts + * + * receipt: + 
{ + "transactionHash": "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b", + "transactionIndex": 0, + "blockHash": "0xef95f2f1ed3ca60b048b4bf67cde2195961e0bba6f70bcbea9a2c4e133e34b46", + "blockNumber": 3, + "contractAddress": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "cumulativeGicUsed": 314159, + «gicUsed": 30234, + "logs": [], + "status": "0x1" + } + */ + initMethod(options) { + logger.warn(`Call method GiantContract.${options.method}`) + //this.contracts[options.contractName][options.method](options.args) + + return this._client.callContract(options) + } + + getLastContractFromTip(cb) { + this._client.getDB().getMetadata() + .then((metadata) => { + this._client.getDB().getBlock(metadata.tip) + .then((block) => { + const tx = block.data[0] + console.log(tx.type) + cb(tx.data[0].code.runTime.code) + }) + }) + } + + getLastContractFromFs(cb) { + // TODO : move to tests + let contractPath = './build/contracts/', + latestContract = (() => { + let latest; + const files = fs.readdirSync(contractPath); + files.forEach(filename => { + const stat = fs.lstatSync(path.join(contractPath, filename)); + if (stat.isDirectory()) + return; + if (!latest) { + latest = {filename, mtime: stat.mtime}; + return; + } + if (stat.mtime > latest.mtime) { + latest.filename = filename; + latest.mtime = stat.mtime; + } + }); + return latest.filename; + })() + + let runTimeName = latestContract.slice(0, -3) + 'RunTime.js' + + fs.readFile(contractPath + runTimeName, {encoding: 'utf-8'}, function (err, code) { + if (!err) { + cb(code) + } else { + console.log(err); + } + }); + } + + mountModule(contractAddress, cb) { + const m = require('module') + const moduleName = `GMD_${contractAddress}` + + this.getLastContractFromTip((code) => { + console.log(code) + var res = require('vm').runInThisContext(m.wrap(code))(exports, require, module, __filename, __dirname) + logger.info(`Mount module ${moduleName}`) + cb(module.exports) + }) + } + + initContract(contractAddress, cb) { + const self = this + + this.mountModule(contractAddress, (ContractClass) => { + //console.log(ContractClass) + + this.getContractMeta(contractAddress, (metadata) => { + logger.info(`Contract ${metadata.className} metadata`) + + if (typeof this.contracts == 'undefined') { + this.contracts = [] + } + + this.contracts[metadata.className] = new ContractClass.default() + + cb(metadata) + }) + }) + } + + whitePaperFee(metadata) { + let metaMethodsList = [] //metadata methods list + + for (let i in metadata.methods) { + metaMethodsList.push(metadata.methods[i].name) + } + + const pfeVars = this.contracts[metadata.className].getPfe() + + const metaMethodsListStr = metaMethodsList.toString() + + for (let m in pfeVars) { + /** + * Check and update method in metadata + */ + if (metaMethodsListStr.indexOf(m) + 1) { + for (let i in metadata.methods) { + if (metadata.methods[i].name == m) { + metadata.methods[i].params.push({'wp': true, 'initialized': false}) + metadata.methods[i].fee = pfeVars[m].fee + } + } + } + } + return metadata + } + + getContractMeta(contractAddress, cb) { + const contract = this._client.getDB().getMetadata() + .then((metadata) => { + logger.info(`Contracts ${metadata.contracts.length}`) + for (let c in metadata.contracts) { + for (let k in metadata.contracts[c]) { + if (k == contractAddress) { + if (typeof metadata.contracts[c][k].dependencies == 'object') { + logger.info(`Contract ${metadata.contracts[c][k].className} \n${k} `) + logger.info(`Contract description: ${metadata.contracts[c][k].description}`) + logger.info(`Contract block: ${metadata.contracts[c][k].block}`) + logger.info(`Initialized: ${metadata.contracts[c][k].initialized}`) + } + //registration of the mounted contracts in giantNode.contractCalls Map + this.contractCalls.set(contractAddress, metadata.contracts[c][k]) + logger.warn(`Initializing Contract ${metadata.contracts[c][k].className}`) + } + } + } + const contract = this.contractCalls.get(contractAddress) + console.log(contract.className) + console.log(contract.methods) + cb(contract) + }) + } + + getAllContracts(cb) { + this._client.getDB().getMetadata() + .then((metadata) => { + logger.info(`Contracts ${metadata.contracts.length}`) + if (giantConfig.debug) { + for (var c in metadata.contracts) { + for (var k in metadata.contracts[c]) { + logger.info(`contract ${metadata.contracts[c][k].className} ${k} `) + } + } + } + cb(metadata.contracts) + }) + } + + checkContractAddress(address, cb) { + let testContractName = address.split('0x') + + if (typeof testContractName[1] != 'undefined') { + if (testContractName[1].length = 64) { + logger.info(`Contract address is well formed`) + + cb(address) + } + } else { + logger.info(`Contract address is not well formed, check by a contract name`) + let name = address + + if (name == 'last') { + this.getLastContract(contractAddress => { + logger.info(`Found last contract ${contractAddress}`) + + cb(contractAddress) + }) + } else { + this._client.getDB().getMetadata() + .then((metadata) => { + let contractNameArr = [] + + for (var c in metadata.contracts) { + for (var k in metadata.contracts[c]) { + if (name == metadata.contracts[c][k].className) { + contractNameArr.push(k) + + logger.info(`contract ${metadata.contracts[c][k].className} ${k} `) + } + } + } + + if (contractNameArr.length) { + if (contractNameArr.length == 1) { + logger.info(`Found contract ${metadata.contracts.length} ${name}`) + + cb(contractNameArr[0]) + } + + if (contractNameArr.length > 1) { + let last = contractNameArr[contractNameArr.length - 1] + + logger.info(`Found ${metadata.contracts.length} times contract ${name}, try last ${last}`) + + cb(last) + } + } else { + logger.info(`Contract by name ${name} not found`) + + cb(false) + } + }) + } + } + } + + getLastContract(cb) { + let contractAddress = '' + + this.getAllContracts((contracts) => { + if (!contracts.length) { + console.log("Contracts not found") + return + } + + for (let i in contracts) { + for (let c in contracts[i]) { + contractAddress = c + } + } + cb(contractAddress) + }) + } + + getWallets() { + this._client.getDB().getWallets() + .then((wallets) => { + return wallets + }) + } + + getInfo(options) { + this._client.getDB().getMetadata() + .then((metadata) => { + if (typeof metadata.cache != 'undefined') { + logger.info(`Network ${options.network} ${metadata.tipHeight} Blocks`) + + logger.info(`Hashes`) + console.log(Object.keys(metadata.cache.hashes)) + + logger.info(`Accounts`) + console.log(this._client.getAccounts()) + + logger.info(`Contracts ${metadata.contracts.length}`) + + for (var c in metadata.contracts) { + for (var k in metadata.contracts[c]) { + logger.info(`contract metadata : name ${metadata.contracts[c][k].className}, address ${metadata.contracts[c][k].contractAddress}`) + logger.info(`txid ${metadata.contracts[c][k].txid} `) + } + } + + this._client.getDB().getBlock(metadata.tip) + .then((block) => { + console.log(block) + logger.info(` + LAST BLOCK v${block.version} : ${metadata.tip} + -------------------------------------------------------------------------------- + Prev hash : ${block.prevHash} + Merkel root : ${block.merkleRoot} + Height: : ${block.height} + Timestamp : ${block.timestamp} + Nonce : ${block.nonce} + `) + this.getWallets() + }) + } else { + logger.info(`Blocks not found`) + } + }) } - callContract (from, contractAddress, method, args) { - return this._client.callContract(from, contractAddress, method, args) + getLastHashes(cb) { + this._client.getDB().getMetadata() + .then((metadata) => { + if (typeof metadata.tip != 'undefined') { + this._client.getDB().getBlock(metadata.tip) + .then((block) => { + if (typeof block.data[0] != 'undefined' && typeof block.data[0].data != 'undefined') { + cb(block.prevHash, block.data[0].data[0].metadata.contractAddress) + } else { + cb(block.prevHash, block.prevHash) + } + }) + } else { + logger.info(`Blocks not found`) + } + }) } - stop () { + stop() { this._client.stop() } -} \ No newline at end of file +} diff --git a/src/network/development/Block.js b/src/network/development/Block.js index 5ea263b..d4e0722 100644 --- a/src/network/development/Block.js +++ b/src/network/development/Block.js @@ -3,7 +3,7 @@ import Transaction from './Transaction' export default class Block { - constructor (options) { + constructor(options) { if (!options) { throw new TypeError('"options" is expected') } @@ -19,9 +19,6 @@ export default class Block { if (!Array.isArray(this.data)) { throw new TypeError('"data" is expected to be an array') } - if (this.data.length && !(this.data[0] instanceof Transaction)) { - this.data = this.data.map(tx => new Transaction(tx)) - } this.height = options.height || 0 this.bits = options.bits || 0 this.nonce = options.nonce || 0 @@ -37,11 +34,11 @@ export default class Block { }) } - static fromJson (json) { + static fromJson(json) { return new Block(JSON.parse(json)) } - toObject () { + toObject() { return { hash: this.hash, prevHash: this.prevHash, @@ -55,7 +52,7 @@ export default class Block { } } - toHeader () { + toHeader() { return { version: this.version, prevHash: this.prevHash, @@ -66,19 +63,19 @@ export default class Block { } } - toJson () { + toJson() { return JSON.stringify(this.toObject()) } - headerToJson () { + headerToJson() { return JSON.stringify(this.toHeader()) } - getHash () { + getHash() { return Hash.sha256sha256(this.headerToJson()) } - validate (chain, callback) { + validate(chain, callback) { const self = this chain.db.getBlock(self.prevHash) diff --git a/src/network/development/Chain.js b/src/network/development/Chain.js index aa7de33..688f3f5 100644 --- a/src/network/development/Chain.js +++ b/src/network/development/Chain.js @@ -1,5 +1,7 @@ +import giantConfig from '../../config' import Miner from './Miner' import Block from './Block' +import Hash from './Hash' import logger from '../../logger' import EventEmitter from 'events' @@ -9,7 +11,7 @@ import levelup from 'levelup' export default class Chain extends EventEmitter { - constructor (options) { + constructor(options) { super() const self = this @@ -50,7 +52,7 @@ export default class Chain extends EventEmitter { }) } - initialize () { + initialize() { const self = this if (!this.genesis) { @@ -73,6 +75,7 @@ export default class Chain extends EventEmitter { if (err) { self.emit('error', err) } else { + console.log(self.tip) self.db._onChainAddBlock(self.genesis, (err) => { if (err) { self.emit('error', err) @@ -85,7 +88,6 @@ export default class Chain extends EventEmitter { } }) } else { - metadata.tip = metadata.tip self.db.getBlock(metadata.tip) .then((tip) => { self.tip = tip @@ -102,12 +104,12 @@ export default class Chain extends EventEmitter { }) } - addBlock (block, callback) { + addBlock(block, callback) { this.blockQueue.push([block, callback]) this._processBlockQueue() } - saveMetadata (callback) { + saveMetadata(callback) { const self = this callback = callback || function () { } @@ -116,17 +118,81 @@ export default class Chain extends EventEmitter { return callback() } - const metadata = { - tip: self.tip ? self.tip.hash : null, - tipHeight: self.tip && self.tip.height ? self.tip.height : 0, - cache: self.cache - } + self.db.getMetadata() + .then((metadata) => { + let blockId = metadata.tip + + self.db.getBlock(self.tip.hash) + .then((block) => { + let contract = {} + var contractsArr = [] + if (typeof metadata != 'undefined' && typeof metadata.contracts != 'undefined') { + contractsArr = metadata.contracts + } + + if (typeof block.data != 'undefined') { + if (false) {//giantConfig.debug + console.log(block.data) + } + + if (typeof block.data[0] != 'undefined' && typeof block.data[0].data != 'undefined') { + //TODO : move contractAddress in contract options + //let contractId = block.data[0].contractAddress + + /** + * TODO : fix contractId (contractAddress) - case not all types tx have contractAddress + * and we cant use self.tip for that + * + */ + + const tx = block.data[0] - self.lastSavedMetadata = new Date() - self.db.putMetadata(metadata, callback) + const contractMetadata = tx.data[0].metadata + + const contractAddress = contractMetadata.contractAddress + + contractMetadata.version = "1.0" + + contractMetadata.block = blockId + + contractMetadata.owner = giantConfig.caller.privateKey + + contractMetadata.initialized = false + + contractMetadata.description = `Sandbox Contract : ${contractAddress}` + + contractMetadata.dependencies = { + "giant-exchange-api": "^0.1.0", + "some-giant-api": "^0.3.6" + } + + contract[contractAddress] = contractMetadata + + contractsArr.push(contract) + } + } + + const metadata = { + tip: self.tip ? self.tip.hash : null, + tipHeight: self.tip && self.tip.height ? self.tip.height : 0, + cache: self.cache, + contracts: contractsArr + } + + self.lastSavedMetadata = new Date() + self.db.putMetadata(metadata, callback) + }) + }) } - startMiner () { + getMetadata() { + self.db.getMetadata() + .then((metadata) => { + return metadata + }) + } + + startMiner() { logger.info('startMiner') if (!this.miner) { this.miner = new Miner({ @@ -139,7 +205,7 @@ export default class Chain extends EventEmitter { } } - buildGenesisBlock (options) { + buildGenesisBlock(options) { if (!options) { options = {} } @@ -155,13 +221,13 @@ export default class Chain extends EventEmitter { return genesis } - stop () { + stop() { if (this.miner) { this.miner.stop() } } - _validateMerkleRoot (block) { + _validateMerkleRoot(block) { const transactions = this.db.getTransactionsFromBlock(block) const merkleRoot = this.db.getMerkleRoot(transactions) if (!merkleRoot || block.merkleRoot === merkleRoot) { @@ -170,7 +236,7 @@ export default class Chain extends EventEmitter { return new Error('Invalid merkleRoot for block, expected merkleRoot to equal: ' + merkleRoot + ' instead got: ' + block.merkleRoot) } - _processBlockQueue () { + _processBlockQueue() { var self = this if (self.processingBlockQueue) { @@ -196,7 +262,7 @@ export default class Chain extends EventEmitter { ) } - _processBlock (block, callback) { + _processBlock(block, callback) { const merkleError = this._validateMerkleRoot(block) if (merkleError) { return callback(merkleError) @@ -210,7 +276,7 @@ export default class Chain extends EventEmitter { ) } - _writeBlock (block, callback) { + _writeBlock(block, callback) { const self = this self.db.getBlock(block.hash) @@ -232,8 +298,12 @@ export default class Chain extends EventEmitter { }) } - _updateTip (block, callback) { - logger.info(`Chain updating the tip for [${block.height}]`, block.toObject()) + _updateTip(block, callback) { + logger.warn(`Chain updating the tip for [${block.height}] debug ${giantConfig.debug}`) + if (giantConfig.debug) { + console.log(block.toObject()) + } + const self = this // FIXME check block.prevHash !== self.tip.hash and reorg @@ -258,12 +328,12 @@ export default class Chain extends EventEmitter { ) } - _validateBlock (block, callback) { + _validateBlock(block, callback) { logger.info(`Chain is validating block: ${block.hash}`) block.validate(this, callback) } - _onMempoolBlock () { + _onMempoolBlock() { var self = this this.addBlock(block, (err) => { if (err) { diff --git a/src/network/development/Contract.js b/src/network/development/Contract.js index e861d4e..f0ddecf 100644 --- a/src/network/development/Contract.js +++ b/src/network/development/Contract.js @@ -6,41 +6,36 @@ import ContractCodeReflection from '../../babel/babel-plugin-contract-code-refle */ export default class Contract { - constructor (options) { + constructor(options) { if (!options) { throw new TypeError('"options" is expected') } - if (!options.hasOwnProperty('code') || !options.code || options.code.length <= 2) { - throw new TypeError('"code" is expected') + if (!options.hasOwnProperty('contractName') || !options.contractName) { + throw new TypeError('"contractName" is expected') + } + if (!options.hasOwnProperty('contractAddress') || !options.contractAddress) { + throw new TypeError('"contractAddress" is expected') } if (!options.hasOwnProperty('feePrice')) { throw new TypeError('"feePrice" is expected') } - this.code = options.code + this.code = options.contractCode this.feePrice = options.feePrice + this.metadata = options.metadata + + } - const result = transform(this.code, { - plugins: [ - [ContractCodeReflection] - ], - ast: true, - comments: false, - code: false - }) - - this.ast = result.ast - this.className = result.ast.metadata.className - this.methods = result.ast.metadata.methods + getMetadata() { + return this.metadata } /** * calculates sufficient fee to call the constructor * @param options */ - getConstructorFee (options) { + getConstructorFee(options) { const loops = options.loops || 1 - return {} } @@ -48,7 +43,7 @@ export default class Contract { * calculates sufficient fee to call the specified method * @param options */ - getMethodFee (options) { + getMethodFee(options) { } } diff --git a/src/network/development/Database.js b/src/network/development/Database.js index 5b668a1..10b3cb3 100644 --- a/src/network/development/Database.js +++ b/src/network/development/Database.js @@ -1,3 +1,4 @@ +import giantConfig from '../../config' import GiantPath from '../../path' import MemPool from './MemPool' import Block from './Block' @@ -23,7 +24,7 @@ const PREFIXES = { export default class Database extends EventEmitter { - constructor (options) { + constructor(options) { super() if (!options) { options = {} @@ -44,22 +45,22 @@ export default class Database extends EventEmitter { }) } - initialize () { + initialize() { this.emit('ready') } - buildGenesisData () { + buildGenesisData() { return { merkleRoot: null, data: [] } } - putMetadata (metadata, callback) { + putMetadata(metadata, callback) { this.store.put('metadata', JSON.stringify(metadata), {}, callback) } - getMetadata () { + getMetadata() { const self = this return new Promise((resolve, reject) => { @@ -79,7 +80,37 @@ export default class Database extends EventEmitter { }) } - getBlock (hash) { + updateWallets(wallets, cb) { + this.store.put('wallets', JSON.stringify(wallets), (err) => { + if (err) { + cb(err) + } else { + cb(`Wallets updated`) + } + }) + } + + getWallets() { + const self = this + + return new Promise((resolve, reject) => { + self.store.get('wallets', {}, (err, data) => { + if (err instanceof levelup.errors.NotFoundError) { + resolve({}) + } else if (err) { + reject(err) + } else { + try { + resolve(JSON.parse(data)) + } catch (e) { + reject(new Error('Could not parse wallets')) + } + } + }) + }) + } + + getBlock(hash) { const self = this return new Promise((resolve, reject) => { @@ -98,7 +129,7 @@ export default class Database extends EventEmitter { }) } - putBlock (block, callback) { + putBlock(block, callback) { const self = this const key = `${PREFIXES.BLOCK}-${block.hash}` const options = { @@ -114,18 +145,37 @@ export default class Database extends EventEmitter { }) } - addTransactionsToBlock (block, transactions) { + addTransactionsToBlock(block, transactions) { + const self = this + + if (this.memPool.transactions.length > giantConfig.maxBlockSize) { + /** + * TODO : Transactions type TRANSFER from Loop + * this.memPool.addTransaction(tx) + */ + + logger.warn(`Maximum Block Size ${giantConfig.maxBlockSize} exceeded! New Block Preparation`) + + /** + * TODO : Put Block + * + * self.putBlock(block, callback) + * + */ + return + } + let txs = this.getTransactionsFromBlock(block) txs = txs.concat(transactions) block.merkleRoot = this.getMerkleRoot(txs) block.data = txs } - getTransactionsFromBlock (block) { + getTransactionsFromBlock(block) { return block.data } - getMerkleRoot (transactions) { + getMerkleRoot(transactions) { const tree = this.getMerkleTree(transactions) const merkleRoot = tree[tree.length - 1] if (!merkleRoot) { @@ -135,16 +185,28 @@ export default class Database extends EventEmitter { } } - validateBlockData (block, callback) { + validateBlockData(block, callback) { const self = this const transactions = self.getTransactionsFromBlock(block) + logger.warn(`Validate Block Data, get Transactions from Block debug ${giantConfig.debug}`) + if (giantConfig.debug) { + console.log(transactions) + } + async.each(transactions, (transaction, done) => { transaction.validate().finally(() => done()) + transaction.options = ['contract valid'] }, callback) } - getMerkleTree (transactions) { + getMemPool() { + logger.warn(`Found ${this.memPool.transactions.length} transactions in memPool`) + + return this.memPool + } + + getMerkleTree(transactions) { var tree = transactions.map((tx) => tx.hash) var j = 0 @@ -160,7 +222,7 @@ export default class Database extends EventEmitter { return tree } - _onChainAddBlock (block, callback) { + _onChainAddBlock(block, callback) { var self = this logger.debug('DB handling new chain block') @@ -189,11 +251,11 @@ export default class Database extends EventEmitter { }) } - _updatePrevHashIndex (block, callback) { + _updatePrevHashIndex(block, callback) { this.store.put(`${PREFIXES.PREV_HASH}-${block.hash}`, block.prevHash, callback) } - _updateTransactions (block, add, callback) { + _updateTransactions(block, add, callback) { const self = this const txs = self.getTransactionsFromBlock(block) @@ -222,7 +284,7 @@ export default class Database extends EventEmitter { callback(null, operations) } - _updateValues (block, add, callback) { + _updateValues(block, add, callback) { const self = this const operations = [] const action = add ? '_patch' : '_unpatch' @@ -247,7 +309,7 @@ export default class Database extends EventEmitter { }) } - _patch (key, diff, callback) { + _patch(key, diff, callback) { const self = this self.get(key, (err, original) => { @@ -271,7 +333,7 @@ export default class Database extends EventEmitter { }) } - _unpatch (key, diff, callback) { + _unpatch(key, diff, callback) { const self = this self.get(key, (err, original) => { diff --git a/src/network/development/Hash.js b/src/network/development/Hash.js index 045c65e..9754e68 100644 --- a/src/network/development/Hash.js +++ b/src/network/development/Hash.js @@ -1,6 +1,12 @@ import crypto from 'crypto' const sha256 = (string) => { + /** + * new code format - object consist 3 versions of the contract code + */ + if (typeof string != 'string') { + string = JSON.stringify(string) + } return crypto.createHash('sha256') .update(string) .digest() @@ -8,6 +14,9 @@ const sha256 = (string) => { } const sha256sha256 = (string) => { + if (typeof string != 'string') { + string = JSON.stringify(string) + } return sha256(sha256(string)) } diff --git a/src/network/development/MemPool.js b/src/network/development/MemPool.js index 6ad01d5..61c5c5d 100644 --- a/src/network/development/MemPool.js +++ b/src/network/development/MemPool.js @@ -1,46 +1,75 @@ import EventEmitter from 'events' +import Contract from "./Contract"; +import logger from "../../logger"; +import giantConfig from "../../config"; export default class MemPool extends EventEmitter { - constructor (options) { + constructor(options) { super() this.db = options.db this.blocks = [] this.transactions = [] } - addTransaction (transaction) { + addTransaction(transaction) { const self = this - return new Promise((resolve, reject) => { - transaction.validate() - .then((result) => { - if (!self.hasTransaction(transaction.hash)) { - self.transactions.push(transaction) - self.emit('transaction', transaction) - resolve(result) - } else { - resolve(result) - } - }) - .catch(reject) - }) + logger.warn(`MemPool addTransaction transaction ${transaction.id} type ${transaction.type}`) + + if (transaction.type == 'generation') { + + return new Promise((resolve, reject) => { + logger.warn(`Transaction type : ${transaction.type}`) + + if (!self.hasTransaction(transaction.txId)) { + self.transactions.push(transaction) + self.emit('transaction', transaction) + } + + resolve({'id': transaction.txId}) + }) + } else { + return new Promise((resolve, reject) => { + transaction.validate() //result like contract move to create method + .then((contract) => { + if (contract) { + + contract.txid = contract.metadata.txid = transaction.getHash() + + logger.warn(`Mempool contract.txid : ${contract.txid}`) + + if (!self.hasTransaction(contract.txid)) { + self.transactions.push(transaction) + self.emit('transaction', transaction) + } + + const inputsOutputs = [transaction.inputs, transaction.outputs] + + resolve(inputsOutputs) + } else { + reject() + } + }) + .catch(reject) + }) + } } - hasTransaction (hash) { + hasTransaction(id) { for (let i = 0; i < this.transactions.length; i++) { - if (this.transactions[i].hash === hash) { + if (this.transactions[i].id === id) { return true } } return false } - getTransactions () { + getTransactions() { return this.transactions } - getTransaction (hash) { + getTransaction(hash) { for (let i = 0; i < this.transactions.length; i++) { if (this.transactions[i].hash === hash) { return this.transactions[i] @@ -49,7 +78,7 @@ export default class MemPool extends EventEmitter { return null } - removeTransaction (txid) { + removeTransaction(txid) { var newTransactions = [] this.transactions.forEach(function (tx) { if (tx.hash !== txid) { @@ -59,26 +88,26 @@ export default class MemPool extends EventEmitter { this.transactions = newTransactions } - addBlock (block) { + addBlock(block) { if (!this.blocks[block.hash]) { this.blocks[block.hash] = block this.emit('block', block) } } - hasBlock (hash) { + hasBlock(hash) { return this.blocks[hash] ? true : false } - getBlock (hash) { + getBlock(hash) { return this.blocks[hash] } - removeBlock (hash) { + removeBlock(hash) { delete this.blocks[hash] } - getBlocks () { + getBlocks() { return this.blocks } } diff --git a/src/network/development/Miner.js b/src/network/development/Miner.js index a866143..42a8781 100644 --- a/src/network/development/Miner.js +++ b/src/network/development/Miner.js @@ -1,11 +1,14 @@ +import giantConfig from '../../config' import Block from './Block' +import Wallet from './Wallet' import logger from '../../logger' +import TransactionType from './TransactionType' import async from 'async' export default class Miner { - constructor (options) { + constructor(options) { const self = this this.db = options.db @@ -13,25 +16,25 @@ export default class Miner { this.memPool = options.memPool this.wallet = options.wallet this.started = false - this.blockTime = options.blockTime || 500 + this.blockTime = options.blockTime || giantConfig.blockTime this.chain.on('genesis', () => { self.wallet.premine() }) } - start () { + start() { logger.info('Started miner') this.started = true this.mineBlocks() } - stop () { + stop() { logger.info('Stopped miner') this.started = false } - mineBlocks () { + mineBlocks() { const self = this async.whilst( @@ -49,28 +52,100 @@ export default class Miner { ) } - mineBlock (callback) { + getPrevTx() { + //if first tx - return null + return + } + + getReceipient() { + let receipient = Math.floor((Math.random() * 10) + 2) + + if (typeof this.wallet.accounts[receipient] == 'undefined') { + return this.wallet.accounts[1] //chainOwner + } + + logger.info(`Found receipient : ${this.wallet.accounts[receipient].publicKey}`) + + logger.info(`Receipient balance: ${this.wallet.accounts[receipient].premine} GIC`) + + return this.wallet.accounts[receipient] + } + + getCallerAddress() { + return this.wallet.accounts[0] + } + + mineBlock(callback) { const self = this const memPoolTransactions = self.memPool.getTransactions() if (memPoolTransactions.length) { let transactions = [] + transactions = transactions.concat(memPoolTransactions) + const block = new Block({ prevHash: self.chain.tip.hash }) - self.db.addTransactionsToBlock(block, transactions) + self.db.validateBlockData(block, () => { - logger.debug(`Builder built block ${block.hash}`) + self.db.addTransactionsToBlock(block, transactions) - self.chain.addBlock(block, (err) => { - if (err) { - self.chain.emit('error', err) - } else { - logger.debug(`Builder successfully added block ${block.hash} to chain`) - } - callback() + logger.debug(`Builder built block ${block.hash}`) + + self.chain.addBlock(block, (err) => { + if (err) { + self.chain.emit('error', err) + } else { + logger.debug(`Builder successfully added block ${block.hash} to chain`) + + self.db.getMetadata() + .then((metadata) => { + console.log('metadata') + console.log(metadata) + if (typeof metadata.cache != 'undefined') { + logger.info(`${metadata.tipHeight} Blocks`) + + logger.info(`Hashes`) + console.log(Object.keys(metadata.cache.hashes)) + + if (typeof this._client != 'undefined') { + logger.info(`Accounts`) + console.log(this._client.getAccounts()) + } + + logger.info(`Contracts ${metadata.contracts.length}`) + + for (var c in metadata.contracts) { + for (var k in metadata.contracts[c]) { + logger.info(`contract metadata : name ${metadata.contracts[c][k].className}, address ${metadata.contracts[c][k].contractAddress}`) + logger.info(`txid ${metadata.contracts[c][k].txid} `) + } + } + + self.db.getBlock(metadata.tip) + .then((block) => { + //console.log(block) + logger.info(` + LAST BLOCK v${block.version} : ${metadata.tip} + -------------------------------------------------------------------------------- + Prev hash : ${block.prevHash} + Merkel root : ${block.merkleRoot} + Height: : ${block.height} + Timestamp : ${block.timestamp} + Nonce : ${block.nonce} + `) + }) + } else { + logger.info(`Blocks not found`) + } + }) + + // giantNode.getInfo(options) + } + callback() + }) }) } else { callback() diff --git a/src/network/development/MockClient.js b/src/network/development/MockClient.js index 8981e38..377e893 100644 --- a/src/network/development/MockClient.js +++ b/src/network/development/MockClient.js @@ -2,15 +2,19 @@ import Database from './Database' import Wallet from './Wallet' import Chain from './Chain' import Contract from './Contract' +import Transaction from './Transaction' import EventEmitter from 'events' +import TransactionType from "./TransactionType"; +import logger from "../../logger"; +import giantConfig from "../../config"; +import Hash from "./Hash"; /** * The Giant mock network, used to compile, test, and debug smart contracts in a development environment */ export default class MockClient extends EventEmitter { - - constructor (options) { + constructor(options) { super() if (!options) { @@ -19,7 +23,9 @@ export default class MockClient extends EventEmitter { options.client = this this.db = options.db = new Database(options) + this.wallet = options.wallet = new Wallet(options) + this.chain = options.chain = new Chain(options) const self = this @@ -53,60 +59,136 @@ export default class MockClient extends EventEmitter { }) } - getAccounts () { + getAccounts() { return this.wallet.getAccounts() } - getBalance () { + getBalance() { return this.wallet.getBalance() } - sendFrom (from, to, amount) { + getDB() { + return this.db + } + + sendFrom(from, to, amount) { + logger.warn(`MockClient call tx transfer. Debug ${giantConfig.debug}`) return new Promise((resolve, reject) => { }) } - deployContract (from, code, options) { + deployContract(options) { const self = this - return new Promise((resolve, reject) => { - const contract = new Contract({ - code: code, - feePrice: options.feePrice || 0.0000001 - }) - const fee = contract.getConstructorFee({ - loops: 10 - }) - const transaction = Transaction.deployContract() - transaction.inputs = options.inputs || [] - transaction.outputs = options.outputs || [] - transaction.feePrice = contract.feePrice - - this.db.memPool.addTransaction(transaction) - .then((tx) => { - contract.name = 'MetaCoin' - contract.address = '0x1G9033a3HdF74E1d7619347bC491d73A36967d72' - contract.fee = 10.0 - contract.methods = { - buyCoin: [], - sendCoin: ['receiver'], - getBalance: ['address'] - } - - resolve(contract) - }) - .catch(reject) + return new Promise((resolve, reject) => { + try { + options.feePrice = options.metadata.deployFee || 1000 + + let transaction = Transaction.deployContract(options) + + logger.warn(`Transaction ${transaction.id} prevBlockHash ${transaction.prevBlockHash}`) + + this.db.memPool.addTransaction(transaction) + .then((inputsOutputs) => { + logger.warn(`Add transaction to memPool. Success. Debug ${giantConfig.debug}`) + + if (giantConfig.debug) { + logger.warn(`Transaction in memPool.`) + console.log(this.db.memPool.getTransactions()) + } + + //https://bitcoin.org/en/glossary/signature-script + //scriptSig = + //scriptPubKey = OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG + + let inputs = inputsOutputs[0][0] + + let outputs = inputsOutputs[1][1] + + for (let i in this.wallet.accounts) { + if (this.wallet.accounts[i].publicKey == inputs.scriptSig) { + if (this.wallet.accounts[i].premine > outputs.value) { + this.wallet.accounts[i].premine = this.wallet.accounts[i].premine - outputs.value + + logger.info(`Caller ${this.wallet.accounts[i].publicKey} spent ${outputs.value} GIC`) + } else { + logger.error(`Caller ${this.wallet.accounts[i].publicKey} can't spent ${outputs.value}, + case have only ${this.wallet.accounts[i].premine} GIC`) + } + } + + if (this.wallet.accounts[i].publicKey == outputs.to) { + this.wallet.accounts[i].premine = this.wallet.accounts[i].premine + outputs.value + } + } + this.db.updateWallets(this.wallet.accounts, (response) => { + logger.info(response) + + resolve(this.wallet.accounts) + }) + }) + .catch(function (error) { + console.log(error) + + reject() + }) + } catch (err) { + console.log(err) + } }) } - callContract (from, contractAddress, method, args) { - return new Promise((resolve, reject) => { + callContract(options) { + const self = this - }) + let wallets = this.db.getWallets() + .then((wallets) => { + for (let i in wallets) { + console.log(wallets[i].publicKey) + if (wallets[i].publicKey == giantConfig.caller.publicKey) { + options.inputValue = wallets[i].premine + console.log(wallets[i].premine) + } + } + + this.db.getMetadata() + .then((metadata) => { + /** + * TODO : find contract in metadata.contracts array + */ + const methods = metadata.contracts[0][options.contractAddress].methods + + options.methodFee = 0 + + for (let i in methods) { + if (options.method == methods[i].name) { + options.feePrice = methods[i].params[0].fee + } + } + + if (!options.feePrice) { + logger.error(`Can't find method fee`) + return + } + + return new Promise((resolve, reject) => { + try { + let transaction = Transaction.callContract(options) + console.log('----------..transaction..-----------') + console.log(transaction) + console.log('----------..transaction..-----------') + resolve() + + } catch (err) { + console.log(err) + } + }) + }) + }) } - stop () { + stop() { this.chain.stop() } } diff --git a/src/network/development/Transaction.js b/src/network/development/Transaction.js index 9f9b98c..8172f0a 100644 --- a/src/network/development/Transaction.js +++ b/src/network/development/Transaction.js @@ -1,19 +1,40 @@ +import giantConfig from '../../config' import Hash from './Hash' import TransactionType from './TransactionType' import Contract from './Contract' +import logger from "../../logger" export default class Transaction { - constructor (options) { - if (!options) { - options = {} - } + constructor(options) { + this.options = options + logger.warn(`Transaction constructor(options) debug ${giantConfig.debug}`) + if (giantConfig.debug) { + console.log(options) + } this.type = options.type || TransactionType.TRANSFER - this.data = options.data - this.feePrice = options.feePrice - this.inputs = options.inputs || [] - this.outputs = options.outputs || [] + + this.feePrice = options.feePrice || giantConfig.feePrice + + this.data = [] + + if (options.type == 'deploy') { + let contract = new Contract(options) + this.data = [contract] + } + + this.contractAddress = options.contractAddress + + this.inputs = options.inputs || this.getInputs() + + this.prevBlockHash = options.prevBlockHash + + this.prevTxId = options.prevTxId + + this.txId = this.getHash() + + this.outputs = options.outputs || this.getOutputs() const hashProperty = { configurable: false, @@ -24,10 +45,11 @@ export default class Transaction { } Object.defineProperty(this, 'hash', hashProperty) + Object.defineProperty(this, 'id', hashProperty) } - static generation () { + static generation() { return new Transaction({ type: TransactionType.GENERATION, inputs: [{ @@ -37,58 +59,157 @@ export default class Transaction { }) } - static sendFrom () { + static sendFrom() { return new Transaction({type: TransactionType.TRANSFER}) } - static deployContract () { - return new Transaction({type: TransactionType.CONTRACT_DEPLOY}) + static deployContract(options) { + options.type = TransactionType.CONTRACT_DEPLOY + + let tx = new Transaction(options) + + tx.getInputs() + + console.log(tx.inputs) + + tx.getOutputs() + + console.log(tx.outputs) + + return tx } - static callContract () { - return new Transaction({type: TransactionType.CONTRACT_CALL}) + static callContract(options) { + options.type = TransactionType.CONTRACT_CALL + + let tx = new Transaction(options) + + tx.getInputs() + + console.log(tx.inputs) + + tx.getOutputs() + + console.log(tx.outputs) + + return tx } - toObject () { + toObject() { const json = { type: this.type, data: this.data, inputs: this.inputs, - outputs: this.outputs + outputs: this.outputs, + prevBlockHash: this.prevBlockHash } if (this.type === TransactionType.CONTRACT_DEPLOY || this.type === TransactionType.CONTRACT_CALL) { json.feePrice = this.feePrice } + return json } - toJson () { + toJson() { return JSON.stringify(this.toObject()) } - getHash () { + getHash() { return Hash.sha256(this.toJson()) } - validate () { + getInputs() { + this.inputs = [{ + value: this.countInput(), //giantConfig.caller.premine, + prevTx: this.prevTxId, + index: 0, + scriptSig: giantConfig.caller.publicKey + }] + + return this.inputs + } + + getOutputs() { + this.outputs = [{ScryptPubKey: ''}, + {to: giantConfig.owner.publicKey, value: this.countOutput()}] + + return this.outputs + } + + countOutput() { + if (this.type == 'call') { + return this.feePrice + } + + if (typeof this.options.metadata != 'undefined') { + return this.options.metadata.deployFee + } else { + return 0 + } + } + + countInput() { + if (typeof this.options.metadata != 'undefined' && this.options.metadata.tipHeight == 1) { + return giantConfig.caller.premine + } else { + return this.options.inputValue + } + } + + scriptSig() { + /** + * ru + * Поле scriptSig состоит из двух составляющих – публичный ключ и подпись. + Хэш от указанного публичного ключа должен соответствовать хэшу получателя, + указанному в предыдущей транзакции. + Публичный ключ используется для проверки указанной в транзакции подписи. + Подпись генерируется на основе хеша некоторых полей данной транзакции. + Таким образом, открытый ключ в совокупности с подписью подтверждает, + что транзакция была создана реальным валидным получателем, на которого ссылается предыдущая транзакция. + Стоит отметить, что подпись никогда не пприменяется к транзакции целиком, и пользователи могут указывать, + какая часть транзакции подписывается (конечно, кроме поля scriptSig).*/ + return + } + + ScryptPubKey() { + /** + scriptPubKey:{ + "asm":"OP_RETURN 54f55fe6f2a0f349e2921d06e63d58712a906fbb4231f1e943da82d6", + "hex":"6a1c54f55fe6f2a0f349e2921d06e63d58712a906fbb4231f1e943da82d6", + "message":"unable to decode tx type!" + }*/ + return + } + + validate() { + + // TODO : validate return new Promise((resolve, reject) => { if (this.type === 'deploy') { - // TODO deploy contract - const contract = new Contract() // Deployed contract object - contract.name = 'MetaCoin' - contract.address = '0x1G9033a3HdF74E1d7619347bC491d73A36967d72' - contract.fee = 10.0 - contract.methods = { - buyCoin: [], - sendCoin: ['receiver'], - getBalance: ['address'] - } + const contract = new Contract(this.options) + resolve(contract) } else if (this.type === 'call') { - // TODO call contract method const result = null // Result of contract's method execution + resolve(result) + } else if (this.type === 'transfer') { + /*sendCoin (receiver) { + const tx = getTransaction() + const sender = getCallerAddress() + const senderBalance = this.balances.get(sender) + if (senderBalance < tx.amount) { + return false + } + + const receiverBalance = this.balances.get(receiver) + this.balances.set(sender, senderBalance - tx.amount) + this.balances.set(receiver, (receiverBalance ? receiverBalance : 0) + tx.amount) + + return true + }*/ + resolve(null) } else { resolve(null) } diff --git a/src/network/development/Wallet.js b/src/network/development/Wallet.js index 74dde0a..ab84d09 100644 --- a/src/network/development/Wallet.js +++ b/src/network/development/Wallet.js @@ -1,13 +1,11 @@ import Transaction from './Transaction' import logger from '../../logger' - import EventEmitter from 'events' - import colors from 'colors/safe' +import giantConfig from "../../config"; export default class Wallet extends EventEmitter { - - constructor (options) { + constructor(options) { super() this.db = options.db @@ -20,60 +18,60 @@ export default class Wallet extends EventEmitter { { privateKey: 'YUsc83YuzURjo5eqezyCKUQXAMXets1Ko6edm9paexd7wD8DYhrU', publicKey: 'GPLkrYE3GdXDoZMz4zhxyBmTiF1N3AQvpH', - premine: 20000 + premine: 20010 }, { privateKey: 'YVu4osRVAJoXQFDikchxQyfbDWuJGV1AxzCj2QyndQGZex4p6ogv', publicKey: 'Gf84TLVMVaEBD1Vb5ZSax39i8VorgB5nC3', - premine: 20000 + premine: 20020 }, { privateKey: 'YUCYpjZzmAQSZfAWg2W6FZdqmAjzV2JcQPSCWJPrLq8UUoS73SEJ', publicKey: 'GKFej3xYYzbv8qD5p2Q6CGxQVz1uFXZcVo', - premine: 20000 + premine: 20030 }, { privateKey: 'YVGWKh4k8V9M3fKAMjWpcHVQMHxopPpgob5ooBBhHi2mc8vopHPf', publicKey: 'GRfRyKN7wkswAeDrmLvg9JSjfoZd29gkTv', - premine: 20000 + premine: 20040 }, { privateKey: 'YUHPzYgrjch1k7SA8kBwNUMhmYYQmZTZQ2ZuTMjwisFwWxULRVYE', publicKey: 'GPe8tZMKBATcvJMu3AY2waXm2Hj6nE3YEM', - premine: 20000 + premine: 20050 }, { privateKey: 'YWReKSYWsbYBg3YVumJ2goETpTc667Na4jwbrLSnWnMv7gLsza2j', publicKey: 'GSQoMjQFm2b942Z9AnGHnfGSspuAZbcfJa', - premine: 20000 + premine: 20060 }, { privateKey: 'YSrY4bh7q2sZNq1NskriUbo8B7UAdf8XHxuh752c3u3Xq1TzTgpN', publicKey: 'GbHkZrS9X6LRifRY73pA87ZuUw4Nn7nd3a', - premine: 20000 + premine: 20070 }, { privateKey: 'YT7Qf6U5CMCxg9oqpT1Z3UVCqx4czSj3gJxLJH6TU74gHCR8T9eE', publicKey: 'GJrnCVYHuzNMNkHstvmUxY6znp5iTcXAmB', - premine: 20000 + premine: 20080 }, { privateKey: 'YTGiw1qxeQFgJTRDwVGwMEXkSCxyxXWcYLGeRHMYekvhECaSiE5P', publicKey: 'GcKg5sgUUXFLxhKoScUFMQMN95iFbrkqVa', - premine: 20000 + premine: 20090 } ] } - initialize () { + initialize() { this.db.on('addblock', (block) => this._processBlock(block)) this.emit('ready') this._showWarning() } - premine () { - const transaction = Transaction.generation() + premine() { + let transaction = Transaction.generation() this.accounts.forEach((account) => { transaction.outputs.push({ @@ -82,41 +80,47 @@ export default class Wallet extends EventEmitter { }) }) - this.db.memPool.addTransaction(transaction) + return transaction } - getAccounts () { + getAccounts() { return this.accounts.map((key) => key.publicKey) } - getBalance () { - return 0 + getBalance() { + return 1000 } - _loadUnpentOutputs () { + _loadUnpentOutputs() { } - _processBlock (block) { + _processBlock(block) { block.data.forEach((transaction) => { }) } - _showWarning () { - let warn = 'Giant Development Network started\n\nPublic Keys:\n' - this.accounts.forEach((account, i) => { - warn += `(${i}) ${account.publicKey}\n` - }) - warn += '\nPrivate Keys:\n' - this.accounts.forEach((account, i) => { - warn += `(${i}) ${account.privateKey}\n` - }) + _showWarning() { + logger.warn(`Giant Development Network started debug ${giantConfig.debug}`) + + if (giantConfig.debug) { + let warn = '' + + this.accounts.forEach((account, i) => { + warn += `(${i}) ${account.publicKey}\n` + }) + warn += '\nPrivate Keys:\n' + this.accounts.forEach((account, i) => { + warn += `(${i}) ${account.privateKey}\n` + }) + + logger.warn(warn) + } - warn += colors['yellow'].bold('\n⚠️ Important ⚠️ : These keys were created for you by giantjs. It is not secure.\n' + - 'Ensure you do not use it on production blockchains, or else you risk losing funds.\n\n') + logger.warn(colors['yellow'].bold('\n⚠️ Important ⚠️ : These keys were created for you by giantjs. It is not secure.\n' + + 'Ensure you do not use it on production blockchains, or else you risk losing funds.\n\n')) - logger.warn(warn) } } \ No newline at end of file diff --git a/src/network/index.js b/src/network/index.js index be87f36..afcef1d 100644 --- a/src/network/index.js +++ b/src/network/index.js @@ -1,6 +1,5 @@ import GiantNode from '../network/GiantNode' import GiantContract from '../compile/GiantContract' -import logger from '../logger' /** * For each or a particular network, we print the following: @@ -10,18 +9,14 @@ import logger from '../logger' * */ export default (name, cmd) => { - const giantNode = new GiantNode({ + + const options = { network: 'development', mining: false - }) + } + const giantNode = new GiantNode(options) giantNode.on('ready', () => { - // print network info - // const networkInfo = giantNode.getInfo() - - // print accounts info - // print deployed contracts info - - + giantNode.getInfo(options) }) } diff --git a/src/path/index.js b/src/path/index.js index a312ff9..2431ab2 100644 --- a/src/path/index.js +++ b/src/path/index.js @@ -42,6 +42,7 @@ const networkPath = touchPath(path.resolve(currentPath, './networks')) export default { getBuildPath: () => buildPath, getTargetContractFile: (name) => path.resolve(buildPath, `./${name}.js`), + getTargetContractFileRunTime: (name) => path.resolve(buildPath, `./${name}RunTime.js`), getContractPath: () => contractPath, getContractFile: (name) => path.resolve(contractPath, `./${name}.js`), getNetworkPath: (name) => path.resolve(networkPath, `./${name}/`), diff --git a/test/Contract.test.js b/test/Contract.test.js index 6cecf3c..f9133ca 100755 --- a/test/Contract.test.js +++ b/test/Contract.test.js @@ -3,8 +3,12 @@ import fs from 'fs' import path from 'path' +import {transform, transformFileSync} from 'babel-core' +import ContractFee from "../dist/babel/babel-plugin-contract-fee"; + import 'chai/register-should' import Contract from '../src/network/development/Contract' +import UglifyJS from 'uglify-js' const metaCoinCode = fs.readFileSync(path.resolve(__dirname, './contracts/MetaCoin.js'), { encoding: 'utf8' @@ -76,6 +80,92 @@ describe('Contract', () => { }) }) + describe('#runTime', () => { + + it('Some contracts files exist', () => { + fs.readdir('./build/contracts', function (err, flist) { + if (err) console.log('Some contracts files error', err.message, err.stack) + flist.should.not.be.null + }) + }) + + it('All contracts have RunTime version', () => { + + fs.readdir('./build/contracts', function (err, flist) { + if (err) console.log('Some contracts files error', err.message, err.stack) + const contractsRunTime = flist.filter(f => f.indexOf('RunTime.js') > -1) + const contracts = flist.filter(f => f.indexOf('RunTime.js') == -1 && f.indexOf('.js') > -1) + + contracts.length.should.be.equal(contractsRunTime.length) + + let c = 0 + for (let i in contracts) { + let RunTimeName = contracts[i].slice(0, -3) + 'RunTime.js' + const found = flist.find(f => f == RunTimeName) + if (typeof found != 'undefined') { + c++ + } + } + + contracts.length.should.be.equal(c) + }) + }) + + it('Last readable contract should be equal by his RunTime version', () => { + + let contractPath = './build/contracts/', contract, contractRunTime, + contractData, + contractRunTimeData, + newContractRunTimeData, + pfeDesc = '\nfunction pfe(pfeVars){console.log(pfeVars)}', + getLatestFile = () => { + let latest; + const files = fs.readdirSync(contractPath); + files.forEach(filename => { + const stat = fs.lstatSync(path.join(contractPath, filename)); + if (stat.isDirectory()) + return; + if (!latest) { + latest = {filename, mtime: stat.mtime}; + return; + } + if (stat.mtime > latest.mtime) { + latest.filename = filename; + latest.mtime = stat.mtime; + } + }); + return latest.filename; + } + + let latestContract = getLatestFile() + + if (latestContract.indexOf('RunTime.js') == -1) { + contract = latestContract + contractRunTime = latestContract.slice(0, -3) + 'RunTime.js' + } else { + contract = latestContract.slice(0, -10) + '.js' + contractRunTime = latestContract + } + + new Promise(function (resolve, reject) { + contractRunTimeData = fs.readFileSync(contractPath + contractRunTime, 'utf8') + resolve() + }).then(() => { + let {code} = transformFileSync(contractPath + contract) + + let result = transform(code, { + 'plugins': [ContractFee] + }) + + newContractRunTimeData = result.code + pfeDesc + + let runTimeCode = UglifyJS.minify(newContractRunTimeData) + + contractRunTimeData.should.be.equal(runTimeCode.code) + }) + }) + }) + describe('#getMethodFee', () => { })