UNPKG

@neo-one/node-protocol

Version:

NEO•ONE NEO node and consensus protocol.

874 lines (873 loc) 139 kB
"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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