Source: Models/Blockchain.js

const Block = require('./Block');
const Transaction = require('./Transaction');

const { isProofValid, generateProof } = require('../Utils/proof');
const SHA256 = require('crypto-js/sha256');

/**
 * This class is the representation of 'blockchain'
 * Blockchain class stores and manages all blocks and transactions
 *  Its the most important aspect of blockchain network
 * @class Blockchain
 */
class Blockchain {
    /**
     * @constructs
     * Constructor of Blockchain class.
     */
    constructor() {
        this.blocks = [Blockchain.createGenesisBlock()];
        this.currentTransactions = [];
        this.miningReward = 50;
        this.balance = 100;
        this.signature = this.calculateSignature();
        this.timestamp = Date.now();
    }

    /**
     * This function creates the genesis block
     * @returns {Block}
     */
    static createGenesisBlock() {
        return new Block(0,1,[],0);
    }

    /**
     * This function return unique SHA256 signature of current block
     * @returns {hash}
     */
    calculateSignature() {
        return SHA256(this.blocks + this.currentTransactions + this.miningReward + this.timestamp).toString();
    }

    /**
     * This functions add block to an array
     * @param block
     */
    mineBlock(block) {
        this.blocks.push(block);
        this.signature = this.calculateSignature();
    }

    /**
     *
     * @param index
     * @param blockInfo
     */
    insertReceivedBlock(index, blockInfo) {
        let block = new Block(index, blockInfo.prevHash, blockInfo.transactions, blockInfo.proof);
        block.timestamp = blockInfo.timestamp;
        this.blocks.push(block);
        this.signature = this.calculateSignature();
    }

    /**
     * This function is my implementation of 'mining'.
     * It takes the currentTransactions and 'mine it' using prof.js utility
     *
     * @param rewardAddress Wallet address of lucky receiver
     */
    mineCurrentTransactions(rewardAddress) {
            if(this.currentTransactions.length >= 2) {
                const prevBlock = this.lastBlock();
                process.env.BREAK = false;
                const block = new Block(prevBlock.getIndex()+1, prevBlock.hashValue(), this.currentTransactions);
                const { proof, dontMine } =  generateProof(prevBlock.getProof());
                block.setProof(proof);
                this.currentTransactions = [];
                if (dontMine !== 'true') {
                    console.log("Mining...");
                    this.mineBlock(block);
                    this.currentTransactions = [
                        new Transaction('MINING REWARD', rewardAddress, this.miningReward)
                    ];
                    console.log("Mined!");
                }
                process.env.BREAK = false;
            }
        this.signature = this.calculateSignature();
    }

    /**
     * This function checks and add transaction to an array
     * @param transaction Transaction to add
     */
    addTransaction(transaction) {
        if(transaction.sender === transaction.receiver)
            throw new Error('You cannot send money to yourself!');

        if(!transaction.sender || !transaction.receiver)
            throw new Error('Transaction must have sender and receiver!');

        if(!transaction.isValid())
            throw new Error('Cannot add invalid transaction!');

        this.currentTransactions.push(transaction);
        this.signature = this.calculateSignature();
    }

    get transactions() {
        return this.currentTransactions;
    }

    /**
     * It returns the balacne of given wallet
     * @param address Wallet addres
     * @returns {number} Amount of coins in wallet
     */
    getBalanceOfAddress(address) {
        let balance = this.balance;
        for (const block of this.blocks) {
            for(const transaction of block.transactions) {
                if(transaction.sender === address)
                    balance -= transaction.amount;
                if(transaction.receiver === address)
                    balance += transaction.amount;
            }
        }
        return balance;
    }

    lastBlock() {
        return this.blocks[this.blocks.length - 1];
    }

    get chain() {
        return this.blocks;
    }

    /**
     * Returns with given index
     * @param index
     * @returns {Block}
     */
    getBlock(index) {
        const wantedBlock = this.blocks.filter(block => {
            return block.index === index;
        });
        return wantedBlock[0];
    }

    /**
     * This function check the validity of the blockchain.
     * It check if all the blocks are valid and also checks their transaction
     * @returns {boolean}
     */
    checkChain() {
        for (let i=1; i<this.blocks.length; i++) {
            const currentBlock = this.blocks[i];
            const prevBlock = this.blocks[i-1];

            if(!currentBlock.hasValidTransactions())
                return false;

            if (currentBlock.prevBlockHash() !== prevBlock.hashValue())
                return false;

            if (!isProofValid(prevBlock.getProof(), currentBlock.getProof()))
                return false;

        }
        return true;
    }

    /**
     * Returns id list of all blocks in blockchain
     * @returns {*[]}
     */
    get listOfId() {
        return this.blocks.map(index => index.index);
    }
}

module.exports = Blockchain;