diff --git a/src/app/configs/server.config.ts b/src/app/configs/server.config.ts index b76ed18..c22feef 100644 --- a/src/app/configs/server.config.ts +++ b/src/app/configs/server.config.ts @@ -4,6 +4,8 @@ import {LocalFilesystem, LocalFilesystemConfig} from "@extollo/util" export default { debug: env('DEBUG_MODE', false), + base_url: env('BASE_URL', 'http://localhost:8000/'), + session: { /* The implementation of @extollo/lib.Session that serves as the session backend. */ driver: MemorySession, diff --git a/src/app/rtdb/BlockResource.ts b/src/app/rtdb/BlockResource.ts index beada80..d599835 100644 --- a/src/app/rtdb/BlockResource.ts +++ b/src/app/rtdb/BlockResource.ts @@ -58,6 +58,7 @@ export interface BlockResourceItem extends FirebaseResourceItem { proof: string; // the generated proof-of-work string timestamp: number; // millisecond unix timestamp when this block was created waitTime: number; // number of milliseconds between last block and this one + peer: string; // the host URL of the peer that submitted this block } /** diff --git a/src/app/units/Blockchain.ts b/src/app/units/Blockchain.ts index 2eb25ab..c7334b5 100644 --- a/src/app/units/Blockchain.ts +++ b/src/app/units/Blockchain.ts @@ -29,6 +29,7 @@ export class Block implements BlockResourceItem { lastBlockUUID: string proof: string waitTime: number + peer: string get config(): Config { return Application.getApplication().make(Config) @@ -44,6 +45,7 @@ export class Block implements BlockResourceItem { this.proof = rec.proof this.timestamp = rec.timestamp this.waitTime = rec.waitTime + this.peer = rec.peer } /** Returns true if this is the genesis block. */ @@ -86,6 +88,7 @@ export class Block implements BlockResourceItem { proof: this.proof, timestamp: this.timestamp, waitTime: this.waitTime, + peer: this.peer, } } @@ -116,6 +119,11 @@ export interface Peer { */ @Singleton() export class Blockchain extends Unit { + private readonly MIN_WAIT_TIME = 1000 + private readonly MAX_WAIT_TIME = 5000 + private readonly PENALTY_INTERVAL = 500 + private readonly MAX_PEERS_PENALTY = 10 + @Inject() protected readonly logging!: Logging @@ -240,6 +248,7 @@ export class Blockchain extends Unit { const peers = await this.getPeers() const time_x_block: {[key: string]: Block} = {} + const time_x_blocks: {[key: string]: Block[]} = {} const time_x_peer: {[key: string]: Peer | true} = {} for ( const peer of peers ) { @@ -248,7 +257,15 @@ export class Blockchain extends Unit { const block = blocks.reverse()[0] if ( !block || block.seqID === validSeqID || !block.seqID ) continue + const penalty = blocks.slice(0, 10) + .map(block => block.peer === peer.host) + .filter(Boolean).length * this.PENALTY_INTERVAL + * (Math.min(peers.length, this.MAX_PEERS_PENALTY)) + + block.waitTime += penalty + time_x_block[block.waitTime] = block + time_x_blocks[block.waitTime] = blocks time_x_peer[block.waitTime] = peer } } @@ -266,11 +283,15 @@ export class Blockchain extends Unit { const block = time_x_block[min] const peer = time_x_peer[min] - await (this.app().make(BlockResource)).push(block) if ( peer === true ) { this.pendingSubmit = undefined this.pendingTransactions = [] + await (this.app().make(BlockResource)).push(block) } else { + await this.firebase.ref('block').transaction((_) => { + return time_x_blocks[min].map(x => x.toItem()) + }) + this.pendingSubmit = undefined await this.attemptSubmit() } @@ -292,7 +313,7 @@ export class Blockchain extends Unit { public async attemptSubmit() { if ( !this.pendingSubmit && this.pendingTransactions.length ) { const lastBlock = await this.getLastBlock() - const waitTime = this.random(3000, 5000) + const waitTime = this.random(this.MIN_WAIT_TIME, this.MAX_WAIT_TIME) const proof = await this.generateProofOfWork(lastBlock, waitTime) const block: BlockResourceItem = { @@ -303,6 +324,7 @@ export class Blockchain extends Unit { lastBlockUUID: lastBlock!.uuid, proof, waitTime, + peer: this.getBaseURL(), firebaseID: '', seqID: -1, @@ -404,6 +426,7 @@ export class Blockchain extends Unit { firebaseID: '', seqID: -1, waitTime: 0, + peer: this.getBaseURL(), }) } @@ -483,6 +506,16 @@ export class Blockchain extends Unit { return !!(await result.signatures?.[0]?.verified) } + /** + * Get the base URL that identifies this peer. + * This should be the endpoint used to fetch the submitted blockchain. + * @protected + */ + protected getBaseURL(): string { + const base = this.config.get('server.base_url') + return `${base}${base.endsWith('/') ? '' : '/'}api/v1/chain/submit` + } + /** Sleep for (roughly) the given number of milliseconds. */ async sleep(ms: number) { await new Promise(res => {