mirror of
https://github.com/hackku21/loc-chain-backend.git
synced 2024-10-27 20:34:03 +00:00
Add realtime subscription and debug endpoint for pushing exposure notifications to chain
This commit is contained in:
parent
69c441ba56
commit
d8cae0f559
@ -3,12 +3,14 @@ import {CommandLine} from "@extollo/cli"
|
|||||||
import {FirebaseUnit} from "./app/units/FirebaseUnit"
|
import {FirebaseUnit} from "./app/units/FirebaseUnit"
|
||||||
import {Blockchain} from "./app/units/Blockchain"
|
import {Blockchain} from "./app/units/Blockchain"
|
||||||
import {Transaction} from "./app/units/rtdb/Transaction"
|
import {Transaction} from "./app/units/rtdb/Transaction"
|
||||||
|
import {Exposure} from "./app/units/rtdb/Exposure"
|
||||||
|
|
||||||
export const Units = [
|
export const Units = [
|
||||||
Config,
|
Config,
|
||||||
FirebaseUnit,
|
FirebaseUnit,
|
||||||
Blockchain,
|
Blockchain,
|
||||||
Transaction,
|
Transaction,
|
||||||
|
Exposure,
|
||||||
Files,
|
Files,
|
||||||
CommandLine,
|
CommandLine,
|
||||||
Controllers,
|
Controllers,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {Inject, Injectable} from "@extollo/di"
|
import {Inject, Injectable} from "@extollo/di"
|
||||||
import {Collection, Iterable} from "@extollo/util"
|
import {collect, Collection, Iterable} from "@extollo/util"
|
||||||
import {FirebaseUnit, RTDBRef} from "./units/FirebaseUnit"
|
import {FirebaseUnit, RTDBRef} from "./units/FirebaseUnit"
|
||||||
import * as firebase from "firebase-admin"
|
import * as firebase from "firebase-admin"
|
||||||
import {Application} from "@extollo/lib"
|
import {Application} from "@extollo/lib"
|
||||||
|
@ -3,6 +3,7 @@ import {Injectable, Inject} from "@extollo/di"
|
|||||||
import {TransactionResource, TransactionResourceItem} from "../../../rtdb/TransactionResource"
|
import {TransactionResource, TransactionResourceItem} from "../../../rtdb/TransactionResource"
|
||||||
import {many, one} from "@extollo/util"
|
import {many, one} from "@extollo/util"
|
||||||
import {Blockchain as BlockchainService} from "../../../units/Blockchain"
|
import {Blockchain as BlockchainService} from "../../../units/Blockchain"
|
||||||
|
import {ExposureResource, ExposureResourceItem} from "../../../rtdb/ExposureResource";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blockchain Controller
|
* Blockchain Controller
|
||||||
@ -42,4 +43,19 @@ export class Blockchain extends Controller {
|
|||||||
await (<TransactionResource> this.make(TransactionResource)).push(item)
|
await (<TransactionResource> this.make(TransactionResource)).push(item)
|
||||||
return one(item)
|
return one(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post a new exposure notification to the blockchain. This is only intended for testing.
|
||||||
|
*/
|
||||||
|
public async postExposure() {
|
||||||
|
const item: ExposureResourceItem = {
|
||||||
|
firebaseID: '',
|
||||||
|
seqID: -1,
|
||||||
|
clientID: String(this.request.input('clientID')),
|
||||||
|
timestamp: parseInt(String(this.request.input('timestamp'))),
|
||||||
|
}
|
||||||
|
|
||||||
|
await (<ExposureResource> this.make(ExposureResource)).push(item)
|
||||||
|
return one(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {error, Middleware} from "@extollo/lib"
|
import {error, Middleware} from "@extollo/lib"
|
||||||
import {Injectable} from "@extollo/di"
|
import {Injectable} from "@extollo/di"
|
||||||
import {HTTPStatus} from "@extollo/util";
|
import {HTTPStatus} from "@extollo/util"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ValidateEncounterTransaction Middleware
|
* ValidateEncounterTransaction Middleware
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
import {error, Middleware} from "@extollo/lib"
|
||||||
|
import {Injectable} from "@extollo/di"
|
||||||
|
import {HTTPStatus} from "@extollo/util"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ValidateExposureTransaction Middleware
|
||||||
|
* --------------------------------------------
|
||||||
|
* Errors out the request if it is missing any fields required to create
|
||||||
|
* a new exposure notification on the blockchain.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateExposureTransaction extends Middleware {
|
||||||
|
public async apply() {
|
||||||
|
const required: string[] = [
|
||||||
|
'clientID',
|
||||||
|
'timestamp',
|
||||||
|
]
|
||||||
|
|
||||||
|
for ( const field of required ) {
|
||||||
|
if ( !this.request.input(field) ) {
|
||||||
|
return error(`Missing required field: ${field}`, HTTPStatus.BAD_REQUEST, 'json')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,5 +5,9 @@ Route.group('/api/v1', () => {
|
|||||||
.pre('DebugOnly')
|
.pre('DebugOnly')
|
||||||
.pre('api:ValidateEncounterTransaction')
|
.pre('api:ValidateEncounterTransaction')
|
||||||
|
|
||||||
|
Route.post('/exposure', 'api:Blockchain.postExposure')
|
||||||
|
.pre('DebugOnly')
|
||||||
|
.pre('api:ValidateExposureTransaction')
|
||||||
|
|
||||||
Route.get('/chain', 'api:Blockchain.readBlockchain')
|
Route.get('/chain', 'api:Blockchain.readBlockchain')
|
||||||
})
|
})
|
||||||
|
24
src/app/rtdb/ExposureResource.ts
Normal file
24
src/app/rtdb/ExposureResource.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {FirebaseResource, FirebaseResourceItem} from "../FirebaseResource"
|
||||||
|
import {Injectable} from "@extollo/di"
|
||||||
|
import {RTDBRef} from "../units/FirebaseUnit"
|
||||||
|
import {AsyncCollection} from "@extollo/util"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface representing a client-submitted encounter transaction.
|
||||||
|
*/
|
||||||
|
export interface ExposureResourceItem extends FirebaseResourceItem {
|
||||||
|
clientID: string; // the exposed client's ID - used as one half of the hashes
|
||||||
|
timestamp: number; // the unix-time in milliseconds when the interaction occurred
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Firebase realtime-database resource for managing exposure transactions.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ExposureResource extends FirebaseResource<ExposureResourceItem> {
|
||||||
|
public static collect(): AsyncCollection<ExposureResourceItem> {
|
||||||
|
return new AsyncCollection<ExposureResourceItem>(new ExposureResource())
|
||||||
|
}
|
||||||
|
|
||||||
|
protected refName: RTDBRef = 'exposure'
|
||||||
|
}
|
@ -6,6 +6,7 @@ import { TransactionResourceItem } from "../rtdb/TransactionResource"
|
|||||||
import * as openpgp from "openpgp"
|
import * as openpgp from "openpgp"
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
import { collect, uuid_v4 } from "@extollo/util"
|
import { collect, uuid_v4 } from "@extollo/util"
|
||||||
|
import {ExposureResourceItem} from "../rtdb/ExposureResource";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility wrapper class for a block in the chain.
|
* Utility wrapper class for a block in the chain.
|
||||||
@ -177,6 +178,32 @@ export class Blockchain extends Unit {
|
|||||||
return new Block(block)
|
return new Block(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the given exposure notifications onto the blockchain.
|
||||||
|
* @param exposures
|
||||||
|
*/
|
||||||
|
public async submitExposures(...exposures: ExposureResourceItem[]) {
|
||||||
|
const lastBlock = await this.getLastBlock()
|
||||||
|
|
||||||
|
this.logging.verbose('Last block:')
|
||||||
|
this.logging.verbose(lastBlock)
|
||||||
|
|
||||||
|
const block: BlockResourceItem = {
|
||||||
|
timestamp: (new Date).getTime(),
|
||||||
|
uuid: uuid_v4(),
|
||||||
|
transactions: exposures,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiate the genesis block of the entire chain.
|
* Instantiate the genesis block of the entire chain.
|
||||||
*/
|
*/
|
||||||
|
72
src/app/units/rtdb/Exposure.ts
Normal file
72
src/app/units/rtdb/Exposure.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { FirebaseUnit } from "../FirebaseUnit"
|
||||||
|
import { Singleton, Inject } from "@extollo/di"
|
||||||
|
import { Unit, Logging } from "@extollo/lib"
|
||||||
|
import { Blockchain } from "../Blockchain"
|
||||||
|
import { ExposureResource, ExposureResourceItem } from "../../rtdb/ExposureResource"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposure Unit
|
||||||
|
* ---------------------------------------
|
||||||
|
* This unit listens for exposure notifications created on the realtime database.
|
||||||
|
* When new ones come through, it validates them, and pushes them onto this
|
||||||
|
* server's blockchain.
|
||||||
|
*/
|
||||||
|
@Singleton()
|
||||||
|
export class Exposure extends Unit {
|
||||||
|
/** True if currently processing transactions. */
|
||||||
|
private processing: boolean = false
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly firebase!: FirebaseUnit
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly blockchain!: Blockchain
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
|
/** Claim the right to process transactions. Returns true if the right was granted. */
|
||||||
|
claim() {
|
||||||
|
if ( !this.processing ) {
|
||||||
|
this.processing = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Release the right to claim transactions. */
|
||||||
|
release() {
|
||||||
|
this.processing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to the transactions reference and wait for new transactions to be added.
|
||||||
|
*/
|
||||||
|
public async up() {
|
||||||
|
this.firebase.ref('exposure').on('child_added', async (snapshot) => {
|
||||||
|
this.logging.debug('Received child_added event for exposures reference.')
|
||||||
|
if ( !this.claim() ) return
|
||||||
|
await this.firebase.trylock('block')
|
||||||
|
|
||||||
|
const exposure: ExposureResourceItem = snapshot.val()
|
||||||
|
|
||||||
|
// Push the exposure transactions onto the chain
|
||||||
|
await this.blockchain.submitExposures(exposure)
|
||||||
|
|
||||||
|
if ( snapshot.key )
|
||||||
|
await (<ExposureResource> this.make(ExposureResource)).ref().child(snapshot.key).remove()
|
||||||
|
|
||||||
|
this.release()
|
||||||
|
await this.firebase.unlock('block')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release listeners and resources before shutdown.
|
||||||
|
*/
|
||||||
|
public async down() {
|
||||||
|
// Release all subscriptions before shutdown
|
||||||
|
this.firebase.ref("transaction").off()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user