Fix logic for validating blockchain and expose debugging endpoint

This commit is contained in:
Garrett Mills 2021-04-10 09:34:04 -05:00
parent f0005a9ed3
commit a3c60a1951
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
3 changed files with 68 additions and 18 deletions

View File

@ -2,7 +2,7 @@ import {Controller} from "@extollo/lib"
import {Injectable, Inject} from "@extollo/di" 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 {Block, Blockchain as BlockchainService} from "../../../units/Blockchain"
import {ExposureResource, ExposureResourceItem} from "../../../rtdb/ExposureResource"; import {ExposureResource, ExposureResourceItem} from "../../../rtdb/ExposureResource";
/** /**
@ -26,6 +26,13 @@ export class Blockchain extends Controller {
})) }))
} }
public async validate() {
const blocks = (await this.blockchain.read()).map(x => new Block(x))
return {
is_valid: await this.blockchain.validate(blocks)
}
}
/** /**
* Post a new transaction to the blockchain. This is only intended for testing. * Post a new transaction to the blockchain. This is only intended for testing.
*/ */

View File

@ -9,5 +9,8 @@ Route.group('/api/v1', () => {
.pre('DebugOnly') .pre('DebugOnly')
.pre('api:ValidateExposureTransaction') .pre('api:ValidateExposureTransaction')
Route.post('/validate', 'api:Blockchain.validate')
.pre('DebugOnly')
Route.get('/chain', 'api:Blockchain.readBlockchain') Route.get('/chain', 'api:Blockchain.readBlockchain')
}) })

View File

@ -44,15 +44,17 @@ export class Block implements BlockResourceItem {
} }
const proof = this.proof const proof = this.proof
const publicKey = this.config.get("app.gpg.key.public") const publicKey = this.config.get("app.gpg.key.public")
const message = openpgp.Message.fromText(proof)
// Verify the signature const result = await openpgp.verify({
const verified = await (await openpgp.verify({ publicKeys: await openpgp.readKey({
message, armoredKey: publicKey,
publicKeys: publicKey }),
})).signatures[0].verified message: await openpgp.readMessage({
armoredMessage: proof,
}),
})
return !!verified return !!(await result.signatures?.[0]?.verified)
} }
/** Generate the hash for this block. */ /** Generate the hash for this block. */
@ -80,7 +82,7 @@ export class Block implements BlockResourceItem {
toString() { toString() {
return [ return [
this.uuid, this.uuid,
JSON.stringify(this.transactions), JSON.stringify(this.transactions || [], undefined, 0),
this.lastBlockHash, this.lastBlockHash,
this.lastBlockUUID, this.lastBlockUUID,
].join('%') ].join('%')
@ -145,7 +147,43 @@ export class Blockchain extends Unit {
*/ */
public async validate(chain: Block[]) { public async validate(chain: Block[]) {
const blocks = collect<Block>(chain) const blocks = collect<Block>(chain)
return (await blocks.promiseMap((block: Block) => block.isGenesis())).every(Boolean) return (
await blocks.promiseMap(async (block, idx) => {
if ( await block.isGenesis() ) {
return true
}
const previous: Block | undefined = blocks.at(idx - 1)
if ( !previous ) {
this.logging.debug(`Chain is invalid: block ${idx} is missing previous ${idx - 1}.`)
return false;
}
if ( !(await this.validateProofOfWork(block, previous)) ) {
this.logging.debug(`Chain is invalid: block ${idx} failed proof of work validation`)
return false;
}
const pass = (
block.lastBlockUUID === previous.uuid
&& block.lastBlockHash === previous.hash()
)
if ( !pass ) {
this.logging.debug(`Chain is invalid: block ${idx} does not match previous.`)
this.logging.debug({
lastBlockUUID: block.lastBlockUUID,
computedLastUUID: previous.uuid,
lastBlockHash: block.lastBlockHash,
computedLastHash: previous.hash(),
})
}
return pass
})
).every(Boolean)
} }
public async refresh() { public async refresh() {
@ -276,7 +314,7 @@ export class Blockchain extends Unit {
privateKeys: await openpgp.readKey({ privateKeys: await openpgp.readKey({
armoredKey: privateKey, armoredKey: privateKey,
}) })
})).toString() }))
} }
/** /**
@ -288,14 +326,16 @@ export class Blockchain extends Unit {
protected async validateProofOfWork(currentBlock: Block, lastBlock: Block): Promise<boolean> { protected async validateProofOfWork(currentBlock: Block, lastBlock: Block): Promise<boolean> {
const proof = lastBlock.proof const proof = lastBlock.proof
const publicKey = this.config.get("app.gpg.key.public") const publicKey = this.config.get("app.gpg.key.public")
const message = openpgp.Message.fromText(proof)
// Verify the signature const result = await openpgp.verify({
const verified = await (await openpgp.verify({ publicKeys: await openpgp.readKey({
message, armoredKey: publicKey,
publicKeys: publicKey }),
})).signatures[0].verified message: await openpgp.readMessage({
armoredMessage: proof,
}),
})
return !!verified return !!(await result.signatures?.[0]?.verified)
} }
} }