tic-traction/Action.js

192 lines
8.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var Ling = require('so.ling')
var ticCrypto = require('tic.crypto')
/** ****************** Public of instance ********************/
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
const MOM = DAD.prototype
MOM.__proto__ = Ling.prototype
/** ****************** Shared by instances ********************/
MOM._tablekey = 'hash'
MOM._model = {
hash: { default: undefined, sqlite: 'TEXT UNIQUE', mysql: 'VARCHAR(64) PRIMARY KEY' }, // 不纳入签名和哈希
version: { default: 0, sqlite: 'INTEGER' },
type: { default: 'Action', sqlite: 'TEXT', mysql: 'VARCHAR(100)' }, // 是否放在 assets里更好这里该放action自己的version
blockHash: { default: undefined, sqlite: 'TEXT', mysql: 'VARCHAR(64)' }, // 不纳入签名和哈希。只为了方便查找
timestamp: { default: undefined, sqlite: 'TEXT', mysql: 'CHAR(24)' },
actorPubkey: { default: undefined, sqlite: 'TEXT', mysql: 'BINARY(32)' },
actorAddress: { default: undefined, sqlite: 'TEXT', mysql: 'VARCHAR(50)' },
actorSignature: { default: undefined, sqlite: 'TEXT', mysql: 'BINARY(64)' }, // 不纳入签名,纳入哈希
toAddress: { default: undefined, sqlite: 'TEXT', mysql: 'VARCHAR(50)' },
amount: { default: 0, sqlite: 'NUMERIC', mysql: 'BIGINT' },
fee: { default: 0, sqlite: 'NUMERIC', mysql: 'BIGINT' },
message: { default: undefined, sqlite: 'TEXT', mysql: 'VARCHAR(256)' },
dataIndex: { default: undefined, sqlite: 'TEXT', mysql: 'VARCHAR(50)' }, // 用于索引json中存储数据
method: { default: undefined, sqlite: 'TEXT' },
json: { default: undefined, sqlite: 'TEXT' } // 给不同类型的 ActionXxx 子类来自定义其所需的数据结构
}
MOM.packMe = function (keypair) { // 由前端调用,后台不创建
this.actorPubkey = keypair.pubkey
this.actorAddress = ticCrypto.pubkey2address(keypair.pubkey)
this.timestamp = new Date()
this.signMe(keypair.seckey)
this.hashMe()
return this
}
MOM.signMe = function (seckey) { // 由前端调用,后台不该进行签名
let json = this.getJson({ exclude: ['hash', 'blockHash', 'actorSignature'] }) // 是前端用户发起事务时签字这时候还不知道进入哪个区块所以不能计入blockHash
this.actorSignature = ticCrypto.sign(json, seckey)
return this
}
MOM.hashMe = function () {
this.hash = ticCrypto.hash(this.getJson({ exclude: ['hash', 'blockHash'] })) // block.hash 受到所包含的actionList影响所以action不能受blockHash影响否则循环了
return this
}
MOM.verifySig = function() {
let json = this.getJson({ exclude: ['hash', 'blockHash', 'actorSignature'] })
let result = ticCrypto.verify(json, this.actorSignature, this.actorPubkey)
return result
}
DAD.verifySig = function (actionData) {
let typedAction = new wo[actionData.type](actionData)
return typedAction.verifySig()
}
MOM.verifyAddress = function () {
return this.actorAddress === ticCrypto.pubkey2address(this.actorPubkey)
}
DAD.verifyAddress = function (actionData) {
let typedAction = new wo[actionData.type](actionData)
return typedAction.verifyAddress()
}
MOM.verifyHash = function () {
return this.hash === ticCrypto.hash(this.getJson({ exclude: ['hash', 'blockHash'] }))
}
DAD.verifyHash = function (actionData) {
let typedAction = new wo[actionData.type](actionData)
return typedAction.verifyHash()
}
DAD.build = function (action, keypair) { // Applicable on client. 客户端调用 Action.build即可新建、并打包成一个完整的子事务不需要亲自调用 constructor, packMe 等方法。
if (action && action.type && keypair && keypair.seckey && keypair.pubkey) {
let typedAction = new wo[action.type](action)
if (typedAction.validateMe()) {
typedAction.packMe(keypair)
return typedAction
}
}
return null
}
MOM.validateMe = function() { // Applicable on chain server. 子类应当覆盖本方法静态的检查事务内容的格式和语义是否符合该子类事务的特性要求。To validate an action's content format.
// to implement in subclasses: 检查子类事务内容的格式
let typedAction = new wo[this.type](this)
return typedAction.validateMe()
}
DAD.validate = function (action) { // Allicable on both client and chain server.
mylog.info(`Validating action type=${action.type} of hash=${action.hash}`)
let typedAction = new wo[action.type](action)
return typedAction.validateMe()
}
MOM.executableMe = async function() { // Applicable on chain server. 子类应当覆盖本方法动态的检查事务内容在当前链状态下是否能执行。To check if an action is executableMe given the current chain status.
let typedAction = new wo[this.type](this)
return await typedAction.executableMe()
}
DAD.executable = async function(action) { // For chain server.
let typedAction = new wo[action.type](action)
if (typedAction.hasOwnProperty('executableMe')) { // 防止子类忘了定义自己的 executableMe
return await typedAction.executableMe()
}else {
return true
}
}
MOM.executeMe = async function() { // For chain server. 子类应当覆盖本方法,执行事务,记录其(除了存入 Action 数据表之外的)副作用到内存数据库或其他地方。
// to implement in subclasses: 把action的影响汇总登记到其他表格用于辅助的、索引的表格方便快速索引、处理。每种事务类型都要重定义这个方法。
let typedAction = new wo[this.type](this)
return await typedAction.executeMe()
}
DAD.execute = async function (action) { // For chain server.
mylog.info(`Excecuting action type=${action.type} of hash=${action.hash}`)
let typedAction = new wo[action.type](action)
return await typedAction.executeMe()
}
// [todo 20190411] 执行事务池中的所有事务
// DAD.executePool = async function() {
// }
/**
* 获取一批交易在出块时调用。调用actionPool的内容被深拷贝到currentActionPool后自动清空。
* 所以在一次出块期间只能调用一次
*/
DAD.getActionBatch = function () {
let actionBatch = {
actionPool: JSON.parse(JSON.stringify(DAD.actionPool)), // deep copy
totalAmount: DAD.actionPoolInfo.totalAmount,
totalFee: DAD.actionPoolInfo.totalFee
}
DAD.actionPool = {}
DAD.actionPoolInfo = {
totalAmount: 0,
totalFee: 0
}
return actionBatch
}
/** ********************* Public of class *******************/
DAD.api = {}
DAD.api.getAction = async function (option) {
return await DAD.getOne(option)
}
DAD.api.getActionList = async function (option) {
return await DAD.getAll(option)
}
DAD.api.prepare = async function (option) {
if (typeof option === 'string') {
try {
option = JSON.parse(option)
} catch (error) {}
}
// 前端发来action数据进行格式检查不检查是否可执行--这和事务类型、执行顺序有关)后放入缓冲池。
if (option && option.Action && option.Action.type && wo[option.Action.type] && option.Action.hash && !DAD.actionPool[option.Action.hash]) {
let typedAction = new wo[option.Action.type](option.Action)
if (typedAction.verifyAddress() && // 只检查所有事务通用的格式
typedAction.verifySig() &&
typedAction.verifyHash() &&
typedAction.validateMe() && // 检查事务的内容是否符合该子类事务的格式
(await typedAction.executableMe()) // 检查事务是否可执行,在当前链的状态下。
) {
DAD.actionPool[option.Action.hash] = typedAction
DAD.actionPoolInfo.totalAmount += option.Action.amount || 0
DAD.actionPoolInfo.totalFee += option.Action.fee || 0
wo.Netnode.broadcast({ Action: option.Action })
return option.Action
}
}
return null // 非法的交易数据
}
/** ******************** Private in class *******************/
DAD.actionPool = {} // 交易池在执行getActionBatch时被清空
// DAD.currentActionPool = {} // 仅包含0~40秒的交易,40~59秒的交易将被堆积到actionPool。
DAD.actionPoolInfo = {
totalAmount: 0,
totalFee: 0
}