mirror of
https://github.com/transatoshi-mw/grin-web-wallet.git
synced 2025-10-06 15:52:47 +00:00
3157 lines
82 KiB
JavaScript
Executable File
3157 lines
82 KiB
JavaScript
Executable File
// Use strict
|
|
"use strict";
|
|
|
|
|
|
// Classes
|
|
|
|
// Node class
|
|
class Node {
|
|
|
|
// Public
|
|
|
|
// Constructor
|
|
constructor(torProxy, settings) {
|
|
|
|
// Set Tor proxy
|
|
this.torProxy = torProxy;
|
|
|
|
// Set settings
|
|
this.settings = settings;
|
|
|
|
// Set retry delay to initial value
|
|
this.retryDelay = Node.INITIAL_RETRY_DELAY_MILLISECONDS;
|
|
|
|
// Set reconnect timeout to no timeout
|
|
this.reconnectTimeout = Node.NO_TIMEOUT;
|
|
|
|
// Set started to false
|
|
this.started = false;
|
|
|
|
// Set connected to fase
|
|
this.connected = false;
|
|
|
|
// Set failed to connect
|
|
this.failedToConnect = false;
|
|
|
|
// Set last height response timestamp
|
|
this.lastHeightResponseTimestamp = Node.NO_HEIGHT_RESPONSE_TIMESTAMP;
|
|
|
|
// Set update height timeout to no timeout
|
|
this.updateHeightTimeout = Node.NO_TIMEOUT;
|
|
|
|
// Set current height to a height with an unknown height
|
|
this.currentHeight = new Height(Node.UNKNOWN_HEIGHT);
|
|
|
|
// Set version obtained to false
|
|
this.versionObtained = false;
|
|
|
|
// Set use custom node to setting's default value
|
|
this.useCustomNode = Node.SETTINGS_USE_CUSTOM_NODE_DEFAULT_VALUE;
|
|
|
|
// Set custom node address to setting's default value
|
|
this.customNodeAddress = Node.SETTINGS_CUSTOM_NODE_ADDRESS_DEFAULT_VALUE;
|
|
|
|
// Set custom node secret to setting's default value
|
|
this.customNodeSecret = Node.SETTINGS_CUSTOM_NODE_SECRET_DEFAULT_VALUE;
|
|
|
|
// Set empty address
|
|
this.emptyAddress = true;
|
|
|
|
// Set cached PMMR indices response to no cached response
|
|
this.cachedPmmrIndicesResponse = Node.NO_CACHED_RESPONSE;
|
|
|
|
// Set cache PMMR indicies parameters to no cache parameters
|
|
this.cachePmmrIndiciesParameters = Node.NO_CACHE_PARAMETERS;
|
|
|
|
// Set cached unspent outputs response to no cached response
|
|
this.cachedUnspentOutputsResponse = Node.NO_CACHED_RESPONSE;
|
|
|
|
// Set cache unspent outputs parameters to no cache parameters
|
|
this.cacheUnspentOutputsParameters = Node.NO_CACHE_PARAMETERS;
|
|
|
|
// Set cached header responses
|
|
this.cachedHeaderResponses = {};
|
|
|
|
// Set cached kernel responses
|
|
this.cachedKernelResponses = {};
|
|
|
|
// Set ignore response index
|
|
this.ignoreResponseIndex = 0;
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Once database is initialized
|
|
Database.onceInitialized(function() {
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Return creating settings
|
|
return Promise.all([
|
|
|
|
// Use custom node setting
|
|
self.settings.createValue(Node.SETTINGS_USE_CUSTOM_NODE_NAME, Node.SETTINGS_USE_CUSTOM_NODE_DEFAULT_VALUE),
|
|
|
|
// Custom node address setting
|
|
self.settings.createValue(Node.SETTINGS_CUSTOM_NODE_ADDRESS_NAME, Node.SETTINGS_CUSTOM_NODE_ADDRESS_DEFAULT_VALUE),
|
|
|
|
// Custom node secret setting
|
|
self.settings.createValue(Node.SETTINGS_CUSTOM_NODE_SECRET_NAME, Node.SETTINGS_CUSTOM_NODE_SECRET_DEFAULT_VALUE)
|
|
|
|
]).then(function() {
|
|
|
|
// Initialize settings
|
|
var settings = [
|
|
|
|
// Use custom node setting
|
|
Node.SETTINGS_USE_CUSTOM_NODE_NAME,
|
|
|
|
// Custom node address setting
|
|
Node.SETTINGS_CUSTOM_NODE_ADDRESS_NAME,
|
|
|
|
// Custom node secret setting
|
|
Node.SETTINGS_CUSTOM_NODE_SECRET_NAME
|
|
];
|
|
|
|
// Return getting settings' values
|
|
return Promise.all(settings.map(function(setting) {
|
|
|
|
// Return getting setting's value
|
|
return self.settings.getValue(setting);
|
|
|
|
})).then(function(settingValues) {
|
|
|
|
// Set use custom node to setting's value
|
|
self.useCustomNode = settingValues[settings.indexOf(Node.SETTINGS_USE_CUSTOM_NODE_NAME)];
|
|
|
|
// Set custom node address to setting's value
|
|
self.customNodeAddress = settingValues[settings.indexOf(Node.SETTINGS_CUSTOM_NODE_ADDRESS_NAME)];
|
|
|
|
// Set custom node secret to setting's value
|
|
self.customNodeSecret = settingValues[settings.indexOf(Node.SETTINGS_CUSTOM_NODE_SECRET_NAME)];
|
|
|
|
// Resolve
|
|
resolve();
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject
|
|
reject();
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject
|
|
reject();
|
|
});
|
|
});
|
|
});
|
|
|
|
// Settings change event
|
|
$(this.settings).on(Settings.CHANGE_EVENT, function(event, setting) {
|
|
|
|
// Initialize node setting changed
|
|
var nodeSettingChanged = false;
|
|
|
|
// Check what setting was changes
|
|
switch(setting[Settings.DATABASE_SETTING_NAME]) {
|
|
|
|
// Use custom node setting
|
|
case Node.SETTINGS_USE_CUSTOM_NODE_NAME:
|
|
|
|
// Set use custom node to setting's value
|
|
self.useCustomNode = setting[Settings.DATABASE_VALUE_NAME];
|
|
|
|
// Set node setting changed
|
|
nodeSettingChanged = true;
|
|
|
|
// Break
|
|
break;
|
|
|
|
// Custom node address setting
|
|
case Node.SETTINGS_CUSTOM_NODE_ADDRESS_NAME:
|
|
|
|
// Set custom node address to setting's value
|
|
self.customNodeAddress = setting[Settings.DATABASE_VALUE_NAME];
|
|
|
|
// Set node setting changed
|
|
nodeSettingChanged = true;
|
|
|
|
// Break
|
|
break;
|
|
|
|
// Custom node secret setting
|
|
case Node.SETTINGS_CUSTOM_NODE_SECRET_NAME:
|
|
|
|
// Set custom node secret to setting's value
|
|
self.customNodeSecret = setting[Settings.DATABASE_VALUE_NAME];
|
|
|
|
// Set node setting changed
|
|
nodeSettingChanged = true;
|
|
|
|
// Break
|
|
break;
|
|
}
|
|
|
|
// Check if a node setting was changed
|
|
if(nodeSettingChanged === true) {
|
|
|
|
// Check if connected to the node
|
|
if(self.versionObtained === true) {
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('Node settings changed. Disconnecting from the node.'));
|
|
}
|
|
|
|
// Clear version obtained
|
|
self.versionObtained = false;
|
|
|
|
// Trigger settings change event
|
|
$(self).trigger(Node.SETTINGS_CHANGE_EVENT);
|
|
|
|
// Restart
|
|
self.restart();
|
|
}
|
|
});
|
|
|
|
// Tor proxy settings change event
|
|
$(this.torProxy).on(TorProxy.SETTINGS_CHANGE_EVENT, function() {
|
|
|
|
// Check if using a custom node, the custom node exists, and not using a custom Tor proxy or a Tor proxy address exists
|
|
if(self.usingCustomNode() === true && self.getAddresses(Consensus.getNetworkType() === Consensus.MAINNET_NETWORK_TYPE)[0]["length"] !== 0 && (self.torProxy.usingCustomTorProxy() === false || self.torProxy.getAddress()["length"] !== 0)) {
|
|
|
|
// Get URL as the first address
|
|
var url = self.getAddresses(Consensus.getNetworkType() === Consensus.MAINNET_NETWORK_TYPE)[0];
|
|
|
|
// Check if Tor proxy will be used to connect to the node
|
|
if(Tor.isTorUrl(url) === true && Tor.isSupported() === false) {
|
|
|
|
// Check if connected
|
|
if(self.isConnected() === true) {
|
|
|
|
// Update height immediately
|
|
self.updateHeight(true);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Clear version obtained
|
|
self.versionObtained = false;
|
|
|
|
// Restart
|
|
self.restart();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Connection warning or close event
|
|
$(this).on(Node.CONNECTION_WARNING_EVENT + " " + Node.CONNECTION_CLOSE_EVENT, function(event, type) {
|
|
|
|
// Check type
|
|
switch(type) {
|
|
|
|
// No connection close type
|
|
case Node.NO_CONNECTION_CLOSE_TYPE:
|
|
|
|
// Check if address exists
|
|
if(self.emptyAddress === false) {
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('Connecting to the node failed.'));
|
|
}
|
|
|
|
// Break
|
|
break;
|
|
|
|
// Disconnected close type
|
|
case Node.DISCONNECTED_CLOSE_TYPE:
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('Disconnected from the node.'));
|
|
|
|
// Break
|
|
break;
|
|
|
|
// Unauthorized warning type
|
|
case Node.UNAUTHORIZED_WARNING_TYPE:
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('You\'re not authorized to connect to the node.'));
|
|
|
|
// Break
|
|
break;
|
|
|
|
// Invalid response warning type
|
|
case Node.INVALID_RESPONSE_WARNING_TYPE:
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('Received an invalid response from the node.'));
|
|
|
|
// Break
|
|
break;
|
|
|
|
// Incompatible warning type
|
|
case Node.INCOMPATIBLE_WARNING_TYPE:
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('Not compatible with node versions less than %1$v.'), [
|
|
|
|
// Version
|
|
Node.MINIMUM_COMPATIBLE_NODE_VERSION
|
|
]);
|
|
|
|
// Break
|
|
break;
|
|
}
|
|
|
|
// Check if reconnect timeout exists
|
|
if(self.reconnectTimeout !== Node.NO_TIMEOUT) {
|
|
|
|
// Clear reconnect timeout
|
|
clearTimeout(self.reconnectTimeout);
|
|
|
|
// Set reconnect timeout to no timeout
|
|
self.reconnectTimeout = Node.NO_TIMEOUT;
|
|
}
|
|
|
|
// Set reconnect timeout
|
|
self.reconnectTimeout = setTimeout(function() {
|
|
|
|
// Set reconnect timeout to no timeout
|
|
self.reconnectTimeout = Node.NO_TIMEOUT;
|
|
|
|
// Connect
|
|
self.connect();
|
|
|
|
}, Math.min(Node.MAXIMUM_RETRY_DELAY_MILLISECONDS, self.retryDelay));
|
|
|
|
// Update retry delay
|
|
self.retryDelay *= Node.RETRY_DELAY_SCALING_FACTOR;
|
|
});
|
|
|
|
// Window online event
|
|
$(window).on("online", function() {
|
|
|
|
// Check if reconnect timeout exists
|
|
if(self.reconnectTimeout !== Node.NO_TIMEOUT) {
|
|
|
|
// Clear reconnect timeout
|
|
clearTimeout(self.reconnectTimeout);
|
|
|
|
// Set reconnect timeout to no timeout
|
|
self.reconnectTimeout = Node.NO_TIMEOUT;
|
|
|
|
// Reset retry delay
|
|
self.retryDelay = Node.INITIAL_RETRY_DELAY_MILLISECONDS;
|
|
|
|
// Connect
|
|
self.connect();
|
|
}
|
|
|
|
// Window offline event
|
|
}).on("offline", function() {
|
|
|
|
// Check if started
|
|
if(self.started === true) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection close event
|
|
$(self).trigger(Node.CONNECTION_CLOSE_EVENT, (self.versionObtained === false) ? Node.NO_CONNECTION_CLOSE_TYPE : Node.DISCONNECTED_CLOSE_TYPE);
|
|
|
|
// Clear version obtained
|
|
self.versionObtained = false;
|
|
}
|
|
|
|
// Window focus event
|
|
}).on("focus", function() {
|
|
|
|
// Check if connected and hasn't received a height response recently
|
|
if(self.isConnected() === true && self.lastHeightResponseTimestamp !== Node.NO_HEIGHT_RESPONSE_TIMESTAMP && self.lastHeightResponseTimestamp < Common.getCurrentTimestamp() - Node.MAXIMUM_NO_HEIGHT_RESPONSE_DURATION_SECONDS)
|
|
|
|
// Update height immediately
|
|
self.updateHeight(true);
|
|
});
|
|
}
|
|
|
|
// Start
|
|
start() {
|
|
|
|
// Check if not already started
|
|
if(this.started === false) {
|
|
|
|
// Set started
|
|
this.started = true;
|
|
|
|
// Connect
|
|
this.connect();
|
|
}
|
|
}
|
|
|
|
// Restart
|
|
restart(disconnectFirst = true) {
|
|
|
|
// Check if not already started
|
|
if(this.started === false)
|
|
|
|
// Start
|
|
this.start();
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if update height timeout exists
|
|
if(this.updateHeightTimeout !== Node.NO_TIMEOUT) {
|
|
|
|
// Clear update height timeout
|
|
clearTimeout(this.updateHeightTimeout);
|
|
|
|
// Set update height timeout to no timeout
|
|
this.updateHeightTimeout = Node.NO_TIMEOUT;
|
|
}
|
|
|
|
// Check if disconnecting first
|
|
if(disconnectFirst === true) {
|
|
|
|
// Clear connected
|
|
this.connected = false;
|
|
|
|
// Clear failed to connect
|
|
this.failedToConnect = false;
|
|
|
|
// Clear last height response timestamp
|
|
this.lastHeightResponseTimestamp = Node.NO_HEIGHT_RESPONSE_TIMESTAMP;
|
|
|
|
// Set current height's height to an unknown height
|
|
this.currentHeight.setHeight(Node.UNKNOWN_HEIGHT);
|
|
}
|
|
|
|
// Check if reconnect timeout exists
|
|
if(this.reconnectTimeout !== Node.NO_TIMEOUT) {
|
|
|
|
// Clear reconnect timeout
|
|
clearTimeout(this.reconnectTimeout);
|
|
|
|
// Set reconnect timeout to no timeout
|
|
this.reconnectTimeout = Node.NO_TIMEOUT;
|
|
}
|
|
|
|
// Reset retry delay
|
|
this.retryDelay = Node.INITIAL_RETRY_DELAY_MILLISECONDS;
|
|
|
|
// Connect
|
|
this.connect(disconnectFirst);
|
|
}
|
|
}
|
|
|
|
// Using custom node
|
|
usingCustomNode() {
|
|
|
|
// Return if using a custom node
|
|
return this.useCustomNode === true;
|
|
}
|
|
|
|
// Get addresses
|
|
getAddresses(isMainnet) {
|
|
|
|
// Check if not using a custom node
|
|
if(this.usingCustomNode() === false) {
|
|
|
|
// Check wallet type
|
|
switch(Consensus.getWalletType()) {
|
|
|
|
// MWC wallet
|
|
case Consensus.MWC_WALLET_TYPE:
|
|
|
|
// Check if mainnet
|
|
if(isMainnet === true) {
|
|
|
|
// Return addresses
|
|
return [
|
|
|
|
// Addresses
|
|
"https://mwc713.mwc.mw",
|
|
"https://mwc7132.mwc.mw",
|
|
"https://mwc7133.mwc.mw",
|
|
"https://mwc7134.mwc.mw",
|
|
"https://mwc7135.mwc.mw",
|
|
"https://mwc7136.mwc.mw"
|
|
];
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Return addresses
|
|
return [
|
|
|
|
// Addresses
|
|
"https://mwc713.floonet.mwc.mw"
|
|
];
|
|
}
|
|
|
|
// Break
|
|
break;
|
|
|
|
// GRIN wallet
|
|
case Consensus.GRIN_WALLET_TYPE:
|
|
|
|
// Check if mainnet
|
|
if(isMainnet === true) {
|
|
|
|
// Return addresses
|
|
return [
|
|
|
|
// Addresses
|
|
"https://grinnode.live:3413"
|
|
];
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Return addresses
|
|
return [
|
|
|
|
// Addresses
|
|
"http://localhost:13413"
|
|
];
|
|
}
|
|
|
|
// Break
|
|
break;
|
|
|
|
// EPIC wallet
|
|
case Consensus.EPIC_WALLET_TYPE:
|
|
|
|
// Check if mainnet
|
|
if(isMainnet === true) {
|
|
|
|
// Return addresses
|
|
return [
|
|
|
|
// Addresses
|
|
"https://node.epiccash.com:3413"
|
|
];
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Return addresses
|
|
return [
|
|
|
|
// Addresses
|
|
"http://localhost:13413"
|
|
];
|
|
}
|
|
|
|
// Break
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Get custom node address
|
|
var customNodeAddress = this.customNodeAddress.trim();
|
|
|
|
// Check if custom node address isn't set
|
|
if(customNodeAddress["length"] === 0) {
|
|
|
|
// Return
|
|
return [
|
|
|
|
// Custom node address
|
|
customNodeAddress
|
|
];
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if custom node address doesn't have a protocol
|
|
if(Common.urlContainsProtocol(customNodeAddress) === false) {
|
|
|
|
// Add protocol to custom node address
|
|
customNodeAddress = Common.HTTP_PROTOCOL + "//" + customNodeAddress;
|
|
}
|
|
|
|
// Return
|
|
return [
|
|
|
|
// Custom node address upgraded if applicable and without any trailing slashes
|
|
Common.removeTrailingSlashes(Common.upgradeApplicableInsecureUrl(customNodeAddress))
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Is connected
|
|
isConnected() {
|
|
|
|
// Return if connected
|
|
return this.connected === true;
|
|
}
|
|
|
|
// Connection failed
|
|
connectionFailed() {
|
|
|
|
// Return if failed to connect
|
|
return this.failedToConnect === true;
|
|
}
|
|
|
|
// Get current height
|
|
getCurrentHeight() {
|
|
|
|
// Return a copy of current height
|
|
return new Height(this.currentHeight.getHeight(), this.currentHeight.getHash());
|
|
}
|
|
|
|
// Get PMMR indices
|
|
getPmmrIndices(startBlockHeight, endBlockHeight) {
|
|
|
|
// Check if response is cached
|
|
if(this.cachedPmmrIndicesResponse !== Node.NO_CACHED_RESPONSE && this.cachePmmrIndiciesParameters !== Node.NO_CACHE_PARAMETERS && startBlockHeight.isEqualTo(this.cachePmmrIndiciesParameters[0]) === true && endBlockHeight.isEqualTo(this.cachePmmrIndiciesParameters[1]) === true) {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return new promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Check if cache is still valid
|
|
if(self.cachedPmmrIndicesResponse !== Node.NO_CACHED_RESPONSE) {
|
|
|
|
// Wait for cached PMMR indices response
|
|
return self.cachedPmmrIndicesResponse.then(function(response) {
|
|
|
|
// Check if cache is still valid
|
|
if(self.cachedPmmrIndicesResponse !== Node.NO_CACHED_RESPONSE) {
|
|
|
|
// Resolve response
|
|
resolve(response);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reject
|
|
reject();
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reject
|
|
reject();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Set cache PMMR indicies parameters
|
|
this.cachePmmrIndiciesParameters = [
|
|
|
|
// Start block height
|
|
startBlockHeight,
|
|
|
|
// End block height
|
|
endBlockHeight
|
|
];
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Set cached PMMR indices response
|
|
this.cachedPmmrIndicesResponse = new Promise(function(resolve, reject) {
|
|
|
|
// Return getting PMMR indices from response
|
|
return self.getResponse("get_pmmr_indices", Node.OBJECT_RESPONSE_TYPE, [
|
|
|
|
// Start block height
|
|
startBlockHeight,
|
|
|
|
// End block height
|
|
endBlockHeight
|
|
], [
|
|
|
|
// Last retrieved index
|
|
"last_retrieved_index",
|
|
|
|
// Highest index
|
|
"highest_index"
|
|
], [
|
|
|
|
// Last retrieved index validation
|
|
function(lastRetrievedIndex) {
|
|
|
|
// Return if last retrieved index is valid
|
|
return (Common.isNumberString(lastRetrievedIndex) === true || lastRetrievedIndex instanceof BigNumber === true) && (new BigNumber(lastRetrievedIndex)).isInteger() === true && (new BigNumber(lastRetrievedIndex)).isPositive() === true;
|
|
},
|
|
|
|
// Highest index validation
|
|
function(highestIndex) {
|
|
|
|
// Return if highest index is valid
|
|
return (Common.isNumberString(highestIndex) === true || highestIndex instanceof BigNumber === true) && (new BigNumber(highestIndex)).isInteger() === true && (new BigNumber(highestIndex)).isPositive() === true;
|
|
}
|
|
|
|
]).then(function(value) {
|
|
|
|
// Resolve
|
|
resolve({
|
|
|
|
// Last retrieved index
|
|
"last_retrieved_index": new BigNumber(value["last_retrieved_index"]),
|
|
|
|
// Highest index
|
|
"highest_index": new BigNumber(value["highest_index"])
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
|
|
// Return new promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Check if getting cached PMMR indices is still valid
|
|
if(self.cachedPmmrIndicesResponse !== Node.NO_CACHED_RESPONSE) {
|
|
|
|
// Return cached PMMR indices response
|
|
return self.cachedPmmrIndicesResponse.then(function(response) {
|
|
|
|
// Resolve response
|
|
resolve(response);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Set cached PMMR indices response to no cached response
|
|
self.cachedPmmrIndicesResponse = Node.NO_CACHED_RESPONSE;
|
|
|
|
// Set cache PMMR indicies parameters to no cache parameters
|
|
self.cachePmmrIndiciesParameters = Node.NO_CACHE_PARAMETERS;
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reject
|
|
reject();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Get unspent outputs
|
|
getUnspentOutputs(startIndex, endIndex, max, pmmrIndices = Node.NO_PMMR_INDICES) {
|
|
|
|
// Check if start index is zero
|
|
if(startIndex.isZero() === true) {
|
|
|
|
// Set start index to first index
|
|
startIndex = Node.FIRST_INDEX;
|
|
}
|
|
|
|
// Check if end index is zero
|
|
if(endIndex.isZero() === true) {
|
|
|
|
// Set end index to first index
|
|
endIndex = Node.FIRST_INDEX;
|
|
}
|
|
|
|
// Check if response is cached
|
|
if(this.cachedUnspentOutputsResponse !== Node.NO_CACHED_RESPONSE && this.cacheUnspentOutputsParameters !== Node.NO_CACHE_PARAMETERS && startIndex.isEqualTo(this.cacheUnspentOutputsParameters[0]) === true && endIndex.isEqualTo(this.cacheUnspentOutputsParameters[1]) === true && max.isEqualTo(this.cacheUnspentOutputsParameters[2]) === true) {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return new promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Check if cache is still valid
|
|
if(self.cachedUnspentOutputsResponse !== Node.NO_CACHED_RESPONSE) {
|
|
|
|
// Wait for cached unspent outputs response
|
|
return self.cachedUnspentOutputsResponse.then(function(response) {
|
|
|
|
// Check if cache is still valid
|
|
if(self.cachedUnspentOutputsResponse !== Node.NO_CACHED_RESPONSE) {
|
|
|
|
// Check if PMMR indices are provided and current height is known
|
|
if(pmmrIndices !== Node.NO_PMMR_INDICES && self.currentHeight.getHeight() !== Node.UNKNOWN_HEIGHT) {
|
|
|
|
// Check if not at the last output
|
|
if(response["highest_index"].isLessThanOrEqualTo(response["last_retrieved_index"]) === false) {
|
|
|
|
// Get next PMMR indicies to populate the cache
|
|
self.getPmmrIndices(response["outputs"][response["outputs"]["length"] - 1]["block_height"], self.currentHeight.getHeight()).then(function(newPmmrIndices) {
|
|
|
|
// Check if start index to one after the last retrieved index
|
|
if(newPmmrIndices["last_retrieved_index"].isEqualTo(pmmrIndices["last_retrieved_index"]) === true && response["last_retrieved_index"].isGreaterThanOrEqualTo(pmmrIndices["last_retrieved_index"]) === true) {
|
|
|
|
// Get next unspent outputs to populate the cache
|
|
self.getUnspentOutputs(response["last_retrieved_index"].plus(1), newPmmrIndices["highest_index"], max).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Get next unspent outputs to populate the cache
|
|
self.getUnspentOutputs(newPmmrIndices["last_retrieved_index"], newPmmrIndices["highest_index"], max).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
// Resolve response
|
|
resolve(response);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reject
|
|
reject();
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reject
|
|
reject();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Set cache unspent outputs parameters
|
|
this.cacheUnspentOutputsParameters = [
|
|
|
|
// Start index
|
|
startIndex,
|
|
|
|
// End index
|
|
endIndex,
|
|
|
|
// Max
|
|
max
|
|
];
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Set cached unspent outputs response
|
|
this.cachedUnspentOutputsResponse = new Promise(function(resolve, reject) {
|
|
|
|
// Return getting unspent outputs from response
|
|
return self.getResponse("get_unspent_outputs", Node.OBJECT_RESPONSE_TYPE, [
|
|
|
|
// Start index
|
|
startIndex,
|
|
|
|
// End index
|
|
endIndex,
|
|
|
|
// Max
|
|
max,
|
|
|
|
// Include proof
|
|
true
|
|
], [
|
|
|
|
// Highest index
|
|
"highest_index",
|
|
|
|
// Last retrieved index
|
|
"last_retrieved_index",
|
|
|
|
// Outputs
|
|
"outputs"
|
|
], [
|
|
|
|
// Highest index validation
|
|
function(highestIndex) {
|
|
|
|
// Return if highest index is valid
|
|
return (Common.isNumberString(highestIndex) === true || highestIndex instanceof BigNumber === true) && (new BigNumber(highestIndex)).isInteger() === true && (new BigNumber(highestIndex)).isGreaterThanOrEqualTo(startIndex) === true;
|
|
},
|
|
|
|
// Last retrieved index validation
|
|
function(lastRetrievedIndex) {
|
|
|
|
// Return if last retrieved index is valid
|
|
return (Common.isNumberString(lastRetrievedIndex) === true || lastRetrievedIndex instanceof BigNumber === true) && (new BigNumber(lastRetrievedIndex)).isInteger() === true && (new BigNumber(lastRetrievedIndex)).isGreaterThanOrEqualTo(startIndex) === true;
|
|
},
|
|
|
|
// Outputs validation
|
|
function(outputs) {
|
|
|
|
// Check if outputs isn't an array
|
|
if(Array.isArray(outputs) === false)
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Go through all outputs
|
|
for(var i = 0; i < outputs["length"]; ++i) {
|
|
|
|
// Get output
|
|
var output = outputs[i];
|
|
|
|
// Check if output is invalid
|
|
if(Object.isObject(output) === false)
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Check if output's commit is invalid
|
|
if("commit" in output === false || Common.isHexString(output["commit"]) === false || Common.hexStringLength(output["commit"]) !== Crypto.COMMIT_LENGTH)
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Check if output's proof is invalid
|
|
if("proof" in output === false || Common.isHexString(output["proof"]) === false || Common.hexStringLength(output["proof"]) !== Crypto.PROOF_LENGTH)
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Check if output's output type is invalid
|
|
if("output_type" in output === false || (output["output_type"] !== Output.COINBASE_TYPE && output["output_type"] !== Output.TRANSACTION_TYPE))
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Check if output's block height is invalid
|
|
if("block_height" in output === false || (Common.isNumberString(output["block_height"]) === false && output["block_height"] instanceof BigNumber === false) || (new BigNumber(output["block_height"])).isInteger() === false || (new BigNumber(output["block_height"])).isGreaterThanOrEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === false)
|
|
|
|
// Return false
|
|
return false;
|
|
}
|
|
|
|
// Return true
|
|
return true;
|
|
}
|
|
|
|
]).then(function(value) {
|
|
|
|
// Initialize result
|
|
var result = {
|
|
|
|
// Highest index
|
|
"highest_index": new BigNumber(value["highest_index"]),
|
|
|
|
// Last retrieved index
|
|
"last_retrieved_index": new BigNumber(value["last_retrieved_index"]),
|
|
|
|
// Outputs
|
|
"outputs": value["outputs"].map(function(output) {
|
|
|
|
// Return
|
|
return {
|
|
|
|
// Commit
|
|
"commit": Common.fromHexString(output["commit"]),
|
|
|
|
// Proof
|
|
"proof": Common.fromHexString(output["proof"], true),
|
|
|
|
// Output type
|
|
"output_type": output["output_type"],
|
|
|
|
// Block height
|
|
"block_height": new BigNumber(output["block_height"])
|
|
};
|
|
})
|
|
};
|
|
|
|
// Check if PMMR indices are provided and current height is known
|
|
if(pmmrIndices !== Node.NO_PMMR_INDICES && self.currentHeight.getHeight() !== Node.UNKNOWN_HEIGHT) {
|
|
|
|
// Check if not at the last output
|
|
if(result["highest_index"].isLessThanOrEqualTo(result["last_retrieved_index"]) === false) {
|
|
|
|
// Get next PMMR indicies to populate the cache
|
|
self.getPmmrIndices(result["outputs"][result["outputs"]["length"] - 1]["block_height"], self.currentHeight.getHeight()).then(function(newPmmrIndices) {
|
|
|
|
// Check if start index to one after the last retrieved index
|
|
if(newPmmrIndices["last_retrieved_index"].isEqualTo(pmmrIndices["last_retrieved_index"]) === true && result["last_retrieved_index"].isGreaterThanOrEqualTo(pmmrIndices["last_retrieved_index"]) === true) {
|
|
|
|
// Get next unspent outputs to populate the cache
|
|
self.getUnspentOutputs(result["last_retrieved_index"].plus(1), newPmmrIndices["highest_index"], max).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Get next unspent outputs to populate the cache
|
|
self.getUnspentOutputs(newPmmrIndices["last_retrieved_index"], newPmmrIndices["highest_index"], max).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
// Resolve result
|
|
resolve(result);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
|
|
// Return new promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Check if getting cached unspent outputs is still valid
|
|
if(self.cachedUnspentOutputsResponse !== Node.NO_CACHED_RESPONSE) {
|
|
|
|
// Return cached unspent outputs response
|
|
return self.cachedUnspentOutputsResponse.then(function(response) {
|
|
|
|
// Resolve response
|
|
resolve(response);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Set cached unspent outputs response to no cached response
|
|
self.cachedUnspentOutputsResponse = Node.NO_CACHED_RESPONSE;
|
|
|
|
// Set cache unspent outputs parameters to no cache parameters
|
|
self.cacheUnspentOutputsParameters = Node.NO_CACHE_PARAMETERS;
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reject
|
|
reject();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Get outputs
|
|
getOutputs(outputCommits) {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Initialize output commits index
|
|
var outputCommitsIndex = 0;
|
|
|
|
// Return getting outputs from response
|
|
return self.getResponse("get_outputs", Node.ARRAY_RESPONSE_TYPE, [
|
|
|
|
// Output commit
|
|
outputCommits.map(function(outputCommit) {
|
|
|
|
// Return output commit as a hex string
|
|
return Common.toHexString(outputCommit);
|
|
}),
|
|
|
|
// Start height
|
|
Node.API_NO_VALUE,
|
|
|
|
// End height
|
|
Node.API_NO_VALUE,
|
|
|
|
// Include proof
|
|
true,
|
|
|
|
// Include Merkle proof
|
|
false
|
|
], [
|
|
|
|
// Commit
|
|
"commit",
|
|
|
|
// Block height
|
|
"block_height",
|
|
|
|
// Output type
|
|
"output_type",
|
|
|
|
// Proof
|
|
"proof"
|
|
], [
|
|
|
|
// Commit validation
|
|
function(commit, index) {
|
|
|
|
// Return if commit is invalid
|
|
if(Common.isHexString(commit) === false || Common.hexStringLength(commit) !== Crypto.COMMIT_LENGTH || index >= outputCommits["length"]) {
|
|
|
|
// Return false
|
|
return false;
|
|
}
|
|
|
|
// Loop while commit isn't for the output commit at the current index
|
|
while(Common.arraysAreEqual(outputCommits[outputCommitsIndex], Common.fromHexString(commit)) === false) {
|
|
|
|
// Increment output commits index and check if at the last output commit
|
|
if(++outputCommitsIndex === outputCommits["length"]) {
|
|
|
|
// Return false
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Increment output commits index
|
|
++outputCommitsIndex;
|
|
|
|
// Return true
|
|
return true;
|
|
},
|
|
|
|
// Block height validation
|
|
function(blockHeight, index) {
|
|
|
|
// Return if block height is valid
|
|
return (Common.isNumberString(blockHeight) === true || blockHeight instanceof BigNumber === true) && (new BigNumber(blockHeight)).isInteger() === true && (new BigNumber(blockHeight)).isGreaterThanOrEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === true;
|
|
},
|
|
|
|
// Output type validation
|
|
function(outputType, index) {
|
|
|
|
// Return if output type is valid
|
|
return outputType === Output.COINBASE_TYPE || outputType === Output.TRANSACTION_TYPE;
|
|
},
|
|
|
|
// Proof validation
|
|
function(proof, index) {
|
|
|
|
// Check if proof is valid
|
|
return Common.isHexString(proof) === true && Common.hexStringLength(proof) === Crypto.PROOF_LENGTH;
|
|
}
|
|
|
|
]).then(function(values) {
|
|
|
|
// Initialize outputs
|
|
var outputs = [];
|
|
|
|
// Go through all requested outputs
|
|
for(var i = 0, j = 0; i < outputCommits["length"]; ++i) {
|
|
|
|
// Check if response exists
|
|
if(j < values["length"]) {
|
|
|
|
// Get output
|
|
var output = values[j];
|
|
|
|
// Check if response is for the current requested output
|
|
if(Common.arraysAreEqual(outputCommits[i], Common.fromHexString(output["commit"])) === true) {
|
|
|
|
// Append output to list
|
|
outputs.push({
|
|
|
|
// Commit
|
|
"commit": Common.fromHexString(output["commit"]),
|
|
|
|
// Proof
|
|
"proof": Common.fromHexString(output["proof"], true),
|
|
|
|
// Output type
|
|
"output_type": output["output_type"],
|
|
|
|
// Block height
|
|
"block_height": new BigNumber(output["block_height"])
|
|
});
|
|
|
|
// Increment output index
|
|
++j;
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Append no output found to list
|
|
outputs.push(Node.NO_OUTPUT_FOUND);
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Append no output found to list
|
|
outputs.push(Node.NO_OUTPUT_FOUND);
|
|
}
|
|
}
|
|
|
|
// Resolve outputs
|
|
resolve(outputs);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Get header
|
|
getHeader(headerHeight) {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Check if response is cached
|
|
if(self.cachedHeaderResponses.hasOwnProperty(headerHeight.toFixed()) === true) {
|
|
|
|
// Resolve cached response
|
|
resolve(self.cachedHeaderResponses[headerHeight.toFixed()]);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Return getting header from response
|
|
return self.getResponse("get_header", Node.OBJECT_RESPONSE_TYPE, [
|
|
|
|
// Header height
|
|
headerHeight,
|
|
|
|
// Hash
|
|
Node.API_NO_VALUE,
|
|
|
|
// Commit
|
|
Node.API_NO_VALUE
|
|
|
|
], [
|
|
|
|
// Height
|
|
"height",
|
|
|
|
// Hash
|
|
"hash",
|
|
|
|
// Timestamp
|
|
"timestamp"
|
|
], [
|
|
|
|
// Height validation
|
|
function(height) {
|
|
|
|
// Return if height is valid
|
|
return (Common.isNumberString(height) === true || height instanceof BigNumber === true) && (new BigNumber(height)).isEqualTo(headerHeight) === true;
|
|
},
|
|
|
|
// Hash validation
|
|
function(hash) {
|
|
|
|
// Return if hash is valid
|
|
return Common.isHexString(hash) === true && Common.hexStringLength(hash) === RecentHeights.HEADER_HASH_LENGTH;
|
|
},
|
|
|
|
// Timestamp validation
|
|
Common.isRfc3339String
|
|
|
|
], false, true).then(function(value) {
|
|
|
|
// Initialize result
|
|
var result = {
|
|
|
|
// Hash
|
|
"hash": Common.fromHexString(value["hash"]),
|
|
|
|
// Timestamp
|
|
"timestamp": Common.rfc3339StringToTimestamp(value["timestamp"])
|
|
};
|
|
|
|
// Cache result
|
|
self.cachedHeaderResponses[headerHeight.toFixed()] = result;
|
|
|
|
// Resolve result
|
|
resolve(result);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Check if no header exists at the height
|
|
if(Node.isNotFoundError(error) === true) {
|
|
|
|
// Cache result
|
|
self.cachedHeaderResponses[headerHeight.toFixed()] = Node.NO_HEADER_FOUND;
|
|
|
|
// Resolve no header found
|
|
resolve(Node.NO_HEADER_FOUND);
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Get kernel
|
|
getKernel(excess, minimumHeight, maximumHeight) {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Check if response is cached
|
|
if(self.cachedKernelResponses.hasOwnProperty(Common.toHexString(excess)) === true) {
|
|
|
|
// Resolve cached response
|
|
resolve(self.cachedKernelResponses[Common.toHexString(excess)]);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Return getting kernel from response
|
|
return self.getResponse("get_kernel", Node.OBJECT_RESPONSE_TYPE, [
|
|
|
|
// Excess
|
|
Common.toHexString(excess),
|
|
|
|
// Minimum height
|
|
minimumHeight,
|
|
|
|
// Maximum height
|
|
maximumHeight
|
|
], [
|
|
|
|
// Kernel
|
|
"tx_kernel",
|
|
|
|
// Height
|
|
"height"
|
|
], [
|
|
|
|
// Kernel validation
|
|
function(kernel) {
|
|
|
|
// Check if kernel is invalid
|
|
if(Object.isObject(kernel) === false)
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Check if kernel's excess is invalid
|
|
if("excess" in kernel === false || Common.isHexString(kernel["excess"]) === false || Common.hexStringLength(kernel["excess"]) !== Crypto.COMMIT_LENGTH || Common.arraysAreEqual(excess, Common.fromHexString(kernel["excess"])) === false)
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Check if kernel's features is invalid
|
|
if("features" in kernel === false || Object.isObject(kernel["features"]) === false || Object.keys(kernel["features"])["length"] !== SlateKernel.TRANSACTION_FEATURES_LENGTH || SlateKernel.textToFeatures(Object.keys(kernel["features"])[0]) === SlateKernel.UNSUPPORTED_FEATURES)
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Return true
|
|
return true;
|
|
},
|
|
|
|
// Height validation
|
|
function(height) {
|
|
|
|
// Return if height is valid
|
|
return (Common.isNumberString(height) === true || height instanceof BigNumber === true) && (new BigNumber(height)).isInteger() === true && (new BigNumber(height)).isGreaterThanOrEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === true;
|
|
}
|
|
|
|
], false, true).then(function(value) {
|
|
|
|
// Initialize result
|
|
var result = {
|
|
|
|
// Kernel
|
|
"tx_kernel": {
|
|
|
|
// Features
|
|
"features": value["tx_kernel"]["features"]
|
|
},
|
|
|
|
// Height
|
|
"height": new BigNumber(value["height"])
|
|
};
|
|
|
|
// Cache result
|
|
self.cachedKernelResponses[Common.toHexString(excess)] = result;
|
|
|
|
// Resolve result
|
|
resolve(result);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Check if no kernel exists in the height range
|
|
if(Node.isNotFoundError(error) === true) {
|
|
|
|
// Cache result
|
|
self.cachedKernelResponses[Common.toHexString(excess)] = Node.NO_KERNEL_FOUND;
|
|
|
|
// Resolve no kernel found
|
|
resolve(Node.NO_KERNEL_FOUND);
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Broadcast transaction
|
|
broadcastTransaction(transaction) {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Return pushing transaction from response
|
|
return self.getResponse("push_transaction", Node.NO_RESPONSE_TYPE, [
|
|
|
|
// Transaction
|
|
transaction,
|
|
|
|
// Fluff
|
|
false,
|
|
|
|
], Node.NO_VALUE, [], false, false, true).then(function(value) {
|
|
|
|
// Resolve value
|
|
resolve(value);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Clear cache
|
|
clearCache() {
|
|
|
|
// Set cached PMMR indices response to no cached response
|
|
this.cachedPmmrIndicesResponse = Node.NO_CACHED_RESPONSE;
|
|
|
|
// Set cache PMMR indicies parameters to no cache parameters
|
|
this.cachePmmrIndiciesParameters = Node.NO_CACHE_PARAMETERS;
|
|
|
|
// Set cached unspent outputs response to no cached response
|
|
this.cachedUnspentOutputsResponse = Node.NO_CACHED_RESPONSE;
|
|
|
|
// Set cache unspent outputs parameters to no cache parameters
|
|
this.cacheUnspentOutputsParameters = Node.NO_CACHE_PARAMETERS;
|
|
|
|
// Clear cached header responses
|
|
this.cachedHeaderResponses = {};
|
|
|
|
// Clear cached kernel responses
|
|
this.cachedKernelResponses = {};
|
|
}
|
|
|
|
// Optimize cache
|
|
optimizeCache() {
|
|
|
|
// Check if current height is known
|
|
if(this.currentHeight.getHeight() !== Node.UNKNOWN_HEIGHT) {
|
|
|
|
// Get minimum used height threshold
|
|
var minimumUsedHeightThreshold = this.currentHeight.getHeight().minus(Node.UNUSED_CACHED_RESPONSE_HEIGHT_VARIATION);
|
|
|
|
// Check if minimum used height threshold is less than the first block height
|
|
if(minimumUsedHeightThreshold.isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true) {
|
|
|
|
// Set minimum used height threshold to the first block height
|
|
minimumUsedHeightThreshold = new BigNumber(Consensus.FIRST_BLOCK_HEIGHT);
|
|
}
|
|
|
|
// Go through all cached header responses
|
|
for(var height in this.cachedHeaderResponses) {
|
|
|
|
if(this.cachedHeaderResponses.hasOwnProperty(height) === true) {
|
|
|
|
// Check if header's height is less than the minimum used height threshold
|
|
if(minimumUsedHeightThreshold.isGreaterThan(height) === true) {
|
|
|
|
// Remove cached header response
|
|
delete this.cachedHeaderResponses[height];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Go through all cached kernel responses
|
|
for(var commit in this.cachedKernelResponses) {
|
|
|
|
if(this.cachedKernelResponses.hasOwnProperty(commit) === true) {
|
|
|
|
// Check if kernel doesn't exist or its height is less than the minimum used height threshold
|
|
if(this.cachedKernelResponses[commit] === Node.NO_KERNEL_FOUND || minimumUsedHeightThreshold.isGreaterThan(this.cachedKernelResponses[commit]["height"]) === true) {
|
|
|
|
// Remove cached kernel response
|
|
delete this.cachedKernelResponses[commit];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Connection open event
|
|
static get CONNECTION_OPEN_EVENT() {
|
|
|
|
// Return connection open event
|
|
return "NodeConnectionOpenEvent";
|
|
}
|
|
|
|
// Connection warning event
|
|
static get CONNECTION_WARNING_EVENT() {
|
|
|
|
// Return connection warning event
|
|
return "NodeConnectionWarningEvent";
|
|
}
|
|
|
|
// Connection close event
|
|
static get CONNECTION_CLOSE_EVENT() {
|
|
|
|
// Return connection close event
|
|
return "NodeConnectionCloseEvent";
|
|
}
|
|
|
|
// Height change event
|
|
static get HEIGHT_CHANGE_EVENT() {
|
|
|
|
// Return height change event
|
|
return "NodeHeightChangeEvent";
|
|
}
|
|
|
|
// Settings change event
|
|
static get SETTINGS_CHANGE_EVENT() {
|
|
|
|
// Return settings change event
|
|
return "NodeSettingsChangeEvent";
|
|
}
|
|
|
|
// Unknown height
|
|
static get UNKNOWN_HEIGHT() {
|
|
|
|
// Return unknown height
|
|
return null;
|
|
}
|
|
|
|
// No header found
|
|
static get NO_HEADER_FOUND() {
|
|
|
|
// Return no header found
|
|
return null;
|
|
}
|
|
|
|
// No kernel found
|
|
static get NO_KERNEL_FOUND() {
|
|
|
|
// Return no kernel found
|
|
return null;
|
|
}
|
|
|
|
// No connection close type
|
|
static get NO_CONNECTION_CLOSE_TYPE() {
|
|
|
|
// Return no connection close type
|
|
return 0;
|
|
}
|
|
|
|
// Disconnected close type
|
|
static get DISCONNECTED_CLOSE_TYPE() {
|
|
|
|
// Return disconnected close type
|
|
return Node.NO_CONNECTION_CLOSE_TYPE + 1;
|
|
}
|
|
|
|
// Incompatible warning type
|
|
static get INCOMPATIBLE_WARNING_TYPE() {
|
|
|
|
// Return incompatible warning type
|
|
return 0;
|
|
}
|
|
|
|
// Invalid response warning type
|
|
static get INVALID_RESPONSE_WARNING_TYPE() {
|
|
|
|
// Return invalid response warning type
|
|
return Node.INCOMPATIBLE_WARNING_TYPE + 1;
|
|
}
|
|
|
|
// Unauthorized warning type
|
|
static get UNAUTHORIZED_WARNING_TYPE() {
|
|
|
|
// Return unauthorized warning type
|
|
return Node.INVALID_RESPONSE_WARNING_TYPE + 1;
|
|
}
|
|
|
|
// Minimum compatible node version
|
|
static get MINIMUM_COMPATIBLE_NODE_VERSION() {
|
|
|
|
// Check wallet type
|
|
switch(Consensus.getWalletType()) {
|
|
|
|
// MWC wallet
|
|
case Consensus.MWC_WALLET_TYPE:
|
|
|
|
// Return minimum compatible node version
|
|
return "4.2.1";
|
|
|
|
// GRIN wallet
|
|
case Consensus.GRIN_WALLET_TYPE:
|
|
|
|
// Return minimum compatible node version
|
|
return "5.0.1";
|
|
|
|
// EPIC wallet
|
|
case Consensus.EPIC_WALLET_TYPE:
|
|
|
|
// Return minimum compatible node version
|
|
return "3.3.2";
|
|
}
|
|
}
|
|
|
|
// No value
|
|
static get NO_VALUE() {
|
|
|
|
// Return no value
|
|
return null;
|
|
}
|
|
|
|
// No output found
|
|
static get NO_OUTPUT_FOUND() {
|
|
|
|
// Return no output found
|
|
return null;
|
|
}
|
|
|
|
// Private
|
|
|
|
// Reset
|
|
reset() {
|
|
|
|
// Check if update height timeout exists
|
|
if(this.updateHeightTimeout !== Node.NO_TIMEOUT) {
|
|
|
|
// Clear update height timeout
|
|
clearTimeout(this.updateHeightTimeout);
|
|
|
|
// Set update height timeout to no timeout
|
|
this.updateHeightTimeout = Node.NO_TIMEOUT;
|
|
}
|
|
|
|
// Clear connected
|
|
this.connected = false;
|
|
|
|
// Set failed to connect
|
|
this.failedToConnect = true;
|
|
|
|
// Clear last height response timestamp
|
|
this.lastHeightResponseTimestamp = Node.NO_HEIGHT_RESPONSE_TIMESTAMP;
|
|
|
|
// Set current height's height to an unknown height
|
|
this.currentHeight.setHeight(Node.UNKNOWN_HEIGHT);
|
|
}
|
|
|
|
// Connect
|
|
connect(disconnectFirst = true) {
|
|
|
|
// Check if disconnecting first
|
|
if(disconnectFirst === true) {
|
|
|
|
// Set empty address
|
|
this.emptyAddress = this.getAddresses(Consensus.getNetworkType() === Consensus.MAINNET_NETWORK_TYPE)[0]["length"] === 0;
|
|
|
|
// Check if address exists
|
|
if(this.emptyAddress === false) {
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('Trying to connect to the node at %1$y.'), [
|
|
|
|
[
|
|
// Text
|
|
this.getAddresses(Consensus.getNetworkType() === Consensus.MAINNET_NETWORK_TYPE)[0],
|
|
|
|
// Is raw data
|
|
true
|
|
]
|
|
]);
|
|
}
|
|
|
|
// Clear connected
|
|
this.connected = false;
|
|
|
|
// Clear failed to connect
|
|
this.failedToConnect = false;
|
|
|
|
// Clear last height response timestamp
|
|
this.lastHeightResponseTimestamp = Node.NO_HEIGHT_RESPONSE_TIMESTAMP;
|
|
|
|
// Clear version obtained
|
|
this.versionObtained = false;
|
|
}
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Get version
|
|
this.getVersion().then(function(version) {
|
|
|
|
// Check if disconnecting first
|
|
if(disconnectFirst === true) {
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('Successfully connected to the node.'));
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('The node\'s version is %1$v.'), [
|
|
|
|
// Version
|
|
version
|
|
]);
|
|
}
|
|
|
|
// Set version obtained
|
|
self.versionObtained = true;
|
|
|
|
// Check if version is compatible
|
|
if(Node.isVersionGreaterThanOrEqual(version, Node.MINIMUM_COMPATIBLE_NODE_VERSION) === true) {
|
|
|
|
// Get tip
|
|
self.getTip(true).then(function(tip) {
|
|
|
|
// Set current tip's height and hash
|
|
self.currentHeight.setHeight(tip["height"]);
|
|
self.currentHeight.setHash(tip["last_block_pushed"]);
|
|
|
|
// Reset retry delay
|
|
self.retryDelay = Node.INITIAL_RETRY_DELAY_MILLISECONDS;
|
|
|
|
// Set connected
|
|
self.connected = true;
|
|
|
|
// Clear failed to connect
|
|
self.failedToConnect = false;
|
|
|
|
// Set last height response timestamp
|
|
self.lastHeightResponseTimestamp = Common.getCurrentTimestamp();
|
|
|
|
// Trigger connection open event
|
|
$(self).trigger(Node.CONNECTION_OPEN_EVENT);
|
|
|
|
// Trigger height change event
|
|
$(self).trigger(Node.HEIGHT_CHANGE_EVENT, [
|
|
|
|
// Height
|
|
tip["height"],
|
|
|
|
// Last block pushed
|
|
tip["last_block_pushed"]
|
|
]);
|
|
|
|
// Update height
|
|
self.updateHeight();
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INCOMPATIBLE_WARNING_TYPE);
|
|
}
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
|
|
// Get response
|
|
getResponse(method, responseType, parameters, value, validate, allowUnconnected = false, allowNotFoundError = false, allowMessageError = false) {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Check if not connected and unconnected isn't allowed
|
|
if(self.connected === false && allowUnconnected === false) {
|
|
|
|
// Reject connection error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.CONNECTION_ERROR,
|
|
|
|
// Response
|
|
Node.NO_RESPONSE
|
|
]);
|
|
}
|
|
|
|
// Otherwise check if first address isn't a valid URL
|
|
else if(Common.isValidUrl(self.getAddresses(Consensus.getNetworkType() === Consensus.MAINNET_NETWORK_TYPE)[0]) === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection close event
|
|
$(self).trigger(Node.CONNECTION_CLOSE_EVENT, (self.versionObtained === false) ? Node.NO_CONNECTION_CLOSE_TYPE : Node.DISCONNECTED_CLOSE_TYPE);
|
|
|
|
// Clear version obtained
|
|
self.versionObtained = false;
|
|
|
|
// Reject connection error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.CONNECTION_ERROR,
|
|
|
|
// Response
|
|
Node.NO_RESPONSE
|
|
]);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Get current ignore response index
|
|
var index = self.ignoreResponseIndex++;
|
|
|
|
// Check if current ignore resposne index is at the max safe integer
|
|
if(index === Number.MAX_SAFE_INTEGER) {
|
|
|
|
// Reset ignore response index
|
|
self.ignoreResponseIndex = 0;
|
|
}
|
|
|
|
// Set ignore response
|
|
var ignoreResponse = false;
|
|
|
|
// Node settings change node index event
|
|
$(self).one(Node.SETTINGS_CHANGE_EVENT + ".node" + index.toFixed(), function() {
|
|
|
|
// Set ignore response
|
|
ignoreResponse = true;
|
|
});
|
|
|
|
// Node connection warning node index event
|
|
$(self).one(Node.CONNECTION_WARNING_EVENT + ".node" + index.toFixed(), function() {
|
|
|
|
// Set ignore response
|
|
ignoreResponse = true;
|
|
});
|
|
|
|
// Node connection close node index event
|
|
$(self).one(Node.CONNECTION_CLOSE_EVENT + ".node" + index.toFixed(), function() {
|
|
|
|
// Set ignore response
|
|
ignoreResponse = true;
|
|
});
|
|
|
|
// Get URL as the first address
|
|
var url = self.getAddresses(Consensus.getNetworkType() === Consensus.MAINNET_NETWORK_TYPE)[0];
|
|
|
|
// Get proxy request
|
|
var proxyRequest = Tor.isTorUrl(url) === true && Tor.isSupported() === false;
|
|
|
|
// Return sending a JSON-RPC request
|
|
return JsonRpc.sendRequest(self.getAddresses(Consensus.getNetworkType() === Consensus.MAINNET_NETWORK_TYPE).map(function(address) {
|
|
|
|
// Return address with Tor proxy if required and the foreign API path
|
|
return ((Tor.isTorUrl(address) === true && Tor.isSupported() === false) ? self.torProxy.getAddress() : "") + address + Node.FOREIGN_API_URL;
|
|
|
|
}), method, parameters, (self.getSecret() === Node.NO_SECRET) ? {} : {
|
|
|
|
// Authorization
|
|
"Authorization": "Basic " + Base64.fromUint8Array((new TextEncoder()).encode(self.getUsername() + ":" + self.getSecret()))
|
|
|
|
}, JsonRpc.DEFAULT_NUMBER_OF_ATTEMPTS, function() {
|
|
|
|
// Return if ignoring response
|
|
return ignoreResponse === true;
|
|
|
|
}).then(function(response) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Turn off node settings change node index event
|
|
$(self).off(Node.SETTINGS_CHANGE_EVENT + ".node" + index.toFixed());
|
|
|
|
// Turn off node connection warning node index event
|
|
$(self).off(Node.CONNECTION_WARNING_EVENT + ".node" + index.toFixed());
|
|
|
|
// Turn off node connection close node index event
|
|
$(self).off(Node.CONNECTION_CLOSE_EVENT + ".node" + index.toFixed());
|
|
}
|
|
|
|
// Check if response isn't valid
|
|
if(Object.isObject(response) === false || "Ok" in response === false) {
|
|
|
|
// Set error to invalid response error
|
|
var error = [
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
];
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Check if message error is allowed and error is a message error
|
|
if(allowMessageError === true && Node.isMessageError(error) === true) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Check if not found error isn't allowed or the error isn't a not found error
|
|
if(allowNotFoundError === false || Node.isNotFoundError(error) === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
}
|
|
|
|
// Reject error
|
|
reject(error);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Set response to its value
|
|
response = response["Ok"];
|
|
|
|
// Check if response doesn't match the expected response type
|
|
if((responseType === Node.ARRAY_RESPONSE_TYPE && Array.isArray(response) === false) || (responseType === Node.OBJECT_RESPONSE_TYPE && Object.isObject(response) === false) || (responseType === Node.NO_RESPONSE_TYPE && response !== Node.API_NO_VALUE)) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Otherwise check if there is no value
|
|
else if(value === Node.NO_VALUE) {
|
|
|
|
// Check if response is no value
|
|
if(response === Node.API_NO_VALUE) {
|
|
|
|
// Set response value to no value
|
|
var responseValue = Node.NO_VALUE;
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Otherwise check if there are multiple values
|
|
else if(Array.isArray(value) === true) {
|
|
|
|
// Check if response is an array
|
|
if(Array.isArray(response) === true) {
|
|
|
|
// Initialize response value
|
|
var responseValue = [];
|
|
|
|
// Go through all parts of the response
|
|
for(var i = 0; i < response["length"]; ++i) {
|
|
|
|
// Set first value
|
|
var firstValue = true;
|
|
|
|
// Go through all values
|
|
for(var j = 0; j < value["length"]; ++j) {
|
|
|
|
// Check if response part doesn't contain the value
|
|
if(Object.isObject(response[i]) === false || value[j] in response[i] === false) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Check if value is invalid
|
|
if(validate[j](response[i][value[j]], i) === false) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Check if first value
|
|
if(firstValue === true) {
|
|
|
|
// Clear first value
|
|
firstValue = false;
|
|
|
|
// Append to response value
|
|
responseValue.push({});
|
|
}
|
|
|
|
// Set value in response value
|
|
responseValue[responseValue["length"] - 1][value[j]] = response[i][value[j]];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise check if response is an object
|
|
else if(Object.isObject(response) === true) {
|
|
|
|
// Initialize response value
|
|
var responseValue = {};
|
|
|
|
// Go through all values
|
|
for(var i = 0; i < value["length"]; ++i) {
|
|
|
|
// Check if response doesn't contain the value
|
|
if(value[i] in response === false) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Check if value is invalid
|
|
if(validate[i](response[value[i]]) === false) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Set value in response value
|
|
responseValue[value[i]] = response[value[i]];
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if response is an array
|
|
if(Array.isArray(response) === true) {
|
|
|
|
// Initialize response value
|
|
var responseValue = [];
|
|
|
|
// Go through all parts of the response
|
|
for(var i = 0; i < response["length"]; ++i) {
|
|
|
|
// Check if response part doesn't contain the value
|
|
if(Object.isObject(response[i]) === false || value in response[i] === false) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Check if value is invalid
|
|
if(validate(response[i][value], i) === false) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Set value in response value
|
|
responseValue.push(response[i][value]);
|
|
}
|
|
}
|
|
|
|
// Otherwise check if response is an object
|
|
else if(Object.isObject(response) === true) {
|
|
|
|
// Check if response doesn't contain the value
|
|
if(value in response === false) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Check if value is invalid
|
|
if(validate(response[value]) === false) {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Set response value to value
|
|
var responseValue = response[value];
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if not ignoring response
|
|
if(ignoreResponse === false) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
}
|
|
|
|
// Reject invalid response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.INVALID_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
response
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Resolve response value
|
|
resolve(responseValue);
|
|
}
|
|
|
|
// On error
|
|
}).catch(function(responseStatusOrResponse) {
|
|
|
|
// Check if ignoring response
|
|
if(ignoreResponse === true) {
|
|
|
|
// Reject connection error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.CANCELED_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
Node.NO_RESPONSE
|
|
]);
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Turn off node settings change node index event
|
|
$(self).off(Node.SETTINGS_CHANGE_EVENT + ".node" + index.toFixed());
|
|
|
|
// Turn off node connection warning node index event
|
|
$(self).off(Node.CONNECTION_WARNING_EVENT + ".node" + index.toFixed());
|
|
|
|
// Turn off node connection close node index event
|
|
$(self).off(Node.CONNECTION_CLOSE_EVENT + ".node" + index.toFixed());
|
|
}
|
|
|
|
// Check if response is provided
|
|
if(typeof responseStatusOrResponse !== "number") {
|
|
|
|
// Set response status to HTTP ok status
|
|
var responseStatus = Common.HTTP_OK_STATUS;
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Set response status to response status
|
|
var responseStatus = responseStatusOrResponse;
|
|
}
|
|
|
|
// Check if response's status isn't no response or a timed out proxy response
|
|
if(responseStatus !== Common.HTTP_NO_RESPONSE_STATUS || (proxyRequest === true && responseStatus !== Common.HTTP_BAD_GATEWAY_STATUS && responseStatus !== Common.HTTP_GATEWAY_TIMEOUT_STATUS && responseStatus !== Common.HTTP_NO_RESPONSE_STATUS)) {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection warning event
|
|
$(self).trigger(Node.CONNECTION_WARNING_EVENT, (self.usingCustomNode() === true && responseStatus === Common.HTTP_UNAUTHORIZED_STATUS) ? Node.UNAUTHORIZED_WARNING_TYPE : Node.INVALID_RESPONSE_WARNING_TYPE);
|
|
|
|
// Reject bad response error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.BAD_RESPONSE_ERROR,
|
|
|
|
// Response
|
|
responseStatus
|
|
]);
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Reset
|
|
self.reset();
|
|
|
|
// Trigger connection close event
|
|
$(self).trigger(Node.CONNECTION_CLOSE_EVENT, (self.versionObtained === false) ? Node.NO_CONNECTION_CLOSE_TYPE : Node.DISCONNECTED_CLOSE_TYPE);
|
|
|
|
// Clear version obtained
|
|
self.versionObtained = false;
|
|
|
|
// Reject connection error
|
|
reject([
|
|
|
|
// Error type
|
|
Node.CONNECTION_ERROR,
|
|
|
|
// Response
|
|
Node.NO_RESPONSE
|
|
]);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Get version
|
|
getVersion() {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Return getting version from response
|
|
return self.getResponse("get_version", Node.OBJECT_RESPONSE_TYPE, [], "node_version", function(nodeVersion) {
|
|
|
|
// Check if node version isn't a string
|
|
if(typeof nodeVersion !== "string")
|
|
|
|
// Return false
|
|
return false;
|
|
|
|
// Return if node version is valid
|
|
return Node.VERSION_PATTERN.test(nodeVersion) === true;
|
|
|
|
}, true).then(function(value) {
|
|
|
|
// Resolve value
|
|
resolve(value);
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Get tip
|
|
getTip(allowUnconnected = false) {
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Return promise
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// Return getting tip from response
|
|
return self.getResponse("get_tip", Node.OBJECT_RESPONSE_TYPE, [], [
|
|
|
|
// Height
|
|
"height",
|
|
|
|
// Last block pushed
|
|
"last_block_pushed"
|
|
], [
|
|
|
|
// Height validation
|
|
function(height) {
|
|
|
|
// Return if height is valid
|
|
return (Common.isNumberString(height) === true || height instanceof BigNumber === true) && (new BigNumber(height)).isInteger() === true && (new BigNumber(height)).isGreaterThanOrEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === true;
|
|
},
|
|
|
|
// Last block pushed validation
|
|
function(lastBlockPushed) {
|
|
|
|
// Return if last block pushed is valid
|
|
return Common.isHexString(lastBlockPushed) === true && Common.hexStringLength(lastBlockPushed) === RecentHeights.HEADER_HASH_LENGTH;
|
|
}
|
|
|
|
], allowUnconnected).then(function(value) {
|
|
|
|
// Check if current height is unknown
|
|
if(self.currentHeight.getHeight() === Node.UNKNOWN_HEIGHT) {
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('The node\'s current height is %1$s.'), [
|
|
|
|
// Tip height
|
|
(new BigNumber(value["height"])).toFixed()
|
|
]);
|
|
}
|
|
|
|
// Otherwise check if current height changed
|
|
else if(self.currentHeight.getHeight().isEqualTo(value["height"]) === false) {
|
|
|
|
// Log message
|
|
Log.logMessage(Language.getDefaultTranslation('The node\'s new height is %1$s.'), [
|
|
|
|
// Tip height
|
|
(new BigNumber(value["height"])).toFixed()
|
|
]);
|
|
}
|
|
|
|
// Resolve
|
|
resolve({
|
|
|
|
// Height
|
|
"height": new BigNumber(value["height"]),
|
|
|
|
// Last block pushed
|
|
"last_block_pushed": Common.fromHexString(value["last_block_pushed"])
|
|
});
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
// Reject error
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Update height
|
|
updateHeight(runImmediately = false) {
|
|
|
|
// Check if update height timeout exists
|
|
if(this.updateHeightTimeout !== Node.NO_TIMEOUT) {
|
|
|
|
// Clear update height timeout
|
|
clearTimeout(this.updateHeightTimeout);
|
|
|
|
// Set update height timeout to no timeout
|
|
this.updateHeightTimeout = Node.NO_TIMEOUT;
|
|
}
|
|
|
|
// Set self
|
|
var self = this;
|
|
|
|
// Check if run immediately
|
|
if(runImmediately === true) {
|
|
|
|
// Get tip
|
|
this.getTip().then(function(tip) {
|
|
|
|
// Set current tip's height and hash
|
|
self.currentHeight.setHeight(tip["height"]);
|
|
self.currentHeight.setHash(tip["last_block_pushed"]);
|
|
|
|
// Set last height response timestamp
|
|
self.lastHeightResponseTimestamp = Common.getCurrentTimestamp();
|
|
|
|
// Trigger height change event
|
|
$(self).trigger(Node.HEIGHT_CHANGE_EVENT, [
|
|
|
|
// Height
|
|
tip["height"],
|
|
|
|
// Last block pushed
|
|
tip["last_block_pushed"],
|
|
|
|
// Ignore synced status
|
|
true
|
|
]);
|
|
|
|
// Update height
|
|
self.updateHeight();
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
});
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Set update height timeout
|
|
this.updateHeightTimeout = setTimeout(function() {
|
|
|
|
// Set update height timeout to no timeout
|
|
self.updateHeightTimeout = Node.NO_TIMEOUT;
|
|
|
|
// Get tip
|
|
self.getTip().then(function(tip) {
|
|
|
|
// Set current tip's height and hash
|
|
self.currentHeight.setHeight(tip["height"]);
|
|
self.currentHeight.setHash(tip["last_block_pushed"]);
|
|
|
|
// Set last height response timestamp
|
|
self.lastHeightResponseTimestamp = Common.getCurrentTimestamp();
|
|
|
|
// Trigger height change event
|
|
$(self).trigger(Node.HEIGHT_CHANGE_EVENT, [
|
|
|
|
// Height
|
|
tip["height"],
|
|
|
|
// Last block pushed
|
|
tip["last_block_pushed"]
|
|
]);
|
|
|
|
// Update height
|
|
self.updateHeight();
|
|
|
|
// Catch errors
|
|
}).catch(function(error) {
|
|
|
|
});
|
|
|
|
}, Node.UPDATE_HEIGHT_INTERVAL_SECONDS * Common.MILLISECONDS_IN_A_SECOND);
|
|
}
|
|
}
|
|
|
|
// Get username
|
|
getUsername(isMainnet) {
|
|
|
|
// Check wallet type
|
|
switch(Consensus.getWalletType()) {
|
|
|
|
// MWC wallet
|
|
case Consensus.MWC_WALLET_TYPE:
|
|
|
|
// Check if mainnet
|
|
if(isMainnet === true)
|
|
|
|
// Return username
|
|
return "mwcmain";
|
|
|
|
// Otherwise
|
|
else
|
|
|
|
// Return address
|
|
return "mwcfloo";
|
|
|
|
// Break
|
|
break;
|
|
|
|
// GRIN wallet
|
|
case Consensus.GRIN_WALLET_TYPE:
|
|
|
|
// Return username
|
|
return "grin";
|
|
|
|
// EPIC wallet
|
|
case Consensus.EPIC_WALLET_TYPE:
|
|
|
|
// Return username
|
|
return "epic";
|
|
}
|
|
}
|
|
|
|
// Get secret
|
|
getSecret() {
|
|
|
|
// Check if not using a custom node
|
|
if(this.usingCustomNode() === false) {
|
|
|
|
// Check wallet type
|
|
switch(Consensus.getWalletType()) {
|
|
|
|
// MWC wallet
|
|
case Consensus.MWC_WALLET_TYPE:
|
|
|
|
// Return secret
|
|
return "11ne3EAUtOXVKwhxm84U";
|
|
|
|
// GRIN or EPIC wallet
|
|
case Consensus.GRIN_WALLET_TYPE:
|
|
case Consensus.EPIC_WALLET_TYPE:
|
|
|
|
// Return secret
|
|
return Node.NO_SECRET;
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else {
|
|
|
|
// Check if custom node's secret doesn't exist
|
|
if(this.customNodeSecret["length"] === 0)
|
|
|
|
// Return no secret
|
|
return Node.NO_SECRET;
|
|
|
|
// Otherwise
|
|
else
|
|
|
|
// Return custom node's secret
|
|
return this.customNodeSecret;
|
|
}
|
|
}
|
|
|
|
// Is version greater than or equal
|
|
static isVersionGreaterThanOrEqual(firstVersion, secondVersion) {
|
|
|
|
// Check if versions are equal
|
|
if(firstVersion === secondVersion)
|
|
|
|
// Return true
|
|
return true;
|
|
|
|
// Get first version components
|
|
var firstVersionComponents = firstVersion.split(Node.VERSION_COMPONENT_PATTERN).map(function(versionComponent) {
|
|
|
|
// Return version component as a number
|
|
return (Common.isNumberString(versionComponent) === false || (new BigNumber(versionComponent)).isInteger() === false) ? Node.versionComponentTextToNumber(versionComponent).toFixed() : versionComponent;
|
|
});
|
|
|
|
// Get second version components
|
|
var secondVersionComponents = secondVersion.split(Node.VERSION_COMPONENT_PATTERN).map(function(versionComponent) {
|
|
|
|
// Return version component as a number
|
|
return (Common.isNumberString(versionComponent) === false || (new BigNumber(versionComponent)).isInteger() === false) ? Node.versionComponentTextToNumber(versionComponent).toFixed() : versionComponent;
|
|
});
|
|
|
|
// Go through all version components
|
|
for(var i = 0; i < Math.max(firstVersionComponents["length"], secondVersionComponents["length"]); ++i) {
|
|
|
|
// Get version component numbers
|
|
var firstVersionComponentNumber = new BigNumber((i < firstVersionComponents["length"]) ? firstVersionComponents[i] : Node.RELEASE_VERSION_COMPONENT_TEXT_VALUE.toFixed());
|
|
var secondVersionComponentNumber = new BigNumber((i < secondVersionComponents["length"]) ? secondVersionComponents[i] : Node.RELEASE_VERSION_COMPONENT_TEXT_VALUE.toFixed());
|
|
|
|
// Check if first version component number is greater than second version component number
|
|
if(firstVersionComponentNumber.isGreaterThan(secondVersionComponentNumber) === true) {
|
|
|
|
// Return true
|
|
return true;
|
|
}
|
|
|
|
// Otherwise check if first version component number is less than second version component number
|
|
else if(firstVersionComponentNumber.isLessThan(secondVersionComponentNumber) === true) {
|
|
|
|
// Return false
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Return true
|
|
return true;
|
|
}
|
|
|
|
// Version component text to number
|
|
static versionComponentTextToNumber(versionComponentText) {
|
|
|
|
// Make version component text lower case
|
|
versionComponentText = versionComponentText.toLowerCase();
|
|
|
|
// Return version component text value or release version component text value if not found
|
|
return (versionComponentText in Node.VERSION_COMPONENT_TEXT_VALUES === true) ? Node.VERSION_COMPONENT_TEXT_VALUES[versionComponentText] : Node.RELEASE_VERSION_COMPONENT_TEXT_VALUE;
|
|
}
|
|
|
|
// Is not found error
|
|
static isNotFoundError(error) {
|
|
|
|
// Return if error is that the value wasn't found
|
|
return Array.isArray(error) === true && error["length"] > Node.ERROR_RESPONSE_INDEX && error[Node.ERROR_TYPE_INDEX] === Node.INVALID_RESPONSE_ERROR && Object.isObject(error[Node.ERROR_RESPONSE_INDEX]) === true && "Err" in error[Node.ERROR_RESPONSE_INDEX] === true && ((Object.isObject(error[Node.ERROR_RESPONSE_INDEX]["Err"]) === true && "NotFound" in error[Node.ERROR_RESPONSE_INDEX]["Err"] === true) || error[Node.ERROR_RESPONSE_INDEX]["Err"] === "NotFound");
|
|
}
|
|
|
|
// Is message error
|
|
static isMessageError(error) {
|
|
|
|
// Return if error contains a message
|
|
return Array.isArray(error) === true && error["length"] > Node.ERROR_RESPONSE_INDEX && error[Node.ERROR_TYPE_INDEX] === Node.INVALID_RESPONSE_ERROR && Object.isObject(error[Node.ERROR_RESPONSE_INDEX]) === true && "Err" in error[Node.ERROR_RESPONSE_INDEX] === true && Object.isObject(error[Node.ERROR_RESPONSE_INDEX]["Err"]) === true && "Internal" in error[Node.ERROR_RESPONSE_INDEX]["Err"] === true && typeof error[Node.ERROR_RESPONSE_INDEX]["Err"]["Internal"] === "string";
|
|
}
|
|
|
|
// Version pattern
|
|
static get VERSION_PATTERN() {
|
|
|
|
// Return version pattern
|
|
return /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[^\d].*)?$/u;
|
|
}
|
|
|
|
// Version component text values
|
|
static get VERSION_COMPONENT_TEXT_VALUES() {
|
|
|
|
// Return version component text values
|
|
return {
|
|
|
|
// Release
|
|
"release": 0,
|
|
|
|
// Beta
|
|
"beta": -1,
|
|
|
|
// Alpha
|
|
"alpha": -2,
|
|
|
|
// Dev
|
|
"dev": -3,
|
|
|
|
// Canary
|
|
"canary": -4
|
|
};
|
|
}
|
|
|
|
// Release version component text value
|
|
static get RELEASE_VERSION_COMPONENT_TEXT_VALUE() {
|
|
|
|
// Return release version component text value
|
|
return Node.VERSION_COMPONENT_TEXT_VALUES["release"];
|
|
}
|
|
|
|
// Version component pattern
|
|
static get VERSION_COMPONENT_PATTERN() {
|
|
|
|
// Return version component pattern
|
|
return /[\.-]/ug;
|
|
}
|
|
|
|
// Connection error
|
|
static get CONNECTION_ERROR() {
|
|
|
|
// Return connection error
|
|
return 0;
|
|
}
|
|
|
|
// Invalid response error
|
|
static get INVALID_RESPONSE_ERROR() {
|
|
|
|
// Return invalid response error
|
|
return Node.CONNECTION_ERROR + 1;
|
|
}
|
|
|
|
// Bad response error
|
|
static get BAD_RESPONSE_ERROR() {
|
|
|
|
// Return bad response error
|
|
return Node.INVALID_RESPONSE_ERROR + 1;
|
|
}
|
|
|
|
// Canceled response error
|
|
static get CANCELED_RESPONSE_ERROR() {
|
|
|
|
// Return canceled response error
|
|
return Node.BAD_RESPONSE_ERROR + 1;
|
|
}
|
|
|
|
// Initial retry delay milliseconds
|
|
static get INITIAL_RETRY_DELAY_MILLISECONDS() {
|
|
|
|
// Return initial retry delay milliseconds
|
|
return 1 * Common.MILLISECONDS_IN_A_SECOND;
|
|
}
|
|
|
|
// Maximum retry delay milliseconds
|
|
static get MAXIMUM_RETRY_DELAY_MILLISECONDS() {
|
|
|
|
// Return maximum retry delay milliseconds
|
|
return Consensus.BLOCK_TIME_SECONDS * Common.MILLISECONDS_IN_A_SECOND;
|
|
}
|
|
|
|
// Retry delay scaling factor
|
|
static get RETRY_DELAY_SCALING_FACTOR() {
|
|
|
|
// Return retry delay scaling factor
|
|
return 3;
|
|
}
|
|
|
|
// No timeout
|
|
static get NO_TIMEOUT() {
|
|
|
|
// Return no timeout
|
|
return null;
|
|
}
|
|
|
|
// Settings use custom node name
|
|
static get SETTINGS_USE_CUSTOM_NODE_NAME() {
|
|
|
|
// Return settings use custom node name
|
|
return "Use Custom Node";
|
|
}
|
|
|
|
// Settings use custom node default value
|
|
static get SETTINGS_USE_CUSTOM_NODE_DEFAULT_VALUE() {
|
|
|
|
// Return settings use custom node default value
|
|
return false;
|
|
}
|
|
|
|
// Settings custom node address name
|
|
static get SETTINGS_CUSTOM_NODE_ADDRESS_NAME() {
|
|
|
|
// Return settings custom node address name
|
|
return "Custom Node Address";
|
|
}
|
|
|
|
// Settings custom node address default value
|
|
static get SETTINGS_CUSTOM_NODE_ADDRESS_DEFAULT_VALUE() {
|
|
|
|
// Return settings custom node address default value
|
|
return "";
|
|
}
|
|
|
|
// Settings custom node secret name
|
|
static get SETTINGS_CUSTOM_NODE_SECRET_NAME() {
|
|
|
|
// Return settings custom node secret name
|
|
return "Custom Node Secret";
|
|
}
|
|
|
|
// Settings custom node secret default value
|
|
static get SETTINGS_CUSTOM_NODE_SECRET_DEFAULT_VALUE() {
|
|
|
|
// Return settings custom node secret default value
|
|
return "";
|
|
}
|
|
|
|
// No response
|
|
static get NO_RESPONSE() {
|
|
|
|
// Return no response
|
|
return null;
|
|
}
|
|
|
|
// Error type index
|
|
static get ERROR_TYPE_INDEX() {
|
|
|
|
// Return error type index
|
|
return 0;
|
|
}
|
|
|
|
// Error response index
|
|
static get ERROR_RESPONSE_INDEX() {
|
|
|
|
// Return error response index
|
|
return Node.ERROR_TYPE_INDEX + 1;
|
|
}
|
|
|
|
// Foreign API version
|
|
static get FOREIGN_API_VERSION() {
|
|
|
|
// Return foreign API version
|
|
return 2;
|
|
}
|
|
|
|
// Foreign API URL
|
|
static get FOREIGN_API_URL() {
|
|
|
|
// Return foreign API URL
|
|
return "/v" + Node.FOREIGN_API_VERSION.toFixed() + "/foreign";
|
|
}
|
|
|
|
// API no value
|
|
static get API_NO_VALUE() {
|
|
|
|
// Return API no value
|
|
return null;
|
|
}
|
|
|
|
// No height response timestamp
|
|
static get NO_HEIGHT_RESPONSE_TIMESTAMP() {
|
|
|
|
// Return no height response timestamp
|
|
return null;
|
|
}
|
|
|
|
// Maximum no height response duration seconds
|
|
static get MAXIMUM_NO_HEIGHT_RESPONSE_DURATION_SECONDS() {
|
|
|
|
// Return maximum no height response duration seconds
|
|
return 3 * Node.UPDATE_HEIGHT_INTERVAL_SECONDS;
|
|
}
|
|
|
|
// No secret
|
|
static get NO_SECRET() {
|
|
|
|
// Return no secret
|
|
return "";
|
|
}
|
|
|
|
// Update height interval seconds
|
|
static get UPDATE_HEIGHT_INTERVAL_SECONDS() {
|
|
|
|
// Return update height interval seconds
|
|
return Consensus.BLOCK_TIME_SECONDS;
|
|
}
|
|
|
|
// Array response type
|
|
static get ARRAY_RESPONSE_TYPE() {
|
|
|
|
// Return array response type
|
|
return 0;
|
|
}
|
|
|
|
// Object response type
|
|
static get OBJECT_RESPONSE_TYPE() {
|
|
|
|
// Return object response type
|
|
return Node.ARRAY_RESPONSE_TYPE + 1;
|
|
}
|
|
|
|
// NO response type
|
|
static get NO_RESPONSE_TYPE() {
|
|
|
|
// Return no response type
|
|
return Node.OBJECT_RESPONSE_TYPE + 1;
|
|
}
|
|
|
|
// No cached response
|
|
static get NO_CACHED_RESPONSE() {
|
|
|
|
// Return no cached response
|
|
return null;
|
|
}
|
|
|
|
// No cache parameters
|
|
static get NO_CACHE_PARAMETERS() {
|
|
|
|
// Return no cache parameters
|
|
return null;
|
|
}
|
|
|
|
// No PMMR indices
|
|
static get NO_PMMR_INDICES() {
|
|
|
|
// Return no PMMR indices
|
|
return null;
|
|
}
|
|
|
|
// Unused cached response height variation
|
|
static get UNUSED_CACHED_RESPONSE_HEIGHT_VARIATION() {
|
|
|
|
// Return unused cached response height variation
|
|
return Consensus.BLOCK_HEIGHT_WEEK;
|
|
}
|
|
|
|
// First index
|
|
static get FIRST_INDEX() {
|
|
|
|
// Return first index
|
|
return new BigNumber(1);
|
|
}
|
|
}
|
|
|
|
|
|
// Main function
|
|
|
|
// Set global object's node
|
|
globalThis["Node"] = Node;
|