From a3c60a1951e1f18cf9218f594b669101cf7908b4 Mon Sep 17 00:00:00 2001 From: garrettmills Date: Sat, 10 Apr 2021 09:34:04 -0500 Subject: [PATCH] Fix logic for validating blockchain and expose debugging endpoint --- .../controllers/api/Blockchain.controller.ts | 9 ++- src/app/http/routes/app.routes.ts | 3 + src/app/units/Blockchain.ts | 74 ++++++++++++++----- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/app/http/controllers/api/Blockchain.controller.ts b/src/app/http/controllers/api/Blockchain.controller.ts index 5453839..065f91c 100644 --- a/src/app/http/controllers/api/Blockchain.controller.ts +++ b/src/app/http/controllers/api/Blockchain.controller.ts @@ -2,7 +2,7 @@ import {Controller} from "@extollo/lib" import {Injectable, Inject} from "@extollo/di" import {TransactionResource, TransactionResourceItem} from "../../../rtdb/TransactionResource" 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"; /** @@ -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. */ diff --git a/src/app/http/routes/app.routes.ts b/src/app/http/routes/app.routes.ts index a6978d8..0226b22 100644 --- a/src/app/http/routes/app.routes.ts +++ b/src/app/http/routes/app.routes.ts @@ -9,5 +9,8 @@ Route.group('/api/v1', () => { .pre('DebugOnly') .pre('api:ValidateExposureTransaction') + Route.post('/validate', 'api:Blockchain.validate') + .pre('DebugOnly') + Route.get('/chain', 'api:Blockchain.readBlockchain') }) diff --git a/src/app/units/Blockchain.ts b/src/app/units/Blockchain.ts index 4ec1980..8c70e16 100644 --- a/src/app/units/Blockchain.ts +++ b/src/app/units/Blockchain.ts @@ -44,15 +44,17 @@ export class Block implements BlockResourceItem { } const proof = this.proof const publicKey = this.config.get("app.gpg.key.public") - const message = openpgp.Message.fromText(proof) - // Verify the signature - const verified = await (await openpgp.verify({ - message, - publicKeys: publicKey - })).signatures[0].verified + const result = await openpgp.verify({ + publicKeys: await openpgp.readKey({ + armoredKey: publicKey, + }), + message: await openpgp.readMessage({ + armoredMessage: proof, + }), + }) - return !!verified + return !!(await result.signatures?.[0]?.verified) } /** Generate the hash for this block. */ @@ -80,7 +82,7 @@ export class Block implements BlockResourceItem { toString() { return [ this.uuid, - JSON.stringify(this.transactions), + JSON.stringify(this.transactions || [], undefined, 0), this.lastBlockHash, this.lastBlockUUID, ].join('%') @@ -145,7 +147,43 @@ export class Blockchain extends Unit { */ public async validate(chain: Block[]) { const blocks = collect(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() { @@ -276,7 +314,7 @@ export class Blockchain extends Unit { privateKeys: await openpgp.readKey({ armoredKey: privateKey, }) - })).toString() + })) } /** @@ -288,14 +326,16 @@ export class Blockchain extends Unit { protected async validateProofOfWork(currentBlock: Block, lastBlock: Block): Promise { const proof = lastBlock.proof const publicKey = this.config.get("app.gpg.key.public") - const message = openpgp.Message.fromText(proof) - // Verify the signature - const verified = await (await openpgp.verify({ - message, - publicKeys: publicKey - })).signatures[0].verified + const result = await openpgp.verify({ + publicKeys: await openpgp.readKey({ + armoredKey: publicKey, + }), + message: await openpgp.readMessage({ + armoredMessage: proof, + }), + }) - return !!verified + return !!(await result.signatures?.[0]?.verified) } }