tic-traction/Action.js
2022-07-03 16:02:58 +08:00

237 lines
8.8 KiB
JavaScript
Raw 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.

const Ling = require('so.ling')
const ticc = 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
const MOM = DAD.prototype
MOM.__proto__ = Ling.prototype
/** ****************** Shared by instances ********************/
MOM._table = DAD.name
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 = async function (keypair) {
// 由前端调用,后台不创建
this.actorPubkey = keypair.pubkey
this.actorAddress = ticc.pubkey_to_address({ pubkey: keypair.pubkey })
this.timestamp = new Date()
await this.signMe(keypair.seckey)
this.hashMe()
return this
}
MOM.signMe = async function (seckey) {
// 由前端调用,后台不该进行签名
let json = this.getJson({ exclude: ['hash', 'blockHash', 'actorSignature'] }) // 是前端用户发起事务时签字这时候还不知道进入哪个区块所以不能计入blockHash
this.actorSignature = await ticc.sign(json, seckey)
return this
}
MOM.hashMe = function () {
this.hash = ticc.hash(this.getJson({ exclude: ['hash', 'blockHash'] })) // block.hash 受到所包含的actionList影响所以action不能受blockHash影响否则循环了
return this
}
MOM.verifySig = async function () {
let json = this.getJson({ exclude: ['hash', 'blockHash', 'actorSignature'] })
let result = await ticc.verify(json, this.actorSignature, this.actorPubkey)
return result
}
DAD.verifySig = async function (actionData) {
let typedAction = new wo[actionData.type](actionData)
return await typedAction.verifySig()
}
MOM.verifyAddress = function () {
return (
this.actorAddress === ticc.pubkey_to_address({ pubkey: this.actorPubkey })
)
}
DAD.verifyAddress = function (actionData) {
let typedAction = new wo[actionData.type](actionData)
return typedAction.verifyAddress()
}
MOM.verifyHash = function () {
return (
this.hash === ticc.hash(this.getJson({ exclude: ['hash', 'blockHash'] }))
)
}
DAD.verifyHash = function (actionData) {
let typedAction = new wo[actionData.type](actionData)
return typedAction.verifyHash()
}
MOM.validateMe = function () {
// Applicable on both client and chain server. 子类应当覆盖本方法,静态的检查事务内容的格式。不能检查 balance 等需要全链数据库的东西,因为本方法也要用在前端检查。
// 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() {
// }
DAD._initTypeDict = function (typedActionDict) {
Object.assign(wo, typedActionDict)
}
DAD.getTypedAction = function (type) {
return wo[type]
}
DAD.createTypedAction = function (action) {
return new wo[action.type](action)
}
DAD.buildUserAction = async function (action, keypair) {
// Applicable on client. 客户端调用 Action.build即可新建、并打包成一个完整的子事务不需要亲自调用 constructor, packMe 等方法。
if (
action &&
action.type &&
keypair &&
keypair.seckey &&
keypair.pubkey &&
ticc.seckey_to_pubkey({ seckey: keypair.seckey }) === keypair.pubkey
) {
let typedAction = new wo[action.type](action)
typedAction.actorPubkey = keypair.pubkey
if (typedAction.validateMe()) {
await typedAction.packMe(keypair) // 在 packMe 里,会把 actorPubkey 转存为 actorAddress。
return typedAction
}
}
return null
}
/**
* 获取一批交易在出块时调用。调用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() && // 只检查所有事务通用的格式
(await 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 }) // 即使对 master 分支的node.server 也报错Cannot read property 'broadcast' of undefined
return option.Action
}
}
return null // 非法的交易数据
}
/** ******************** Private in class *******************/
DAD.actionPool = {} // 交易池在执行getActionBatch时被清空
// DAD.currentActionPool = {} // 仅包含0~40秒的交易,40~59秒的交易将被堆积到actionPool。
DAD.actionPoolInfo = {
totalAmount: 0,
totalFee: 0
}