Skip to content
This repository has been archived by the owner on Apr 6, 2020. It is now read-only.

Move some db ops to DBManager #91

Merged
merged 5 commits into from
Feb 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["@babel/env"],
"plugins": ["@babel/plugin-transform-runtime"]
}
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: node_js
node_js:
- "6"
- "8"
- "9"
- "10"
- "11"
env:
- CXX=g++-4.8
addons:
Expand All @@ -18,9 +18,9 @@ matrix:
fast_finish: true
include:
- os: linux
node_js: "6"
node_js: "8"
env: CXX=g++-4.8 TEST_SUITE=coveralls
- os: linux
node_js: "6"
node_js: "8"
env: CXX=g++-4.8 TEST_SUITE=lint
script: npm run $TEST_SUITE
7 changes: 0 additions & 7 deletions babel.config.js

This file was deleted.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"coverage": "nyc npm run test && nyc report --reporter=text-lcov > .nyc_output/lcov.info",
"coveralls": "npm run coverage && coveralls <.nyc_output/lcov.info",
"lint": "standard",
"test": "tape ./test/*.js"
"test": "babel-tape-runner ./test/*.js"
},
"repository": {
"type": "git",
Expand All @@ -29,6 +29,7 @@
},
"homepage": "https://github.com/ethereumjs/ethereumjs-blockchain#readme",
"dependencies": {
"@babel/runtime": "^7.3.1",
holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
"async": "^2.6.1",
"ethashjs": "~0.0.7",
"ethereumjs-block": "~2.2.0",
Expand All @@ -38,12 +39,15 @@
"level-mem": "^3.0.1",
"lru-cache": "^5.1.1",
"safe-buffer": "^5.1.2",
"semaphore": "^1.1.0"
"semaphore": "^1.1.0",
"util": "^0.11.1"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.2.2",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"babel-tape-runner": "^3.0.0",
"coveralls": "^3.0.2",
"nyc": "^13.0.1",
"standard": "^11.0.1",
Expand Down
194 changes: 194 additions & 0 deletions src/dbManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
const BN = require('bn.js')
const level = require('level-mem')
const rlp = require('rlp')
const Block = require('ethereumjs-block')
const Cache = require('./cache')
const {
headsKey,
headHeaderKey,
headBlockKey,
hashToNumberKey,
numberToHashKey,
tdKey,
bodyKey,
headerKey
} = require('./util')

/**
* Abstraction over db to facilitate storing/fetching blockchain-related
* data, such as blocks and headers, indices, and the head block.
*/
module.exports = class DBManager {
constructor (db, common) {
this._db = db
this._common = common
this._cache = {
td: new Cache({ max: 1024 }),
header: new Cache({ max: 512 }),
body: new Cache({ max: 256 }),
numberToHash: new Cache({ max: 2048 }),
hashToNumber: new Cache({ max: 2048 })
}
}

/**
* Fetches iterator heads from the db.
* @returns Promise
*/
getHeads () {
return this.get(headsKey, { valueEncoding: 'json' })
}

holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Fetches header of the head block.
* @returns Promise
*/
getHeadHeader () {
return this.get(headHeaderKey)
}

/**
* Fetches head block.
* @returns Promise
*/
getHeadBlock () {
return this.get(headBlockKey)
}

/**
* Fetches a block (header and body), given a block tag
* which can be either its hash or its number.
* @param {Buffer|BN|number} blockTag - Hash or number of the block
* @returns Promise
*/
async getBlock (blockTag) {
// determine BlockTag type
if (Number.isInteger(blockTag)) {
blockTag = new BN(blockTag)
}

let number
let hash
if (Buffer.isBuffer(blockTag)) {
hash = blockTag
number = await this.hashToNumber(blockTag)
} else if (BN.isBN(blockTag)) {
number = blockTag
hash = await this.numberToHash(blockTag)
} else {
throw new Error('Unknown blockTag type')
}

const header = (await this.getHeader(hash, number)).raw
let body
try {
body = await this.getBody(hash, number)
} catch (e) {
body = [[], []]
}

return new Block([header].concat(body), {common: this._common})
}

holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Fetches body of a block given its hash and number.
* @param {Buffer} hash
* @param {BN} number
* @returns Promise
*/
async getBody (hash, number) {
const key = bodyKey(number, hash)
return rlp.decode(await this.get(key, { cache: 'body' }))
}

/**
* Fetches header of a block given its hash and number.
* @param {Buffer} hash
* @param {BN} number
* @returns Promise
*/
async getHeader (hash, number) {
const key = headerKey(number, hash)
let encodedHeader = await this.get(key, { cache: 'header' })
return new Block.Header(rlp.decode(encodedHeader), { common: this._common })
}

vpulim marked this conversation as resolved.
Show resolved Hide resolved
/**
* Fetches total difficulty for a block given its hash and number.
* @param {Buffer} hash
* @param {BN} number
* @returns Promise
*/
async getTd (hash, number) {
const key = tdKey(number, hash)
const td = await this.get(key, { cache: 'td' })
return new BN(rlp.decode(td))
}

holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Performs a block hash to block number lookup.
* @param {Buffer} hash
* @returns Promise
*/
async hashToNumber (hash) {
const key = hashToNumberKey(hash)
return new BN(await this.get(key, { cache: 'hashToNumber' }))
}

holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Performs a block number to block hash lookup.
* @param {BN} number
* @returns Promise
*/
async numberToHash (number) {
if (number.ltn(0)) {
throw new level.errors.NotFoundError()
}

const key = numberToHashKey(number)
return this.get(key, { cache: 'numberToHash' })
}

holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Fetches a key from the db. If `opts.cache` is specified
* it first tries to load from cache, and on cache miss will
* try to put the fetched item on cache afterwards.
* @param {Buffer} key
* @param {Object} opts - Options and their default values are:
* - {string} [keyEncoding='binary']
* - {string} [valueEncodingr='binary']
* - {string} [cache=undefined] name of cache to use
* @returns Promise
*/
async get (key, opts = {}) {
const dbOpts = {
keyEncoding: opts.keyEncoding || 'binary',
valueEncoding: opts.valueEncoding || 'binary'
}

if (opts.cache) {
if (!this._cache[opts.cache]) {
throw new Error(`Invalid cache: ${opts.cache}`)
}

let value = this._cache[opts.cache].get(key)
if (!value) {
value = await this._db.get(key, dbOpts)
this._cache[opts.cache].set(key, value)
}

return value
}

return this._db.get(key, dbOpts)
}

/**
* Performs a batch operation on db.
* @param {Array} ops
* @returns Promise
*/
batch (ops) {
return this._db.batch(ops)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Can you add JSDoc comments to the publicly exposed functions?

Copy link
Member

Choose a reason for hiding this comment

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

(hmm, second thought: or maybe we'll leave it until integrating auto-generated documentation, up to you)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will try to add a bit at least for now. This made me think, should all these methods be public or private? On Blockchain it makes sense to have them as private, but they're the core functionality of DBManager 🤔 What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I am also getting unsure on various fronts. Wasn't getting aware that DBManager isn't really prepared to go into the public space (right?). Are all publicly documented API methods from the README still kept in the main Blockchain class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It doesn't go public by default, when users do const Blockchain = require('ethereumjs-blockchain') they'd get the Blockchain class, and they'd have to explicitly import DBManager to get access to these methods. Should be fine.
Yeah, the API hasn't changed and the methods in Blockchain only act as a wrapper for methods in DBManager.

Loading