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 { export default {
debug: env('DEBUG_MODE', false), debug: env('DEBUG_MODE', false),
base_url: env('BASE_URL', 'http://localhost:8000/'),
session: { session: {
/* The implementation of @extollo/lib.Session that serves as the session backend. */ /* The implementation of @extollo/lib.Session that serves as the session backend. */
driver: MemorySession, driver: MemorySession,

View File

@ -58,6 +58,7 @@ export interface BlockResourceItem extends FirebaseResourceItem {
proof: string; // the generated proof-of-work string proof: string; // the generated proof-of-work string
timestamp: number; // millisecond unix timestamp when this block was created timestamp: number; // millisecond unix timestamp when this block was created
waitTime: number; // number of milliseconds between last block and this one 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 lastBlockUUID: string
proof: string proof: string
waitTime: number waitTime: number
peer: string
get config(): Config { get config(): Config {
return Application.getApplication().make(Config) return Application.getApplication().make(Config)
@ -44,6 +45,7 @@ export class Block implements BlockResourceItem {
this.proof = rec.proof this.proof = rec.proof
this.timestamp = rec.timestamp this.timestamp = rec.timestamp
this.waitTime = rec.waitTime this.waitTime = rec.waitTime
this.peer = rec.peer
} }
/** Returns true if this is the genesis block. */ /** Returns true if this is the genesis block. */
@ -86,6 +88,7 @@ export class Block implements BlockResourceItem {
proof: this.proof, proof: this.proof,
timestamp: this.timestamp, timestamp: this.timestamp,
waitTime: this.waitTime, waitTime: this.waitTime,
peer: this.peer,
} }
} }
@ -116,6 +119,11 @@ export interface Peer {
*/ */
@Singleton() @Singleton()
export class Blockchain extends Unit { 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() @Inject()
protected readonly logging!: Logging protected readonly logging!: Logging
@ -240,6 +248,7 @@ export class Blockchain extends Unit {
const peers = await this.getPeers() const peers = await this.getPeers()
const time_x_block: {[key: string]: Block} = {} const time_x_block: {[key: string]: Block} = {}
const time_x_blocks: {[key: string]: Block[]} = {}
const time_x_peer: {[key: string]: Peer | true} = {} const time_x_peer: {[key: string]: Peer | true} = {}
for ( const peer of peers ) { for ( const peer of peers ) {
@ -248,7 +257,15 @@ export class Blockchain extends Unit {
const block = blocks.reverse()[0] const block = blocks.reverse()[0]
if ( !block || block.seqID === validSeqID || !block.seqID ) continue 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_block[block.waitTime] = block
time_x_blocks[block.waitTime] = blocks
time_x_peer[block.waitTime] = peer time_x_peer[block.waitTime] = peer
} }
} }
@ -266,11 +283,15 @@ export class Blockchain extends Unit {
const block = time_x_block[min] const block = time_x_block[min]
const peer = time_x_peer[min] const peer = time_x_peer[min]
await (<BlockResource>this.app().make(BlockResource)).push(block)
if ( peer === true ) { if ( peer === true ) {
this.pendingSubmit = undefined this.pendingSubmit = undefined
this.pendingTransactions = [] this.pendingTransactions = []
await (<BlockResource>this.app().make(BlockResource)).push(block)
} else { } else {
await this.firebase.ref('block').transaction((_) => {
return time_x_blocks[min].map(x => x.toItem())
})
this.pendingSubmit = undefined this.pendingSubmit = undefined
await this.attemptSubmit() await this.attemptSubmit()
} }
@ -292,7 +313,7 @@ export class Blockchain extends Unit {
public async attemptSubmit() { public async attemptSubmit() {
if ( !this.pendingSubmit && this.pendingTransactions.length ) { if ( !this.pendingSubmit && this.pendingTransactions.length ) {
const lastBlock = await this.getLastBlock() 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 proof = await this.generateProofOfWork(lastBlock, waitTime)
const block: BlockResourceItem = { const block: BlockResourceItem = {
@ -303,6 +324,7 @@ export class Blockchain extends Unit {
lastBlockUUID: lastBlock!.uuid, lastBlockUUID: lastBlock!.uuid,
proof, proof,
waitTime, waitTime,
peer: this.getBaseURL(),
firebaseID: '', firebaseID: '',
seqID: -1, seqID: -1,
@ -404,6 +426,7 @@ export class Blockchain extends Unit {
firebaseID: '', firebaseID: '',
seqID: -1, seqID: -1,
waitTime: 0, waitTime: 0,
peer: this.getBaseURL(),
}) })
} }
@ -483,6 +506,16 @@ export class Blockchain extends Unit {
return !!(await result.signatures?.[0]?.verified) 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. */ /** Sleep for (roughly) the given number of milliseconds. */
async sleep(ms: number) { async sleep(ms: number) {
await new Promise<void>(res => { await new Promise<void>(res => {