From 4fe26a0b095625c756183f2e0884e9804ce04c62 Mon Sep 17 00:00:00 2001 From: "luk.lu" Date: Tue, 9 Apr 2019 20:16:58 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=87=E7=94=A8=E6=96=B0=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=9AAction*.js=20=E4=BB=8E=20node.server=20=E4=B8=AD?= =?UTF-8?q?=E5=88=A0=E9=99=A4=EF=BC=8C=E9=9B=86=E4=B8=AD=E5=AD=98=E6=94=BE?= =?UTF-8?q?=E4=BA=8E=E6=9C=AC=E5=BA=93=E3=80=82=20=E8=BF=99=E6=A0=B7?= =?UTF-8?q?=E6=B6=88=E9=99=A4=E4=BA=86=E5=90=8C=E4=B8=80=E4=BB=BD=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=87=BA=E7=8E=B0=E5=9C=A8=E4=B8=A4=E5=A4=84=E7=9A=84?= =?UTF-8?q?=E4=B8=8D=E8=89=AF=E7=BB=93=E6=9E=84=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E4=BA=86=E5=90=8C=E6=AD=A5=E7=9A=84=E5=9B=B0=E9=9A=BE=E3=80=82?= =?UTF-8?q?=20=E5=BD=93=20node.server=20=E9=9C=80=E8=A6=81=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=E4=BF=AE=E6=94=B9=20ActionXxx.js=20=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E5=8F=AA=E8=A6=81=E5=9C=A8=20server.js=20=E9=87=8C=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=20require('../tic.action').ActionXxx=20=E5=8D=B3?= =?UTF-8?q?=E5=8F=AF=E3=80=82=E4=B8=80=E5=A4=84=E4=BF=AE=E6=94=B9=EF=BC=8C?= =?UTF-8?q?=E5=88=B0=E5=A4=84=E5=8F=AF=E7=94=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Action.js | 42 +++++++--- ActionLockProof.js | 26 ++++++ ActionMultisig.js | 203 +++++++++++++++++++++++++++++++++++++++++++++ ActionStore.js | 33 ++++++++ ActionTac.js | 76 +++++++++++++++++ ActionTransfer.js | 21 +++-- 6 files changed, 381 insertions(+), 20 deletions(-) create mode 100644 ActionLockProof.js create mode 100644 ActionMultisig.js create mode 100644 ActionStore.js create mode 100644 ActionTac.js diff --git a/Action.js b/Action.js index 15a844a..bae21af 100644 --- a/Action.js +++ b/Action.js @@ -6,6 +6,7 @@ var Ticrypto = require('tic.crypto') const DAD = module.exports = function Action (prop) { this._class = this.constructor.name this.setProp(prop) + this.type = this.constructor.name } DAD.__proto__ = Ling DAD._table = DAD.name @@ -29,7 +30,7 @@ MOM._model = { message: { default: undefined, sqlite: 'TEXT', mysql: 'VARCHAR(256)' }, dataIndex: { default: undefined, sqlite: 'TEXT', mysql: 'VARCHAR(50)' }, // 用于索引json中存储数据, method: { default: undefined, sqlite: 'TEXT' }, - data: { default: undefined, sqlite: 'TEXT' } + json: { default: undefined, sqlite: 'TEXT' } // 给不同类型的 ActionXxx 子类来自定义其所需的数据结构 } MOM.packMe = function (keypair) { // 由前端调用,后台不创建 @@ -78,14 +79,28 @@ DAD.verifyHash = function (action) { return action.hash === Ticrypto.hash(DAD.getJson(action, { exclude: ['hash', 'blockHash'] })) } -DAD.execute = function () { // 子类应当覆盖本方法。把action的影响,汇总登记到其他表格(用于辅助的、索引的表格),方便快速索引、处理。每种事务类型都要重定义这个方法。 - // save to account or other tables - return this +MOM.validateMe = async function() { // 子类应当覆盖本方法。 + // to implement in subclasses: 检查子类事务内容的格式 + let typedAction = new wo[this.type](this) + return await typedAction.validateMe() +} +DAD.validate = async function (action) { + mylog.info(`Validating action type=${action.type} of hash=${action.hash}`) + let typedAction = new wo[action.type](action) + return await typedAction.validateMe() } -DAD.calculateFee = function () { - return 1000 +MOM.executeMe = async function() { // 子类应当覆盖本方法。 + // to implement in subclasses: 把action的影响,汇总登记到其他表格(用于辅助的、索引的表格),方便快速索引、处理。每种事务类型都要重定义这个方法。 + let typedAction = new wo[this.type](this) + return await typedAction.executeMe() } +DAD.execute = async function (action) { + mylog.info(`Excecuting action type=${action.type} of hash=${action.hash}`) + let typedAction = new wo[action.type](action) + return await typedAction.executeMe() +} + /** * 获取一批交易,在出块时调用。调用actionPool的内容被深拷贝到currentActionPool后自动清空。 * 所以在一次出块期间只能调用一次 @@ -116,24 +131,29 @@ DAD.api.getActionList = async function (option) { } DAD.api.prepare = async function (option) { - // 前端发来action数据,进行初步检查(不检查是否可执行--这和事务类型、执行顺序有关,只检查格式是否有效--这是所有事务通用的规范)后放入缓冲池。 + if (typeof option === 'string') { + try { + option = JSON.parse(option) + } catch (error) {} + } + // 前端发来action数据,进行格式检查(不检查是否可执行--这和事务类型、执行顺序有关)后放入缓冲池。 if (option && option.Action && option.Action.type && option.Action.hash && !DAD.actionPool[option.Action.hash]) { - if (DAD.verifyAddress(option.Action) && + if (DAD.verifyAddress(option.Action) && // 只检查所有事务通用的格式 DAD.verifySig(option.Action) && DAD.verifyHash(option.Action) && !DAD.actionPool[option.Action.hash] && - (await wo[option.Action.type].validate(option.Action)) + (await DAD.validate(option.Action)) // 调用子类的 validate 方法,检查子类的事务内容格式 ) { DAD.actionPool[option.Action.hash] = option.Action DAD.actionPoolInfo.totalAmount += option.Action.amount || 0 DAD.actionPoolInfo.totalFee += option.Action.fee || 0 - wo.Node.broadcast('/Action/prepare', option) + wo.NodeNet.broadcast({ Action: option.Action }) return option.Action } } return null // 非法的交易数据 } - +wo.NodeNet.on('broadcast', DAD.api.prepare) /** ******************** Private in class *******************/ DAD.actionPool = {} // 交易池,在执行getActionBatch时被清空 diff --git a/ActionLockProof.js b/ActionLockProof.js new file mode 100644 index 0000000..9a95a10 --- /dev/null +++ b/ActionLockProof.js @@ -0,0 +1,26 @@ +'use strict' +/** + Layer 2 + 1.从链上申请取得余额锁定证明或者额度锁定证明Proof of Locked(POL) + LockProof + { + hash: '0x12324tgw423fa23hrio2uhiut32gt823i4uth243i9', + amount: 100, + blockHeight: 10086, + blockHash: '0xweg34ga243yt54yh543hj65tbxfdgbxdfbx', + actorAddress: 'TARbCVuy5g2u2uUYGVIUYyvuIUvhj3', + actorPubkey: '8912rbiu2341t98o43bguierg98374h7tou34trnh32io87g7ayub', + actorSignature: 'iu12b3ru32tqoknhiaubwefvbuwfibw', + packerPubkey: 'aopiwhgawe9igawonibirugnbiuwehnsjc' + packerSignature: '0xuihg87324hfuiweaoigwnhjriaerwogireabhwg', + expire: 100 //100块后作废 + } + 2.应用层验证POL,检查是否标准,是否过期 + */ +/* + 聚合交易 + { + actorAddress:[], + toAddress:[] + } +*/ diff --git a/ActionMultisig.js b/ActionMultisig.js new file mode 100644 index 0000000..0138d58 --- /dev/null +++ b/ActionMultisig.js @@ -0,0 +1,203 @@ +const Action = require('./Action.js') + +/** ****************** Public of instance ********************/ + +const DAD = module.exports = function ActionMultisig (prop) { + this._class = this.constructor.name + this.setProp(prop) // 没有定义 DAD.prototype._model,因此继承了上级Action.prototype._model,因此通过this.setProp,继承了上级Action定义的实例自有数据。另一个方案是,调用 Action.call(this, prop) + this.type = this.constructor.name +} +DAD.__proto__ = Action +// DAD._table=DAD.name // 注释掉,从而继承父类Action的数据库表格名 +const MOM = DAD.prototype +MOM.__proto__ = Action.prototype + +MOM.signMe = function (seckey) { // 由前端调用,后台不该进行签名 + let json = this.getJson({ exclude: ['hash', 'blockHash', 'actorSignature', 'json'] }) // 是前端用户发起事务时签字,这时候还不知道进入哪个区块,所以不能计入blockHash + this.actorSignature = wo.Crypto.sign(json, seckey) + return this +} + +MOM.verifySig = function () { + let json = this.getJson(({ exclude: ['hash', 'blockHash', 'actorSignature', 'json'] })) + let res = wo.Crypto.verify(json, this.actorSignature, this.actorPubkey) + return res +} + +MOM.verifyAddress = function () { + return this.actorAddress === wo.Crypto.pubkey2address(this.actorPubkey) +} + +MOM.hashMe = function () { + this.hash = wo.Crypto.hash(this.getJson({ exclude: ['hash', 'blockHash', 'json'] })) // block.hash 受到所包含的actionList影响,所以action不能受blockHash影响,否则循环了 + return this +} + +MOM.verifyHash = function () { + return this.hash === wo.Crypto.hash(this.getJson({ exclude: ['hash', 'blockHash', 'json'] })) +} + +MOM.packMe = function (keypair) { // 由前端调用,后台不创建 + this.actorPubkey = keypair.pubkey + this.actorAddress = wo.Crypto.pubkey2address(keypair.pubkey) + this.timestamp = new Date() + + this.signMe(keypair.seckey) + this.hashMe() + return this +} + +MOM.checkMultiSig = function (account) { + let json = this.getJson(({ exclude: ['hash', 'blockHash', 'actorSignature', 'json'] })) + let sigers = Object.keys(this.json) // 公钥列表 + // 交易发起人的签名在prepare的verifySig里已经检查过合法性, + if (account.multiSignatures.keysgroup.indexOf(this.actorPubkey) === -1) { + let M = 1 // 如果不在keysgroup里,可以把交易发起人算一个有效的签名,因此M从1算起 + } else { + let M = 0 // 如果发起人已经在keysgroup里则从0算起 + } + for (let i of sigers) // 该交易内已签名的每一个公钥 + { + if (account.multiSignatures.keysgroup.indexOf(i) !== -1 && wo.Crypto.verify(json, this.json[i], i)) { + M++ + } + } + return M >= account.multiSignatures.min +} +/** ****************** Shared by instances ********************/ + +/* +1.创建多重签名账户 +{ + "ActionMultisig":{ + "actorPubkey": "actorPubkey", + "actorAddress": "actorAddress", + "actorSignature":"actorSignature", + "fee":1, + "json":{ + act: "create", + min: 2, + lifetime: 10, //暂时无用,因为每个交易的挂起时间需求可能不同 + keysgroup:[ + pubkey_a, + pubkey_b, + ... + pubkey_n + ] + } + } +} +2.多重签名账户交易 +step1:发起一个多重签名账户的交易。该交易只是在缓存里,为了给多重签名账户的控制者们提供真正要写入区块链的源Action数据 +{ + "ActionMultisig":{ + "amount": 100, + "fee": 1, + "actorPubkey": "actorPubkey", + "actorAddress": "actorAddress", + "actorSignature":"actorSignature", + "toAddress": "toAddress", + "json":{ + act: "createTransfer", + lifetime: 10, + } + } +} +step2:所有人签名 +{ + "ActionMultisig":{ + "amount": 100, + "fee": 1, + "actorPubkey": "actorPubkey", + "actorAddress": "actorAddress", + "actorSignature":"actorSignature", + "toAddress": "toAddress", + "json":{ + act: 'addSig', + signature: 'signature', + } + } +} +step3:发起人申请执行 +{ + "ActionMultisig":{ + "amount": 100, + "fee": 1, + "actorPubkey": "actorPubkey", + "actorAddress": "actorAddress", + "actorSignature":"actorSignature", + "toAddress": "toAddress", + "json":{ + act:'emitTransfer' + 'pubkey1':'sig1', + 'pubkey2':'sig2', + ...... + 'pubkeyn':'sign', + } + } +} +*/ +MOM.validateMe = async function () { + if (this.json.act === 'createTransfer') // 创建挂起的多重签名事务 + { + DAD.pendingPool[this.hash] = this + return false + } else if (this.json.act === 'addSig') // 签名者签名 + { + DAD.pendingPool[this.hash].json[this.actorPubkey] = this.json.signature + return false + } else { + return wo.Crypto.isAddress(this.toAddress) && + this.fee >= wo.Config.MIN_FEE_ActionTransfer && + (await wo.Store.getBalance(this.actorAddress)) >= this.amount + this.fee && // Todo:引入缓存账户 + this.toAddress != this.actorAddress + } +} + +MOM.executeMe = async function () { + switch (this.json.act) { + // 多重签名账户注册 + case 'sign': + { + let actor = await wo.Account.getOne({ Account: { address: this.actorAddress } }) + if (actor && actor.type !== 'multisig') { + // 检查账户类型,只有不是多重签名账户的才可以执行 + // todo:类型检查,安全操作 + await actor.setMe({ Account: { multiSignatures: { + min: this.json.min, + ttl: this.json.ttl, // 该账户交易的最大挂起时间 + keysgroup: this.json.keysgroup + } }, + cond: { address: actor.address } }) + } + return this + } + // 多重签名账户执行转账 + case 'emitTransfer': + { + let sender = await wo.Account.getOne({ Account: { address: this.actorAddress } }) + if (sender && this.checkMultiSig(sender) && this.toAddress != this.actorAddress && sender.balance >= this.amount + this.fee) { + await sender.setMe({ Account: { balance: sender.balance - this.amount - this.fee }, cond: { address: sender.address } }) + let getter = await wo.Account.getOne({ Account: { address: this.toAddress } }) + if (getter) { + await getter.setMe({ Account: { balance: getter.balance + this.amount }, cond: { address: getter.address } }) + } else { + await wo.Account.addOne({ Account: { address: this.toAddress } }) + } + // mylog.info('Excecuted action='+JSON.stringify(this)) + delete DAD.pendingPool[this.hash] + return this + } + + // mylog.info('balance('+sender.address+')='+sender.balance+' is less than '+this.amount+', 无法转账') + return null + } + } +} + +DAD.pendingPool = {} // 存放所有待签名的多重签名账户交易 + +/* 为挂起状态的多重签名交易提供查询服务 */ +DAD.api.pendingAction = function (option) { + return DAD.pendingPool[option.id] +} diff --git a/ActionStore.js b/ActionStore.js new file mode 100644 index 0000000..10879ab --- /dev/null +++ b/ActionStore.js @@ -0,0 +1,33 @@ +const Action = require('./Action.js') + +/** ****************** Public of instance ********************/ + +const DAD = module.exports = function ActionStore (prop) { + this._class = this.constructor.name + this.setProp(prop) // 没有定义 DAD.prototype._model,因此继承了上级Action.prototype._model,因此通过this.setProp,继承了上级Action定义的实例自有数据。另一个方案是,调用 Action.call(this, prop) + this.type = this.constructor.name +} +DAD.__proto__ = Action +// DAD._table=DAD.name // 注释掉,从而继承父类Action的数据库表格名 +const MOM = DAD.prototype +MOM.__proto__ = Action.prototype + +/** ****************** Shared by instances ********************/ + +MOM.validateMe = function () { + // check size, account balance >= fee, fee>wo.Config.MIN_FEE_ActionStore + return this.fee >= wo.Config.MIN_FEE_ActionStore +} + +MOM.executeMe = async function () { + let actor = await wo.Account.getOne({ Account: { address: this.actorAddress } }) + if (actor && actor.balance >= this.fee) { + await actor.setMe({ Account: { balance: actor.balance - this.fee }, cond: { address: actor.address } }) // todo 20190409: change to use wo.Store + return this + } + return null +} + +/** ****************** Public of class ********************/ + +DAD.api = {} diff --git a/ActionTac.js b/ActionTac.js new file mode 100644 index 0000000..5f6b8f6 --- /dev/null +++ b/ActionTac.js @@ -0,0 +1,76 @@ +const Action = require('./Action.js') +const Methods = ['create', 'transfer', 'exchange', 'mount'] +async function actValidator (action) { + switch (action.data.method) { + case 'create': + if (action.data.name && action.data.symbol && action.data.decimals && + Number.isSafeInteger(Number(action.data.decimals)) && + !await wo.Tac.getOne({ Tac: { name: action.data.name, symbol: action.data.symbol } }) + ) { return true } + return false + case 'transfer': + return action.amount && action.amount > 0 && action.actorAddress && action.toAddress && action.actorAddress !== action.toAddress + case 'exchange': + return true + case 'mount': + return true + default: + return null + } +} + +class ActionTac extends Action { + constructor (prop) { + super() + Object.defineProperty(this, '_class', { + value: 'ActionTac', + enumerable: true, + writable: false + }), + Object.defineProperty(this, 'type', { + value: 'ActionTac', + enumerable: true, + writable: false + }), + Object.defineProperty(this, 'data', { + value: prop, + enumerable: true, + writable: false + }) + } + static async validate (action) { // todo 20190409: MOM.validateMe + return Methods.includes(action.data.method) && await actValidator(action) + } + + static async execute (action) { // todo 20190409: MOM.executeMe + if (action && action.data.method) { + switch (action.data.method) { + case 'create': + delete action._class + let tac = new wo.Tac( + Object.assign(action.data, + action.actorAddress, + action.actorPubkey, + action.actorSignature + )) + tac.address = wo.Crypto.pubkey2address(wo.Crypto.hash(action.actorSignature, action.hash)) + return await tac.addMe() + case 'transfer': + // 内部交易,转发到应用链进程来处理 + await wo.Store.decrease(action.actorAddress, 0 - action.amount, action.address) + await wo.Store.increase(action.toAddress, action.amount, action.address) + return true + case 'exchange': + // Bancor类型 + return wo.Tac.exchange(action) + case 'mount': + return wo.Tac.mount(action) + default: + return 0 + } + } + return 0 + } +} + +module.exports = ActionTac diff --git a/ActionTransfer.js b/ActionTransfer.js index 3b570fb..693a8ab 100644 --- a/ActionTransfer.js +++ b/ActionTransfer.js @@ -9,20 +9,23 @@ DAD.__proto__ = Action const MOM = DAD.prototype MOM.__proto__ = Action.prototype -DAD.validate = async function (action) { +MOM.validateMe = async function () { // if (sender && sender.type !== 'multisig' && action.toAddress != action.actorAddress && sender.balance >= action.amount + action.fee){ - let sender = await wo.Store.getBalance(action.actorAddress) - return action.actorAddress && action.toAddress && action.toAddress != action.actorAddress && action.amount && action.amount > 0 && sender >= action.amount + action.fee && action.fee >= wo.Config.MIN_FEE_ActionTransfer + let balance = await wo.Store.getBalance(this.actorAddress) + return this.actorAddress && this.toAddress && this.toAddress != this.actorAddress + && this.amount && this.amount > 0 && this.fee >= wo.Config.MIN_FEE_ActionTransfer && balance >= this.amount + this.fee } -DAD.execute = async function (action) { - let sender = await wo.Store.getBalance(action.actorAddress) - if (sender >= action.amount + action.fee) { - await wo.Store.decrease(action.actorAddress, action.amount + action.fee) - await wo.Store.increase(action.toAddress, action.amount) - return true +MOM.executeMe = async function () { + let balance = await wo.Store.getBalance(this.actorAddress) + if (balance >= this.amount + this.fee) { + await wo.Store.decrease(this.actorAddress, this.amount + this.fee) + await wo.Store.increase(this.toAddress, this.amount) + return this } return null } +/** ****************** Public of class ********************/ + DAD.api = {}