You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 lines
4.1 KiB

import { FirebaseUnit } from "../FirebaseUnit"
import { TransactionResource, TransactionResourceItem } from "../../rtdb/TransactionResource"
import { Singleton, Inject } from "@extollo/di"
import { Unit, Logging } from "@extollo/lib"
import * as openpgp from "openpgp"
import { Blockchain } from "../Blockchain"
/**
* Transaction Unit
* ---------------------------------------
* Put some description here.
*/
@Singleton()
export class Transaction extends Unit {
private processing: boolean = false
@Inject()
protected readonly firebase!: FirebaseUnit
@Inject()
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) {
// verify signature
const result1 = await openpgp.verify({
publicKeys: await openpgp.readKey({
armoredKey: transaction2.partnerPublicKey
}),
message: await openpgp.readMessage({
armoredMessage: transaction1.validationSignature,
}),
})
const result2 = await openpgp.verify({
publicKeys: await openpgp.readKey({
armoredKey: transaction1.partnerPublicKey
}),
message: await openpgp.readMessage({
armoredMessage: transaction2.validationSignature,
}),
})
return (await result1.signatures[0].verified) && (await result2.signatures[0].verified)
}
public async up() {
this.firebase.ref("transaction").on("child_added", async () => {
this.logging.debug('Received child_added event for transactions reference.')
if ( !this.claim() ) return
// array of pairs of transaction resource items
let groupedTransactions: [TransactionResourceItem, TransactionResourceItem][] = []
// collection of transaction resource items
let transactions = await TransactionResource.collect().collect()
// compare each item
await transactions.promiseMap(async transaction1 => {
// for each item that is not itself
await transactions.where('combinedHash', '!=', transaction1.combinedHash)
// get a second item
.promiseMap(async transaction2 => {
//if the item matches
if ( await this.compareTransactions(transaction1, transaction2) ) {
// and remove the two matching items
transactions = transactions.whereNotIn("combinedHash", [transaction1.combinedHash, transaction2.combinedHash])
// insert grouped items into groupedTransactions
groupedTransactions.push([transaction1, transaction2])
}
})
})
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) {
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[1].firebaseID).remove()
}
this.release()
})
}
public async down() {
// Release all subscriptions before shutdown
this.firebase.ref("transaction").off()
}
}