Clean up transaction logging logic for encounters

working-state
Garrett Mills 3 years ago
parent 934b322835
commit 6ba038e4bf
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246

@ -1,10 +1,14 @@
import {Config, Controllers, HTTPServer, Files, Middlewares, Routing, Unit} from '@extollo/lib' import {Config, Controllers, HTTPServer, Files, Middlewares, Routing, Unit} from '@extollo/lib'
import {CommandLine} from "@extollo/cli"; import {CommandLine} from "@extollo/cli"
import {FirebaseUnit} from "./app/units/FirebaseUnit"; import {FirebaseUnit} from "./app/units/FirebaseUnit"
import {Blockchain} from "./app/units/Blockchain"
import {Transaction} from "./app/units/rtdb/Transaction"
export const Units = [ export const Units = [
Config, Config,
FirebaseUnit, FirebaseUnit,
Blockchain,
Transaction,
Files, Files,
CommandLine, CommandLine,
Controllers, Controllers,

@ -25,7 +25,7 @@ export class FirebaseResource<T extends FirebaseResourceItem> extends Iterable<T
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 || 0) + 1) res((this.resolveObject(snapshot.val()).reverse()?.[0]?.seqID ?? -1) + 1)
}, rej) }, rej)
}) })
} }
@ -66,7 +66,22 @@ export class FirebaseResource<T extends FirebaseResourceItem> extends Iterable<T
*/ */
async push(item: T): Promise<T> { async push(item: T): Promise<T> {
item.seqID = await this.getNextID() item.seqID = await this.getNextID()
// @ts-ignore
delete item.firebaseID
await this.ref().push(item) await this.ref().push(item)
// Look up the firebaseID
await new Promise<void>((res, rej) => {
this.ref().orderByChild('seqID')
.limitToLast(1)
.on('value', snapshot => {
if ( snapshot.val() ) {
item.firebaseID = Object.keys(snapshot.val())[0]
}
res()
})
})
return item return item
} }

