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