Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blockchain: internal refactor #895

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/block/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
test-build
dist.browser
16 changes: 16 additions & 0 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,22 @@ export class Block {
return this.header.canonicalDifficulty(parentBlock.header)
}

/**
* Returns the canonical difficulty of any new block, given the timestamp of the consecutive block and (optionally) the block number and the common
* @param block - The block to get the canonical difficulty for
* @param timestamp - The timestamp of the consecutive block
* @param number - Optional, the number of the consecutive block
* @param common - Optional, the `common` to use in the difficulty calculation
*/
public static getCanonicalDifficulty(
block: Block,
timestamp: BN,
number?: BN,
common?: Common
): BN {
return BlockHeader.getCanonicalDifficulty(block.header, timestamp, number, common)
}

/**
* Checks that the block's `difficulty` matches the canonical difficulty.
*
Expand Down
60 changes: 39 additions & 21 deletions packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,28 +254,34 @@ export class BlockHeader {
}

/**
* Returns the canonical difficulty for this block.
*
* @param parentBlockHeader - the header from the parent `Block` of this header
* @param header - The `header` where we wish to build upon
* @param timestamp - The `BN` timestamp which we should calculate the difficulty for
* @param number - The `BN` block number. Defaults to the parent blocks' `number` + 1
* @param common - The `Common` to use: defaults to the parent blocks` `Common`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to do this change to static we should really drop the parent here on all fronts and just call this block. There is no information lost here and this simplifies things and this parent doesn't make any sense any more in a static context.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have just pushed fd19635 but I am actually not sure anymore why I made this static in the first place. Since we always need a header (or a block) for this function, it makes more sense to remove static, just use the current header and let the caller specify the timestamp + (optionally) number/common?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense, was just wondering too if this double structure with one static and one non-static method really makes sense

*/
canonicalDifficulty(parentBlockHeader: BlockHeader): BN {
const hardfork = this._getHardfork()
const blockTs = this.timestamp
const { timestamp: parentTs, difficulty: parentDif } = parentBlockHeader
const minimumDifficulty = new BN(
this._common.paramByHardfork('pow', 'minimumDifficulty', hardfork)
)

public static getCanonicalDifficulty(
header: BlockHeader,
timestamp: BN,
number?: BN,
common?: Common
): BN {
const blockTs = timestamp.clone()
const { timestamp: parentTs, difficulty: parentDif } = header
common = common || header._common
let num = (number || header.number).clone().addn(1)
const hardfork = common.hardfork() || common.activeHardfork(num.toNumber())
const minimumDifficulty = new BN(common.paramByHardfork('pow', 'minimumDifficulty', hardfork))
const offset = parentDif.div(
new BN(this._common.paramByHardfork('pow', 'difficultyBoundDivisor', hardfork))
new BN(common.paramByHardfork('pow', 'difficultyBoundDivisor', hardfork))
)
let num = this.number.clone()

// We use a ! here as TS cannot follow this hardfork-dependent logic, but it always gets assigned
let dif!: BN

if (this._common.hardforkGteHardfork(hardfork, 'byzantium')) {
if (common.hardforkGteHardfork(hardfork, 'byzantium')) {
// max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99) (EIP100)
const uncleAddend = parentBlockHeader.uncleHash.equals(KECCAK256_RLP_ARRAY) ? 1 : 2
const uncleAddend = header.uncleHash.equals(KECCAK256_RLP_ARRAY) ? 1 : 2
let a = blockTs.sub(parentTs).idivn(9).ineg().iaddn(uncleAddend)
const cutoff = new BN(-99)
// MAX(cutoff, a)
Expand All @@ -285,25 +291,25 @@ export class BlockHeader {
dif = parentDif.add(offset.mul(a))
}

if (this._common.hardforkGteHardfork(hardfork, 'muirGlacier')) {
if (common.hardforkGteHardfork(hardfork, 'muirGlacier')) {
// Istanbul/Berlin difficulty bomb delay (EIP2384)
num.isubn(9000000)
if (num.ltn(0)) {
num = new BN(0)
}
} else if (this._common.hardforkGteHardfork(hardfork, 'constantinople')) {
} else if (common.hardforkGteHardfork(hardfork, 'constantinople')) {
// Constantinople difficulty bomb delay (EIP1234)
num.isubn(5000000)
if (num.ltn(0)) {
num = new BN(0)
}
} else if (this._common.hardforkGteHardfork(hardfork, 'byzantium')) {
} else if (common.hardforkGteHardfork(hardfork, 'byzantium')) {
// Byzantium difficulty bomb delay (EIP649)
num.isubn(3000000)
if (num.ltn(0)) {
num = new BN(0)
}
} else if (this._common.hardforkGteHardfork(hardfork, 'homestead')) {
} else if (common.hardforkGteHardfork(hardfork, 'homestead')) {
// 1 - (block_timestamp - parent_timestamp) // 10
let a = blockTs.sub(parentTs).idivn(10).ineg().iaddn(1)
const cutoff = new BN(-99)
Expand All @@ -315,9 +321,7 @@ export class BlockHeader {
} else {
// pre-homestead
if (
parentTs
.addn(this._common.paramByHardfork('pow', 'durationLimit', hardfork))
.cmp(blockTs) === 1
parentTs.addn(common.paramByHardfork('pow', 'durationLimit', hardfork)).cmp(blockTs) === 1
) {
dif = offset.add(parentDif)
} else {
Expand All @@ -337,6 +341,20 @@ export class BlockHeader {
return dif
}

/**
* Returns the canonical difficulty for this block.
*
* @param parentBlock - the parent `Block` of this header
*/
canonicalDifficulty(parentHeader: BlockHeader): BN {
return BlockHeader.getCanonicalDifficulty(
parentHeader,
this.timestamp,
this.number,
this._common
)
}

/**
* Checks that the block's `difficulty` matches the canonical difficulty.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/blockchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"tsc": "ethereumjs-config-tsc",
"lint": "ethereumjs-config-lint",
"lint:fix": "ethereumjs-config-lint-fix",
"test": "tape -r ts-node/register ./test/index.ts"
"test": "tape -r ts-node/register ./test/*.ts"
},
"repository": {
"type": "git",
Expand Down
File renamed without changes.
145 changes: 145 additions & 0 deletions packages/blockchain/src/db/databaseOperation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import BN = require('bn.js')
import {
HEADS_KEY,
HEAD_HEADER_KEY,
HEAD_BLOCK_KEY,
tdKey,
headerKey,
bodyKey,
numberToHashKey,
hashToNumberKey,
} from './dbConstants'

import { CacheMap } from './dbManager'

export enum DatabaseOperationTarget {
Heads,
HeadHeader,
HeadBlock,
HashToNumber,
NumberToHash,
TotalDifficulty,
Body,
Header,
}

/**
* @hidden
*/
export interface DBOp {
type?: String
key: Buffer | string
keyEncoding: String
valueEncoding?: String
value?: Buffer | object
}

// a Database Key is identified by a block hash, a block number, or both
export type DatabaseKey = {
blockNumber?: BN
blockHash?: Buffer
}

export class DatabaseOperation {
public operationTarget: DatabaseOperationTarget
public baseDBOp: DBOp
public cacheString: string | undefined

private constructor(operationTarget: DatabaseOperationTarget, key?: DatabaseKey) {
this.operationTarget = operationTarget

this.baseDBOp = {
key: '',
keyEncoding: 'binary',
valueEncoding: 'binary',
}

switch (operationTarget) {
case DatabaseOperationTarget.Heads: {
this.baseDBOp.key = HEADS_KEY
this.baseDBOp.valueEncoding = 'json'
break
}
case DatabaseOperationTarget.HeadHeader: {
this.baseDBOp.key = HEAD_HEADER_KEY
break
}
case DatabaseOperationTarget.HeadBlock: {
this.baseDBOp.key = HEAD_BLOCK_KEY
break
}
case DatabaseOperationTarget.HashToNumber: {
this.baseDBOp.key = hashToNumberKey(key!.blockHash!)
this.cacheString = 'hashToNumber'
break
}
case DatabaseOperationTarget.NumberToHash: {
this.baseDBOp.key = numberToHashKey(key!.blockNumber!)
this.cacheString = 'numberToHash'
break
}
case DatabaseOperationTarget.TotalDifficulty: {
this.baseDBOp.key = tdKey(key!.blockNumber!, key!.blockHash!)
this.cacheString = 'td'
break
}
case DatabaseOperationTarget.Body: {
this.baseDBOp.key = bodyKey(key!.blockNumber!, key!.blockHash!)
this.cacheString = 'body'
break
}
case DatabaseOperationTarget.Header: {
this.baseDBOp.key = headerKey(key!.blockNumber!, key!.blockHash!)
this.cacheString = 'header'
break
}
}
}

public static get(
operationTarget: DatabaseOperationTarget,
key?: DatabaseKey
): DatabaseOperation {
return new DatabaseOperation(operationTarget, key)
}

// set operation: note: value/key is not in default order
public static set(
operationTarget: DatabaseOperationTarget,
value: Buffer | object,
key?: DatabaseKey
): DatabaseOperation {
const databaseOperation = new DatabaseOperation(operationTarget, key)
databaseOperation.baseDBOp.value = value
databaseOperation.baseDBOp.type = 'put'

if (operationTarget == DatabaseOperationTarget.Heads) {
databaseOperation.baseDBOp.valueEncoding = 'json'
} else {
databaseOperation.baseDBOp.valueEncoding = 'binary'
}

return databaseOperation
}

public static del(
operationTarget: DatabaseOperationTarget,
key?: DatabaseKey
): DatabaseOperation {
const databaseOperation = new DatabaseOperation(operationTarget, key)
databaseOperation.baseDBOp.type = 'del'
return databaseOperation
}

public updateCache(cacheMap: CacheMap) {
if (this.cacheString && cacheMap[this.cacheString] && Buffer.isBuffer(this.baseDBOp.value)) {
if (this.baseDBOp.type == 'put') {
cacheMap[this.cacheString].set(this.baseDBOp.key, this.baseDBOp.value)
} else if (this.baseDBOp.type == 'del') {
cacheMap[this.cacheString].del(this.baseDBOp.key)
} else {
throw new Error('unsupported db operation on cache')
}
}
}
}
Loading