int-cli
Version:
INT is the new generation of bottom-up created system of IoT and blockchain
530 lines (529 loc) • 24.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const assert = require('assert');
const error_code_1 = require("../error_code");
const writer_1 = require("../lib/writer");
const chain_1 = require("../chain");
const block_1 = require("./block");
const context_1 = require("./context");
const reader_1 = require("../lib/reader");
const libAddress = require("../address");
const digest = require('../lib/digest');
var DBFT_SYNC_CMD_TYPE;
(function (DBFT_SYNC_CMD_TYPE) {
DBFT_SYNC_CMD_TYPE[DBFT_SYNC_CMD_TYPE["prepareRequest"] = 23] = "prepareRequest";
DBFT_SYNC_CMD_TYPE[DBFT_SYNC_CMD_TYPE["prepareResponse"] = 24] = "prepareResponse";
DBFT_SYNC_CMD_TYPE[DBFT_SYNC_CMD_TYPE["changeview"] = 25] = "changeview";
DBFT_SYNC_CMD_TYPE[DBFT_SYNC_CMD_TYPE["end"] = 26] = "end";
})(DBFT_SYNC_CMD_TYPE = exports.DBFT_SYNC_CMD_TYPE || (exports.DBFT_SYNC_CMD_TYPE = {}));
var ConsensusState;
(function (ConsensusState) {
ConsensusState[ConsensusState["none"] = 0] = "none";
ConsensusState[ConsensusState["waitingCreate"] = 1] = "waitingCreate";
ConsensusState[ConsensusState["waitingProposal"] = 2] = "waitingProposal";
ConsensusState[ConsensusState["waitingVerify"] = 3] = "waitingVerify";
ConsensusState[ConsensusState["waitingAgree"] = 4] = "waitingAgree";
ConsensusState[ConsensusState["waitingBlock"] = 5] = "waitingBlock";
ConsensusState[ConsensusState["changeViewSent"] = 10] = "changeViewSent";
ConsensusState[ConsensusState["changeViewSucc"] = 11] = "changeViewSucc";
})(ConsensusState || (ConsensusState = {}));
class DbftConsensusNode extends events_1.EventEmitter {
constructor(options) {
super();
this.m_changeView = new Map();
this.m_currView = 0;
this.m_network = options.network;
this.m_globalOptions = options.globalOptions;
this.m_state = ConsensusState.none;
this.m_secret = options.secret;
this.m_address = libAddress.addressFromSecretKey(this.m_secret);
this.m_pubkey = libAddress.publicKeyFromSecretKey(this.m_secret);
let initBound = (conns) => {
for (let conn of conns) {
this._beginSyncWithNode(conn);
}
};
let connOut = this.m_network.node.getOutbounds();
initBound(connOut);
let connIn = this.m_network.node.getInbounds();
initBound(connIn);
this.m_network.on('inbound', (conn) => {
this._beginSyncWithNode(conn);
});
this.m_network.on('outbound', (conn) => {
this._beginSyncWithNode(conn);
});
}
on(event, listener) {
return super.on(event, listener);
}
once(event, listener) {
return super.once(event, listener);
}
get base() {
return this.m_network;
}
get logger() {
return this.m_network.logger;
}
async init() {
// await this.m_node.init();
let hr = await this.m_network.headerStorage.getHeader(0);
if (hr.err) {
this.logger.error(`dbft consensus node init failed for ${hr.err}`);
return hr.err;
}
this.m_genesisTime = hr.header.timestamp;
// let err = await this.m_node.initialOutbounds();
// if (err) {
// this.logger.error(`dbft consensus node init failed for ${err}`);
// return err;
// }
return error_code_1.ErrorCode.RESULT_OK;
}
_cancel() {
this.m_state = ConsensusState.none;
this.m_context = undefined;
this.m_changeView = new Map();
this.m_currView = 0;
this._resetTimer();
}
updateTip(header, nextMiners, totalView) {
// TODO: 这里还需要比较两个header 的work,只有大的时候覆盖
if (this.m_tip) {
this.logger.info(`updateTip this.m_state=${this.m_state} totalView=${totalView} header_number=${header.number},${this.m_tip.header.hash} ${header.hash}`);
}
else {
this.logger.info(`updateTip this.m_state=${this.m_state} ${totalView}`);
}
if (!this.m_tip || this.m_tip.header.hash !== header.hash) {
this.m_tip = {
header,
nextMiners,
totalView
};
this._cancel();
this.m_network.setValidators(nextMiners);
}
}
async agreeProposal(block) {
if (this.m_state !== ConsensusState.waitingVerify) {
this.logger.warn(`skip agreeProposal in state `, this.m_state);
return error_code_1.ErrorCode.RESULT_SKIPPED;
}
let curContext = this.m_context;
assert(curContext && curContext.block);
if (!curContext || !curContext.block) {
this.logger.error(`agreeProposal in invalid context `, curContext);
return error_code_1.ErrorCode.RESULT_SKIPPED;
}
if (!this.m_tip.header.isPreBlock(block.header)) {
this.logger.error(`agreeProposal block ${block.header.hash} ${block.number} in invalid context block ${this.m_tip.header.hash} ${this.m_tip.header.number}`);
return error_code_1.ErrorCode.RESULT_SKIPPED;
}
this.m_state = ConsensusState.waitingAgree;
let newContext = {
curView: curContext.curView,
block,
signs: new Map()
};
// 可能已经收到了其他节点的验证信息
for (let [k, v] of curContext.preSigns) {
if (v.hash === block.hash) {
newContext.signs.set(k, v.signInfo);
}
}
this.m_context = newContext;
const sign = libAddress.sign(block.hash, this.m_secret);
this._sendPrepareResponse(block, sign);
this._onPrepareResponse({ hash: block.hash, pubkey: this.m_pubkey, sign });
return error_code_1.ErrorCode.RESULT_OK;
}
async newProposal(block) {
assert(this.m_tip);
if (!this.m_tip) {
return error_code_1.ErrorCode.RESULT_SKIPPED;
}
if (this.m_state !== ConsensusState.waitingProposal) {
this.logger.warn(`dbft conensus newProposal ${block.header.hash} ${block.header.number} while not in blockCreated state`);
return error_code_1.ErrorCode.RESULT_SKIPPED;
}
if (!this.m_tip.header.isPreBlock(block.header)) {
this.logger.warn(`dbft conensus newProposal ${block.header.hash} ${block.header.number} while in another context ${this.m_tip.header.hash} ${this.m_tip.header.number}`);
return error_code_1.ErrorCode.RESULT_SKIPPED;
}
this.logger.info(`newProposal miners=${JSON.stringify(this.m_network.getValidators())}, blockhash=${block.hash}`);
if (this.m_network.getValidators().length > 1) {
let i = 0;
}
// 先对不完整的块进行签名,保证block的正常发送
block.header.updateContent(block.content);
let err = block.header.signBlock(this.m_secret);
block.header.updateHash();
if (err) {
return error_code_1.ErrorCode.RESULT_INVALID_BLOCK;
}
this._sendPrepareRequest(block);
this.m_state = ConsensusState.waitingVerify;
let curContext = {
curView: this.m_currView,
block,
preSigns: new Map()
};
this.m_context = curContext;
return error_code_1.ErrorCode.RESULT_OK;
}
async _resetTimer() {
let tr = await this._nextTimeout();
if (tr.err === error_code_1.ErrorCode.RESULT_SKIPPED) {
return tr.err;
}
if (this.m_timer) {
clearTimeout(this.m_timer);
delete this.m_timer;
}
this.m_timer = setTimeout(async () => {
delete this.m_timer;
this._onTimeout();
this._resetTimer();
}, tr.timeout);
return error_code_1.ErrorCode.RESULT_OK;
}
_isOneOfMiner() {
return this.m_tip.nextMiners.indexOf(this.m_address) >= 0;
}
_onTimeout() {
assert(this.m_tip);
if (!this.m_tip) {
this.logger.warn(`dbft consensus has no tip when time out`);
return;
}
if (this.m_state === ConsensusState.waitingCreate) {
this.m_state = ConsensusState.waitingProposal;
let newContext = {
curView: this.m_currView,
preSigns: new Map()
};
this.m_context = newContext;
let now = Date.now() / 1000;
let blockHeader = new block_1.DbftBlockHeader();
blockHeader.setPreBlock(this.m_tip.header);
blockHeader.timestamp = now;
blockHeader.view = this.m_currView;
this.emit('createBlock', blockHeader);
}
else {
// 超时,发起changeview
let newView = 0;
if (this.m_state === ConsensusState.changeViewSent) {
newView = this.m_context.expectView + 1;
}
else {
newView = this.m_currView + 1;
}
this.logger.debug(`${this.m_address} _onTimeout changeview ${newView}`);
const sign = libAddress.sign(Buffer.from(digest.md5(Buffer.from(this.m_tip.header.hash + newView.toString(), 'hex')).toString('hex')), this.m_secret);
this._sendChangeView(newView, sign);
this.m_state = ConsensusState.changeViewSent;
let newContext = {
curView: this.m_currView,
expectView: newView
};
this.m_context = newContext;
this._onChangeView(newView, this.m_pubkey);
}
}
async _sendPrepareRequest(block) {
let writer = new writer_1.BufferWriter();
let err = block.encode(writer);
let data = writer.render();
let pkg = chain_1.PackageStreamWriter.fromPackage(DBFT_SYNC_CMD_TYPE.prepareRequest, null, data.length).writeData(data);
this.m_network.broadcastToValidators(pkg);
}
_sendPrepareResponse(block, sign) {
let writer = new writer_1.BufferWriter();
writer.writeBytes(this.m_pubkey);
writer.writeBytes(sign);
let data = writer.render();
let pkg = chain_1.PackageStreamWriter.fromPackage(DBFT_SYNC_CMD_TYPE.prepareResponse, { hash: block.hash }, data.length).writeData(data);
this.m_network.broadcastToValidators(pkg);
}
_sendChangeView(newView, sign) {
let writer = new writer_1.BufferWriter();
writer.writeBytes(this.m_pubkey);
writer.writeBytes(sign);
let data = writer.render();
let pkg = chain_1.PackageStreamWriter.fromPackage(DBFT_SYNC_CMD_TYPE.changeview, { newView }, data.length).writeData(data);
this.m_network.broadcastToValidators(pkg);
}
_beginSyncWithNode(conn) {
conn.on('pkg', async (pkg) => {
if (pkg.header.cmdType === DBFT_SYNC_CMD_TYPE.prepareRequest) {
let block = this.base.newBlock();
let reader = new reader_1.BufferReader(pkg.copyData());
let err = block.decode(reader);
if (err) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`recv invalid prepareRequest from `, conn.fullRemote);
return;
}
if (!block.header.verifySign()) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`recv invalid signature prepareRequest from `, conn.fullRemote);
return;
}
if (!block.verify()) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`recv invalid block in prepareRequest from `, conn.fullRemote);
return;
}
block.header.updateHash();
this._onPrepareRequest({ block }, conn);
}
else if (pkg.header.cmdType === DBFT_SYNC_CMD_TYPE.prepareResponse) {
const hash = pkg.body.hash;
let reader = new reader_1.BufferReader(pkg.copyData());
let pubkey;
let sign;
try {
pubkey = reader.readBytes(33);
sign = reader.readBytes(64);
}
catch (e) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`decode prepareResponse failed `, e);
return;
}
if (!libAddress.verify(hash, sign, pubkey)) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`prepareResponse verify sign invalid hash=${hash},pubkey=${pubkey.toString('hex')},sign=${sign.toString('hex')}`);
return;
}
if (libAddress.addressFromPublicKey(pubkey) === this.m_address) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`prepareResponse got my sign`);
return;
}
this._onPrepareResponse({ hash, pubkey, sign }, conn);
}
else if (pkg.header.cmdType === DBFT_SYNC_CMD_TYPE.changeview) {
if (!this.m_tip) {
return;
}
const newView = pkg.body.newView;
let reader = new reader_1.BufferReader(pkg.copyData());
let pubkey;
let sign;
try {
pubkey = reader.readBytes(33);
sign = reader.readBytes(64);
}
catch (e) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`decode changeView failed `, e);
return;
}
let viewBuf = Buffer.from(digest.md5(Buffer.from(this.m_tip.header.hash + newView.toString(), 'hex')).toString('hex'));
if (!libAddress.verify(viewBuf, sign, pubkey)) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`changeView verify sign invalid`);
return;
}
this._onChangeView(newView, pubkey, conn);
// this.emit('changeview', pkg.body);
}
});
}
_onChangeView(newView, pubkey, from) {
let id = libAddress.addressFromPublicKey(pubkey);
this.logger.info(`_onChangeView receive correct changview from ${id} newView=${newView}`);
if (this.m_changeView.has(id)) {
if (this.m_changeView.get(id) === newView) {
// 多次发送同一个view的ChangeView消息,ban it ?
return;
}
}
this.m_changeView.set(id, newView);
let viewCount = new Map();
for (let [_key, view] of this.m_changeView) {
viewCount.has(view) ? viewCount.set(view, viewCount.get(view) + 1) : viewCount.set(view, 1);
if (context_1.DbftContext.isAgreeRateReached(this.m_globalOptions, this.m_tip.nextMiners.length, viewCount.get(view))) {
this.m_changeView = new Map();
this.m_currView = view;
let newContext = {
curView: view
};
this.m_context = newContext;
this.m_state = ConsensusState.changeViewSucc;
this.logger.info(`_onChangeView enter ConsensusState.changeViewSucc view=${view}`);
this._resetTimer();
break;
}
}
}
_onPrepareRequest(pkg, from) {
if (!this.m_tip) {
this.logger.warn(`_onPrepareRequest while no tip`);
return;
}
if (this.m_state === ConsensusState.waitingProposal) {
assert(this.m_context);
let curContext = this.m_context;
if (!this.m_tip.header.isPreBlock(pkg.block.header)) {
this.logger.error(`_onPrepareRequest got block ${pkg.block.header.hash} ${pkg.block.header.number} while tip is ${this.m_tip.header.hash} ${this.m_tip.header.number}`);
return;
}
let header = pkg.block.header;
if (curContext.curView !== header.view) {
// 有可能漏了change view,两边view 不一致
this.logger.error(`_onPrepareRequest got block ${header.hash} ${header.number} ${header.view} while cur view is ${curContext.curView}`);
return;
}
let due = context_1.DbftContext.getDueNextMiner(this.m_globalOptions, this.m_tip.header, this.m_tip.nextMiners, curContext.curView);
if (header.miner !== due) {
// TODO: ban it
// this.base.banConnection();
this.logger.error(`_onPrepareRequest recv prepareRequest's block ${pkg.block.header.hash} number=${pkg.block.header.number} miner=${header.miner},pubkey=${header.pubkey.toString('hex')} not match due miner ${due}`);
return;
}
this.m_state = ConsensusState.waitingVerify;
let newContext = {
curView: curContext.curView,
block: pkg.block,
preSigns: curContext.preSigns
};
this.m_context = newContext;
this.logger.info(`_onPrepareRequest, dbft consensus enter waitingVerify ${header.hash} ${header.number}`);
this.emit('verifyBlock', pkg.block);
}
else {
// 其他状态都忽略
this.logger.warn(`_onPrepareRequest in invalid state `, this.m_state);
}
}
_onPrepareResponse(pkg, from) {
if (!this.m_tip) {
this.logger.warn(`_onPrepareResponse while no tip`);
return;
}
if (this.m_state !== ConsensusState.waitingAgree
&& this.m_state !== ConsensusState.waitingProposal
&& this.m_state !== ConsensusState.waitingVerify) {
this.logger.info(`_onPrepareResponse in invalid state `, this.m_state);
return;
}
assert(this.m_context);
const address = libAddress.addressFromPublicKey(pkg.pubkey);
if (this.m_tip.nextMiners.indexOf(address) < 0) {
this.logger.warn(`_onPrepareResponse got ${address} 's sign not in next miners`);
return;
}
if (this.m_state !== ConsensusState.waitingAgree) {
let curContext = this.m_context;
if (curContext.preSigns.has(address)) {
this.logger.warn(`_onPrepareResponse {not ConsensusState.waitingProposal} got ${address} 's duplicated sign`);
return;
}
curContext.preSigns.set(address, { hash: pkg.hash, signInfo: { pubkey: pkg.pubkey, sign: pkg.sign } });
this.logger.info(`_onPrepareResponse {not ConsensusState.waitingProposal} receive correct signed prepare response from ${address} hash=${pkg.hash}`);
}
else {
let curContext = this.m_context;
if (curContext.block.hash !== pkg.hash) {
this.logger.warn(`_onPrepareResponse got ${pkg.hash} while waiting ${curContext.block.hash}`);
return;
}
if (curContext.signs.has(address)) {
this.logger.warn(`_onPrepareResponse got ${address} 's duplicated sign`);
return;
}
this.logger.info(`_onPrepareResponse receive correct signed prepare response from ${address} hash=${pkg.hash}`);
curContext.signs.set(address, { pubkey: pkg.pubkey, sign: pkg.sign });
if (context_1.DbftContext.isAgreeRateReached(this.m_globalOptions, this.m_tip.nextMiners.length, curContext.signs.size)) {
this.logger.info(`_onPrepareResponse dbft consensus node enter state waitingBlock miners=${this.m_tip.nextMiners.length}, ${curContext.block.hash} ${curContext.block.number}`);
this.m_state = ConsensusState.waitingBlock;
let signs = [];
for (let s of curContext.signs.values()) {
signs.push(s);
}
this.emit('mineBlock', curContext.block, signs);
}
}
}
async _nextTimeout() {
if (!this.m_tip) {
return { err: error_code_1.ErrorCode.RESULT_SKIPPED };
}
if (!this._isOneOfMiner()) {
return { err: error_code_1.ErrorCode.RESULT_SKIPPED };
}
// view=0 非miner timeout=base+ 2^1;miner timeout=base+2^0
// view=1 非miner timeout=base+ 2^1+2^2;miner timeout=base+2^0+2^1
// view=2 非miner timeout=base+ 2^1+2^2+2^3;miner timeout=base+2^0+2^1+2^2
// view=n 非miner timeout=base+ 2^1+2^2+2^3+...+2^(n+1)次方;miner timeout=base+2^0+2^1+2^2+...+2^n
// 非miner: 2^1+2^2+...+2^n = 2^(n+2)-2^1 miner: 2^0+2^1+2^2+...+2^n = 2^(n+1)-2^0
while (true) {
let due = context_1.DbftContext.getDueNextMiner(this.m_globalOptions, this.m_tip.header, this.m_tip.nextMiners, this.m_currView);
if (this.m_state === ConsensusState.none || this.m_state === ConsensusState.changeViewSucc) {
if (this.m_address === due) {
this.m_state = ConsensusState.waitingCreate;
let newContext = {
curView: this.m_currView
};
this.m_context = newContext;
this.logger.debug(`dbft consensus enter waitingCreate ,due=${due},tipnumber=${this.m_tip.header.number}`);
}
else {
this.m_state = ConsensusState.waitingProposal;
let newContext = {
curView: this.m_currView,
preSigns: new Map()
};
this.m_context = newContext;
this.logger.debug(`dbft consensus enter waitingProposal ,due=${due},tipnumber=${this.m_tip.header.number}`);
}
}
let blockInterval = this.m_globalOptions.blockInterval;
let intervalCount = this.m_tip.totalView;
let contextView = 0;
if (this.m_context) {
contextView = this.m_context.curView;
}
if (due === this.m_address) {
if (this.m_state === ConsensusState.waitingCreate) {
intervalCount += Math.pow(2, contextView + 1) - 1;
}
else {
// miner此时和非miner在同一个时刻触发timeout
intervalCount += Math.pow(2, contextView + 2) - 2;
}
}
else {
intervalCount += Math.pow(2, contextView + 2) - 2;
}
let nextTime = this.m_genesisTime + intervalCount * blockInterval;
let now = Date.now() / 1000;
if (nextTime > now) {
this.logger.info(`_nextTimeout intervalCount=${intervalCount},totalView=${this.m_tip.totalView},contextView=${contextView},due=${due},tipnumber=${this.m_tip.header.number},timeout=${(nextTime - now) * 1000}`);
return { err: error_code_1.ErrorCode.RESULT_OK, timeout: (nextTime - now) * 1000 };
}
else {
// this.logger.debug(`_nextTimeout RESULT_SKIPPED intervalCount=${intervalCount},totalView=${this.m_tip.totalView},contextView=${contextView},due=${due},tipnumber=${this.m_tip.header.number},nextTime=${nextTime}, now=${now}`);
// return {err: ErrorCode.RESULT_SKIPPED};
this.logger.debug(`_nextTimeout RESULT_SKIPPED`);
this.m_currView++;
this.m_state = ConsensusState.none;
}
}
}
}
exports.DbftConsensusNode = DbftConsensusNode;