Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

Commit

Permalink
feat: new IPLD Format API
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The API is now async/await based

There are numerous changes, the most significant one is that the API
is no longer callback based, but it using async/await.

If you want to access the original JavaScript Ethereum object, it is
now stored in a property called `_ethObj`. So if you e.g. called
`deserialized.hash()`, you now have to call
`deserialized._ethObj.hash()`.

For the full new API please see the [IPLD Formats spec].

[IPLD Formats spec]: https://github.com/ipld/interface-ipld-format
  • Loading branch information
vmx committed May 8, 2019
1 parent 4eaa791 commit dc19aa7
Show file tree
Hide file tree
Showing 24 changed files with 1,222 additions and 1,210 deletions.
81 changes: 21 additions & 60 deletions eth-account-snapshot/index.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,31 @@
'use strict'
const EthAccount = require('ethereumjs-account')
const multicodec = require('multicodec')

const cidFromHash = require('../util/cidFromHash')
const createResolver = require('../util/createResolver')
const emptyCodeHash = require('../util/emptyCodeHash')

module.exports = createResolver('eth-account-snapshot', EthAccount, mapFromEthObj)


function mapFromEthObj (account, options, callback) {
const paths = []

// external links

paths.push({
path: 'storage',
value: { '/': cidFromHash('eth-storage-trie', account.stateRoot).toBaseEncodedString() }
})

// resolve immediately if empty, otherwise link to code
if (emptyCodeHash.equals(account.codeHash)) {
paths.push({
path: 'code',
value: Buffer.from(''),
})
} else {
paths.push({
path: 'code',
value: { '/': cidFromHash('raw', account.codeHash).toBaseEncodedString() }
})
const deserialize = (serialized) => {
const ethObj = new EthAccount(serialized)

const deserialized = {
balance: ethObj.balance,
code: emptyCodeHash.equals(ethObj.codeHash)
? Buffer.alloc(0)
: cidFromHash(multicodec.RAW, ethObj.codeHash),
codeHash: ethObj.codeHash,
isEmpty: ethObj.isEmpty(),
isContract: ethObj.isContract(),
nonce: ethObj.nonce,
stateRoot: ethObj.stateRoot,
storage: cidFromHash(multicodec.ETH_STORAGE_TRIE, ethObj.stateRoot),
_ethObj: ethObj
}

// external links as data

paths.push({
path: 'stateRoot',
value: account.stateRoot
})

paths.push({
path: 'codeHash',
value: account.codeHash
})
Object.defineProperty(deserialized, '_ethObj', { enumerable: false })

// internal data

paths.push({
path: 'nonce',
value: account.nonce
})

paths.push({
path: 'balance',
value: account.balance
})

// helpers

paths.push({
path: 'isEmpty',
value: account.isEmpty()
})

paths.push({
path: 'isContract',
value: account.isContract()
})

callback(null, paths)
return deserialized
}

module.exports = createResolver(multicodec.ETH_ACCOUNT_SNAPSHOT, deserialize)
106 changes: 74 additions & 32 deletions eth-account-snapshot/test/resolver.spec.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,104 @@
/* eslint-env mocha */
'use strict'

const expect = require('chai').expect
const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
const dagEthAccount = require('../index')
const resolver = dagEthAccount.resolver
const Account = require('ethereumjs-account')
const emptyCodeHash = require('../../util/emptyCodeHash')
const CID = require('cids')
const multicodec = require('multicodec')

describe('IPLD format resolver (local)', () => {
let testBlob
let testData = {
nonce: new Buffer('02', 'hex'),
balance: new Buffer('04a817c800', 'hex'),
const testData = {
nonce: Buffer.from('02', 'hex'),
balance: Buffer.from('04a817c800', 'hex'),
codeHash: emptyCodeHash,
stateRoot: new Buffer('012304a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex')
stateRoot: Buffer.from('012304a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex')
}

before((done) => {
const testAccount = new Account(testData)
dagEthAccount.util.serialize(testAccount, (err, result) => {
if (err) return done(err)
testBlob = result
done()
})
const testAccount = new Account(testData)
const testBlob = dagEthAccount.util.serialize({
_ethObj: testAccount
})

it('multicodec is eth-account-snapshot', () => {
expect(resolver.multicodec).to.equal('eth-account-snapshot')
expect(dagEthAccount.codec).to.equal(multicodec.ETH_ACCOUNT_SNAPSHOT)
})

it('defaultHashAlg is keccak-256', () => {
expect(resolver.defaultHashAlg).to.equal('keccak-256')
expect(dagEthAccount.defaultHashAlg).to.equal(multicodec.KECCAK_256)
})

describe('resolver.resolve', () => {
it('path within scope', () => {
resolver.resolve(testBlob, 'nonce', (err, result) => {
expect(err).to.not.exist
expect(result.value.toString('hex')).to.equal(testData.nonce.toString('hex'))
})
const result = resolver.resolve(testBlob, 'nonce')
expect(result.value).to.eql(testData.nonce)
})

it('resolves "storage" to correct type', () => {
const result = resolver.resolve(testBlob, 'storage')
expect(CID.isCID(result.value)).to.be.true()
})

it('resolves "code" to correct type', () => {
const result = resolver.resolve(testBlob, 'storage')
expect(CID.isCID(result.value)).to.be.true()
})

it('resolves "stateRoot" to correct type', () => {
const result = resolver.resolve(testBlob, 'stateRoot')
expect(Buffer.isBuffer(result.value)).to.be.true()
})

it('resolves "codeHash" to correct type', () => {
const result = resolver.resolve(testBlob, 'codeHash')
expect(Buffer.isBuffer(result.value)).to.be.true()
})

it('resolves "nonce" to correct type', () => {
const result = resolver.resolve(testBlob, 'nonce')
expect(Buffer.isBuffer(result.value)).to.be.true()
})

it('resolves "balance" to correct type', () => {
const result = resolver.resolve(testBlob, 'balance')
expect(Buffer.isBuffer(result.value)).to.be.true()
})

it('resolves empty code', () => {
resolver.resolve(testBlob, 'code', (err, result) => {
expect(err).to.not.exist
expect(result.remainderPath).to.equal('')
expect(Buffer.isBuffer(result.value)).to.equal(true)
expect(result.value.length).to.equal(0)
})
it('resolves "isEmpty" to correct type', () => {
const result = resolver.resolve(testBlob, 'isEmpty')
expect(result.value).to.be.false()
})

it('resolves "isContract" to correct type', () => {
const result = resolver.resolve(testBlob, 'isContract')
expect(result.value).to.be.false()
})
})

it('resolves empty code', () => {
const result = resolver.resolve(testBlob, 'code')
expect(result.remainderPath).to.equal('')
expect(Buffer.isBuffer(result.value)).to.be.true()
expect(result.value.length).to.equal(0)
})

describe('resolver.tree', () => {
it('basic sanity test', () => {
resolver.tree(testBlob, (err, paths) => {
expect(err).to.not.exist
expect(Array.isArray(paths)).to.eql(true)
})
it('basic sanity test', async () => {
const tree = resolver.tree(testBlob)
const paths = [...tree]
expect(paths).to.have.members([
'storage',
'code',
'stateRoot',
'codeHash',
'nonce',
'balance',
'isEmpty',
'isContract'
])
})
})
})
84 changes: 16 additions & 68 deletions eth-block-list/index.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,26 @@
'use strict'
const waterfall = require('async/waterfall')
const each = require('async/each')
const asyncify = require('async/asyncify')
const RLP = require('rlp')
const EthBlockHead = require('ethereumjs-block/header')
const multihash = require('multihashing-async')
const cidFromHash = require('../util/cidFromHash')
const ethBlockResolver = require('../eth-block').resolver
const multicodec = require('multicodec')
const ipldEthBlock = require('../eth-block')
const createResolver = require('../util/createResolver')

const ethBlockListResolver = createResolver('eth-block-list', undefined, mapFromEthObj)
const util = ethBlockListResolver.util
util.serialize = asyncify((ethBlockList) => {
const rawOmmers = ethBlockList.map((ethBlock) => ethBlock.raw)
return RLP.encode(rawOmmers)
})
util.deserialize = asyncify((serialized) => {
const deserialize = (serialized) => {
const rawOmmers = RLP.decode(serialized)
return rawOmmers.map((rawBlock) => new EthBlockHead(rawBlock))
})
util.cid = (blockList, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
options = options || {}
const hashAlg = options.hashAlg || 'keccak-256'
const version = typeof options.version === 'undefined' ? 1 : options.version

waterfall([
(cb) => util.serialize(blockList, cb),
(data, cb) => multihash.digest(data, hashAlg, cb),
asyncify((mhash) => cidFromHash('eth-block-list', mhash, options))
], callback)
}

module.exports = ethBlockListResolver


function mapFromEthObj (ethBlockList, options, callback) {
let paths = []

// external links (none)

// external links as data (none)
const deserialized = rawOmmers.map((rawBlock) => {
return ipldEthBlock.util.deserialize(rawBlock)
})

// helpers
deserialized.count = deserialized.length

paths.push({
path: 'count',
value: ethBlockList.length
})
return deserialized
}

// internal data
const ethBlockListResolver = createResolver(
multicodec.ETH_BLOCK_LIST, deserialize)

// add paths for each block
each(ethBlockList, (ethBlock, next) => {
const index = ethBlockList.indexOf(ethBlock)
const blockPath = index.toString()
// block root
paths.push({
path: blockPath,
value: ethBlock
})
// block children
ethBlockResolver._mapFromEthObject(ethBlock, {}, (err, subpaths) => {
if (err) return next(err)
// append blockPath to each subpath
subpaths.forEach((path) => path.path = blockPath + '/' + path.path)
paths = paths.concat(subpaths)
next()
})
}, (err) => {
if (err) return callback(err)
callback(null, paths)
})
ethBlockListResolver.util.serialize = (ethBlockList) => {
const rawOmmers = ethBlockList.map((ethBlock) => ethBlock._ethObj.raw)
return RLP.encode(rawOmmers)
}

module.exports = ethBlockListResolver
Loading

0 comments on commit dc19aa7

Please sign in to comment.