Home Reference Source Test

src/main/generic/consensus/nano/NanoConsensus.js

class NanoConsensus extends BaseConsensus {
    /**
     * @param {NanoChain} blockchain
     * @param {NanoMempool} mempool
     * @param {Network} network
     */
    constructor(blockchain, mempool, network) {
        super(blockchain, mempool, network);
        /** @type {NanoChain} */
        this._blockchain = blockchain;
        /** @type {NanoMempool} */
        this._mempool = mempool;

        /** @type {Subscription} */
        this._subscription = Subscription.BLOCKS_ONLY;
    }

    /**
     * @param {Array.<Address>} addresses
     */
    subscribeAccounts(addresses) {
        this.subscribe(Subscription.fromAddresses(addresses));
        this._mempool.evictExceptAddresses(addresses);
        for (const /** @type {NanoConsensusAgent} */ agent of this._agents.valueIterator()) {
            agent.requestMempool();
        }
    }

    /**
     * @param {Peer} peer
     * @returns {BaseConsensusAgent}
     * @override
     */
    _newConsensusAgent(peer) {
        return new NanoConsensusAgent(this._blockchain, this._mempool, this._network.time, peer, this._invRequestManager, this._subscription);
    }

    /**
     * @param {Peer} peer
     * @override
     */
    _onPeerJoined(peer) {
        const agent = super._onPeerJoined(peer);

        // Forward sync events.
        this.bubble(agent, 'sync-chain-proof', 'verify-chain-proof', 'transaction-relayed');

        return agent;
    }

    /**
     * @param {Block} head
     * @override
     */
    async _onHeadChanged(head) {
        if (!this._established) return;

        // Update mempool.
        try {
            const includedTransactions = await this._requestTransactionsProof(this._subscription.addresses, head);
            this._mempool.changeHead(head, includedTransactions);
        } catch (e) {
            Log.e(NanoConsensus, `Failed to retrieve transaction proof to update mempool: ${e.message || e}`);
        }

        // Relay block *after* requesting the TransactionsProof. Otherwise, we might
        // send the request to a peer (first) that has not adopted the new block yet.
        super._onHeadChanged(head);
    }

    /**
     * @param {Transaction} tx
     * @protected
     */
    _onTransactionAdded(tx) {
        // Don't relay transactions added to the mempool.
    }

    /**
     * @param {Address} address
     * @param {Hash} [blockHash]
     * @returns {Promise.<Account>}
     */
    async getAccount(address, blockHash = null) {
        return (await this.getAccounts([address], blockHash))[0];
    }

    /**
     * @param {Array.<Address>} addresses
     * @param {Hash} [blockHash]
     * @returns {Promise.<Array<Account>>}
     */
    async getAccounts(addresses, blockHash) {
        blockHash = blockHash ? blockHash : this._blockchain.headHash;
        const agents = [];
        for (const agent of this._agents.valueIterator()) {
            if (agent.synced
                && agent.knowsBlock(blockHash)
                && !Services.isNanoNode(agent.peer.peerAddress.services)) {
                agents.push(agent);
            }
        }

        for (const /** @type {NanoConsensusAgent} */ agent of agents) {
            try {
                return await agent.getAccounts(blockHash, addresses); // eslint-disable-line no-await-in-loop
            } catch (e) {
                Log.w(NanoConsensus, `Failed to retrieve accounts ${addresses} from ${agent.peer.peerAddress}: ${e}`);
                // Try the next peer.
            }
        }

        // No peer supplied the requested account, fail.
        throw new Error(`Failed to retrieve accounts ${addresses}`);
    }

    /**
     * @param {Transaction} transaction
     * @returns {Promise.<void>}
     */
    async relayTransaction(transaction) {
        // Fail if we are not connected to at least one full/light node.
        if (!this._agents.values().some(agent => !Services.isNanoNode(agent.peer.peerAddress.services))) {
            throw new Error('Failed to relay transaction - only nano nodes connected');
        }

        // Store transaction in mempool.
        if (!(await this._mempool.pushTransaction(transaction))) {
            throw new Error('Failed to relay transaction - mempool rejected transaction');
        }

        // Relay transaction to all connected peers.
        let relayed = false;
        for (const agent of this._agents.values()) {
            relayed = agent.relayTransaction(transaction) || relayed;
        }

        // Fail if the transaction was not relayed.
        if (!relayed) {
            throw new Error('Failed to relay transaction - no agent relayed transaction');
        }
    }

    /** @type {NanoChain} */
    get blockchain() {
        return this._blockchain;
    }

    /** @type {NanoMempool} */
    get mempool() {
        return this._mempool;
    }
}
Class.register(NanoConsensus);