Skip to content

Commit

Permalink
Merge pull request #427 from ethereumjs/test/fix-cache
Browse files Browse the repository at this point in the history
Add unit tests for cache, convert it to es6 class
  • Loading branch information
holgerd77 committed Jan 29, 2019
2 parents 0d965b1 + 1482946 commit 8d6ec92
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 215 deletions.
277 changes: 162 additions & 115 deletions lib/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,144 +3,191 @@ const Tree = require('functional-red-black-tree')
const Account = require('ethereumjs-account')
const async = require('async')

var Cache = module.exports = function (trie) {
this._cache = Tree()
this._checkpoints = []
this._trie = trie
}

Cache.prototype.put = function (key, val, fromTrie) {
var modified = !fromTrie
this._update(key, val, modified, false)
}

// returns the queried account or an empty account
Cache.prototype.get = function (key) {
var account = this.lookup(key)
if (!account) {
account = new Account()
module.exports = class Cache {
constructor (trie) {
this._cache = Tree()
this._checkpoints = []
this._trie = trie
}
return account
}

// returns the queried account or undefined
Cache.prototype.lookup = function (key) {
key = key.toString('hex')
/**
* Puts account to cache under its address.
* @param {Buffer} key - Address of account
* @param {Account} val - Account
* @param {bool} [fromTrie]
*/
put (key, val, fromTrie = false) {
const modified = !fromTrie
this._update(key, val, modified, false)
}

var it = this._cache.find(key)
if (it.node) {
var account = new Account(it.value.val)
/**
* Returns the queried account or an empty account.
* @param {Buffer} key - Address of account
*/
get (key) {
let account = this.lookup(key)
if (!account) {
account = new Account()
}
return account
}
}

Cache.prototype._lookupAccount = function (address, cb) {
var self = this
self._trie.get(address, function (err, raw) {
if (err) return cb(err)
var account = new Account(raw)
cb(null, account)
})
}
/**
* Returns the queried account or undefined.
* @param {buffer} key - Address of account
*/
lookup (key) {
key = key.toString('hex')

const it = this._cache.find(key)
if (it.node) {
const account = new Account(it.value.val)
return account
}
}

Cache.prototype.getOrLoad = function (key, cb) {
var self = this
var account = this.lookup(key)
if (account) {
async.nextTick(cb, null, account)
} else {
self._lookupAccount(key, function (err, account) {
/**
* Looks up address in underlying trie.
* @param {Buffer} address - Address of account
* @param {Function} cb - Callback with params (err, account)
*/
_lookupAccount (address, cb) {
this._trie.get(address, (err, raw) => {
if (err) return cb(err)
self._update(key, account, false, false)
var account = new Account(raw)
cb(null, account)
})
}
}

Cache.prototype.warm = function (addresses, cb) {
var self = this
// shim till async supports iterators
var accountArr = []
addresses.forEach(function (val) {
if (val) accountArr.push(val)
})

async.eachSeries(accountArr, function (addressHex, done) {
var address = Buffer.from(addressHex, 'hex')
self._lookupAccount(address, function (err, account) {
if (err) return done(err)
self._update(address, account, false, false)
done()
/**
* Looks up address in cache, if not found, looks it up
* in the underlying trie.
* @param {Buffer} key - Address of account
* @param {Function} cb - Callback with params (err, account)
*/
getOrLoad (key, cb) {
const account = this.lookup(key)
if (account) {
async.nextTick(cb, null, account)
} else {
this._lookupAccount(key, (err, account) => {
if (err) return cb(err)
this._update(key, account, false, false)
cb(null, account)
})
}
}

/**
* Warms cache by loading their respective account from trie
* and putting them in cache.
* @param {Array} addresses - Array of addresses
* @param {Function} cb - Callback
*/
warm (addresses, cb) {
// shim till async supports iterators
var accountArr = []
addresses.forEach((val) => {
if (val) accountArr.push(val)
})
}, cb)
}

Cache.prototype.flush = function (cb) {
var it = this._cache.begin
var self = this
var next = true
async.whilst(function () {
return next
}, function (done) {
if (it.value && it.value.modified) {
it.value.modified = false
it.value.val = it.value.val.serialize()
self._trie.put(Buffer.from(it.key, 'hex'), it.value.val, function () {
next = it.hasNext
it.next()
async.eachSeries(accountArr, (addressHex, done) => {
var address = Buffer.from(addressHex, 'hex')
this._lookupAccount(address, (err, account) => {
if (err) return done(err)
this._update(address, account, false, false)
done()
})
} else if (it.value && it.value.deleted) {
it.value.modified = false
it.value.deleted = false
it.value.val = (new Account()).serialize()
self._trie.del(Buffer.from(it.key, 'hex'), function () {
}, cb)
}

/**
* Flushes cache by updating accounts that have been modified
* and removing accounts that have been deleted.
* @param {function} cb - Callback
*/
flush (cb) {
const it = this._cache.begin
let next = true
async.whilst(() => next, (done) => {
if (it.value && it.value.modified) {
it.value.modified = false
it.value.val = it.value.val.serialize()
this._trie.put(Buffer.from(it.key, 'hex'), it.value.val, () => {
next = it.hasNext
it.next()
done()
})
} else if (it.value && it.value.deleted) {
it.value.modified = false
it.value.deleted = false
it.value.val = (new Account()).serialize()
this._trie.del(Buffer.from(it.key, 'hex'), () => {
next = it.hasNext
it.next()
done()
})
} else {
next = it.hasNext
it.next()
done()
})
} else {
next = it.hasNext
it.next()
async.nextTick(done)
}
}, cb)
}
async.nextTick(done)
}
}, cb)
}

Cache.prototype.checkpoint = function () {
this._checkpoints.push(this._cache)
}
/**
* Marks current state of cache as checkpoint, which can
* later on be reverted or commited.
*/
checkpoint () {
this._checkpoints.push(this._cache)
}

Cache.prototype.revert = function () {
this._cache = this._checkpoints.pop(this._cache)
}
/**
* Revert changes to cache last checkpoint (no effect on trie).
*/
revert () {
this._cache = this._checkpoints.pop()
}

Cache.prototype.commit = function () {
this._checkpoints.pop()
}
/**
* Commits to current state of cache (no effect on trie).
*/
commit () {
this._checkpoints.pop()
}

Cache.prototype.clear = function () {
this._cache = Tree()
}
/**
* Clears cache.
*/
clear () {
this._cache = Tree()
}

Cache.prototype.del = function (key) {
this._update(key, new Account(), false, true)
}
/**
* Marks address as deleted in cache.
* @params {Buffer} key - Address
*/
del (key) {
this._update(key, new Account(), false, true)
}

Cache.prototype._update = function (key, val, modified, deleted) {
key = key.toString('hex')
var it = this._cache.find(key)
if (it.node) {
this._cache = it.update({
val: val,
modified: modified,
deleted: deleted
})
} else {
this._cache = this._cache.insert(key, {
val: val,
modified: modified,
deleted: deleted
})
_update (key, val, modified, deleted) {
key = key.toString('hex')
const it = this._cache.find(key)
if (it.node) {
this._cache = it.update({
val: val,
modified: modified,
deleted: deleted
})
} else {
this._cache = this._cache.insert(key, {
val: val,
modified: modified,
deleted: deleted
})
}
}
}
Loading

0 comments on commit 8d6ec92

Please sign in to comment.