import * as crypto from 'crypto' ; import axios , { AxiosResponse } from 'axios' ; import * as stringify from 'json-stable-stringify' ; import Transaction from './Transaction' ; import Block from './Block' ; import { hash , generateKeyPair , signWithPrivateKey , verifyWithPublicKey , } from './Crypto' ; class Blockchain { public chain : Array < Block > = [ ] ; public pendingTransactions : Array < Transaction > = [ ] ; public nodes : Set < string > = new Set ( ) ; public difficulty : number = 4 ; public reward : number = 10 ; public publicKey : string ; private privateKey : string ; constructor ( ) { [ this . publicKey , this . privateKey ] = generateKeyPair ( ) ; const genesisBlock = this . createBlock ( [ ] , undefined , 0 , 1337 ) ; this . addBlock ( genesisBlock ) ; } public registerNode ( node ) { this . nodes . add ( node ) ; } public createTransaction ( sender , recipient , amount , coinbase = false , timestamp = Date . now ( ) ) { return { sender , recipient , amount , coinbase , timestamp , } ; } public signTransaction ( transaction ) { const transactionHash = hash ( stringify ( transaction ) ) ; const signature = signWithPrivateKey ( this . privateKey , transactionHash ) ; transaction . signature = signature ; return transaction ; } public validateTransaction ( transaction ) { const { signature , ... transactionData } = transaction ; const transactionHash = hash ( stringify ( transactionData ) ) ; return verifyWithPublicKey ( transaction . sender , transactionHash , signature ) ; } public addTransaction ( transaction ) { this . pendingTransactions . push ( transaction ) ; } public async broadcastTransaction ( transaction ) { for ( const node of this . nodes ) { console . log ( `Sending transaction to ${ node }` ) ; await axios . post ( `http://${ node }/transactions` , transaction ) ; } console . log ( `Broadcast done\n` ) ; } public createBlock ( transactions , previousHash , timestamp = Date . now ( ) , nonce = 0 ) { return { transactions , previousHash , timestamp , nonce , difficulty : this . difficulty , } ; } public validateBlock ( block ) { const difficulty = block . difficulty ; const blockHash = hash ( stringify ( block ) ) ; const zeroString = '0' . repeat ( difficulty ) ; return blockHash . indexOf ( zeroString ) === 0 ; } public mine ( ) { const coinbaseTransaction = this . signTransaction ( this . createTransaction ( this . publicKey , this . publicKey , this . reward , true ) ) ; const transactions = [ coinbaseTransaction , ... this . pendingTransactions ] ; const lastBlock = this . chain [ this . chain . length - 1 ] ; const previousHash = hash ( stringify ( lastBlock ) ) ; const block = this . createBlock ( transactions , previousHash , Date . now ( ) , 0 ) ; const minedBlock = this . proofOfWork ( block ) ; this . addBlock ( minedBlock ) ; return minedBlock ; } public proofOfWork ( block ) { block . nonce = 0 ; while ( ! this . validateBlock ( block ) ) { block . nonce += 1 ; } return block ; } public addBlock ( block ) { this . chain . push ( block ) ; this . pendingTransactions = this . pendingTransactions . filter ( transaction => ! block . transactions . map ( blockTransaction => blockTransaction . signature ) . includes ( transaction . signature ) ) ; } public async broadcastBlock ( block ) { for ( const node of this . nodes ) { console . log ( `Sending block to ${ node }` ) ; await axios . post ( `http://${ node }/blocks` , block ) ; } console . log ( `Broadcast done\n` ) ; } public validateChain ( ) { for ( let index = this . chain . length - 1 ; index > 0 ; index -= 1 ) { const block = this . chain [ index ] ; const previusBlock = this . chain [ index - 1 ] ; const previusBlockHash = hash ( stringify ( previusBlock ) ) ; if ( ! ( this . validateBlock ( block ) && ( block . previousHash === previusBlockHash ) ) ) { return false ; } } return true ; } } export default Blockchain ;