import {Singleton, Inject} from "@extollo/di" import {Unit, Logging} from "@extollo/lib" import {FirebaseUnit} from "./FirebaseUnit" import {BlockResource, BlockResourceItem, BlockTransaction} from "../rtdb/BlockResource" import {TransactionResourceItem} from "../rtdb/TransactionResource" import * as crypto from "crypto" import {collect} from "@extollo/util"; export class Block implements BlockResourceItem { firebaseID: string; seqID: number; uuid: string; combinedHash: string; timestamp: number; encodedGPSLocation: string; transactions: BlockTransaction[]; lastBlockHash: string; lastBlockUUID: string; constructor(rec: BlockResourceItem) { this.firebaseID = rec.firebaseID; this.seqID = rec.seqID this.uuid = rec.uuid this.combinedHash = rec.combinedHash this.timestamp = rec.timestamp this.encodedGPSLocation = rec.encodedGPSLocation this.transactions = rec.transactions this.lastBlockHash = rec.lastBlockHash this.lastBlockUUID = rec.lastBlockUUID } hash() { return crypto.createHash('sha256') .update(this.toString(), 'utf-8') .digest('hex') } toString() { return [ this.uuid, this.combinedHash, this.timestamp.toString(), this.encodedGPSLocation, JSON.stringify(this.transactions), this.lastBlockHash, this.lastBlockUUID, ].join('%') } } export interface Peer { host: string, name?: string, } /** * Blockchain Unit * --------------------------------------- * Main service for interacting with the contact blockchain. */ @Singleton() export class Blockchain extends Unit { @Inject() protected readonly logging!: Logging @Inject() protected readonly firebase!: FirebaseUnit public async hasPeer(host: string): Promise { const peers = await this.getPeers() return peers.some(peer => peer.host.toLowerCase() === host.toLowerCase()) } public async getPeers(): Promise { const data = await this.firebase.ref('peers').once('value') return (data.val() as Peer[]) || [] } public async registerPeer(peer: Peer) { if ( !(await this.hasPeer(peer.host)) ) { await this.firebase.ref('peers').push().set(peer) } } public async validate(chain: Block[]) { const blocks = collect(chain) return blocks.every((block: Block, idx: number) => { if ( idx === 0 ) return true; // TODO handle genesis return block.lastBlockHash === blocks.at(idx)!.hash() }) } public async refresh() { } public async submitBlock(afterBlock: Block, proofToken: string) { } public async submitTransactions(group: [TransactionResourceItem, TransactionResourceItem]) { // Not sure yet } public async getLastBlock(): Promise { const rec: BlockResourceItem | undefined = await BlockResource.collect().last() return rec ? new Block(rec) : undefined } public async up() { } public async down() { } protected async generateProofOfWork(lastBlock: Block): Promise { const hash = lastBlock.hash() // Create a signature from the hash using the server's private key return hash } }