int-cli
Version:
INT is the new generation of bottom-up created system of IoT and blockchain
292 lines (291 loc) • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const assert = require('assert');
const sqlite = require("sqlite");
const sqlite3 = require("sqlite3");
const util_1 = require("util");
const core_1 = require("../../core");
class ChainEventStorage {
constructor(options) {
this.m_dbPath = options.dbPath;
this.m_logger = options.logger;
this.m_eventDefinations = options.eventDefinations;
}
async init(options) {
const readonly = util_1.isNullOrUndefined(options.readonly) ? false : options.readonly;
let sqliteOptions = {};
if (!readonly) {
sqliteOptions.mode = sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE;
}
else {
sqliteOptions.mode = sqlite3.OPEN_READONLY;
}
try {
this.m_db = await sqlite.open(this.m_dbPath, sqliteOptions);
}
catch (e) {
this.m_logger.error(`open database failed`, e);
return { err: core_1.ErrorCode.RESULT_EXCEPTION };
}
if (readonly) {
const _lbr = await this.getLatestBlock();
if (_lbr.err === core_1.ErrorCode.RESULT_NOT_FOUND) {
return { err: core_1.ErrorCode.RESULT_INVALID_STATE };
}
return _lbr;
}
let err = await this._initBlocksTable();
if (err) {
return { err };
}
for (let [name, def] of this.m_eventDefinations.entries()) {
err = await this._initEventTable(name, def);
if (err) {
return { err };
}
}
return this.getLatestBlock();
}
async getLatestBlock() {
let latest;
try {
latest = await this.m_db.get(`SELECT * FROM blocks ORDER BY number DESC`);
}
catch (e) {
this.m_logger.error('sql get latest block failed ', e);
return { err: core_1.ErrorCode.RESULT_EXCEPTION };
}
if (!latest) {
return { err: core_1.ErrorCode.RESULT_NOT_FOUND };
}
return { err: core_1.ErrorCode.RESULT_OK, latest };
}
async _initBlocksTable() {
try {
await this.m_db.run(`CREATE TABLE IF NOT EXISTS "blocks"("number" INTEGER NOT NULL UNIQUE, "hash" CHAR(64) NOT NULL)`);
}
catch (e) {
this.m_logger.error(`init blocks table failed `, e);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
return core_1.ErrorCode.RESULT_OK;
}
async _initEventTable(name, def) {
let sqlCreateIndex = '';
const tblName = this._eventTblName(name);
if (def.indices) {
for (let index of def.indices) {
const colName = this._indexColName(index);
sqlCreateIndex += `, ${colName} MULTICHAR NOT NULL`;
}
sqlCreateIndex += ');';
for (let index of def.indices) {
const colName = this._indexColName(index);
sqlCreateIndex += `CREATE INDEX ${colName} ON ${tblName}(${colName});`;
}
}
const sqlCreate = `CREATE TABLE IF NOT EXISTS ${tblName} ("index" INTEGER NOT NULL, "block_number" INTERGER NOT NULL` + sqlCreateIndex;
try {
await this.m_db.run(sqlCreate);
}
catch (e) {
this.m_logger.error(`init event ${name} table failed `, e);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
return core_1.ErrorCode.RESULT_OK;
}
_eventTblName(name) {
return `"event@${name}"`;
}
_indexColName(index) {
return `"index@${index}"`;
}
async revertToBlock(blockNumber) {
try {
await this.m_db.run('BEGIN;');
}
catch (e) {
this.m_logger.error('revert to block failed for begin transaction ', e);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
let err;
do {
try {
this.m_db.run(`DELETE FROM blocks WHERE number > ${blockNumber}`);
}
catch (e) {
this.m_logger.error(`sql delete from blocks failed for `, e);
err = core_1.ErrorCode.RESULT_EXCEPTION;
break;
}
for (const name of this.m_eventDefinations.keys()) {
try {
this.m_db.run(`DELETE FROM ${this._eventTblName(name)} WHERE block_number > ${blockNumber}`);
}
catch (e) {
this.m_logger.error(`sql delete from event failed for `, e);
err = core_1.ErrorCode.RESULT_EXCEPTION;
break;
}
}
} while (false);
if (err) {
await this.m_db.run('ROLLBACK;');
return err;
}
try {
await this.m_db.run('COMMIT;');
}
catch (e) {
this.m_logger.error('revert to block failed for commit transaction', e);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
return core_1.ErrorCode.RESULT_OK;
}
async addBlock(block) {
try {
await this.m_db.run('BEGIN;');
}
catch (e) {
this.m_logger.error('add block failed for begin transaction ', e);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
let err = await this._addBlock(block);
if (err) {
await this.m_db.run('ROLLBACK;');
return err;
}
try {
await this.m_db.run('COMMIT;');
}
catch (e) {
this.m_logger.error('add block failed for commit transaction', e);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
return core_1.ErrorCode.RESULT_OK;
}
// this.m_logger.error(`undefined event ${log.name} in block number: ${block.number} hash: ${block.hash} receipt index ${i}`);
_sqlAddEvent(blockNumber, eventIndex, log) {
const def = this.m_eventDefinations.get(log.name);
if (!def) {
return { err: core_1.ErrorCode.RESULT_EXCEPTION };
}
let sql = `INSERT INTO ${this._eventTblName(log.name)} ("index", "block_number"`;
const param = util_1.isNullOrUndefined(log.param) ? {} : log.param;
if (def.indices) {
for (const index of def.indices) {
sql += `, ${this._indexColName(index)}`;
}
sql += `) VALUES (${eventIndex}, ${blockNumber}`;
for (const index of def.indices) {
const indexValue = JSON.stringify(param[index]);
sql += `, '${indexValue}'`;
}
sql += ')';
}
return { err: core_1.ErrorCode.RESULT_OK, sql };
}
async _addBlock(block) {
const receipts = block.content.receipts;
let eventIndex = 0;
let sqls = [];
const sqlAddBlock = `INSERT INTO blocks (number, hash) VALUES (${block.number}, "${block.hash}")`;
sqls.push(sqlAddBlock);
for (const name of this.m_eventDefinations.keys()) {
// 这里为block在每个event table中加入一条index 为 -1的记录;
// 当查询某个block的event时,为了区分block不存在和block中没有event的情况
// block通过addBlock加入到event table之后,select from event table至少会返回index 为-1这条记录;
// block没有通过addBlock加入到event table的话, select会返回无记录
let rlog = new core_1.EventLog();
rlog.name = name;
const sr = this._sqlAddEvent(block.number, -1, rlog);
if (sr.err) {
this.m_logger.error(`add replaceholder event sql for block hash: ${block.hash} number: ${block.number} on event ${name} failed ${core_1.stringifyErrorCode(sr.err)}`);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
sqls.push(sr.sql);
}
for (let i = 0; i < receipts.length; ++i) {
let r = receipts[i];
if (r.sourceType === core_1.ReceiptSourceType.preBlockEvent
|| r.sourceType === core_1.ReceiptSourceType.transaction
|| r.sourceType === core_1.ReceiptSourceType.postBlockEvent) {
for (let l of r.eventLogs) {
const sr = this._sqlAddEvent(block.number, eventIndex, l);
if (sr.err) {
this.m_logger.error(`add event sql for block hash: ${block.hash} number: ${block.number} on event ${eventIndex} failed ${core_1.stringifyErrorCode(sr.err)}`);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
sqls.push(sr.sql);
++eventIndex;
}
}
else {
assert(false, `invalid receipt source type of block number: ${block.number} hash: ${block.hash} receipt index ${i}`);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
}
const runOps = sqls.map((sql) => this.m_db.run(sql));
try {
await Promise.all(runOps);
}
catch (e) {
this.m_logger.error(`sql add block failed for `, e);
return core_1.ErrorCode.RESULT_EXCEPTION;
}
return core_1.ErrorCode.RESULT_OK;
}
async getEvents(options) {
let events = new Map();
let err;
for (let hash of options.blocks) {
let sqls = [];
for (const [event, filterSql] of options.querySql.entries()) {
let _sql = `SELECT e."index" AS "index" FROM ${this._eventTblName(event)} AS e LEFT JOIN blocks AS b ON e."block_number" = b.number WHERE b.hash = "${hash}" AND (e."index" = -1 OR (e."index" >= 0 `;
if (!util_1.isNull(filterSql)) {
_sql += ` AND (` + filterSql + ')';
}
_sql += `))`;
sqls.push(_sql);
}
if (!sqls.length) {
events.set(hash, []);
continue;
}
let sqlGet;
if (sqls.length === 1) {
sqlGet = sqls[0];
}
else {
sqlGet = sqls[0];
for (let sql of sqls.slice(1)) {
sqlGet += ` UNION ${sql} `;
}
}
sqlGet += ` ORDER BY "index" `;
let records;
try {
records = await this.m_db.all(sqlGet);
}
catch (e) {
this.m_logger.error(`sql get events of ${hash} failed `, e);
err = core_1.ErrorCode.RESULT_EXCEPTION;
break;
}
if (records.length) {
let blockEvents = [];
for (const r of records) {
if (r.index >= 0) {
blockEvents.push(r.index);
}
}
events.set(hash, blockEvents);
}
}
if (err) {
return { err };
}
return { err: core_1.ErrorCode.RESULT_OK, events };
}
}
exports.ChainEventStorage = ChainEventStorage;