mirror of
				https://github.com/hackku21/loc-chain-backend.git
				synced 2025-06-13 12:53:59 +00:00 
			
		
		
		
	Add Blockchain.submitTransactions
This commit is contained in:
		
							parent
							
								
									6441a5cad7
								
							
						
					
					
						commit
						3b7e72adab
					
				| @ -4,6 +4,9 @@ import {FirebaseUnit, RTDBRef} from "./units/FirebaseUnit" | ||||
| import * as firebase from "firebase-admin" | ||||
| import {Application} from "@extollo/lib"; | ||||
| 
 | ||||
| /** | ||||
|  * Base interface for an item in a Firebase RTDB collection. | ||||
|  */ | ||||
| export interface FirebaseResourceItem { | ||||
|     firebaseID: string; | ||||
|     seqID: number; | ||||
| @ -17,15 +20,17 @@ export class FirebaseResource<T extends FirebaseResourceItem> extends Iterable<T | ||||
|         return Application.getApplication().make<FirebaseUnit>(FirebaseUnit).ref(this.refName) | ||||
|     } | ||||
| 
 | ||||
|     /** Get the next sequential ID. */ | ||||
|     async getNextID(): Promise<number> { | ||||
|         return new Promise<number>((res, rej) => { | ||||
|             this.ref().orderByChild('seqID') | ||||
|                 .on('value', snapshot => { | ||||
|                     res(this.resolveObject(snapshot.val()).reverse()?.[0]?.seqID || 1) | ||||
|                     res((this.resolveObject(snapshot.val()).reverse()?.[0]?.seqID || 0) + 1) | ||||
|                 }, rej) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /** Get the record at the ith index. */ | ||||
|     async at(i: number): Promise<T | undefined> { | ||||
|         return new Promise<T | undefined>((res, rej) => { | ||||
|             this.ref().orderByChild('seqID') | ||||
| @ -34,6 +39,7 @@ export class FirebaseResource<T extends FirebaseResourceItem> extends Iterable<T | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /** Fetch an array of records in a range. */ | ||||
|     async range(start: number, end: number): Promise<Collection<T>> { | ||||
|         return new Promise<Collection<T>>((res, rej) => { | ||||
|             this.ref().orderByChild('seqID') | ||||
| @ -44,6 +50,7 @@ export class FirebaseResource<T extends FirebaseResourceItem> extends Iterable<T | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /** Count the items in the collection. */ | ||||
|     async count(): Promise<number> { | ||||
|         return new Promise<number>((res, rej) => { | ||||
|             this.ref().orderByChild('seqID') | ||||
| @ -53,6 +60,21 @@ export class FirebaseResource<T extends FirebaseResourceItem> extends Iterable<T | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Push a new item into the collection. | ||||
|      * @param item | ||||
|      */ | ||||
|     async push(item: T): Promise<T> { | ||||
|         item.seqID = await this.getNextID() | ||||
|         await this.ref().push(item) | ||||
|         return item | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given the value of a realtime-database snapshot, resolve it to an array of T. | ||||
|      * @param snapshot | ||||
|      * @protected | ||||
|      */ | ||||
|     protected resolveObject(snapshot: any | null | undefined) { | ||||
|         if ( !snapshot ) snapshot = {} | ||||
| 
 | ||||
|  | ||||
| @ -3,17 +3,24 @@ import {Injectable} from "@extollo/di" | ||||
| import {RTDBRef} from "../units/FirebaseUnit" | ||||
| import {AsyncCollection} from "@extollo/util" | ||||
| 
 | ||||
| /** | ||||
|  * A block transaction representing an encounter between two clients. | ||||
|  */ | ||||
| export interface BlockEncounterTransaction { | ||||
|     combinedHash: string; | ||||
|     timestamp: number; | ||||
|     encodedGPSLocation: string; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * A block transaction representing an infected client. | ||||
|  */ | ||||
| export interface BlockInfectionTransaction { | ||||
|     clientID: string; | ||||
|     timestamp: number; | ||||
| } | ||||
| 
 | ||||
| /** Union type of all possible block transactions. */ | ||||
| export type BlockTransaction = BlockInfectionTransaction | BlockEncounterTransaction | ||||
| 
 | ||||
| export function isBlockEncounterTransaction(what: any): what is BlockEncounterTransaction { | ||||
| @ -37,11 +44,11 @@ export function isBlockTransaction(what: any): what is BlockTransaction { | ||||
|     return isBlockEncounterTransaction(what) || isBlockInfectionTransaction(what) | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Interface representing a single block in the chain. | ||||
|  */ | ||||
| export interface BlockResourceItem extends FirebaseResourceItem { | ||||
|     uuid: string; | ||||
|     combinedHash: string; | ||||
|     timestamp: number; | ||||
|     encodedGPSLocation: string; | ||||
|     transactions: BlockTransaction[]; | ||||
|     lastBlockHash: string; | ||||
|     lastBlockUUID: string; | ||||
|  | ||||
| @ -3,6 +3,9 @@ import {Injectable} from "@extollo/di" | ||||
| import {RTDBRef} from "../units/FirebaseUnit" | ||||
| import {AsyncCollection} from "@extollo/util"; | ||||
| 
 | ||||
| /** | ||||
|  * Interface representing a client-submitted encounter transaction. | ||||
|  */ | ||||
| export interface TransactionResourceItem extends FirebaseResourceItem { | ||||
|     combinedHash: string; | ||||
|     timestamp: number; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import {Singleton, Inject} from "@extollo/di" | ||||
| import {Unit, Logging, Config} from "@extollo/lib" | ||||
| import {FirebaseUnit} from "./FirebaseUnit" | ||||
| import {BlockResource, BlockResourceItem, BlockTransaction} from "../rtdb/BlockResource" | ||||
| import {BlockEncounterTransaction, BlockResource, BlockResourceItem, BlockTransaction} from "../rtdb/BlockResource" | ||||
| import {TransactionResourceItem} from "../rtdb/TransactionResource" | ||||
| import * as openpgp from "openpgp" | ||||
| import * as crypto from "crypto" | ||||
| @ -11,9 +11,6 @@ export class Block implements BlockResourceItem { | ||||
|     firebaseID: string; | ||||
|     seqID: number; | ||||
|     uuid: string; | ||||
|     combinedHash: string; | ||||
|     timestamp: number; | ||||
|     encodedGPSLocation: string; | ||||
|     transactions: BlockTransaction[]; | ||||
|     lastBlockHash: string; | ||||
|     lastBlockUUID: string; | ||||
| @ -23,9 +20,6 @@ export class Block implements 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 | ||||
| @ -38,12 +32,21 @@ export class Block implements BlockResourceItem { | ||||
|             .digest('hex') | ||||
|     } | ||||
| 
 | ||||
|     toItem(): BlockResourceItem { | ||||
|         return { | ||||
|             seqID: this.seqID, | ||||
|             firebaseID: this.firebaseID, | ||||
|             uuid: this.uuid, | ||||
|             transactions: this.transactions, | ||||
|             lastBlockHash: this.lastBlockHash, | ||||
|             lastBlockUUID: this.lastBlockUUID, | ||||
|             proof: this.proof, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     toString() { | ||||
|         return [ | ||||
|             this.uuid, | ||||
|             this.combinedHash, | ||||
|             this.timestamp.toString(), | ||||
|             this.encodedGPSLocation, | ||||
|             JSON.stringify(this.transactions), | ||||
|             this.lastBlockHash, | ||||
|             this.lastBlockUUID, | ||||
| @ -72,22 +75,38 @@ export class Blockchain extends Unit { | ||||
|     @Inject() | ||||
|     protected readonly config!: Config | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if the given host is registered as a peer. | ||||
|      * @param host | ||||
|      */ | ||||
|     public async hasPeer(host: string): Promise<boolean> { | ||||
|         const peers = await this.getPeers() | ||||
|         return peers.some(peer => peer.host.toLowerCase() === host.toLowerCase()) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a list of all registered peers. | ||||
|      */ | ||||
|     public async getPeers(): Promise<Peer[]> { | ||||
|         const data = await this.firebase.ref('peers').once('value') | ||||
|         return (data.val() as Peer[]) || [] | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Register a new host as a peer of this instance. | ||||
|      * @param peer | ||||
|      */ | ||||
|     public async registerPeer(peer: Peer) { | ||||
|         if ( !(await this.hasPeer(peer.host)) ) { | ||||
|             await this.firebase.ref('peers').push().set(peer) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given an array of blocks in chain-order, validate the chain. | ||||
|      * @param chain | ||||
|      * @return boolean - true if the chain is valid | ||||
|      */ | ||||
|     public async validate(chain: Block[]) { | ||||
|         const blocks = collect<Block>(chain) | ||||
|         return blocks.every((block: Block, idx: number) => { | ||||
| @ -101,14 +120,35 @@ export class Blockchain extends Unit { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public async submitBlock(block: BlockResourceItem, afterBlock: Block, proofToken: string) { | ||||
|         const newBlock = new Block(block) | ||||
|         newBlock.lastBlockHash = afterBlock.hash() | ||||
|         newBlock.lastBlockUUID = afterBlock.uuid | ||||
|     public async submitTransactions(group: [TransactionResourceItem, TransactionResourceItem]) { | ||||
|         let lastBlock = await this.getLastBlock() | ||||
|         if ( !lastBlock ) await this.getGenesisBlock() | ||||
| 
 | ||||
|         const block: BlockResourceItem = { | ||||
|             uuid: uuid_v4(), | ||||
|             transactions: group.map(item => this.getEncounterTransaction(item)), | ||||
|             lastBlockHash: lastBlock!.hash(), | ||||
|             lastBlockUUID: lastBlock!.uuid, | ||||
|             proof: await this.generateProofOfWork(lastBlock!), | ||||
| 
 | ||||
|             firebaseID: '', | ||||
|             seqID: -1, | ||||
|         } | ||||
| 
 | ||||
|         await (<BlockResource> this.app().make(BlockResource)).push(block) | ||||
|         return new Block(block) | ||||
|     } | ||||
| 
 | ||||
|     public async submitTransactions(group: [TransactionResourceItem, TransactionResourceItem]) { | ||||
|         // Not sure yet
 | ||||
|     public async getGenesisBlock(): Promise<Block> { | ||||
|         return new Block({ | ||||
|             uuid: '0000', | ||||
|             transactions: [], | ||||
|             lastBlockHash: '', | ||||
|             lastBlockUUID: '', | ||||
|             proof: '', | ||||
|             firebaseID: '', | ||||
|             seqID: -1, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public async getLastBlock(): Promise<Block | undefined> { | ||||
| @ -124,6 +164,14 @@ export class Blockchain extends Unit { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     protected getEncounterTransaction(item: TransactionResourceItem): BlockEncounterTransaction { | ||||
|         return { | ||||
|             combinedHash: item.combinedHash, | ||||
|             timestamp: item.timestamp, | ||||
|             encodedGPSLocation: item.encodedGPSLocation, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected async generateProofOfWork(lastBlock: Block): Promise<string> { | ||||
|         const hashString = lastBlock.hash() | ||||
|         const privateKey = this.config.get("app.gpg.key.private") | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user