Add time-based penalty system for repeat block-pushers

This commit is contained in:
Garrett Mills 2021-04-10 12:10:52 -05:00
parent 9e4164632c
commit 8f34fd3be0
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
3 changed files with 38 additions and 2 deletions

View File

@ -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,

View File

@ -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
}
/**

View File

@ -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 (<BlockResource>this.app().make(BlockResource)).push(block)
if ( peer === true ) {
this.pendingSubmit = undefined
this.pendingTransactions = []
await (<BlockResource>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<void>(res => {