import * as Debug from "debug" ; const debug = Debug ( "blockchain:blockchain" ) ; import BlockHandler from "./BlockHandler" ; import Signature from "./crypto/Signature" ; import Address from "./crypto/Address" ; import Database from "./db/Database" ; import ConfigInterface from "./interfaces/ConfigInterface" ; import BlockInterface from "./interfaces/BlockInterface" ; import TransactionInterface from "./interfaces/TransactionInterface" ; import Peer from "./Peer" ; import { parseHost } from "./utils" ; import AddressInterface from "./interfaces/AddressInterface" ; export default class Blockchain { private config : ConfigInterface ; public nodeAddress : AddressInterface ; public blockHandler : BlockHandler ; public signature : Signature ; public address : Address ; private db : Database ; private currentTransactions : TransactionInterface [ ] ; private conflictResolvationRunning : boolean ; private p2p : Peer ; constructor ( config ) { this . config = config ; const { difficulty , nodeAddress } = this . config . blockchain ; this . db = new Database ( config ) ; this . blockHandler = new BlockHandler ( this , this . db , difficulty ) ; this . signature = new Signature ( config ) ; this . address = new Address ( config ) ; this . nodeAddress = nodeAddress || this . address . createAddress ( ) ; this . currentTransactions = [ ] ; this . conflictResolvationRunning = false ; this . p2p = new Peer ( config ) ; } private removeTransactionForSignature ( signature ? ) { let i = 0 ; for ( const transaction of this . currentTransactions ) { if ( transaction . signature === signature ) { this . currentTransactions . splice ( i , 1 ) ; return true ; } i ++ ; } return false ; } param address public registerNode ( address ) { this . p2p . registerNode ( address ) ; } param address public removeNode ( address ) { this . p2p . removeNode ( address ) ; } /**
     * returns false if this node is currently busy
     */ public doesAcceptOperations ( ) { return ! this . conflictResolvationRunning ; } param host public async registerSelfAtOtherNode ( host ) { return await this . p2p . registerSelfAtOtherNode ( host ) ; } /**
     * resolve conflicts faster by only fetching the chain sizes
     * from the nodes
     */ public async resolveConflicts ( ) { debug ( "resolving conflicts fast." ) ; if ( this . conflictResolvationRunning ) { debug ( "conflict resolvation still running." ) ; return false ; } this . conflictResolvationRunning = true ; const resolveRecursive = async ( ) => { if ( ! this . getNodesCount ( ) ) { debug ( "no more nodes left" ) ; return false ; } const { host } = await this . findLeadingNode ( ) ; if ( host === null ) { debug ( "current node is leading node." ) ; return false ; } const chain = await this . p2p . fetchChainFromNode ( host ) ; if ( ! chain || ! chain . length ) { debug ( "fetch failed, removing node" ) ; this . removeNode ( host ) ; return resolveRecursive ( ) ; } const oldLength = await this . getLength ( ) ; await this . db . replaceFullChain ( chain ) ; const newLength = await this . getLength ( ) ; debug ( "replacing chain with" , newLength , "from" , host ) ; if ( newLength < oldLength ) { debug ( "fetched chain is smaller than expected, removing node" ) ; this . removeNode ( host ) ; return resolveRecursive ( ) ; } const blockchain = await this . db . getFullDBAsBlocks ( ) ; if ( ! this . blockHandler . isChainValid ( blockchain ) ) { debug ( "fetched chain is invalid, removing node" ) ; this . removeNode ( host ) ; return resolveRecursive ( ) ; } debug ( "fetched and exchanged chain is valid, synced with" , host ) ; return true ; } ; return resolveRecursive ( ) . then ( ( result ) => { this . conflictResolvationRunning = false ; return result ; } ) . catch ( ( error ) => { this . conflictResolvationRunning = false ; debug ( "conflict resolvation failed with error" , error . message ) ; return false ; } ) ; } /**
     * searches for the leading node amonst all known nodes
     * using their chains length
     */ public async findLeadingNode ( ) { debug ( "searching for leading node" ) ; const fetchPromises = [ ] ; this . p2p . nodes . forEach ( ( host ) => { fetchPromises . push ( this . p2p . fetchChainLengthFromNode ( host ) . then ( ( length ) => { return { host , length , } ; } ) ) ; } ) ; return Promise . all ( fetchPromises ) . then ( async ( results ) => { let leadingHost = null ; let maxLength = await this . getLength ( ) ; results . forEach ( ( { host , length } ) => { if ( length > maxLength ) { leadingHost = host ; maxLength = length ; } } ) ; return { host : leadingHost , length : maxLength , } ; } ) ; } param transaction param signature public async publishTransactionToKnownNodes ( transaction ) { const publishPromises = [ ] ; this . p2p . nodes . forEach ( ( host ) => { publishPromises . push ( this . p2p . publishTransaction ( transaction , host ) ) ; } ) ; return Promise . all ( publishPromises ) ; } param block public async publishBlockToKnownNodes ( block ) { const publishPromises = [ ] ; this . p2p . nodes . forEach ( ( host ) => { publishPromises . push ( this . p2p . publishBlock ( block , host ) ) ; } ) ; return Promise . all ( publishPromises ) ; } public applyTransactionInformation ( newTransaction ) { let found = false ; this . currentTransactions . forEach ( ( transaction ) => { if ( newTransaction . sender === transaction . sender && newTransaction . recipient === transaction . recipient ) { found = true ; } } ) ; if ( found ) { debug ( "transaction already exists, wont add." ) ; return false ; } debug ( "transaction does not exist, adding" ) ; this . addTransaction ( newTransaction ) ; return true ; } public async applyBlockInformation ( block ) { if ( ! this . blockHandler . isBlockValid ( block ) ) { debug ( "block from other node is invalid, wont add" , block . index ) ; return false ; } if ( await this . db . checkIfBlockExists ( block ) ) { debug ( "block already exists" , block . index ) ; return false ; } block . transactions . forEach ( ( transaction ) => { this . removeTransactionForSignature ( transaction . signature ) ; } ) ; await this . db . storeBlock ( block ) ; debug ( "added block from other node" , block . index ) ; return true ; } public getNodesAsList ( ) { const list = [ ] ; this . p2p . nodes . forEach ( ( host ) => { list . push ( host ) ; } ) ; return list ; } public getNodesCount ( ) { return this . p2p . nodes . size ; } public getBlockHandler ( ) { return this . blockHandler ; } public getDatabase ( ) { return this . db ; } /**
     * db-proxy
     */ public async getLength ( ) { return this . db . getChainLength ( ) ; } public async getNextBlockIndex ( ) { return ( await this . getLength ( ) ) + 1 ; } public getCurrentTransactions ( ) { return this . currentTransactions ; } public clearTransactions ( ) { this . currentTransactions = [ ] ; } /**
     * db-proxy
     */ public async getLastBlock ( ) { return this . db . getLastBlock ( ) ; } /**
     * db-proxy
     */ public streamChainFromDB ( ) { return this . db . getWholeChain ( ) ; } param block public async storeBlock ( block ) { await this . db . storeBlock ( block ) ; } param address public async getAddressBalance ( address ) { return await this . db . getBalanceOfAddress ( address ) ; } param address public async getAddressTransactions ( address ) { return await this . db . getTransactionsOfAddress ( address ) ; } /**
     * creates a new block (stores it in database) and publishes it
     */ public async addBlock ( proof , previousHash , publish = true ) { const block = await this . blockHandler . newBlock ( proof , previousHash ) ; if ( publish ) { this . publishBlockToKnownNodes ( block ) . catch ( ( ) => { } ) ; } return block ; } param transaction param publish public async addTransaction ( transaction , publish = true ) { this . currentTransactions . push ( transaction ) ; if ( publish ) { this . publishTransactionToKnownNodes ( transaction ) . catch ( ( ) => { } ) ; } return await this . getNextBlockIndex ( ) ; } public async init ( ) { await this . db . init ( ) ; const lastBlock = await this . db . getLastBlock ( ) ; if ( ! lastBlock ) { this . addTransaction ( { amount : 0 , sender : this . config . blockchain . mintAddress , recipient : this . config . blockchain . mintAddress , timestamp : Date . now ( ) , payload : "" , signature : "" , } , false ) ; await this . blockHandler . newBlock ( 100 , "1" ) ; debug ( "no block present, created genisis block" ) ; } else { debug ( "blocks present, wont create genisis block" ) ; } await this . resolveConflicts ( ) ; } public close ( ) { this . db . close ( ) ; } }