@neo-one/node-protocol
Version:
NEO•ONE NEO node and consensus protocol.
874 lines (873 loc) • 139 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const client_common_1 = require("@neo-one/client-common");
const client_switch_1 = require("@neo-one/client-switch");
const logger_1 = require("@neo-one/logger");
const node_consensus_1 = require("@neo-one/node-consensus");
const node_core_1 = require("@neo-one/node-core");
const utils_1 = require("@neo-one/utils");
const bloem_1 = require("bloem");
const bloom_filter_1 = tslib_1.__importDefault(require("bloom-filter"));
const cross_fetch_1 = tslib_1.__importDefault(require("cross-fetch"));
const ip_address_1 = require("ip-address");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const lru_cache_1 = tslib_1.__importDefault(require("lru-cache"));
const Command_1 = require("./Command");
const errors_1 = require("./errors");
const Message_1 = require("./Message");
const payload_1 = require("./payload");
const logger = logger_1.nodeLogger.child({ component: 'node-protocol' });
const messageReceivedTag = utils_1.labelToTag(utils_1.Labels.COMMAND_NAME);
const messagesReceived = client_switch_1.globalStats.createMeasureInt64('messages/received', client_switch_1.MeasureUnit.UNIT);
const messagesFailed = client_switch_1.globalStats.createMeasureInt64('messages/failed', client_switch_1.MeasureUnit.UNIT);
const mempoolSize = client_switch_1.globalStats.createMeasureInt64('mempool/size', client_switch_1.MeasureUnit.UNIT);
const NEO_PROTOCOL_MESSAGES_RECEIVED_TOTAL = client_switch_1.globalStats.createView('neo_protocol_messages_received_total', messagesReceived, client_switch_1.AggregationType.COUNT, [messageReceivedTag], 'number of messages received');
client_switch_1.globalStats.registerView(NEO_PROTOCOL_MESSAGES_RECEIVED_TOTAL);
const NEO_PROTOCOL_MESSAGES_FAILURES_TOTAL = client_switch_1.globalStats.createView('neo_protocol_messages_failures_total', messagesFailed, client_switch_1.AggregationType.COUNT, [messageReceivedTag], 'number of message failures');
client_switch_1.globalStats.registerView(NEO_PROTOCOL_MESSAGES_FAILURES_TOTAL);
const NEO_PROTOCOL_MEMPOOL_SIZE = client_switch_1.globalStats.createView('neo_protocol_mempool_size', mempoolSize, client_switch_1.AggregationType.LAST_VALUE, [], 'current mempool size');
client_switch_1.globalStats.registerView(NEO_PROTOCOL_MEMPOOL_SIZE);
const createPeerBloomFilter = ({ filter, k, tweak, }) => new bloom_filter_1.default({
vData: Buffer.from(filter),
nHashFuncs: k,
nTweak: tweak,
});
const createScalingBloomFilter = () => new bloem_1.ScalingBloem(0.05, {
initial_capacity: 100000,
scaling: 4,
});
const compareTransactionAndFees = (val1, val2) => {
const a = val1.networkFee.divn(val1.transaction.size);
const b = val2.networkFee.divn(val2.transaction.size);
if (a.lt(b)) {
return -1;
}
if (b.lt(a)) {
return 1;
}
return val1.transaction.hash.compare(val2.transaction.hash);
};
const MEM_POOL_SIZE = 5000;
const GET_ADDR_PEER_COUNT = 200;
const GET_BLOCKS_COUNT = 500;
const GET_BLOCKS_BUFFER = GET_BLOCKS_COUNT / 3;
const GET_BLOCKS_TIME_MS = 10000;
const GET_BLOCKS_THROTTLE_MS = 1000;
const TRIM_MEMPOOL_THROTTLE = 5000;
const GET_BLOCKS_CLOSE_COUNT = 2;
const UNHEALTHY_PEER_SECONDS = 300;
const LOCAL_HOST_ADDRESSES = new Set(['', '0.0.0.0', 'localhost', '127.0.0.1', '::', '::1']);
class Node {
constructor({ blockchain, createNetwork, options, }) {
this.mutableUnhealthyPeerSeconds = UNHEALTHY_PEER_SECONDS;
this.requestBlocks = lodash_1.default.debounce(() => {
const peer = this.mutableBestPeer;
const previousBlock = this.blockchain.previousBlock;
const block = previousBlock === undefined ? this.blockchain.currentBlock : previousBlock;
if (peer !== undefined && block.index < peer.data.startHeight) {
if (this.mutableGetBlocksRequestsCount > GET_BLOCKS_CLOSE_COUNT) {
this.mutableBestPeer = this.findBestPeer(peer);
this.network.blacklistAndClose(peer);
this.mutableGetBlocksRequestsCount = 0;
}
else if (this.shouldRequestBlocks()) {
if (this.mutableGetBlocksRequestsIndex === block.index) {
this.mutableGetBlocksRequestsCount += 1;
}
else {
this.mutableGetBlocksRequestsCount = 1;
this.mutableGetBlocksRequestsIndex = block.index;
}
this.mutableGetBlocksRequestTime = Date.now();
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.getblocks,
payload: new payload_1.GetBlocksPayload({
hashStart: [block.hash],
}),
}));
}
this.requestBlocks();
}
}, GET_BLOCKS_THROTTLE_MS);
this.onRequestEndpoints = lodash_1.default.throttle(() => {
this.relay(this.createMessage({ command: Command_1.Command.getaddr }));
this.fetchEndpointsFromRPC();
}, 5000);
this.trimMemPool = lodash_1.default.throttle(async () => {
const memPool = Object.values(this.mutableMemPool);
if (memPool.length > MEM_POOL_SIZE) {
const transactionAndFees = await Promise.all(memPool.map(async (transaction) => {
const networkFee = await transaction.getNetworkFee({
getOutput: this.blockchain.output.get,
governingToken: this.blockchain.settings.governingToken,
utilityToken: this.blockchain.settings.utilityToken,
fees: this.blockchain.settings.fees,
registerValidatorFee: this.blockchain.settings.registerValidatorFee,
});
return { transaction, networkFee };
}));
const hashesToRemove = lodash_1.default.take(transactionAndFees.slice().sort(compareTransactionAndFees), this.blockchain.settings.memPoolSize).map((transactionAndFee) => transactionAndFee.transaction.hashHex);
hashesToRemove.forEach((hash) => {
delete this.mutableMemPool[hash];
});
client_switch_1.globalStats.record([
{
measure: mempoolSize,
value: Object.keys(this.mutableMemPool).length,
},
]);
}
}, TRIM_MEMPOOL_THROTTLE);
this.negotiate = async (peer) => {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.version,
payload: new payload_1.VersionPayload({
protocolVersion: 0,
services: payload_1.SERVICES.NODE_NETWORK,
timestamp: Math.round(Date.now() / 1000),
port: this.externalPort,
nonce: this.nonce,
userAgent: this.userAgent,
startHeight: this.blockchain.currentBlockIndex,
relay: true,
}),
}));
const message = await peer.receiveMessage(30000);
let versionPayload;
if (message.value.command === Command_1.Command.version) {
versionPayload = message.value.payload;
}
else {
throw new errors_1.NegotiationError(message);
}
this.checkVersion(peer, message, versionPayload);
const { host } = node_core_1.getEndpointConfig(peer.endpoint);
let address;
if (payload_1.NetworkAddress.isValid(host)) {
address = new payload_1.NetworkAddress({
host,
port: versionPayload.port,
timestamp: versionPayload.timestamp,
services: versionPayload.services,
});
}
this.sendMessage(peer, this.createMessage({ command: Command_1.Command.verack }));
const nextMessage = await peer.receiveMessage(30000);
if (nextMessage.value.command !== Command_1.Command.verack) {
throw new errors_1.NegotiationError(nextMessage);
}
return {
data: {
nonce: versionPayload.nonce,
startHeight: versionPayload.startHeight,
mutableBloomFilter: undefined,
address,
},
relay: versionPayload.relay,
};
};
this.checkPeerHealth = (peer, prevHealth) => {
const checkTimeSeconds = utils_1.utils.nowSeconds();
const blockIndex = this.mutableBlockIndex[peer.endpoint];
if (prevHealth === undefined) {
return { healthy: true, checkTimeSeconds, blockIndex };
}
if (prevHealth.blockIndex !== undefined && blockIndex !== undefined && prevHealth.blockIndex < blockIndex) {
return { healthy: true, checkTimeSeconds, blockIndex };
}
if (prevHealth.blockIndex === blockIndex &&
utils_1.utils.nowSeconds() - prevHealth.checkTimeSeconds < this.mutableUnhealthyPeerSeconds) {
return {
healthy: true,
checkTimeSeconds: prevHealth.checkTimeSeconds,
blockIndex: prevHealth.blockIndex,
};
}
return { healthy: false, checkTimeSeconds, blockIndex };
};
this.onEvent = (event) => {
if (event.event === 'PEER_CONNECT_SUCCESS') {
const { connectedPeer } = event;
if (this.mutableBestPeer === undefined ||
this.mutableBestPeer.data.startHeight + 100 < connectedPeer.data.startHeight) {
this.mutableBestPeer = connectedPeer;
this.resetRequestBlocks();
this.requestBlocks();
}
}
else if (event.event === 'PEER_CLOSED' &&
this.mutableBestPeer !== undefined &&
this.mutableBestPeer.endpoint === event.peer.endpoint) {
this.mutableBestPeer = this.findBestPeer();
this.resetRequestBlocks();
this.requestBlocks();
}
};
this.blockchain = blockchain;
this.network = createNetwork({
negotiate: this.negotiate,
checkPeerHealth: this.checkPeerHealth,
createMessageTransform: () => new Message_1.MessageTransform(this.blockchain.deserializeWireContext),
onMessageReceived: (peer, message) => {
this.onMessageReceived(peer, message);
},
onRequestEndpoints: this.onRequestEndpoints.bind(this),
onEvent: this.onEvent,
});
this.options = options;
const { externalPort = 0 } = options;
this.externalPort = externalPort;
this.nonce = Math.floor(Math.random() * client_common_1.utils.UINT_MAX_NUMBER);
this.userAgent = `NEO:neo-one-js:1.0.0-preview`;
this.mutableMemPool = {};
this.mutableKnownBlockHashes = createScalingBloomFilter();
this.tempKnownBlockHashes = new Set();
this.mutableKnownTransactionHashes = createScalingBloomFilter();
this.tempKnownTransactionHashes = new Set();
this.mutableKnownHeaderHashes = createScalingBloomFilter();
this.tempKnownHeaderHashes = new Set();
this.mutableGetBlocksRequestsCount = 1;
this.consensusCache = new lru_cache_1.default(10000);
this.mutableBlockIndex = {};
}
get consensus() {
return this.mutableConsensus;
}
get connectedPeers() {
return this.network.connectedPeers.map((peer) => peer.endpoint);
}
get memPool() {
return this.mutableMemPool;
}
async reset() {
this.mutableMemPool = {};
this.mutableKnownBlockHashes = createScalingBloomFilter();
this.tempKnownBlockHashes.clear();
this.mutableKnownTransactionHashes = createScalingBloomFilter();
this.tempKnownTransactionHashes.clear();
this.mutableKnownHeaderHashes = createScalingBloomFilter();
this.tempKnownHeaderHashes.clear();
this.mutableGetBlocksRequestsCount = 1;
this.consensusCache.reset();
this.mutableBlockIndex = {};
}
async start() {
let disposable = utils_1.noopDisposable;
try {
this.network.start();
logger.debug({ title: 'neo_protocol_start' }, 'Protocol started.');
disposable = utils_1.composeDisposables(disposable, () => {
this.network.stop();
logger.debug({ title: 'neo_protocol_stop' }, 'Protocol stopped.');
});
if (this.options.consensus !== undefined) {
const mutableConsensus = new node_consensus_1.Consensus({
options: this.options.consensus,
node: this,
});
this.mutableConsensus = mutableConsensus;
const consensusDisposable = await mutableConsensus.start();
disposable = utils_1.composeDisposables(disposable, consensusDisposable);
}
this.mutableUnhealthyPeerSeconds =
this.options.unhealthyPeerSeconds === undefined ? UNHEALTHY_PEER_SECONDS : this.options.unhealthyPeerSeconds;
return disposable;
}
catch (err) {
await disposable();
throw err;
}
}
async relayTransaction(transaction, { throwVerifyError = false, forceAdd = false, } = {
throwVerifyError: false,
forceAdd: false,
}) {
const result = {};
if (transaction.type === node_core_1.TransactionType.Miner ||
this.mutableMemPool[transaction.hashHex] !== undefined ||
this.tempKnownTransactionHashes.has(transaction.hashHex)) {
return result;
}
if (!this.mutableKnownTransactionHashes.has(transaction.hash)) {
this.tempKnownTransactionHashes.add(transaction.hashHex);
try {
const memPool = Object.values(this.mutableMemPool);
if (memPool.length > MEM_POOL_SIZE / 2 && !forceAdd) {
this.mutableKnownTransactionHashes.add(transaction.hash);
return result;
}
let logLabels = { [utils_1.Labels.NEO_TRANSACTION_HASH]: transaction.hashHex };
let finalResult;
try {
let foundTransaction;
try {
foundTransaction = await this.blockchain.transaction.tryGet({
hash: transaction.hash,
});
}
finally {
logLabels = Object.assign({ [utils_1.Labels.NEO_TRANSACTION_FOUND]: foundTransaction !== undefined }, logLabels);
}
let verifyResult;
if (foundTransaction === undefined) {
verifyResult = await this.blockchain.verifyTransaction({
transaction,
memPool: Object.values(this.mutableMemPool),
});
const verified = verifyResult.verifications.every(({ failureMessage }) => failureMessage === undefined);
if (verified) {
this.mutableMemPool[transaction.hashHex] = transaction;
client_switch_1.globalStats.record([
{
measure: mempoolSize,
value: Object.keys(this.mutableMemPool).length,
},
]);
if (this.mutableConsensus !== undefined) {
this.mutableConsensus.onTransactionReceived(transaction);
}
this.relayTransactionInternal(transaction);
await this.trimMemPool();
}
}
this.mutableKnownTransactionHashes.add(transaction.hash);
finalResult = { verifyResult };
logger.debug(Object.assign({ title: 'neo_relay_transaction' }, logLabels));
}
catch (error) {
logger.error(Object.assign({ title: 'neo_relay_transaction', error }, logLabels));
throw error;
}
return finalResult;
}
catch (error) {
if (error.code === undefined ||
typeof error.code !== 'string' ||
!error.code.includes('VERIFY') ||
throwVerifyError) {
throw error;
}
}
finally {
this.tempKnownTransactionHashes.delete(transaction.hashHex);
}
}
return result;
}
async relayBlock(block) {
await this.persistBlock(block);
}
relayConsensusPayload(payload) {
const message = this.createMessage({
command: Command_1.Command.inv,
payload: new payload_1.InvPayload({
type: payload_1.InventoryType.Consensus,
hashes: [payload.hash],
}),
});
this.consensusCache.set(payload.hashHex, payload);
this.relay(message);
}
syncMemPool() {
this.relay(this.createMessage({ command: Command_1.Command.mempool }));
}
relay(message) {
this.network.relay(message.serializeWire());
}
relayTransactionInternal(transaction) {
const message = this.createMessage({
command: Command_1.Command.inv,
payload: new payload_1.InvPayload({
type: payload_1.InventoryType.Transaction,
hashes: [transaction.hash],
}),
});
const messagePayload = message.serializeWire();
this.network.connectedPeers.forEach((peer) => {
if (peer.relay && this.testFilter(peer.data.mutableBloomFilter, transaction)) {
peer.write(messagePayload);
}
});
}
sendMessage(peer, message) {
peer.write(message.serializeWire());
}
findBestPeer(bestPeer) {
let peers = this.network.connectedPeers;
if (bestPeer !== undefined) {
peers = peers.filter((peer) => peer.endpoint !== bestPeer.endpoint);
}
const result = lodash_1.default.maxBy(peers, (peer) => peer.data.startHeight);
if (result === undefined) {
return undefined;
}
return lodash_1.default.shuffle(peers.filter((peer) => peer.data.startHeight === result.data.startHeight))[0];
}
resetRequestBlocks() {
this.mutableGetBlocksRequestsIndex = undefined;
this.mutableGetBlocksRequestsCount = 0;
}
shouldRequestBlocks() {
const block = this.blockchain.currentBlock;
const getBlocksRequestTime = this.mutableGetBlocksRequestTime;
return (this.mutableGetBlocksRequestsIndex === undefined ||
block.index - this.mutableGetBlocksRequestsIndex > GET_BLOCKS_BUFFER ||
getBlocksRequestTime === undefined ||
Date.now() - getBlocksRequestTime > GET_BLOCKS_TIME_MS);
}
checkVersion(peer, message, version) {
if (version.nonce === this.nonce) {
this.network.permanentlyBlacklist(peer.endpoint);
throw new errors_1.NegotiationError(message, 'Nonce equals my nonce.');
}
const connectedPeer = this.network.connectedPeers.find((otherPeer) => version.nonce === otherPeer.data.nonce);
if (connectedPeer !== undefined) {
throw new errors_1.AlreadyConnectedError('Already connected to nonce.');
}
}
ready() {
const peer = this.mutableBestPeer;
const block = this.blockchain.currentBlock;
return peer !== undefined && block.index >= peer.data.startHeight;
}
async fetchEndpointsFromRPC() {
try {
await this.doFetchEndpointsFromRPC();
}
catch (_a) {
}
}
async doFetchEndpointsFromRPC() {
const { rpcURLs = [] } = this.options;
await Promise.all(rpcURLs.map(async (rpcURL) => this.fetchEndpointsFromRPCURL(rpcURL)));
}
async fetchEndpointsFromRPCURL(rpcURL) {
try {
const response = await cross_fetch_1.default(rpcURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'getpeers',
params: [],
}),
});
if (!response.ok) {
throw new Error(`Failed to fetch peers from ${rpcURL}: ${response.status} ${response.statusText}`);
}
const result = await response.json();
if (typeof result === 'object' &&
result.error !== undefined &&
typeof result.error === 'object' &&
typeof result.error.code === 'number' &&
typeof result.error.message === 'string') {
throw new Error(result.error);
}
const connected = result.result.connected;
connected
.map((peer) => {
const { address, port } = peer;
let canonicalForm = new ip_address_1.Address6(address).canonicalForm();
if (canonicalForm == undefined) {
canonicalForm = ip_address_1.Address6.fromAddress4(address).canonicalForm();
}
return { host: canonicalForm == undefined ? '' : canonicalForm, port };
})
.filter((endpoint) => !LOCAL_HOST_ADDRESSES.has(endpoint.host))
.map((endpoint) => node_core_1.createEndpoint({
type: 'tcp',
host: endpoint.host,
port: endpoint.port,
}))
.forEach((endpoint) => this.network.addEndpoint(endpoint));
}
catch (error) {
logger.error({ title: 'neo_protocol_fetch_endpoints_error', [utils_1.Labels.HTTP_URL]: rpcURL, error }, `Failed to fetch endpoints from ${rpcURL}`);
}
}
onMessageReceived(peer, message) {
try {
new Promise(async (resolve) => {
switch (message.value.command) {
case Command_1.Command.addr:
this.onAddrMessageReceived(message.value.payload);
break;
case Command_1.Command.block:
await this.onBlockMessageReceived(peer, message.value.payload);
break;
case Command_1.Command.consensus:
await this.onConsensusMessageReceived(message.value.payload);
break;
case Command_1.Command.filteradd:
this.onFilterAddMessageReceived(peer, message.value.payload);
break;
case Command_1.Command.filterclear:
this.onFilterClearMessageReceived(peer);
break;
case Command_1.Command.filterload:
this.onFilterLoadMessageReceived(peer, message.value.payload);
break;
case Command_1.Command.getaddr:
this.onGetAddrMessageReceived(peer);
break;
case Command_1.Command.getblocks:
await this.onGetBlocksMessageReceived(peer, message.value.payload);
break;
case Command_1.Command.getdata:
await this.onGetDataMessageReceived(peer, message.value.payload);
break;
case Command_1.Command.getheaders:
await this.onGetHeadersMessageReceived(peer, message.value.payload);
break;
case Command_1.Command.headers:
await this.onHeadersMessageReceived(peer, message.value.payload);
break;
case Command_1.Command.inv:
this.onInvMessageReceived(peer, message.value.payload);
break;
case Command_1.Command.mempool:
this.onMemPoolMessageReceived(peer);
break;
case Command_1.Command.tx:
await this.onTransactionReceived(message.value.payload);
break;
case Command_1.Command.verack:
this.onVerackMessageReceived(peer);
break;
case Command_1.Command.version:
this.onVersionMessageReceived(peer);
break;
case Command_1.Command.alert:
break;
case Command_1.Command.merkleblock:
break;
case Command_1.Command.notfound:
break;
case Command_1.Command.ping:
break;
case Command_1.Command.pong:
break;
case Command_1.Command.reject:
break;
default:
utils_1.utils.assertNever(message.value);
}
resolve();
}).catch(() => {
});
}
catch (_a) {
}
}
onAddrMessageReceived(addr) {
addr.addresses
.filter((address) => !LOCAL_HOST_ADDRESSES.has(address.host))
.map((address) => node_core_1.createEndpoint({
type: 'tcp',
host: address.host,
port: address.port,
}))
.forEach((endpoint) => this.network.addEndpoint(endpoint));
}
async onBlockMessageReceived(peer, block) {
const blockIndex = this.mutableBlockIndex[peer.endpoint];
this.mutableBlockIndex[peer.endpoint] = Math.max(block.index, blockIndex === undefined ? 0 : blockIndex);
await this.relayBlock(block);
}
async persistBlock(block) {
if (this.blockchain.currentBlockIndex > block.index || this.tempKnownBlockHashes.has(block.hashHex)) {
return;
}
if (!this.mutableKnownBlockHashes.has(block.hash)) {
this.tempKnownBlockHashes.add(block.hashHex);
try {
const foundBlock = await this.blockchain.block.tryGet({
hashOrIndex: block.hash,
});
if (foundBlock === undefined) {
try {
await this.blockchain.persistBlock({ block });
if (this.mutableConsensus !== undefined) {
this.mutableConsensus.onPersistBlock();
}
const peer = this.mutableBestPeer;
if (peer !== undefined && block.index > peer.data.startHeight) {
this.relay(this.createMessage({
command: Command_1.Command.inv,
payload: new payload_1.InvPayload({
type: payload_1.InventoryType.Block,
hashes: [block.hash],
}),
}));
}
logger.info({ title: 'neo_relay_block', [utils_1.Labels.NEO_BLOCK_INDEX]: block.index });
}
catch (error) {
logger.error({ title: 'neo_relay_block', [utils_1.Labels.NEO_BLOCK_INDEX]: block.index, error });
throw error;
}
}
this.mutableKnownBlockHashes.add(block.hash);
this.mutableKnownHeaderHashes.add(block.hash);
block.transactions.forEach((transaction) => {
delete this.mutableMemPool[transaction.hashHex];
this.mutableKnownTransactionHashes.add(transaction.hash);
});
client_switch_1.globalStats.record([
{
measure: mempoolSize,
value: Object.keys(this.mutableMemPool).length,
},
]);
}
finally {
this.tempKnownBlockHashes.delete(block.hashHex);
}
}
}
async onConsensusMessageReceived(payload) {
const { consensus } = this;
if (consensus !== undefined) {
await this.blockchain.verifyConsensusPayload(payload);
consensus.onConsensusPayloadReceived(payload);
}
}
onFilterAddMessageReceived(peer, filterAdd) {
if (peer.data.mutableBloomFilter !== undefined) {
peer.data.mutableBloomFilter.insert(filterAdd.data);
}
}
onFilterClearMessageReceived(peer) {
peer.data.mutableBloomFilter = undefined;
}
onFilterLoadMessageReceived(peer, filterLoad) {
peer.data.mutableBloomFilter = createPeerBloomFilter(filterLoad);
}
onGetAddrMessageReceived(peer) {
const addresses = lodash_1.default.take(lodash_1.default.shuffle(this.network.connectedPeers.map((connectedPeer) => connectedPeer.data.address).filter(utils_1.utils.notNull)), GET_ADDR_PEER_COUNT);
if (addresses.length > 0) {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.addr,
payload: new payload_1.AddrPayload({ addresses }),
}));
}
}
async onGetBlocksMessageReceived(peer, getBlocks) {
const headers = await this.getHeaders(getBlocks, this.blockchain.currentBlockIndex);
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.inv,
payload: new payload_1.InvPayload({
type: payload_1.InventoryType.Block,
hashes: headers.map((header) => header.hash),
}),
}));
}
async onGetDataMessageReceived(peer, getData) {
switch (getData.type) {
case payload_1.InventoryType.Transaction:
await Promise.all(getData.hashes.map(async (hash) => {
let transaction = this.mutableMemPool[client_common_1.common.uInt256ToHex(hash)];
if (transaction === undefined) {
transaction = await this.blockchain.transaction.tryGet({ hash });
}
if (transaction !== undefined) {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.tx,
payload: transaction,
}));
}
}));
break;
case payload_1.InventoryType.Block:
await Promise.all(getData.hashes.map(async (hash) => {
const block = await this.blockchain.block.tryGet({
hashOrIndex: hash,
});
if (block !== undefined) {
if (peer.data.mutableBloomFilter === undefined) {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.block,
payload: block,
}));
}
else {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.merkleblock,
payload: this.createMerkleBlockPayload({
block,
flags: block.transactions.map((transaction) => this.testFilter(peer.data.mutableBloomFilter, transaction)),
}),
}));
}
}
}));
break;
case payload_1.InventoryType.Consensus:
getData.hashes.forEach((hash) => {
const payload = this.consensusCache.get(client_common_1.common.uInt256ToHex(hash));
if (payload !== undefined) {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.consensus,
payload,
}));
}
});
break;
default:
utils_1.utils.assertNever(getData.type);
}
}
async onGetHeadersMessageReceived(peer, getBlocks) {
const headers = await this.getHeaders(getBlocks, this.blockchain.currentHeader.index);
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.headers,
payload: new payload_1.HeadersPayload({ headers }),
}));
}
async onHeadersMessageReceived(peer, headersPayload) {
const headers = headersPayload.headers.filter((header) => !this.mutableKnownHeaderHashes.has(header.hash) && !this.tempKnownHeaderHashes.has(header.hashHex));
if (headers.length > 0) {
headers.forEach((header) => {
this.tempKnownHeaderHashes.add(header.hashHex);
});
try {
await this.blockchain.persistHeaders(headers);
headers.forEach((header) => {
this.mutableKnownHeaderHashes.add(header.hash);
});
}
finally {
headers.forEach((header) => {
this.tempKnownHeaderHashes.delete(header.hashHex);
});
}
}
if (this.blockchain.currentHeader.index < peer.data.startHeight) {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.getheaders,
payload: new payload_1.GetBlocksPayload({
hashStart: [this.blockchain.currentHeader.hash],
}),
}));
}
}
onInvMessageReceived(peer, inv) {
let hashes;
switch (inv.type) {
case payload_1.InventoryType.Transaction:
hashes = inv.hashes.filter((hash) => !this.mutableKnownTransactionHashes.has(hash) &&
!this.tempKnownTransactionHashes.has(client_common_1.common.uInt256ToHex(hash)));
break;
case payload_1.InventoryType.Block:
hashes = inv.hashes.filter((hash) => !this.mutableKnownBlockHashes.has(hash) && !this.tempKnownBlockHashes.has(client_common_1.common.uInt256ToHex(hash)));
break;
case payload_1.InventoryType.Consensus:
hashes = inv.hashes;
break;
default:
utils_1.utils.assertNever(inv.type);
hashes = [];
}
if (hashes.length > 0) {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.getdata,
payload: new payload_1.InvPayload({ type: inv.type, hashes }),
}));
}
}
onMemPoolMessageReceived(peer) {
this.sendMessage(peer, this.createMessage({
command: Command_1.Command.inv,
payload: new payload_1.InvPayload({
type: payload_1.InventoryType.Transaction,
hashes: Object.values(this.mutableMemPool).map((transaction) => transaction.hash),
}),
}));
}
async onTransactionReceived(transaction) {
if (this.ready()) {
if (transaction.type === node_core_1.TransactionType.Miner) {
if (this.mutableConsensus !== undefined) {
this.mutableConsensus.onTransactionReceived(transaction);
}
}
else {
await this.relayTransaction(transaction);
}
}
}
onVerackMessageReceived(peer) {
peer.close();
}
onVersionMessageReceived(peer) {
peer.close();
}
async getHeaders(getBlocks, maxHeight) {
let hashStopIndexPromise = Promise.resolve(maxHeight);
if (!getBlocks.hashStop.equals(client_common_1.common.ZERO_UINT256)) {
hashStopIndexPromise = this.blockchain.header
.tryGet({ hashOrIndex: getBlocks.hashStop })
.then((hashStopHeader) => hashStopHeader === undefined ? maxHeight : Math.min(hashStopHeader.index, maxHeight));
}
const [hashStartHeaders, hashEnd] = await Promise.all([
Promise.all(getBlocks.hashStart.map(async (hash) => this.blockchain.header.tryGet({ hashOrIndex: hash }))),
hashStopIndexPromise,
]);
const hashStartHeader = lodash_1.default.head(lodash_1.default.orderBy(hashStartHeaders.filter(utils_1.utils.notNull), [(header) => header.index]));
if (hashStartHeader === undefined) {
return [];
}
const hashStart = hashStartHeader.index + 1;
if (hashStart > maxHeight) {
return [];
}
return Promise.all(lodash_1.default.range(hashStart, Math.min(hashStart + GET_BLOCKS_COUNT, hashEnd)).map(async (index) => this.blockchain.header.get({ hashOrIndex: index })));
}
testFilter(bloomFilterIn, transaction) {
const bloomFilter = bloomFilterIn;
if (bloomFilter === undefined) {
return true;
}
return (bloomFilter.contains(transaction.hash) ||
transaction.outputs.some((output) => bloomFilter.contains(output.address)) ||
transaction.inputs.some((input) => bloomFilter.contains(input.serializeWire())) ||
transaction.scripts.some((script) => bloomFilter.contains(client_common_1.crypto.toScriptHash(script.verification))) ||
(transaction.type === node_core_1.TransactionType.Register &&
transaction instanceof node_core_1.RegisterTransaction &&
bloomFilter.contains(transaction.asset.admin)));
}
createMerkleBlockPayload({ block, flags, }) {
const tree = new node_core_1.MerkleTree(block.transactions.map((transaction) => transaction.hash)).trim(flags);
const mutableBuffer = Buffer.allocUnsafe(Math.floor((flags.length + 7) / 8));
for (let i = 0; i < flags.length; i += 1) {
if (flags[i]) {
mutableBuffer[Math.floor(i / 8)] |= 1 << i % 8;
}
}
return new payload_1.MerkleBlockPayload({
version: block.version,
previousHash: block.previousHash,
merkleRoot: block.merkleRoot,
timestamp: block.timestamp,
index: block.index,
consensusData: block.consensusData,
nextConsensus: block.nextConsensus,
script: block.script,
transactionCount: block.transactions.length,
hashes: tree.toHashArray(),
flags: mutableBuffer,
});
}
createMessage(value) {
return new Message_1.Message({
magic: this.blockchain.settings.messageMagic,
value,
});
}
}
exports.Node = Node;
//# sourceMappingURL=data:application/json;charset=utf8;base64,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