@neo-one/node-protocol-esnext-esm
Version:
NEO•ONE NEO node and consensus protocol.
874 lines (872 loc) • 39 kB
JavaScript
import { common, crypto, utils } from '@neo-one/client-common-esnext-esm';
import { AggregationType, globalStats, MeasureUnit } from '@neo-one/client-switch-esnext-esm';
import { createChild, nodeLogger } from '@neo-one/logger-esnext-esm';
import { Consensus } from '@neo-one/node-consensus-esnext-esm';
import { createEndpoint, getEndpointConfig, MerkleTree, RegisterTransaction, TransactionType, } from '@neo-one/node-core-esnext-esm';
import { composeDisposables, Labels, labelToTag, noopDisposable, utils as commonUtils, } from '@neo-one/utils-esnext-esm';
import { ScalingBloem } from 'bloem';
import BloomFilter from 'bloom-filter';
import fetch from 'cross-fetch';
import { Address6 } from 'ip-address';
import _ from 'lodash';
import LRUCache from 'lru-cache';
import { Command } from './Command';
import { AlreadyConnectedError, NegotiationError } from './errors';
import { Message, MessageTransform } from './Message';
import { AddrPayload, GetBlocksPayload, HeadersPayload, InventoryType, InvPayload, MerkleBlockPayload, NetworkAddress, SERVICES, VersionPayload, } from './payload';
const logger = createChild(nodeLogger, { component: 'node-protocol' });
const messageReceivedTag = labelToTag(Labels.COMMAND_NAME);
const messagesReceived = globalStats.createMeasureInt64('messages/received', MeasureUnit.UNIT);
const messagesFailed = globalStats.createMeasureInt64('messages/failed', MeasureUnit.UNIT);
const mempoolSize = globalStats.createMeasureInt64('mempool/size', MeasureUnit.UNIT);
const NEO_PROTOCOL_MESSAGES_RECEIVED_TOTAL = globalStats.createView('neo_protocol_messages_received_total', messagesReceived, AggregationType.COUNT, [messageReceivedTag], 'number of messages received');
globalStats.registerView(NEO_PROTOCOL_MESSAGES_RECEIVED_TOTAL);
const NEO_PROTOCOL_MESSAGES_FAILURES_TOTAL = globalStats.createView('neo_protocol_messages_failures_total', messagesFailed, AggregationType.COUNT, [messageReceivedTag], 'number of message failures');
globalStats.registerView(NEO_PROTOCOL_MESSAGES_FAILURES_TOTAL);
const NEO_PROTOCOL_MEMPOOL_SIZE = globalStats.createView('neo_protocol_mempool_size', mempoolSize, AggregationType.LAST_VALUE, [], 'current mempool size');
globalStats.registerView(NEO_PROTOCOL_MEMPOOL_SIZE);
const createPeerBloomFilter = ({ filter, k, tweak, }) => new BloomFilter({
vData: Buffer.from(filter),
nHashFuncs: k,
nTweak: tweak,
});
const createScalingBloomFilter = () => new 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']);
export class Node {
constructor({ blockchain, createNetwork, options, }) {
this.mutableUnhealthyPeerSeconds = UNHEALTHY_PEER_SECONDS;
this.requestBlocks = _.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.getblocks,
payload: new GetBlocksPayload({
hashStart: [block.hash],
}),
}));
}
this.requestBlocks();
}
}, GET_BLOCKS_THROTTLE_MS);
this.onRequestEndpoints = _.throttle(() => {
this.relay(this.createMessage({ command: Command.getaddr }));
this.fetchEndpointsFromRPC();
}, 5000);
this.trimMemPool = _.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 = _.take(transactionAndFees.slice().sort(compareTransactionAndFees), this.blockchain.settings.memPoolSize).map((transactionAndFee) => transactionAndFee.transaction.hashHex);
hashesToRemove.forEach((hash) => {
delete this.mutableMemPool[hash];
});
globalStats.record([
{
measure: mempoolSize,
value: Object.keys(this.mutableMemPool).length,
},
]);
}
}, TRIM_MEMPOOL_THROTTLE);
this.negotiate = async (peer) => {
this.sendMessage(peer, this.createMessage({
command: Command.version,
payload: new VersionPayload({
protocolVersion: 0,
services: 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.version) {
versionPayload = message.value.payload;
}
else {
throw new NegotiationError(message);
}
this.checkVersion(peer, message, versionPayload);
const { host } = getEndpointConfig(peer.endpoint);
let address;
if (NetworkAddress.isValid(host)) {
address = new NetworkAddress({
host,
port: versionPayload.port,
timestamp: versionPayload.timestamp,
services: versionPayload.services,
});
}
this.sendMessage(peer, this.createMessage({ command: Command.verack }));
const nextMessage = await peer.receiveMessage(30000);
if (nextMessage.value.command !== Command.verack) {
throw new NegotiationError(nextMessage);
}
return {
data: {
nonce: versionPayload.nonce,
startHeight: versionPayload.startHeight,
mutableBloomFilter: undefined,
address,
},
relay: versionPayload.relay,
};
};
this.checkPeerHealth = (peer, prevHealth) => {
const checkTimeSeconds = commonUtils.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 &&
commonUtils.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 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() * 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 LRUCache(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 = noopDisposable;
try {
this.network.start();
logger.debug({ name: 'neo_protocol_start' }, 'Protocol started.');
disposable = composeDisposables(disposable, () => {
this.network.stop();
logger.debug({ name: 'neo_protocol_stop' }, 'Protocol stopped.');
});
if (this.options.consensus !== undefined) {
const mutableConsensus = new Consensus({
options: this.options.consensus,
node: this,
});
this.mutableConsensus = mutableConsensus;
const consensusDisposable = await mutableConsensus.start();
disposable = 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 === 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 = { [Labels.NEO_TRANSACTION_HASH]: transaction.hashHex };
let finalResult;
try {
let foundTransaction;
try {
foundTransaction = await this.blockchain.transaction.tryGet({
hash: transaction.hash,
});
}
finally {
logLabels = {
[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;
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({ name: 'neo_relay_transaction', ...logLabels });
}
catch (err) {
logger.error({ name: 'neo_relay_transaction', err, ...logLabels });
throw err;
}
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.inv,
payload: new InvPayload({
type: InventoryType.Consensus,
hashes: [payload.hash],
}),
});
this.consensusCache.set(payload.hashHex, payload);
this.relay(message);
}
syncMemPool() {
this.relay(this.createMessage({ command: Command.mempool }));
}
relay(message) {
this.network.relay(message.serializeWire());
}
relayTransactionInternal(transaction) {
const message = this.createMessage({
command: Command.inv,
payload: new InvPayload({
type: 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 = _.maxBy(peers, (peer) => peer.data.startHeight);
if (result === undefined) {
return undefined;
}
return _.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 NegotiationError(message, 'Nonce equals my nonce.');
}
const connectedPeer = this.network.connectedPeers.find((otherPeer) => version.nonce === otherPeer.data.nonce);
if (connectedPeer !== undefined) {
throw new 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 {
}
}
async doFetchEndpointsFromRPC() {
const { rpcURLs = [] } = this.options;
await Promise.all(rpcURLs.map(async (rpcURL) => this.fetchEndpointsFromRPCURL(rpcURL)));
}
async fetchEndpointsFromRPCURL(rpcURL) {
try {
const response = await fetch(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 Address6(address).canonicalForm();
if (canonicalForm == undefined) {
canonicalForm = Address6.fromAddress4(address).canonicalForm();
}
return { host: canonicalForm == undefined ? '' : canonicalForm, port };
})
.filter((endpoint) => !LOCAL_HOST_ADDRESSES.has(endpoint.host))
.map((endpoint) => createEndpoint({
type: 'tcp',
host: endpoint.host,
port: endpoint.port,
}))
.forEach((endpoint) => this.network.addEndpoint(endpoint));
}
catch (err) {
logger.error({ name: 'neo_protocol_fetch_endpoints_error', [Labels.HTTP_URL]: rpcURL, err }, `Failed to fetch endpoints from ${rpcURL}`);
}
}
onMessageReceived(peer, message) {
try {
new Promise(async (resolve) => {
switch (message.value.command) {
case Command.addr:
this.onAddrMessageReceived(message.value.payload);
break;
case Command.block:
await this.onBlockMessageReceived(peer, message.value.payload);
break;
case Command.consensus:
await this.onConsensusMessageReceived(message.value.payload);
break;
case Command.filteradd:
this.onFilterAddMessageReceived(peer, message.value.payload);
break;
case Command.filterclear:
this.onFilterClearMessageReceived(peer);
break;
case Command.filterload:
this.onFilterLoadMessageReceived(peer, message.value.payload);
break;
case Command.getaddr:
this.onGetAddrMessageReceived(peer);
break;
case Command.getblocks:
await this.onGetBlocksMessageReceived(peer, message.value.payload);
break;
case Command.getdata:
await this.onGetDataMessageReceived(peer, message.value.payload);
break;
case Command.getheaders:
await this.onGetHeadersMessageReceived(peer, message.value.payload);
break;
case Command.headers:
await this.onHeadersMessageReceived(peer, message.value.payload);
break;
case Command.inv:
this.onInvMessageReceived(peer, message.value.payload);
break;
case Command.mempool:
this.onMemPoolMessageReceived(peer);
break;
case Command.tx:
await this.onTransactionReceived(message.value.payload);
break;
case Command.verack:
this.onVerackMessageReceived(peer);
break;
case Command.version:
this.onVersionMessageReceived(peer);
break;
case Command.alert:
break;
case Command.merkleblock:
break;
case Command.notfound:
break;
case Command.ping:
break;
case Command.pong:
break;
case Command.reject:
break;
default:
commonUtils.assertNever(message.value);
}
resolve();
}).catch(() => {
});
}
catch {
}
}
onAddrMessageReceived(addr) {
addr.addresses
.filter((address) => !LOCAL_HOST_ADDRESSES.has(address.host))
.map((address) => 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.inv,
payload: new InvPayload({
type: InventoryType.Block,
hashes: [block.hash],
}),
}));
}
logger.info({ name: 'neo_relay_block', [Labels.NEO_BLOCK_INDEX]: block.index });
}
catch (err) {
logger.error({ name: 'neo_relay_block', [Labels.NEO_BLOCK_INDEX]: block.index, err });
throw err;
}
}
this.mutableKnownBlockHashes.add(block.hash);
this.mutableKnownHeaderHashes.add(block.hash);
block.transactions.forEach((transaction) => {
delete this.mutableMemPool[transaction.hashHex];
this.mutableKnownTransactionHashes.add(transaction.hash);
});
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 = _.take(_.shuffle(this.network.connectedPeers.map((connectedPeer) => connectedPeer.data.address).filter(commonUtils.notNull)), GET_ADDR_PEER_COUNT);
if (addresses.length > 0) {
this.sendMessage(peer, this.createMessage({
command: Command.addr,
payload: new AddrPayload({ addresses }),
}));
}
}
async onGetBlocksMessageReceived(peer, getBlocks) {
const headers = await this.getHeaders(getBlocks, this.blockchain.currentBlockIndex);
this.sendMessage(peer, this.createMessage({
command: Command.inv,
payload: new InvPayload({
type: InventoryType.Block,
hashes: headers.map((header) => header.hash),
}),
}));
}
async onGetDataMessageReceived(peer, getData) {
switch (getData.type) {
case InventoryType.Transaction:
await Promise.all(getData.hashes.map(async (hash) => {
let transaction = this.mutableMemPool[common.uInt256ToHex(hash)];
if (transaction === undefined) {
transaction = await this.blockchain.transaction.tryGet({ hash });
}
if (transaction !== undefined) {
this.sendMessage(peer, this.createMessage({
command: Command.tx,
payload: transaction,
}));
}
}));
break;
case 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.block,
payload: block,
}));
}
else {
this.sendMessage(peer, this.createMessage({
command: Command.merkleblock,
payload: this.createMerkleBlockPayload({
block,
flags: block.transactions.map((transaction) => this.testFilter(peer.data.mutableBloomFilter, transaction)),
}),
}));
}
}
}));
break;
case InventoryType.Consensus:
getData.hashes.forEach((hash) => {
const payload = this.consensusCache.get(common.uInt256ToHex(hash));
if (payload !== undefined) {
this.sendMessage(peer, this.createMessage({
command: Command.consensus,
payload,
}));
}
});
break;
default:
commonUtils.assertNever(getData.type);
}
}
async onGetHeadersMessageReceived(peer, getBlocks) {
const headers = await this.getHeaders(getBlocks, this.blockchain.currentHeader.index);
this.sendMessage(peer, this.createMessage({
command: Command.headers,
payload: new 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.getheaders,
payload: new GetBlocksPayload({
hashStart: [this.blockchain.currentHeader.hash],
}),
}));
}
}
onInvMessageReceived(peer, inv) {
let hashes;
switch (inv.type) {
case InventoryType.Transaction:
hashes = inv.hashes.filter((hash) => !this.mutableKnownTransactionHashes.has(hash) &&
!this.tempKnownTransactionHashes.has(common.uInt256ToHex(hash)));
break;
case InventoryType.Block:
hashes = inv.hashes.filter((hash) => !this.mutableKnownBlockHashes.has(hash) && !this.tempKnownBlockHashes.has(common.uInt256ToHex(hash)));
break;
case InventoryType.Consensus:
hashes = inv.hashes;
break;
default:
commonUtils.assertNever(inv.type);
hashes = [];
}
if (hashes.length > 0) {
this.sendMessage(peer, this.createMessage({
command: Command.getdata,
payload: new InvPayload({ type: inv.type, hashes }),
}));
}
}
onMemPoolMessageReceived(peer) {
this.sendMessage(peer, this.createMessage({
command: Command.inv,
payload: new InvPayload({
type: InventoryType.Transaction,
hashes: Object.values(this.mutableMemPool).map((transaction) => transaction.hash),
}),
}));
}
async onTransactionReceived(transaction) {
if (this.ready()) {
if (transaction.type === 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(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 = _.head(_.orderBy(hashStartHeaders.filter(commonUtils.notNull), [(header) => header.index]));
if (hashStartHeader === undefined) {
return [];
}
const hashStart = hashStartHeader.index + 1;
if (hashStart > maxHeight) {
return [];
}
return Promise.all(_.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(crypto.toScriptHash(script.verification))) ||
(transaction.type === TransactionType.Register &&
transaction instanceof RegisterTransaction &&
bloomFilter.contains(transaction.asset.admin)));
}
createMerkleBlockPayload({ block, flags, }) {
const tree = new 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 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({
magic: this.blockchain.settings.messageMagic,
value,
});
}
}
//# sourceMappingURL=Node.js.map