bch-slpjs
Version:
Simple Ledger Protocol (SLP) JavaScript Library
207 lines • 12.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const slp_1 = require("./slp");
const slpjs_1 = require("./slpjs");
const bitcore = require("bitcore-lib-cash");
const bignumber_js_1 = require("bignumber.js");
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
class LocalValidator {
constructor(BITBOX, getRawTransactions) {
this.BITBOX = BITBOX;
this.getRawTransactions = getRawTransactions;
this.slp = new slp_1.Slp(BITBOX);
this.cachedValidations = {};
this.cachedRawTransactions = {};
}
addValidationFromStore(hex, isValid) {
let id = this.BITBOX.Crypto.sha256(this.BITBOX.Crypto.sha256(Buffer.from(hex, 'hex'))).reverse().toString('hex');
if (!this.cachedValidations[id])
this.cachedValidations[id] = { hex: hex, validity: isValid, parents: [], details: null, invalidReason: null };
if (!this.cachedRawTransactions[id])
this.cachedRawTransactions[id] = hex;
}
waitForCurrentValidationProcessing(txid) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: Add some timeout?
let cached = this.cachedValidations[txid];
while (true) {
if (typeof cached.validity === 'boolean')
break;
yield sleep(10);
}
});
}
waitForTransactionPreProcessing(txid) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: Add some timeout?
while (true) {
if (this.cachedValidations[txid].hex && (this.cachedValidations[txid].details || typeof this.cachedValidations.validity === 'boolean'))
break;
yield sleep(10);
}
//await sleep(100); // short wait to make sure parent's properties gets set first.
return;
});
}
getRawTransaction(txid) {
return __awaiter(this, void 0, void 0, function* () {
if (this.cachedRawTransactions[txid])
return this.cachedRawTransactions[txid];
this.cachedRawTransactions[txid] = (yield this.getRawTransactions([txid]))[0];
if (this.cachedRawTransactions[txid])
return this.cachedRawTransactions[txid];
return null;
});
}
isValidSlpTxid(txid) {
return __awaiter(this, void 0, void 0, function* () {
if (txid && !this.cachedValidations[txid]) {
this.cachedValidations[txid] = { hex: null, validity: null, parents: [], details: null, invalidReason: null };
this.cachedValidations[txid].hex = yield this.getRawTransaction(txid);
}
// Check to see how we should proceed based on the validation-cache state
if (!this.cachedValidations[txid].hex)
yield this.waitForTransactionPreProcessing(txid);
if (typeof this.cachedValidations[txid].validity === 'boolean')
return this.cachedValidations[txid].validity;
if (this.cachedValidations[txid].details)
yield this.waitForCurrentValidationProcessing(txid);
// Check SLP message validity
let txn = new bitcore.Transaction(this.cachedValidations[txid].hex);
let slpmsg;
try {
slpmsg = this.cachedValidations[txid].details = this.slp.parseSlpOutputScript(txn.outputs[0]._scriptBuffer);
}
catch (e) {
this.cachedValidations[txid].invalidReason = "SLP OP_RETURN parsing error (" + e.message + ").";
return this.cachedValidations[txid].validity = false;
}
// Check DAG validity
if (slpmsg.transactionType === slpjs_1.SlpTransactionType.GENESIS) {
return this.cachedValidations[txid].validity = true;
}
else if (slpmsg.transactionType === slpjs_1.SlpTransactionType.MINT) {
for (let i = 0; i < txn.inputs.length; i++) {
let input_txid = txn.inputs[i].prevTxId.toString('hex');
let input_txhex = yield this.getRawTransaction(input_txid);
if (input_txhex) {
let input_tx = new bitcore.Transaction(input_txhex);
try {
let input_slpmsg = this.slp.parseSlpOutputScript(input_tx.outputs[0]._scriptBuffer);
if (input_slpmsg.transactionType === slpjs_1.SlpTransactionType.GENESIS)
input_slpmsg.tokenIdHex = input_txid;
if (input_slpmsg.tokenIdHex === slpmsg.tokenIdHex) {
if (input_slpmsg.transactionType === slpjs_1.SlpTransactionType.GENESIS || input_slpmsg.transactionType === slpjs_1.SlpTransactionType.MINT) {
if (txn.inputs[i].outputIndex === input_slpmsg.batonVout)
this.cachedValidations[txid].parents.push({ txid: txn.inputs[i].prevTxId.toString('hex'), versionType: input_slpmsg.versionType, valid: null, inputQty: null });
}
}
}
catch (_) { }
}
}
if (this.cachedValidations[txid].parents.length !== 1) {
this.cachedValidations[txid].invalidReason = "MINT transaction must have 1 valid baton parent.";
return this.cachedValidations[txid].validity = false;
}
}
else if (slpmsg.transactionType === slpjs_1.SlpTransactionType.SEND) {
let tokenOutQty = slpmsg.sendOutputs.reduce((t, v) => { return t.plus(v); }, new bignumber_js_1.default(0));
let tokenInQty = new bignumber_js_1.default(0);
for (let i = 0; i < txn.inputs.length; i++) {
let input_txid = txn.inputs[i].prevTxId.toString('hex');
let input_txhex = yield this.getRawTransaction(input_txid);
if (input_txhex) {
let input_tx = new bitcore.Transaction(input_txhex);
try {
let input_slpmsg = this.slp.parseSlpOutputScript(input_tx.outputs[0]._scriptBuffer);
if (input_slpmsg.transactionType === slpjs_1.SlpTransactionType.GENESIS)
input_slpmsg.tokenIdHex = input_txid;
if (input_slpmsg.tokenIdHex === slpmsg.tokenIdHex) {
if (input_slpmsg.transactionType === slpjs_1.SlpTransactionType.SEND) {
tokenInQty = tokenInQty.plus(input_slpmsg.sendOutputs[txn.inputs[i].outputIndex]);
this.cachedValidations[txid].parents.push({ txid: txn.inputs[i].prevTxId.toString('hex'), versionType: input_slpmsg.versionType, valid: null, inputQty: input_slpmsg.sendOutputs[txn.inputs[i].outputIndex] });
}
else if (input_slpmsg.transactionType === slpjs_1.SlpTransactionType.GENESIS || input_slpmsg.transactionType === slpjs_1.SlpTransactionType.MINT) {
if (txn.inputs[i].outputIndex === 1)
tokenInQty = tokenInQty.plus(input_slpmsg.genesisOrMintQuantity);
this.cachedValidations[txid].parents.push({ txid: txn.inputs[i].prevTxId.toString('hex'), versionType: input_slpmsg.versionType, valid: null, inputQty: input_slpmsg.genesisOrMintQuantity });
}
}
}
catch (_) { }
}
}
// Check token inputs are greater than token outputs (includes valid and invalid inputs)
if (tokenOutQty.isGreaterThan(tokenInQty)) {
this.cachedValidations[txid].invalidReason = "Token outputs are greater than possible token inputs.";
return this.cachedValidations[txid].validity = false;
}
}
// Set validity validation-cache for parents, and handle MINT condition with no valid input
for (let i = 0; i < this.cachedValidations[txid].parents.length; i++) {
let valid = yield this.isValidSlpTxid(this.cachedValidations[txid].parents[i].txid);
this.cachedValidations[txid].parents.find(p => p.txid === this.cachedValidations[txid].parents[i].txid).valid = valid;
if (this.cachedValidations[txid].details.transactionType === slpjs_1.SlpTransactionType.MINT && !valid) {
this.cachedValidations[txid].invalidReason = "MINT transaction with invalid baton parent.";
return this.cachedValidations[txid].validity = false;
}
}
// Check valid inputs are greater than token outputs
if (this.cachedValidations[txid].details.transactionType === slpjs_1.SlpTransactionType.SEND) {
let validInputQty = this.cachedValidations[txid].parents.reduce((t, v) => { return v.valid ? t.plus(v.inputQty) : t; }, new bignumber_js_1.default(0));
let tokenOutQty = slpmsg.sendOutputs.reduce((t, v) => { return t.plus(v); }, new bignumber_js_1.default(0));
if (tokenOutQty.isGreaterThan(validInputQty)) {
this.cachedValidations[txid].invalidReason = "Token outputs are greater than valid token inputs.";
return this.cachedValidations[txid].validity = false;
}
}
// Check versionType is not different from any valid parent
if (this.cachedValidations[txid].parents.filter(p => p.valid).length > 0) {
let validVersionType = this.cachedValidations[txid].parents.find(p => p.valid).versionType;
if (this.cachedValidations[txid].details.versionType !== validVersionType) {
this.cachedValidations[txid].invalidReason = "SLP version/type mismatch from valid parent.";
return this.cachedValidations[txid].validity = false;
}
}
// For case with 0 token SEND with no valid parents, must check GENESIS validity / versionType.
else if (this.cachedValidations[txid].details.transactionType === slpjs_1.SlpTransactionType.SEND) {
let slpmsg = this.cachedValidations[txid].details;
let valid = yield this.isValidSlpTxid(slpmsg.tokenIdHex);
if (valid) {
let genesisTxn = new bitcore.Transaction(this.cachedValidations[slpmsg.tokenIdHex].hex);
let genesisMsg = this.slp.parseSlpOutputScript(genesisTxn.outputs[0]._scriptBuffer);
if (genesisMsg.versionType !== slpmsg.versionType) {
this.cachedValidations[txid].invalidReason = "SLP version/type mismatch from valid GENESIS.";
return this.cachedValidations[txid].validity = false;
}
}
else {
this.cachedValidations[txid].invalidReason = "SEND has 0 outputs, but has invalid token GENESIS.";
console.log(this.cachedValidations[slpmsg.tokenIdHex].invalidReason);
return this.cachedValidations[txid].validity = false;
}
}
return this.cachedValidations[txid].validity = true;
});
}
validateSlpTransactions(txids) {
return __awaiter(this, void 0, void 0, function* () {
let res = [];
for (let i = 0; i < txids.length; i++) {
res.push((yield this.isValidSlpTxid) ? txids[i] : '');
}
return res.filter((id) => id.length > 0);
});
}
}
exports.LocalValidator = LocalValidator;
//# sourceMappingURL=localvalidator.js.map