@nanocat/friday-serialized
Version:
Tx, Block and other data defenition for friday framework
344 lines (298 loc) • 10.6 kB
JavaScript
const bitowl = require('bitowl');
module.exports = (app) => {
class block extends app.PRIMITIVE {
constructor(data) {
super();
this.type = 'build';
this.version = app.config.blockversion;
this.prev = '';
this.merkle = '';
this.time = 0;
this.bits = 0;
this.nonce = 0;
this.tx = [];
this.hex = '';
this.validation_errors = [];
this.hash = null;
if (data) {
if (!(data instanceof Buffer || typeof data == 'string'))
throw new Error('Block:Constructor, data must be string (hex) or buffer instance');
this.hex = new Buffer(data, 'hex');
this.type = 'raw';
this.init();
}
}
init() {
if (this.type == 'build') {
//create hex
this.hex = this.toBuffer();
}
if (this.type == 'raw') {
//decode hex
this.fromJSON(bitowl.data.unpack(this.hex))
}
if (this.type == 'json') {
//create hex
this.hex = this.toBuffer();
}
this.hash = this.getHash('hex');
if (this.version > app.config.blockversion) {
this.emit("unsupportedversion", app.config.blockversion, this.version);
}//else is okay
}
addTxFromHEX(hex) {
this.tx.push(new app.TX.fromHEX(hex));
this.updateMerkle();
return this;
}
addTxFromJSON(json) {
this.tx.push(new app.TX.fromJSON(json));
this.updateMerkle();
return this;
}
addTx(tx) {
if (!(tx instanceof app.TX || tx instanceof app.PRIMITIVE))
this.throwError('Object is not TX', 'not_tx_obj');
this.tx.push(tx);
this.updateMerkle();
return this;
}
addTxList(hexArr) {
for (let i in hexArr) {
if (hexArr[i] instanceof app.TX || tx instanceof app.PRIMITIVE)
this.tx.push(hexArr[i])
else if ((typeof hexArr[i] === 'string' || hexArr[i] instanceof Buffer))
this.tx.push(app.TX.fromHEX(hexArr[i]));
else if ((hexArr[i] instanceof Object))
this.tx.push(app.TX.fromJSON(hexArr[i]));
}
this.updateMerkle();
return this;
}
updateMerkle() {
let ids = [];
for (let i in this.tx) {
if (this.tx[i])
ids.push(this.tx[i].getId())
}
return this.merkle = this.getMerkleRoot(ids);
}
getHash(format) {
//new block
if (!this.hash || format == 'raw') {
if (!this.merkle)
this.updateMerkle();
let h = this.createHash(this.getHeaderBytes());
if (format == 'hex' || !format)
return this.hash = h.toString('hex');
else
return h;
} else {
return this.hash;
}
}
getFee() {
let a = 0;
for (let i in this.tx) {
a += this.tx[i].getFee();
}
return a;
}
getSize() {
let a = 0;
for (let i in this.tx) {
a += this.tx[i].getSize();
}
let txsize = Math.ceil(this.tx.length.toString(16).length) / 2;//can be %2 != 0, first digit!
a += this.getHeaderBytes().length + 5 + txsize;//bitowl notation - tx: [] 0102TTXX05(SIZEOFLENGTH) type|key(var_str)|value, value is array: value.length|value[0],...,value[n]
return a;
}
getHeader() {
return {
v: this.version,
p: this.prev,
m: this.merkle ? this.merkle : this.updateMerkle(),
t: this.time,
b: this.bits,
n: this.nonce
}
}
getHeaderBytes() {
return bitowl.data.pack(this.getHeader());
}
getHeaderHex() {
return this.getHeaderBytes().toString('hex');
}
toHex() {
return this.toBuffer().toString('hex');
}
toBuffer() {
return bitowl.data.pack(this.toJSON());
}
fromHex(hex) {
this.type = 'raw';
this.hex = new Buffer(hex, 'hex');
this.init();
return this;
}
toJSON(rules) {
if (!rules)
rules = "";
let o = {
v: this.version,
p: this.prev,
m: this.merkle ? this.merkle : this.updateMerkle(),
t: this.time,
b: this.bits,
n: this.nonce,
tx: [
]
}
if (rules.split(",").indexOf('hash') != -1) {
o.hash = this.getId();
}
for (let i in this.tx) {
if (this.tx[i])
o.tx.push(this.tx[i].toJSON(rules));
}
return o;
}
fromJSON(json, additionalInfoRulesArray) {
if (!additionalInfoRulesArray)
additionalInfoRulesArray = [];
this.type = 'json';
this.version = json.v;
this.prev = json.p;
this.merkle = json.m;
this.time = json.t;
this.bits = json.b;
this.nonce = json.n;
for (let i in additionalInfoRulesArray){
this[additionalInfoRulesArray[i]] = json[additionalInfoRulesArray[i]];
}
for (let i in json.tx) {
let t = app.TX.fromJSON(json.tx[i], additionalInfoRulesArray)
this.tx.push(t);
}
this.init();
return this;
}
isValid(context) {
this.emit("beforevalidation", context);
let validator = new app.BLOCK.VALIDATOR(this, context);
let res = validator.isValid();
if (!res)
this.validation_errors = validator.getErrors();
this.emit("aftervalidation", res, validator.getLog(), validator.getErrors());
return res;
}
getLastErrorCodes() {
return this.validation_errors;
}
send() {
throw new Error('TX::send must be implemented');
}
getId() {
return this.hash;
}
getVersion() {
return this.version;
};
getBits() {
return this.bits;
}
getPrevId() {
return this.prev;
}
getTime() {
return this.time;
}
getNonce() {
return this.nonce;
}
}
block.fromJSON = (data) => {
if (data instanceof app.BLOCK)
return data;
return (new app.BLOCK()).fromJSON(data);
}
block.fromHEX = (hex) => {
return (new app.BLOCK()).fromHex(hex);
}
block.validate = (block, context) => {
return block.isValid(context);
}
block.generateNewBlockTemplate = (timestamp, coinbaseBytes, keystore, currentValidators) => {
let mempool = new app.BLOCK().getMemPool();
let fee = 0;
let txlist = [];
for (let i in mempool) {
fee += mempool[i].fee;
txlist.push(mempool[i]);
}
let latest = new app.BLOCK().getTop();
if (app.config.genesisMode)
latest = { height: -1, id: '0000000000000000000000000000000000000000000000000000000000000000' };
let coinbase = app.TX.createCoinbase(fee, coinbaseBytes, keystore.private, currentValidators, latest.height);
txlist.unshift(coinbase.toJSON());
return {
v: app.config.blockversion,
p: latest.id,
h: latest.height + 1,
t: timestamp,
n: currentValidators.indexOf(keystore.public),
b: currentValidators.length,
tx: txlist
};
}
block.createNewBlock = (coinbaseBytes, keystore, currentValidators) => {
if (!keystore)
throw new Error('can not create new block without keystore');
if (!currentValidators)
throw new Error('can not create new block without current validators list');
return new app.BLOCK().fromJSON(app.BLOCK.generateNewBlockTemplate(new app.BLOCK().getCurrentTime(), coinbaseBytes, keystore, currentValidators));
}
class validator {
constructor(block, context) {
this.block = block;
this.context = context;
this.errors = [];
this.log = [];
}
addError(msg, code) {
this.errors.push({ message: msg, code: code });
return false;
}
getErrors() {
return this.errors;
}
getLog() {
return this.log;
}
isValid() {
let res = 0, err = [];
for (let i in validator.rules) {
try {
let r = validator.rules[i].apply(this.block, [this, this.context || {}]);
this.log.push({ 'action': i, 'status': r });
if (r)
res += 1;
} catch (e) {
this.errors.push({ code: e.code, message: e.message, exception: true });
}
}
if (app.config.validationalert) {
for (let k in this.errors) {
this.block.throwError(this.errors[k].message, this.errors[k].code);
}
}
return res == Object.keys(validator.rules).length;
}
static addRule(name, fnc) {
validator.rules[name] = fnc;
}
}
validator.rules = {};
block.VALIDATOR = validator;
return block
}