@ -156,6 +156,9 @@ export class Blockchain extends Unit {
public async submitTransactions(group: [TransactionResourceItem, TransactionResourceItem]) { public async submitTransactions(group: [TransactionResourceItem, TransactionResourceItem]) {
const lastBlock = await this.getLastBlock() const lastBlock = await this.getLastBlock()
this.logging.verbose('Last block:')
this.logging.verbose(lastBlock)
const block: BlockResourceItem = { const block: BlockResourceItem = {
timestamp: (new Date).getTime(), timestamp: (new Date).getTime(),
uuid: uuid_v4(), uuid: uuid_v4(),
@ -178,6 +181,7 @@ export class Blockchain extends Unit {
public async getGenesisBlock(): Promise<Block> { public async getGenesisBlock(): Promise<Block> {
const message = openpgp.Message.fromText("0000") const message = openpgp.Message.fromText("0000")
const privateKey = this.config.get("app.gpg.key.private") const privateKey = this.config.get("app.gpg.key.private")
return new Block({ return new Block({
timestamp: (new Date).getTime(), timestamp: (new Date).getTime(),
uuid: '0000', uuid: '0000',
@ -186,8 +190,10 @@ export class Blockchain extends Unit {
lastBlockUUID: '', lastBlockUUID: '',
proof: (await openpgp.sign({ proof: (await openpgp.sign({
message, message,
privateKeys: privateKey privateKeys: await openpgp.readKey({
})).toString(), armoredKey: privateKey
}),
})),
firebaseID: '', firebaseID: '',
seqID: -1, seqID: -1,
}) })
@ -202,7 +208,7 @@ export class Blockchain extends Unit {
const genesis = (await this.getGenesisBlock()).toItem() const genesis = (await this.getGenesisBlock()).toItem()
await (<BlockResource>this.app().make(BlockResource)).push(genesis) await (<BlockResource>this.app().make(BlockResource)).push(genesis)
return this.getLastBlock() return new Block(genesis)
} }
/** /**
@ -231,7 +237,9 @@ export class Blockchain extends Unit {
// Sign the hash using the server's private key // Sign the hash using the server's private key
return (await openpgp.sign({ return (await openpgp.sign({
message, message,
privateKeys: privateKey privateKeys: await openpgp.readKey({
armoredKey: privateKey,
})
})).toString() })).toString()
} }

@ -12,49 +12,71 @@ import { Blockchain } from "../Blockchain"
*/ */
@Singleton() @Singleton()
export class Transaction extends Unit { export class Transaction extends Unit {
private processing: boolean = false
@Inject() @Inject()
protected readonly firebase!: FirebaseUnit protected readonly firebase!: FirebaseUnit
@Inject() @Inject()
protected readonly blockchain!: Blockchain protected readonly blockchain!: Blockchain
@Inject()
protected readonly logging!: Logging
claim() {
if ( !this.processing ) {
this.processing = true
return true
}
return false
}
release() {
this.processing = false
}
public async compareTransactions(transaction1: TransactionResourceItem, transaction2: TransactionResourceItem) { public async compareTransactions(transaction1: TransactionResourceItem, transaction2: TransactionResourceItem) {
// verify signature // verify signature
const result1 = await openpgp.verify({ const result1 = await openpgp.verify({
publicKeys: await openpgp.readKey({ publicKeys: await openpgp.readKey({
armoredKey: transaction2.partnerPublicKey armoredKey: transaction2.partnerPublicKey
}), }),
message: openpgp.Message.fromText(transaction1.combinedHash), message: await openpgp.readMessage({
signature: await openpgp.readSignature({ armoredMessage: transaction1.validationSignature,
armoredSignature: transaction1.validationSignature // parse detached signature }),
})
}) })
const result2 = await openpgp.verify({ const result2 = await openpgp.verify({
publicKeys: await openpgp.readKey({ publicKeys: await openpgp.readKey({
armoredKey: transaction1.partnerPublicKey armoredKey: transaction1.partnerPublicKey
}), }),
message: openpgp.Message.fromText(transaction2.combinedHash), message: await openpgp.readMessage({
signature: await openpgp.readSignature({ armoredMessage: transaction2.validationSignature,
armoredSignature: transaction2.validationSignature // parse detached signature }),
})
}) })
return await (result1.signatures[0].verified) && await (result2.signatures[0].verified)
return (await result1.signatures[0].verified) && (await result2.signatures[0].verified)
} }
public async up() { public async up() {
this.firebase.ref("transaction").on("value", async () => { this.firebase.ref("transaction").on("child_added", async () => {
// array of pairs of tranaction resource items this.logging.debug('Received child_added event for transactions reference.')
const groupedTransactions: [TransactionResourceItem, TransactionResourceItem][] = [] if ( !this.claim() ) return
// array of pairs of transaction resource items
let groupedTransactions: [TransactionResourceItem, TransactionResourceItem][] = []
// collection of transaction resource items // collection of transaction resource items
let transactions = await TransactionResource.collect().collect() let transactions = await TransactionResource.collect().collect()
// compare each item // compare each item
transactions.each(transaction1 => { await transactions.promiseMap(async transaction1 => {
// for each item that is not itself // for each item that is not itself
transactions.where("combinedHash", "!=", transaction1.combinedHash) await transactions.where('combinedHash', '!=', transaction1.combinedHash)
// get a second item // get a second item
.each(transaction2 => { .promiseMap(async transaction2 => {
//if the item matches //if the item matches
if (this.compareTransactions(transaction1, transaction2)) { if ( await this.compareTransactions(transaction1, transaction2) ) {
// and remove the two matching items // and remove the two matching items
transactions = transactions.whereNotIn("combinedHash", [transaction1.combinedHash, transaction2.combinedHash]) transactions = transactions.whereNotIn("combinedHash", [transaction1.combinedHash, transaction2.combinedHash])
// insert grouped items into groupedTransactions // insert grouped items into groupedTransactions
@ -62,14 +84,34 @@ export class Transaction extends Unit {
} }
}) })
}) })
const seenCombinedHashes: string[] = []
groupedTransactions = groupedTransactions.filter(group => {
const key = group.map(x => x.combinedHash).sort().join('-')
if ( !seenCombinedHashes.includes(key) ) {
seenCombinedHashes.push(key)
return true
}
return false
})
for (const group of groupedTransactions) { for (const group of groupedTransactions) {
await this.blockchain.submitTransactions(group) const block = await this.blockchain.submitTransactions(group)
this.logging.verbose('Created block:')
this.logging.verbose(block)
await this.firebase.ref("transaction").child(group[0].firebaseID).remove() await this.firebase.ref("transaction").child(group[0].firebaseID).remove()
await this.firebase.ref("transaction").child(group[1].firebaseID).remove() await this.firebase.ref("transaction").child(group[1].firebaseID).remove()
} }
this.release()
}) })
} }
public async down() { public async down() {
// Release all subscriptions before shutdown
this.firebase.ref("transaction").off()
} }
} }

Loading…
Cancel
Save