@cityofzion/neon-api
Version:
Neon-API module: High level API for neon-js
167 lines • 6.34 kB
JavaScript
import { tx, u } from "@cityofzion/neon-core";
export var ValidationAttributes;
(function (ValidationAttributes) {
ValidationAttributes[ValidationAttributes["None"] = 0] = "None";
ValidationAttributes[ValidationAttributes["ValidUntilBlock"] = 1] = "ValidUntilBlock";
ValidationAttributes[ValidationAttributes["SystemFee"] = 2] = "SystemFee";
ValidationAttributes[ValidationAttributes["NetworkFee"] = 4] = "NetworkFee";
ValidationAttributes[ValidationAttributes["Script"] = 8] = "Script";
ValidationAttributes[ValidationAttributes["All"] = 15] = "All";
})(ValidationAttributes || (ValidationAttributes = {}));
/**
* A class with functions to validate transaction
*/
export class TransactionValidator {
constructor(rpc, transaction) {
this.rpcClient = rpc;
this.transaction = transaction;
}
/**
* validate validUntilBlock.
* @param autoFix - autofix when number is below current height.
*/
async validateValidUntilBlock(autoFix = false) {
const { validUntilBlock: prev } = this.transaction;
const height = await this.rpcClient.getBlockCount();
// Suggest a lifespan of approx. 1hr based on 15s blocks
const suggestion = TransactionValidator.TX_LIFESPAN_SUGGESTION + height - 1;
if (prev <= height ||
prev >= height + tx.Transaction.MAX_TRANSACTION_LIFESPAN) {
if (autoFix) {
this.transaction.validUntilBlock = suggestion;
return fixed(prev, suggestion);
}
return invalid(prev, suggestion, "Your transaction lifespan was out of range.");
}
if (prev - height <= 20) {
return suggest(prev, suggestion, "Your transaction has a very limited lifespan. Consider increasing it.");
}
return valid();
}
/**
* Validate intents
*/
async validateScript() {
const { state } = await this.rpcClient.invokeScript(this.transaction.script, this.transaction.signers);
if (state !== "HALT") {
return err("Encountered FAULT when validating script.");
}
return valid();
}
/**
* validate systemFee
* @param autoFix - autofix when fee is too low.
*/
async validateSystemFee(autoFix = false) {
const { script, signers, systemFee: prev } = this.transaction;
const invokeResponse = await this.rpcClient.invokeScript(script, signers);
if (invokeResponse.state === "FAULT") {
return err("Cannot get precise systemFee as script execution on node reports FAULT.");
}
const gasConsumed = invokeResponse.gasconsumed;
const suggestion = u.BigInteger.fromDecimal(gasConsumed, 0);
const compareResult = suggestion.compare(prev);
if (compareResult > 0) {
// Did not hit the minimum fees to run the script.
if (autoFix) {
this.transaction.systemFee = suggestion;
return fixed(prev, suggestion);
}
return invalid(prev, suggestion, "Insufficient fees attached to run the script.");
}
else if (compareResult < 0) {
// Overpaying for the script.
return suggest(prev, suggestion, "Overpaying for running the script.");
}
return valid();
}
/**
* Validate NetworkFee
* @param autoFix - autofix when fee is too low.
*/
async validateNetworkFee(autoFix = false) {
const { networkFee: prev } = this.transaction;
const calculateResponse = await this.rpcClient.calculateNetworkFee(this.transaction);
const suggestion = u.BigInteger.fromNumber(calculateResponse);
const compareResult = suggestion.compare(prev);
if (compareResult > 0) {
// Underpaying
if (autoFix) {
this.transaction.networkFee = suggestion;
return fixed(prev, suggestion);
}
return invalid(prev, suggestion, "Insufficient network fees.");
}
else if (compareResult < 0) {
// Overpaying
return suggest(prev, suggestion, "Overpaying network fee.");
}
return valid();
}
async validate(attrs, autoFix = ValidationAttributes.None) {
const validationTasks = [];
const output = {
valid: true,
result: {},
};
if (attrs & ValidationAttributes.ValidUntilBlock) {
validationTasks.push(this.validateValidUntilBlock((autoFix & ValidationAttributes.ValidUntilBlock) ===
ValidationAttributes.ValidUntilBlock).then((s) => (output.result.validUntilBlock = s)));
}
if (attrs & ValidationAttributes.SystemFee) {
validationTasks.push(this.validateSystemFee((autoFix & ValidationAttributes.SystemFee) ===
ValidationAttributes.SystemFee).then((s) => (output.result.systemFee = s)));
}
if (attrs & ValidationAttributes.NetworkFee) {
validationTasks.push(this.validateNetworkFee((autoFix & ValidationAttributes.NetworkFee) ===
ValidationAttributes.NetworkFee).then((s) => (output.result.networkFee = s)));
}
if (attrs & ValidationAttributes.Script) {
validationTasks.push(this.validateScript().then((s) => (output.result.script = s)));
}
await Promise.all(validationTasks);
output.valid = Object.values(output.result)
.map((r) => (r ? r.valid : true))
.reduce((a, b) => a && b);
return output;
}
}
TransactionValidator.TX_LIFESPAN_SUGGESTION = 240;
function valid() {
return { valid: true, fixed: false };
}
function fixed(prev, suggestion, message) {
return {
valid: true,
fixed: true,
prev,
suggestion,
message,
};
}
function err(message) {
return {
valid: false,
fixed: false,
message,
};
}
function suggest(prev, suggestion, message) {
return {
valid: true,
fixed: false,
prev,
suggestion,
message,
};
}
function invalid(prev, suggestion, message) {
return {
valid: false,
fixed: false,
prev,
suggestion,
message,
};
}
//# sourceMappingURL=validator.js.map