From dc19aa708ace1e18eb6097d9ac87aef95d3fd0c8 Mon Sep 17 00:00:00 2001 From: Volker Mische Date: Wed, 17 Apr 2019 13:38:32 +0200 Subject: [PATCH] feat: new IPLD Format API 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 --- eth-account-snapshot/index.js | 81 ++-- eth-account-snapshot/test/resolver.spec.js | 106 ++++-- eth-block-list/index.js | 84 +---- eth-block-list/test/resolver.spec.js | 223 ++++++----- eth-block/index.js | 121 ++---- eth-block/test/resolver.spec.js | 417 ++++++++++++--------- eth-state-trie/index.js | 5 +- eth-state-trie/test/resolver.spec.js | 248 ++++++------ eth-storage-trie/index.js | 4 +- eth-storage-trie/test/resolver.spec.js | 209 ++++++----- eth-tx-trie/index.js | 5 +- eth-tx-trie/test/resolver.spec.js | 195 +++++----- eth-tx/index.js | 94 ++--- eth-tx/test/resolver.spec.js | 215 +++++++---- package.json | 8 +- util/cidFromEthObj.js | 10 - util/cidFromHash.js | 5 +- util/createIsLink.js | 21 -- util/createResolver.js | 154 ++++---- util/createTrieResolver.js | 171 ++++----- util/createUtil.js | 47 ++- util/emptyCodeHash.js | 2 +- util/emptyStateRoot.js | 2 - util/isExternalLink.js | 5 - 24 files changed, 1222 insertions(+), 1210 deletions(-) delete mode 100644 util/cidFromEthObj.js delete mode 100644 util/createIsLink.js delete mode 100644 util/emptyStateRoot.js delete mode 100644 util/isExternalLink.js diff --git a/eth-account-snapshot/index.js b/eth-account-snapshot/index.js index 20834fe..67aeda1 100644 --- a/eth-account-snapshot/index.js +++ b/eth-account-snapshot/index.js @@ -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) diff --git a/eth-account-snapshot/test/resolver.spec.js b/eth-account-snapshot/test/resolver.spec.js index b1f375f..1964c14 100644 --- a/eth-account-snapshot/test/resolver.spec.js +++ b/eth-account-snapshot/test/resolver.spec.js @@ -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' + ]) }) }) }) diff --git a/eth-block-list/index.js b/eth-block-list/index.js index 10e2e5f..d7b1313 100644 --- a/eth-block-list/index.js +++ b/eth-block-list/index.js @@ -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 diff --git a/eth-block-list/test/resolver.spec.js b/eth-block-list/test/resolver.spec.js index 6a69bc8..894c849 100644 --- a/eth-block-list/test/resolver.spec.js +++ b/eth-block-list/test/resolver.spec.js @@ -1,11 +1,14 @@ /* eslint-env mocha */ 'use strict' -const expect = require('chai').expect +const chai = require('chai') +chai.use(require('dirty-chai')) +const expect = chai.expect +const CID = require('cids') const multihash = require('multihashes') -const RLP = require('rlp') -const EthBlock = require('ethereumjs-block') +const multicodec = require('multicodec') const EthBlockFromRpc = require('ethereumjs-block/from-rpc') + const dagEthBlockList = require('../index') const resolver = dagEthBlockList.resolver const block97Data = require('./data/block97.json') @@ -13,125 +16,161 @@ const ommerData0 = require('./data/ommer0.json') const ommerData1 = require('./data/ommer1.json') describe('IPLD format resolver (local)', () => { - let testBlob - let ethBlock = EthBlockFromRpc(block97Data, [ommerData0, ommerData1]) - - before((done) => { - dagEthBlockList.util.serialize(ethBlock.uncleHeaders, (err, result) => { - if (err) return done(err) - testBlob = result - done() - }) + const ethBlock = EthBlockFromRpc(block97Data, [ommerData0, ommerData1]) + const uncleHeaders = ethBlock.uncleHeaders.map((block) => { + return { _ethObj: block } }) + const testBlob = dagEthBlockList.util.serialize(uncleHeaders) it('multicodec is eth-block-list', () => { - expect(resolver.multicodec).to.equal('eth-block-list') + expect(dagEthBlockList.codec).to.equal(multicodec.ETH_BLOCK_LIST) }) it('defaultHashAlg is keccak-256', () => { - expect(resolver.defaultHashAlg).to.equal('keccak-256') + expect(dagEthBlockList.defaultHashAlg).to.equal(multicodec.KECCAK_256) }) describe('resolver.resolve', () => { - it('uncle #0', (done) => { - resolver.resolve(testBlob, '0', (err, result) => { - expect(err).to.not.exist() - expect(result.value.hash().toString('hex')).to.equal('acfa207ce9d5139b85ecfdc197f8d283fc241f95f176f008f44aab35ef1f901f') - expect(result.remainderPath).to.equal('') - done() - }) + it('uncle #0', () => { + const result = resolver.resolve(testBlob, '0') + expect( + result.value._ethObj.hash().toString('hex') + ).to.equal( + 'acfa207ce9d5139b85ecfdc197f8d283fc241f95f176f008f44aab35ef1f901f' + ) + expect(result.remainderPath).to.equal('') }) - it('uncle #1', (done) => { - resolver.resolve(testBlob, '1', (err, result) => { - expect(err).to.not.exist() - expect(result.value.hash().toString('hex')).to.equal('fe426f2eb0adc88f05ea737da1ebb79e03bca546563ad74bda7bffeb37ad4d6a') - expect(result.remainderPath).to.equal('') - done() - }) + it('uncle #1', () => { + const result = resolver.resolve(testBlob, '1') + expect( + result.value._ethObj.hash().toString('hex') + ).to.equal( + 'fe426f2eb0adc88f05ea737da1ebb79e03bca546563ad74bda7bffeb37ad4d6a' + ) + expect(result.remainderPath).to.equal('') }) - it('uncle count', (done) => { - resolver.resolve(testBlob, 'count', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.equal(2) - expect(result.remainderPath).to.equal('') - done() - }) + it('uncle count', () => { + const result = resolver.resolve(testBlob, 'count') + expect(result.value).to.equal(2) + expect(result.remainderPath).to.equal('') }) - it('resolve block data off uncle #0', (done) => { - resolver.resolve(testBlob, '0/timestamp', (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath.length).to.equal(0) - expect(result.value.toString('hex')).to.equal('55ba43df') - expect(result.remainderPath).to.equal('') - done() - }) + it('resolve block data off uncle #0', () => { + const result = resolver.resolve(testBlob, '0/timestamp') + expect(result.remainderPath.length).to.equal(0) + expect(result.value.toString('hex')).to.equal('55ba43df') + expect(result.remainderPath).to.equal('') + }) + + it('resolves root to correct type', () => { + const result = resolver.resolve(testBlob, '') + expect(Array.isArray(result.value)).to.be.true() + }) + + it('resolves "0" to correct type', () => { + const result = resolver.resolve(testBlob, '0') + expect(typeof result.value === 'object').to.be.true() + }) + + // Only testing `parent` here as the rest of the properties is already + // tested by the eth-block tests + it('resolves "parent" to correct type', () => { + const result = resolver.resolve(testBlob, '0/parent') + expect(CID.isCID(result.value)).to.be.true() }) }) describe('resolver.tree', () => { - it('returns all uncles', (done) => { - resolver.tree(testBlob, (err, paths) => { - expect(err).to.not.exist() - expect(typeof paths).to.eql('object') - expect(Array.isArray(paths)).to.eql(true) - const expectedPaths = ethBlock.uncleHeaders.length * 21 + 1 - expect(paths.length).to.eql(expectedPaths) - done() - }) + it('returns all uncles', () => { + const tree = resolver.tree(testBlob) + const paths = [...tree] + expect(paths).to.have.members([ + 'count', + '0', + '0/parent', + '0/ommers', + '0/transactions', + '0/transactionReceipts', + '0/state', + '0/parentHash', + '0/ommerHash', + '0/transactionTrieRoot', + '0/transactionReceiptTrieRoot', + '0/stateRoot', + '0/authorAddress', + '0/bloom', + '0/difficulty', + '0/number', + '0/gasLimit', + '0/gasUsed', + '0/timestamp', + '0/extraData', + '0/mixHash', + '0/nonce', + '1', + '1/parent', + '1/ommers', + '1/transactions', + '1/transactionReceipts', + '1/state', + '1/parentHash', + '1/ommerHash', + '1/transactionTrieRoot', + '1/transactionReceiptTrieRoot', + '1/stateRoot', + '1/authorAddress', + '1/bloom', + '1/difficulty', + '1/number', + '1/gasLimit', + '1/gasUsed', + '1/timestamp', + '1/extraData', + '1/mixHash', + '1/nonce' + ]) }) }) describe('util', () => { - it('generates correct cid', (done) => { - dagEthBlockList.util.cid(ethBlock.uncleHeaders, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block-list') - const mhash = multihash.decode(cid.multihash) - expect(mhash.name).to.equal('keccak-256') - expect(mhash.digest.toString('hex')).to.equal(ethBlock.header.uncleHash.toString('hex')) - done() - }) + it('generates correct cid', async () => { + const cid = await dagEthBlockList.util.cid(testBlob) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block-list') + const mhash = multihash.decode(cid.multihash) + expect(mhash.name).to.equal('keccak-256') + expect(mhash.digest.toString('hex')).to.equal(ethBlock.header.uncleHash.toString('hex')) }) - it('should create CID, no options', (done) => { - dagEthBlockList.util.cid(ethBlock.uncleHeaders, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block-list') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-256') - done() - }) + it('should create CID, no options', async () => { + const cid = await dagEthBlockList.util.cid(testBlob) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block-list') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-256') }) - it('should create CID, empty options', (done) => { - dagEthBlockList.util.cid(ethBlock.uncleHeaders, {}, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block-list') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-256') - done() - }) + it('should create CID, empty options', async () => { + const cid = await dagEthBlockList.util.cid(ethBlock.uncleHeaders, {}) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block-list') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-256') }) - it('should create CID, hashAlg', (done) => { - dagEthBlockList.util.cid(ethBlock.uncleHeaders, { hashAlg: 'keccak-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block-list') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-512') - done() + it('should create CID, hashAlg', async () => { + const cid = await dagEthBlockList.util.cid(testBlob, { + hashAlg: multicodec.KECCAK_512 }) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block-list') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-512') }) - }) }) diff --git a/eth-block/index.js b/eth-block/index.js index 2212c70..6426b74 100644 --- a/eth-block/index.js +++ b/eth-block/index.js @@ -1,99 +1,40 @@ 'use strict' const EthBlockHeader = require('ethereumjs-block/header') +const multicodec = require('multicodec') const cidFromHash = require('../util/cidFromHash') const createResolver = require('../util/createResolver') -module.exports = createResolver('eth-block', EthBlockHeader, mapFromEthObj) +const deserialize = (serialized) => { + const ethObj = new EthBlockHeader(serialized) + const deserialized = { + authorAddress: ethObj.coinbase, + bloom: ethObj.bloom, + difficulty: ethObj.difficulty, + extraData: ethObj.extraData, + gasLimit: ethObj.gasLimit, + gasUsed: ethObj.gasUsed, + mixHash: ethObj.mixHash, + nonce: ethObj.nonce, + number: ethObj.number, + ommerHash: ethObj.uncleHash, + ommers: cidFromHash(multicodec.ETH_BLOCK_LIST, ethObj.uncleHash), + parent: cidFromHash(multicodec.ETH_BLOCK, ethObj.parentHash), + parentHash: ethObj.parentHash, + state: cidFromHash(multicodec.ETH_STATE_TRIE, ethObj.stateRoot), + stateRoot: ethObj.stateRoot, + timestamp: ethObj.timestamp, + transactions: cidFromHash(multicodec.ETH_TX_TRIE, ethObj.transactionsTrie), + transactionReceipts: cidFromHash( + multicodec.ETH_TX_RECEIPT_TRIE, ethObj.receiptTrie), + transactionReceiptTrieRoot: ethObj.receiptTrie, + transactionTrieRoot: ethObj.transactionsTrie, + _ethObj: ethObj + } -function mapFromEthObj (ethObj, options, callback) { - const paths = [] + Object.defineProperty(deserialized, '_ethObj', { enumerable: false }) - // external links - paths.push({ - path: 'parent', - value: { '/': cidFromHash('eth-block', ethObj.parentHash).toBaseEncodedString() } - }) - paths.push({ - path: 'ommers', - value: { '/': cidFromHash('eth-block-list', ethObj.uncleHash).toBaseEncodedString() } - }) - paths.push({ - path: 'transactions', - value: { '/': cidFromHash('eth-tx-trie', ethObj.transactionsTrie).toBaseEncodedString() } - }) - paths.push({ - path: 'transactionReceipts', - value: { '/': cidFromHash('eth-tx-receipt-trie', ethObj.receiptTrie).toBaseEncodedString() } - }) - paths.push({ - path: 'state', - value: { '/': cidFromHash('eth-state-trie', ethObj.stateRoot).toBaseEncodedString() } - }) - - // external links as data - paths.push({ - path: 'parentHash', - value: ethObj.parentHash - }) - paths.push({ - path: 'ommerHash', - value: ethObj.uncleHash - }) - paths.push({ - path: 'transactionTrieRoot', - value: ethObj.transactionsTrie - }) - paths.push({ - path: 'transactionReceiptTrieRoot', - value: ethObj.receiptTrie - }) - paths.push({ - path: 'stateRoot', - value: ethObj.stateRoot - }) - - // internal data - paths.push({ - path: 'authorAddress', - value: ethObj.coinbase - }) - paths.push({ - path: 'bloom', - value: ethObj.bloom - }) - paths.push({ - path: 'difficulty', - value: ethObj.difficulty - }) - paths.push({ - path: 'number', - value: ethObj.number - }) - paths.push({ - path: 'gasLimit', - value: ethObj.gasLimit - }) - paths.push({ - path: 'gasUsed', - value: ethObj.gasUsed - }) - paths.push({ - path: 'timestamp', - value: ethObj.timestamp - }) - paths.push({ - path: 'extraData', - value: ethObj.extraData - }) - paths.push({ - path: 'mixHash', - value: ethObj.mixHash - }) - paths.push({ - path: 'nonce', - value: ethObj.nonce - }) - - callback(null, paths) + return deserialized } + +module.exports = createResolver(multicodec.ETH_BLOCK, deserialize) diff --git a/eth-block/test/resolver.spec.js b/eth-block/test/resolver.spec.js index 7d2338a..10a7df8 100644 --- a/eth-block/test/resolver.spec.js +++ b/eth-block/test/resolver.spec.js @@ -6,229 +6,296 @@ chai.use(require('dirty-chai')) const expect = chai.expect const CID = require('cids') const EthBlockHeader = require('ethereumjs-block/header') -const multihashing = require('multihashing-async') const multihash = require('multihashes') -const waterfall = require('async/waterfall') -const asyncify = require('async/asyncify') +const multicodec = require('multicodec') const ipldEthBlock = require('../index') -const isExternalLink = require('../../util/isExternalLink') const resolver = ipldEthBlock.resolver describe('IPLD format resolver (local)', () => { - let testBlob - let testEthBlock - let testData = { + const testData = { // 12345678901234567890123456789012 - parentHash: new Buffer('0100000000000000000000000000000000000000000000000000000000000000', 'hex'), - uncleHash: new Buffer('0200000000000000000000000000000000000000000000000000000000000000', 'hex'), - coinbase: new Buffer('0300000000000000000000000000000000000000', 'hex'), - stateRoot: new Buffer('0400000000000000000000000000000000000000000000000000000000000000', 'hex'), - transactionsTrie: new Buffer('0500000000000000000000000000000000000000000000000000000000000000', 'hex'), - receiptTrie: new Buffer('0600000000000000000000000000000000000000000000000000000000000000', 'hex'), - // bloom: new Buffer('07000000000000000000000000000000', 'hex'), - difficulty: new Buffer('0800000000000000000000000000000000000000000000000000000000000000', 'hex'), - number: new Buffer('0900000000000000000000000000000000000000000000000000000000000000', 'hex'), - gasLimit: new Buffer('1000000000000000000000000000000000000000000000000000000000000000', 'hex'), - gasUsed: new Buffer('1100000000000000000000000000000000000000000000000000000000000000', 'hex'), - timestamp: new Buffer('1200000000000000000000000000000000000000000000000000000000000000', 'hex'), - extraData: new Buffer('1300000000000000000000000000000000000000000000000000000000000000', 'hex'), - mixHash: new Buffer('1400000000000000000000000000000000000000000000000000000000000000', 'hex'), - nonce: new Buffer('1500000000000000000000000000000000000000000000000000000000000000', 'hex') + parentHash: Buffer.from('0100000000000000000000000000000000000000000000000000000000000000', 'hex'), + uncleHash: Buffer.from('0200000000000000000000000000000000000000000000000000000000000000', 'hex'), + coinbase: Buffer.from('0300000000000000000000000000000000000000', 'hex'), + stateRoot: Buffer.from('0400000000000000000000000000000000000000000000000000000000000000', 'hex'), + transactionsTrie: Buffer.from('0500000000000000000000000000000000000000000000000000000000000000', 'hex'), + receiptTrie: Buffer.from('0600000000000000000000000000000000000000000000000000000000000000', 'hex'), + // bloom: Buffer.from('07000000000000000000000000000000', 'hex'), + difficulty: Buffer.from('0800000000000000000000000000000000000000000000000000000000000000', 'hex'), + number: Buffer.from('0900000000000000000000000000000000000000000000000000000000000000', 'hex'), + gasLimit: Buffer.from('1000000000000000000000000000000000000000000000000000000000000000', 'hex'), + gasUsed: Buffer.from('1100000000000000000000000000000000000000000000000000000000000000', 'hex'), + timestamp: Buffer.from('1200000000000000000000000000000000000000000000000000000000000000', 'hex'), + extraData: Buffer.from('1300000000000000000000000000000000000000000000000000000000000000', 'hex'), + mixHash: Buffer.from('1400000000000000000000000000000000000000000000000000000000000000', 'hex'), + nonce: Buffer.from('1500000000000000000000000000000000000000000000000000000000000000', 'hex') } - - before((done) => { - testEthBlock = new EthBlockHeader(testData) - - waterfall([ - (cb) => ipldEthBlock.util.serialize(testEthBlock, cb), - (serialized, cb) => multihashing(serialized, 'keccak-256', (err, hash) => { - if (err) { - return cb(err) - } - testBlob = serialized - cb() - }) - ], done) + const testEthBlock = new EthBlockHeader(testData) + const testBlob = ipldEthBlock.util.serialize({ + _ethObj: testEthBlock }) it('multicodec is eth-block', () => { - expect(resolver.multicodec).to.equal('eth-block') + expect(ipldEthBlock.codec).to.equal(multicodec.ETH_BLOCK) }) it('defaultHashAlg is keccak-256', () => { - expect(resolver.defaultHashAlg).to.equal('keccak-256') + expect(ipldEthBlock.defaultHashAlg).to.equal(multicodec.KECCAK_256) }) - it('can parse the cid', (done) => { - ipldEthBlock.util.cid(testEthBlock, (err, cid) => { - expect(err).not.to.exist() - let encodedCid = cid.toBaseEncodedString() - let reconstructedCid = new CID(encodedCid) - expect(cid.version).to.equal(reconstructedCid.version) - expect(cid.codec).to.equal(reconstructedCid.codec) - expect(cid.multihash.toString('hex')).to.equal(reconstructedCid.multihash.toString('hex')) - done() - }) + it('can parse the cid', async () => { + const cid = await ipldEthBlock.util.cid(testBlob) + const encodedCid = cid.toBaseEncodedString() + const reconstructedCid = new CID(encodedCid) + expect(cid.version).to.equal(reconstructedCid.version) + expect(cid.codec).to.equal(reconstructedCid.codec) + expect(cid.multihash.toString('hex')).to.equal(reconstructedCid.multihash.toString('hex')) }) describe('resolver.resolve', () => { it('path within scope', () => { - resolver.resolve(testBlob, 'number', (err, result) => { - expect(err).not.to.exist() - expect(result.value.toString('hex')).to.equal(testData.number.toString('hex')) - }) + const result = resolver.resolve(testBlob, 'number') + expect(result.value.toString('hex')).to.equal(testData.number.toString('hex')) }) - }) - it('resolver.tree', () => { - resolver.tree(testBlob, (err, paths) => { - expect(err).not.to.exist() - expect(Array.isArray(paths)).to.eql(true) - expect(paths.length).to.eql(20) - paths.forEach((path) => { - expect(typeof path).to.eql('string') - }) + it('resolves "parent" to correct type', () => { + const result = resolver.resolve(testBlob, 'parent') + expect(CID.isCID(result.value)).to.be.true() + }) + + it('resolves "ommers" to correct type', () => { + const result = resolver.resolve(testBlob, 'ommers') + expect(CID.isCID(result.value)).to.be.true() + }) + + it('resolves "transactions" to correct type', () => { + const result = resolver.resolve(testBlob, 'transactions') + expect(CID.isCID(result.value)).to.be.true() + }) + + it('resolves "transactionReceipts" to correct type', () => { + const result = resolver.resolve(testBlob, 'transactionReceipts') + expect(CID.isCID(result.value)).to.be.true() + }) + + it('resolves "state" to correct type', () => { + const result = resolver.resolve(testBlob, 'state') + expect(CID.isCID(result.value)).to.be.true() + }) + + it('resolves "parentHash" to correct type', () => { + const result = resolver.resolve(testBlob, 'parentHash') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "ommerHash" to correct type', () => { + const result = resolver.resolve(testBlob, 'ommerHash') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "transactionTrieRoot" to correct type', () => { + const result = resolver.resolve(testBlob, 'transactionTrieRoot') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "transactionReceiptTrieRoot" to correct type', () => { + const result = resolver.resolve(testBlob, 'transactionReceiptTrieRoot') + expect(Buffer.isBuffer(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 "authorAddress" to correct type', () => { + const result = resolver.resolve(testBlob, 'authorAddress') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "bloom" to correct type', () => { + const result = resolver.resolve(testBlob, 'bloom') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "difficulty" to correct type', () => { + const result = resolver.resolve(testBlob, 'difficulty') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "number" to correct type', () => { + const result = resolver.resolve(testBlob, 'number') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "gasLimit" to correct type', () => { + const result = resolver.resolve(testBlob, 'gasLimit') + expect(Buffer.isBuffer(result.value)).to.be.true() }) + + it('resolves "gasUsed" to correct type', () => { + const result = resolver.resolve(testBlob, 'gasUsed') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "timestamp" to correct type', () => { + const result = resolver.resolve(testBlob, 'timestamp') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "extraData" to correct type', () => { + const result = resolver.resolve(testBlob, 'extraData') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "mixHash" to correct type', () => { + const result = resolver.resolve(testBlob, 'mixHash') + 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('resolver.tree', async () => { + const tree = resolver.tree(testBlob) + const paths = [...tree] + expect(paths).to.have.members([ + 'parent', + 'ommers', + 'transactions', + 'transactionReceipts', + 'state', + 'parentHash', + 'ommerHash', + 'transactionTrieRoot', + 'transactionReceiptTrieRoot', + 'stateRoot', + 'authorAddress', + 'bloom', + 'difficulty', + 'number', + 'gasLimit', + 'gasUsed', + 'timestamp', + 'extraData', + 'mixHash', + 'nonce' + ]) }) describe('util', () => { - it('should create CID, no options', (done) => { - ipldEthBlock.util.cid(testEthBlock, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-256') - done() - }) + it('should create CID, no options', async () => { + const cid = await ipldEthBlock.util.cid(testBlob) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-256') }) - it('should create CID, empty options', (done) => { - ipldEthBlock.util.cid(testEthBlock, {}, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-256') - done() - }) + it('should create CID, empty options', async () => { + const cid = await ipldEthBlock.util.cid(testBlob, {}) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-256') }) - it('should create CID, hashAlg', (done) => { - ipldEthBlock.util.cid(testEthBlock, { hashAlg: 'keccak-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-512') - done() + it('should create CID, hashAlg', async () => { + const cid = await ipldEthBlock.util.cid(testBlob, { + hashAlg: multicodec.KECCAK_512 }) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-512') }) }) }) describe('manual ancestor walking', () => { + let cid1 + let cid2 let ethBlock1 let ethBlock2 let ethBlock3 - let cid1 - let cid2 - let cid3 - - before((done) => { - - waterfall([ - asyncify(() => { - return ethBlock1 = new EthBlockHeader({ - number: 1 - }) - }), - ipldEthBlock.util.cid, - asyncify((cid) => { cid1 = cid }), - - asyncify(() => { - return ethBlock2 = new EthBlockHeader({ - number: 2, - parentHash: ethBlock1.hash() - }) - }), - ipldEthBlock.util.cid, - asyncify((cid) => { cid2 = cid }), - - asyncify(() => { - return ethBlock3 = new EthBlockHeader({ - number: 3, - parentHash: ethBlock2.hash() - }) - }), - ipldEthBlock.util.cid, - asyncify((cid) => { cid3 = cid }), - ], done) - }) + let serialized1 + let serialized2 + let serialized3 - it('root path (same as get)', (done) => { - ipldEthBlock.resolver._resolveFromEthObject(ethBlock1, '/', (err, result) => { - expect(err).to.not.exist() + before(async () => { + ethBlock1 = { + _ethObj: new EthBlockHeader({ + number: 1 + }) + } + serialized1 = ipldEthBlock.util.serialize(ethBlock1) + cid1 = await ipldEthBlock.util.cid(serialized1) - ipldEthBlock.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() + ethBlock2 = { + _ethObj: new EthBlockHeader({ + number: 2, + parentHash: ethBlock1._ethObj.hash() }) - }) + } + serialized2 = ipldEthBlock.util.serialize(ethBlock2) + cid2 = await ipldEthBlock.util.cid(serialized2) + + ethBlock3 = { + _ethObj: new EthBlockHeader({ + number: 3, + parentHash: ethBlock2._ethObj.hash() + }) + } + serialized3 = ipldEthBlock.util.serialize(ethBlock3) + await ipldEthBlock.util.cid(serialized3) }) - it('value within 1st node scope', (done) => { - ipldEthBlock.resolver._resolveFromEthObject(ethBlock3, 'number', (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath).to.eql('') - expect(isExternalLink(result.value)).to.eql(false) - expect(result.value.toString('hex')).to.eql('03') - done() - }) + it('root path (same as get)', () => { + const result = ipldEthBlock.resolver.resolve(serialized1, '/') + const deserialized = ipldEthBlock.util.deserialize(serialized1) + expect(result.value).to.eql(deserialized) }) - it('value within nested scope (1 level)', (done) => { - ipldEthBlock.resolver._resolveFromEthObject(ethBlock3, 'parent/number', (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath).to.eql('number') - expect(isExternalLink(result.value)).to.eql(true) - expect(result.value['/']).to.eql(cid2.toBaseEncodedString()) - ipldEthBlock.resolver._resolveFromEthObject(ethBlock2, result.remainderPath, (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath).to.eql('') - expect(isExternalLink(result.value)).to.eql(false) - expect(result.value.toString('hex')).to.eql('02') - done() - }) - }) + it('value within 1st node scope', () => { + const result = ipldEthBlock.resolver.resolve(serialized3, 'number') + expect(result.remainderPath).to.eql('') + expect(CID.isCID(result.value)).to.be.false() + expect(result.value.toString('hex')).to.eql('03') }) - it('value within nested scope (1 level)', (done) => { - ipldEthBlock.resolver._resolveFromEthObject(ethBlock3, 'parent/parent/number', (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath).to.eql('parent/number') - expect(isExternalLink(result.value)).to.eql(true) - expect(result.value['/']).to.eql(cid2.toBaseEncodedString()) - ipldEthBlock.resolver._resolveFromEthObject(ethBlock2, result.remainderPath, (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath).to.eql('number') - expect(isExternalLink(result.value)).to.eql(true) - expect(result.value['/']).to.eql(cid1.toBaseEncodedString()) - ipldEthBlock.resolver._resolveFromEthObject(ethBlock1, result.remainderPath, (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath).to.eql('') - expect(isExternalLink(result.value)).to.eql(false) - expect(result.value.toString('hex')).to.eql('01') - done() - }) - }) - }) + it('value within nested scope (1 level)', () => { + const result = ipldEthBlock.resolver.resolve(serialized3, 'parent/number') + expect(result.remainderPath).to.eql('number') + expect(CID.isCID(result.value)).to.be.true() + expect(result.value.equals(cid2)).to.be.true() + + const result2 = ipldEthBlock.resolver.resolve(serialized2, result.remainderPath) + expect(result2.remainderPath).to.eql('') + expect(CID.isCID(result2.value)).to.be.false() + expect(result2.value.toString('hex')).to.eql('02') }) -}) + it('value within nested scope (2 levels)', () => { + const result = ipldEthBlock.resolver.resolve(serialized3, 'parent/parent/number') + expect(result.remainderPath).to.eql('parent/number') + expect(CID.isCID(result.value)).to.be.true() + expect(result.value.equals(cid2)).to.be.true() + + const result2 = ipldEthBlock.resolver.resolve(serialized2, result.remainderPath) + expect(result2.remainderPath).to.eql('number') + expect(CID.isCID(result2.value)).to.be.true() + expect(result2.value.equals(cid1)).to.be.true() + const result3 = ipldEthBlock.resolver.resolve(serialized1, result2.remainderPath) + expect(result3.remainderPath).to.eql('') + expect(CID.isCID(result3.value)).to.be.false() + expect(result3.value.toString('hex')).to.eql('01') + }) +}) diff --git a/eth-state-trie/index.js b/eth-state-trie/index.js index f7bfc7f..96cb68e 100644 --- a/eth-state-trie/index.js +++ b/eth-state-trie/index.js @@ -1,9 +1,10 @@ 'use strict' -/* eslint max-nested-callbacks: ["error", 5] */ +const multicodec = require('multicodec') const ethAccountSnapshotResolver = require('../eth-account-snapshot') const createTrieResolver = require('../util/createTrieResolver') -const ethStateTrieResolver = createTrieResolver('eth-state-trie', ethAccountSnapshotResolver) +const ethStateTrieResolver = createTrieResolver( + multicodec.ETH_STATE_TRIE, ethAccountSnapshotResolver) module.exports = ethStateTrieResolver diff --git a/eth-state-trie/test/resolver.spec.js b/eth-state-trie/test/resolver.spec.js index c210099..8d7564c 100644 --- a/eth-state-trie/test/resolver.spec.js +++ b/eth-state-trie/test/resolver.spec.js @@ -4,188 +4,164 @@ const chai = require('chai') chai.use(require('dirty-chai')) const expect = chai.expect -const async = require('async') const Account = require('ethereumjs-account') const Trie = require('merkle-patricia-tree') -const TrieNode = require('merkle-patricia-tree/trieNode') -const multihashing = require('multihashing-async') +const multicodec = require('multicodec') const CID = require('cids') -const cidFromHash = require('../../util/cidFromHash') +const promisify = require('promisify-es6') const ipldEthStateTrie = require('../index') -const isExternalLink = require('../../util/isExternalLink') const resolver = ipldEthStateTrie.resolver describe('IPLD format resolver (local)', () => { // setup contract test data let testContract let testContractData = { - balance: new Buffer('012345', 'hex'), - codeHash: new Buffer('abcd04a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex'), - stateRoot: new Buffer('012304a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex') - } - function prepareTestContract (done) { - testContract = new Account(testContractData) - done() + balance: Buffer.from('012345', 'hex'), + codeHash: Buffer.from('abcd04a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex'), + stateRoot: Buffer.from('012304a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex') } // setup external account test data let testExternalAccount let testExternalAccountData = { - balance: new Buffer('abcdef', 'hex'), - nonce: new Buffer('02', 'hex') - } - function prepareTestExternalAccount (done) { - testExternalAccount = new Account(testExternalAccountData) - done() + balance: Buffer.from('abcdef', 'hex'), + nonce: Buffer.from('02', 'hex') } // setup test trie - let trie - let trieNodes = {} let dagNodes = {} - before((done) => { - trie = new Trie() - async.waterfall([ - (cb) => prepareTestContract(cb), - (cb) => prepareTestExternalAccount(cb), - (cb) => populateTrie(trie, cb), - (cb) => dumpTrieNonInlineNodes(trie, trieNodes, cb), - // (cb) => logTrie(trie, cb), - (cb) => async.mapValues(trieNodes, (node, key, cb) => { - ipldEthStateTrie.util.serialize(node, cb) - }, cb) - ], (err, result) => { - if (err) { - return done(err) - } - dagNodes = result - done() + before(async () => { + testContract = new Account(testContractData) + testExternalAccount = new Account(testExternalAccountData) + const trie = await populateTrie() + const trieNodes = await dumpTrieNonInlineNodes(trie) + Object.entries(trieNodes).map(([key, node]) => { + dagNodes[key] = ipldEthStateTrie.util.serialize({ _ethObj: node }) }) }) - function populateTrie (trie, cb) { - async.series([ - (cb) => trie.put(new Buffer('000a0a00', 'hex'), testExternalAccount.serialize(), cb), - (cb) => trie.put(new Buffer('000a0a01', 'hex'), testExternalAccount.serialize(), cb), - (cb) => trie.put(new Buffer('000a0a02', 'hex'), testExternalAccount.serialize(), cb), - (cb) => trie.put(new Buffer('000a0b00', 'hex'), testExternalAccount.serialize(), cb), - (cb) => trie.put(new Buffer('000b0a00', 'hex'), testContract.serialize(), cb), - (cb) => trie.put(new Buffer('000b0b00', 'hex'), testContract.serialize(), cb), - (cb) => trie.put(new Buffer('000c0a00', 'hex'), testContract.serialize(), cb) - ], (err) => { - if (err) return cb(err) - cb() - }) - } - - function cid (data, cb) { - multihashing(data, 'keccak-256', (err, hash) => { - if (err) { - return cb(err) - } - const cid = new CID(1, resolver.multicodec, hash) - cb(null, cid) - }) + async function populateTrie () { + const trie = new Trie() + const put = promisify(trie.put.bind(trie)) + await put(Buffer.from('000a0a00', 'hex'), testExternalAccount.serialize()) + await put(Buffer.from('000a0a01', 'hex'), testExternalAccount.serialize()) + await put(Buffer.from('000a0a02', 'hex'), testExternalAccount.serialize()) + await put(Buffer.from('000a0b00', 'hex'), testExternalAccount.serialize()) + await put(Buffer.from('000b0a00', 'hex'), testContract.serialize()) + await put(Buffer.from('000b0b00', 'hex'), testContract.serialize()) + await put(Buffer.from('000c0a00', 'hex'), testContract.serialize()) + return trie } it('multicodec is eth-state-trie', () => { - expect(resolver.multicodec).to.equal('eth-state-trie') + expect(ipldEthStateTrie.codec).to.equal(multicodec.ETH_STATE_TRIE) }) it('defaultHashAlg is keccak-256', () => { - expect(resolver.defaultHashAlg).to.equal('keccak-256') + expect(ipldEthStateTrie.defaultHashAlg).to.equal(multicodec.KECCAK_256) }) describe('resolver.resolve', () => { - it('root node resolves to branch', (done) => { - let rootNode = dagNodes[''] - resolver.resolve(rootNode, '0/0/0/c/0/a/0/0/codeHash', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('c/0/a/0/0/codeHash') - expect(isExternalLink(trieNode)).to.eql(true) - cid(dagNodes['0/0/0'], (err, cid) => { - expect(err).to.not.exist() - expect(trieNode['/']).to.eql(cid.toBaseEncodedString()) - done() - }) - }) + it('root node resolves to branch', async () => { + const rootNode = dagNodes[''] + const result = resolver.resolve(rootNode, '0/0/0/c/0/a/0/0/codeHash') + const trieNode = result.value + expect(result.remainderPath).to.eql('c/0/a/0/0/codeHash') + expect(CID.isCID(trieNode)).to.be.true() + const cid = await ipldEthStateTrie.util.cid(dagNodes['0/0/0']) + expect(trieNode.equals(cid)).to.be.true() + }) + + it('neck node resolves down to c branch', async () => { + const neckNode = dagNodes['0/0/0'] + const result = resolver.resolve(neckNode, 'c/0/a/0/0/codeHash') + const trieNode = result.value + expect(result.remainderPath).to.eql('0/a/0/0/codeHash') + expect(CID.isCID(trieNode)).to.be.true() + const cid = await ipldEthStateTrie.util.cid(dagNodes['0/0/0/c']) + expect(trieNode.equals(cid)).to.be.true() }) - it('neck node resolves down to c branch', (done) => { - let neckNode = dagNodes['0/0/0'] - resolver.resolve(neckNode, 'c/0/a/0/0/codeHash', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('0/a/0/0/codeHash') - expect(isExternalLink(trieNode)).to.eql(true) - cid(dagNodes['0/0/0/c'], (err, cid) => { - expect(err).to.not.exist() - expect(trieNode['/']).to.eql(cid.toBaseEncodedString()) - done() - }) - }) + it('"c" branch node resolves down to account data', () => { + const cBranchNode = dagNodes['0/0/0/c'] + const result = resolver.resolve(cBranchNode, '0/a/0/0/codeHash') + const trieNode = result.value + expect(result.remainderPath).to.eql('') + expect(CID.isCID(trieNode)).to.be.false() + expect(Buffer.isBuffer(result.value)).to.eql(true) + expect(result.value.toString('hex')).to.eql(testContract.codeHash.toString('hex')) }) - it('"c" branch node resolves down to account data', (done) => { - let cBranchNode = dagNodes['0/0/0/c'] - resolver.resolve(cBranchNode, '0/a/0/0/codeHash', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('') - expect(isExternalLink(trieNode)).to.eql(false) - expect(Buffer.isBuffer(result.value)).to.eql(true) - expect(result.value.toString('hex')).to.eql(testContract.codeHash.toString('hex')) - done() - }) + it('resolves "0" to correct type', () => { + const result = resolver.resolve(dagNodes['0/0/0/c'], '0') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() + }) + + it('resolves "0/a" to correct type', () => { + const result = resolver.resolve(dagNodes['0/0/0/c'], '0/a') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() + }) + + it('resolves "0/a/0" to correct type', () => { + const result = resolver.resolve(dagNodes['0/0/0/c'], '0/a/0') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() + }) + + it('resolves "0/a/0/0" to correct type', () => { + const result = resolver.resolve(dagNodes['0/0/0/c'], '0/a/0/0') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() + }) + + // Only testing `storage` here as the rest of the properties is already + // tested by the eth-account-snapshot tests + it('resolves "0/a/0/0/storage" to correct type', () => { + const result = resolver.resolve(dagNodes['0/0/0/c'], '0/a/0/0/storage') + expect(CID.isCID(result.value)).to.be.true() }) }) describe('resolver.tree', () => { - it('"c" branch node lists account paths', (done) => { - let cBranchNode = dagNodes['0/0/0/c'] - resolver.tree(cBranchNode, (err, childPaths) => { - expect(err).to.not.exist() - expect(Array.isArray(childPaths)).to.eql(true) - childPaths.forEach((path) =>{ - expect(typeof path).to.eql('string') - }) - expect(childPaths.length).to.eql(9) - expect(childPaths).to.contain('0/a/0/0/balance') - done() - }) + it('"c" branch node lists account paths', () => { + const cBranchNode = dagNodes['0/0/0/c'] + const tree = resolver.tree(cBranchNode) + const paths = [...tree] + expect(paths).to.have.members([ + '0', + '0/a', + '0/a/0', + '0/a/0/0', + '0/a/0/0/storage', + '0/a/0/0/code', + '0/a/0/0/stateRoot', + '0/a/0/0/codeHash', + '0/a/0/0/nonce', + '0/a/0/0/balance', + '0/a/0/0/isEmpty', + '0/a/0/0/isContract' + ]) }) }) }) - -function dumpTrieNonInlineNodes (trie, fullNodes, cb) { - trie._findDbNodes((nodeRef, node, key, next) => { - fullNodes[nibbleToPath(key)] = node - next() - }, cb) -} - -function logTrie (trie, cb) { - trie._walkTrie(trie.root, (nodeRef, node, key, walkController) => { - console.log('node:', nibbleToPath(key), node.type, TrieNode.isRawNode(nodeRef) ? 'raw':'hashed', 'ref:'+cidFromHash('eth-state-trie', nodeRef).toBaseEncodedString()) - var children = node.getChildren() - if (node.type === 'leaf') { - console.log(' + value') - } - children.forEach((childData, index) => { - var keyExtension = childData[0] - var childRef = childData[1] - console.log(' -', nibbleToPath(keyExtension), TrieNode.isRawNode(childRef) ? 'raw':'hashed', 'ref:'+cidFromHash('eth-state-trie', childRef).toBaseEncodedString()) +function dumpTrieNonInlineNodes (trie) { + const fullNodes = {} + return new Promise((resolve, reject) => { + trie._findDbNodes((nodeRef, node, key, next) => { + fullNodes[nibbleToPath(key)] = node + next() + }, (err) => { + if (err) { + return reject(err) + } + return resolve(fullNodes) }) - walkController.next() - }, cb) + }) } function nibbleToPath (data) { return data.map((num) => num.toString(16)).join('/') } - -function contains (array, item) { - return array.indexOf(item) !== -1 -} diff --git a/eth-storage-trie/index.js b/eth-storage-trie/index.js index ecc5833..0a32014 100644 --- a/eth-storage-trie/index.js +++ b/eth-storage-trie/index.js @@ -1,8 +1,8 @@ 'use strict' -/* eslint max-nested-callbacks: ["error", 5] */ +const multicodec = require('multicodec') const createTrieResolver = require('../util/createTrieResolver') -const ethStorageTrieResolver = createTrieResolver('eth-storage-trie') +const ethStorageTrieResolver = createTrieResolver(multicodec.ETH_STORAGE_TRIE) module.exports = ethStorageTrieResolver diff --git a/eth-storage-trie/test/resolver.spec.js b/eth-storage-trie/test/resolver.spec.js index d72f1d7..ccfe5d5 100644 --- a/eth-storage-trie/test/resolver.spec.js +++ b/eth-storage-trie/test/resolver.spec.js @@ -1,124 +1,151 @@ /* eslint-env mocha */ 'use strict' -const expect = require('chai').expect -const async = require('async') +const chai = require('chai') +chai.use(require('dirty-chai')) +const expect = chai.expect +const CID = require('cids') const Trie = require('merkle-patricia-tree') -const TrieNode = require('merkle-patricia-tree/trieNode') +const multicodec = require('multicodec') +const promisify = require('promisify-es6') const ipldEthStateTrie = require('../index') -const isExternalLink = require('../../util/isExternalLink') const resolver = ipldEthStateTrie.resolver describe('IPLD format resolver (local)', () => { // setup test trie - let trie - let trieNodes = [] let dagNodes - before((done) => { - trie = new Trie() - async.waterfall([ - (cb) => populateTrie(trie, cb), - (cb) => dumpTrieNonInlineNodes(trie, trieNodes, cb), - (cb) => async.map(trieNodes, ipldEthStateTrie.util.serialize, cb) - ], (err, result) => { - if (err) return done(err) - dagNodes = result - done() + before(async () => { + const trie = await populateTrie() + const trieNodes = await dumpTrieNonInlineNodes(trie) + dagNodes = trieNodes.map((node) => { + return ipldEthStateTrie.util.serialize({ _ethObj: node }) }) }) - function populateTrie (trie, cb) { - async.series([ - (cb) => trie.put(new Buffer('000a0a00', 'hex'), new Buffer('cafe01', 'hex'), cb), - (cb) => trie.put(new Buffer('000a0a01', 'hex'), new Buffer('cafe02', 'hex'), cb), - (cb) => trie.put(new Buffer('000a0a02', 'hex'), new Buffer('cafe03', 'hex'), cb), - (cb) => trie.put(new Buffer('000a0b00', 'hex'), new Buffer('cafe04', 'hex'), cb), - (cb) => trie.put(new Buffer('000b0a00', 'hex'), new Buffer('cafe05', 'hex'), cb), - (cb) => trie.put(new Buffer('000b0b00', 'hex'), new Buffer('cafe06', 'hex'), cb), - (cb) => trie.put(new Buffer('000c0a00', 'hex'), new Buffer('cafe07', 'hex'), cb) - ], (err) => { - if (err) return cb(err) - cb() - }) + async function populateTrie () { + const trie = new Trie() + const put = promisify(trie.put.bind(trie)) + await put(Buffer.from('000a0a00', 'hex'), Buffer.from('cafe01', 'hex')) + await put(Buffer.from('000a0a01', 'hex'), Buffer.from('cafe02', 'hex')) + await put(Buffer.from('000a0a02', 'hex'), Buffer.from('cafe03', 'hex')) + await put(Buffer.from('000a0b00', 'hex'), Buffer.from('cafe04', 'hex')) + await put(Buffer.from('000b0a00', 'hex'), Buffer.from('cafe05', 'hex')) + await put(Buffer.from('000b0b00', 'hex'), Buffer.from('cafe06', 'hex')) + await put(Buffer.from('000c0a00', 'hex'), Buffer.from('cafe07', 'hex')) + return trie } - // function logTrie(cb){ - // async.each(dagNodes, (node, next) => { - // let index = dagNodes.indexOf(node) - // let trieNode = trieNodes[index] - // resolver.tree(node, (err, paths) => { - // if (err) return next(err) - // let cidForHash = require('ipld-eth-trie/src/common').cidForHash - // let cid = cidForHash('eth-storage-trie', trieNode.hash()) - // console.log(index, paths.map(path => path.path), cid.toBaseEncodedString()) - // next() - // }) - // }, cb) - // } - it('multicodec is eth-storage-trie', () => { - expect(resolver.multicodec).to.equal('eth-storage-trie') + expect(ipldEthStateTrie.codec).to.equal(multicodec.ETH_STORAGE_TRIE) }) it('defaultHashAlg is keccak-256', () => { - expect(resolver.defaultHashAlg).to.equal('keccak-256') + expect(ipldEthStateTrie.defaultHashAlg).to.equal(multicodec.KECCAK_256) }) describe('resolver.resolve', () => { - it('root node resolves to neck', (done) => { - let rootNode = dagNodes[0] - resolver.resolve(rootNode, '0/0/0/c/0/a/0/0/', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('c/0/a/0/0/') - expect(isExternalLink(trieNode)).to.eql(true) - done() - }) + it('root node resolves to neck', () => { + const rootNode = dagNodes[0] + const result = resolver.resolve(rootNode, '0/0/0/c/0/a/0/0/') + const trieNode = result.value + expect(result.remainderPath).to.eql('c/0/a/0/0') + expect(CID.isCID(trieNode)).to.be.true() + }) + + it('neck node resolves "c" down to buffer', () => { + const node = dagNodes[1] + const result = resolver.resolve(node, 'c/0/a/0/0/') + const trieNode = result.value + expect(result.remainderPath).to.eql('') + expect(CID.isCID(trieNode)).to.be.false() + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('neck node resolves "b" down to branch', () => { + const node = dagNodes[1] + const result = resolver.resolve(node, 'b/0/a/0/0/') + const trieNode = result.value + expect(result.remainderPath).to.eql('0/a/0/0') + expect(CID.isCID(trieNode)).to.be.true() + }) + + it('neck node resolves "a" down to branch', () => { + const node = dagNodes[1] + const result = resolver.resolve(node, 'a/0/a/0/0/') + const trieNode = result.value + expect(result.remainderPath).to.eql('0/a/0/0') + expect(CID.isCID(trieNode)).to.be.true() + }) + + it('resolves "a" to correct type', () => { + const result = resolver.resolve(dagNodes[1], 'a') + expect(CID.isCID(result.value)).to.be.true() + }) + + it('resolves "b" to correct type', () => { + const result = resolver.resolve(dagNodes[1], 'b') + expect(CID.isCID(result.value)).to.be.true() }) - it('neck node resolves "c" down to buffer', (done) => { - let node = dagNodes[1] - resolver.resolve(node, 'c/0/a/0/0/', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('') - expect(isExternalLink(trieNode)).to.eql(false) - expect(Buffer.isBuffer(result.value)).to.eql(true) - done() - }) + it('resolves "c" to correct type', () => { + const result = resolver.resolve(dagNodes[1], 'c') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() }) - it('neck node resolves "b" down to branch', (done) => { - let node = dagNodes[1] - resolver.resolve(node, 'b/0/a/0/0/', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('0/a/0/0/') - expect(isExternalLink(trieNode)).to.eql(true) - done() - }) + it('resolves "c/0" to correct type', () => { + const result = resolver.resolve(dagNodes[1], 'c/0') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() }) - it('neck node resolves "a" down to branch', (done) => { - let node = dagNodes[1] - resolver.resolve(node, 'a/0/a/0/0/', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('0/a/0/0/') - expect(isExternalLink(trieNode)).to.eql(true) - done() - }) + it('resolves "c/0/a" to correct type', () => { + const result = resolver.resolve(dagNodes[1], 'c/0/a') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() + }) + + it('resolves "c/0/a/0" to correct type', () => { + const result = resolver.resolve(dagNodes[1], 'c/0/a/0') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() + }) + + it('resolves "c/0/a/0/0" to correct type', () => { + const result = resolver.resolve(dagNodes[1], 'c/0/a/0/0') + expect(CID.isCID(result.value)).to.be.false() + expect(typeof result.value === 'object').to.be.true() }) }) -}) -function dumpTrieNonInlineNodes (trie, fullNodes, cb) { - trie._findDbNodes((nodeRef, node, key, next) => { - fullNodes.push(node) - next() - }, cb) -} + describe('resolver.tree', () => { + it('returns all uncles', () => { + const tree = resolver.tree(dagNodes[1]) + const paths = [...tree] + expect(paths).to.have.members([ + 'a', + 'b', + 'c', + 'c/0', + 'c/0/a', + 'c/0/a/0', + 'c/0/a/0/0' + ]) + }) + }) +}) -function contains (array, item) { - return array.indexOf(item) !== -1 +function dumpTrieNonInlineNodes (trie) { + const fullNodes = [] + return new Promise((resolve, reject) => { + trie._findDbNodes((nodeRef, node, key, next) => { + fullNodes.push(node) + next() + }, (err) => { + if (err) { + return reject(err) + } + return resolve(fullNodes) + }) + }) } diff --git a/eth-tx-trie/index.js b/eth-tx-trie/index.js index 71826d7..973ff80 100644 --- a/eth-tx-trie/index.js +++ b/eth-tx-trie/index.js @@ -1,9 +1,10 @@ 'use strict' -/* eslint max-nested-callbacks: ["error", 5] */ +const multicodec = require('multicodec') const ethTxResolver = require('../eth-tx') const createTrieResolver = require('../util/createTrieResolver') -const ethTxTrieResolver = createTrieResolver('eth-tx-trie', ethTxResolver) +const ethTxTrieResolver = createTrieResolver( + multicodec.ETH_TX_TRIE, ethTxResolver) module.exports = ethTxTrieResolver diff --git a/eth-tx-trie/test/resolver.spec.js b/eth-tx-trie/test/resolver.spec.js index 2df6290..b6aeb7e 100644 --- a/eth-tx-trie/test/resolver.spec.js +++ b/eth-tx-trie/test/resolver.spec.js @@ -1,144 +1,149 @@ /* eslint-env mocha */ 'use strict' -const expect = require('chai').expect -const async = require('async') +const chai = require('chai') +chai.use(require('dirty-chai')) +const expect = chai.expect +const CID = require('cids') const EthBlock = require('ethereumjs-block') const EthTx = require('ethereumjs-tx') -const Trie = require('merkle-patricia-tree') -const ipldEthStateTrie = require('../index') -const isExternalLink = require('../../util/isExternalLink') -const resolver = ipldEthStateTrie.resolver +const multicodec = require('multicodec') +const promisify = require('promisify-es6') +const ipldEthTxTrie = require('../index') +const resolver = ipldEthTxTrie.resolver describe('IPLD format resolver (local)', () => { // setup test trie let ethBlock - let trie - let trieNodes = [] let dagNodes - before((done) => { - trie = new Trie() - async.waterfall([ - (cb) => populateTrie(cb), - (cb) => dumpTrieDbNodes(trie, trieNodes, cb), - (cb) => async.map(trieNodes, ipldEthStateTrie.util.serialize, cb) - ], (err, result) => { - if (err) return done(err) - dagNodes = result - done() + before(async () => { + const trie = await populateTrie() + const trieNodes = await dumpTrieDbNodes(trie) + dagNodes = trieNodes.map((node) => { + return ipldEthTxTrie.util.serialize({ _ethObj: node }) }) }) - function populateTrie (cb) { + async function populateTrie () { ethBlock = new EthBlock() // taken from block 0xc596cb892b649b4917da8c6b78611346d55daf7bcf4375da86a2d98810888e84 ethBlock.transactions = [ new EthTx({ - to: new Buffer('0c7c0b72004a7a66ffa780637427fed0c4faac47', 'hex'), - from: new Buffer('41959417325160f8952bc933ae8317b4e5140dda', 'hex'), - gas: new Buffer('5e1b', 'hex'), - gasPrice: new Buffer('098bca5a00', 'hex'), + to: Buffer.from('0c7c0b72004a7a66ffa780637427fed0c4faac47', 'hex'), + from: Buffer.from('41959417325160f8952bc933ae8317b4e5140dda', 'hex'), + gas: Buffer.from('5e1b', 'hex'), + gasPrice: Buffer.from('098bca5a00', 'hex'), input: null, - nonce: new Buffer('00', 'hex'), - value: new Buffer('44004c09e76a0000', 'hex'), - r: new Buffer('7150d00a9dcd8a8287ad220010c52ff2608906b746de23c993999768091ff210', 'hex'), - s: new Buffer('5585fabcd1dc415e1668d4cbc2d419cf0381bf9707480ad2f86d0800732f6d7e', 'hex'), - v: new Buffer('1b', 'hex') + nonce: Buffer.from('00', 'hex'), + value: Buffer.from('44004c09e76a0000', 'hex'), + r: Buffer.from('7150d00a9dcd8a8287ad220010c52ff2608906b746de23c993999768091ff210', 'hex'), + s: Buffer.from('5585fabcd1dc415e1668d4cbc2d419cf0381bf9707480ad2f86d0800732f6d7e', 'hex'), + v: Buffer.from('1b', 'hex') }), new EthTx({ - to: new Buffer('f4702bb51b8270729db362b0d4f82a56bdd66c65', 'hex'), - from: new Buffer('56ce1399be2831f8a3f918a0408c05bbad658ef3', 'hex'), - gas: new Buffer('5208', 'hex'), - gasPrice: new Buffer('04e3b29200', 'hex'), + to: Buffer.from('f4702bb51b8270729db362b0d4f82a56bdd66c65', 'hex'), + from: Buffer.from('56ce1399be2831f8a3f918a0408c05bbad658ef3', 'hex'), + gas: Buffer.from('5208', 'hex'), + gasPrice: Buffer.from('04e3b29200', 'hex'), input: null, - nonce: new Buffer('9d', 'hex'), - value: new Buffer('120a871cc0020000', 'hex'), - r: new Buffer('5d92c10b5789801d4ce0fc558eedc6e6cccbaf0105a7c1f909feabcedfe56cd9', 'hex'), - s: new Buffer('72cc370fa5fd3b43c2ba4e9e70fea1b5e950b4261ab4274982d8ae15a3403a33', 'hex'), - v: new Buffer('1b', 'hex') + nonce: Buffer.from('9d', 'hex'), + value: Buffer.from('120a871cc0020000', 'hex'), + r: Buffer.from('5d92c10b5789801d4ce0fc558eedc6e6cccbaf0105a7c1f909feabcedfe56cd9', 'hex'), + s: Buffer.from('72cc370fa5fd3b43c2ba4e9e70fea1b5e950b4261ab4274982d8ae15a3403a33', 'hex'), + v: Buffer.from('1b', 'hex') }), new EthTx({ - to: new Buffer('b8201140a49b0d5b65a23b4b2fa8a6efff87c576', 'hex'), - from: new Buffer('1e9939daaad6924ad004c2560e90804164900341', 'hex'), - gas: new Buffer('9858', 'hex'), - gasPrice: new Buffer('04a817c800', 'hex'), + to: Buffer.from('b8201140a49b0d5b65a23b4b2fa8a6efff87c576', 'hex'), + from: Buffer.from('1e9939daaad6924ad004c2560e90804164900341', 'hex'), + gas: Buffer.from('9858', 'hex'), + gasPrice: Buffer.from('04a817c800', 'hex'), input: null, - nonce: new Buffer('022f5d', 'hex'), - value: new Buffer('0de4ea09ac8f1e88', 'hex'), - r: new Buffer('7ee15b226f6c767ccace78a4b5b4cbf0be6ec20a899e058d3c95977bacd0cbd5', 'hex'), - s: new Buffer('27e75bcd3bfd199e8c3e3f0c90b0d39f01b773b3da64060e06c0d568ae5c7523', 'hex'), - v: new Buffer('1b', 'hex') + nonce: Buffer.from('022f5d', 'hex'), + value: Buffer.from('0de4ea09ac8f1e88', 'hex'), + r: Buffer.from('7ee15b226f6c767ccace78a4b5b4cbf0be6ec20a899e058d3c95977bacd0cbd5', 'hex'), + s: Buffer.from('27e75bcd3bfd199e8c3e3f0c90b0d39f01b773b3da64060e06c0d568ae5c7523', 'hex'), + v: Buffer.from('1b', 'hex') }), new EthTx({ - to: new Buffer('c4f381af25c41786110242623373cc9c7647f3f1', 'hex'), - from: new Buffer('ea674fdde714fd979de3edf0f56aa9716b898ec8', 'hex'), - gas: new Buffer('015f90', 'hex'), - gasPrice: new Buffer('04a817c800', 'hex'), + to: Buffer.from('c4f381af25c41786110242623373cc9c7647f3f1', 'hex'), + from: Buffer.from('ea674fdde714fd979de3edf0f56aa9716b898ec8', 'hex'), + gas: Buffer.from('015f90', 'hex'), + gasPrice: Buffer.from('04a817c800', 'hex'), input: null, - nonce: new Buffer('0fc02d', 'hex'), - value: new Buffer('0e139507cd50c018', 'hex'), - r: new Buffer('059934eeace580cc2bdc292415976692c751f0bcb025930bd40fcc31e91208f3', 'hex'), - s: new Buffer('77ff34a10a3de0d906a0363b4bdbc0e9a06cb4378476d96dfd446225d8d9949c', 'hex'), - v: new Buffer('1c', 'hex') + nonce: Buffer.from('0fc02d', 'hex'), + value: Buffer.from('0e139507cd50c018', 'hex'), + r: Buffer.from('059934eeace580cc2bdc292415976692c751f0bcb025930bd40fcc31e91208f3', 'hex'), + s: Buffer.from('77ff34a10a3de0d906a0363b4bdbc0e9a06cb4378476d96dfd446225d8d9949c', 'hex'), + v: Buffer.from('1c', 'hex') }) ] - ethBlock.genTxTrie((err) => { - if (err) return cb(err) - trie = ethBlock.txTrie - cb() - }) + await promisify(ethBlock.genTxTrie.bind(ethBlock))() + return ethBlock.txTrie } it('multicodec is eth-tx-trie', () => { - expect(resolver.multicodec).to.equal('eth-tx-trie') + expect(ipldEthTxTrie.codec).to.equal(multicodec.ETH_TX_TRIE) }) it('defaultHashAlg is keccak-256', () => { - expect(resolver.defaultHashAlg).to.equal('keccak-256') + expect(ipldEthTxTrie.defaultHashAlg).to.equal(multicodec.KECCAK_256) }) describe('resolver.resolve', () => { - it('root node resolving first tx value', (done) => { - let rootNode = dagNodes[0] - resolver.resolve(rootNode, '8/0/value', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('0/value') - expect(isExternalLink(trieNode)).to.eql(true) - done() - }) + it('root node resolving first tx value', () => { + const rootNode = dagNodes[0] + const result = resolver.resolve(rootNode, '8/0/value') + const trieNode = result.value + expect(result.remainderPath).to.eql('0/value') + expect(CID.isCID(trieNode)).to.be.true() }) - it('"8" branch node resolves down to tx value', (done) => { - let branchNode = dagNodes[2] - resolver.resolve(branchNode, '0/value', (err, result) => { - expect(err).to.not.exist() - let trieNode = result.value - expect(result.remainderPath).to.eql('') - expect(isExternalLink(trieNode)).to.eql(false) - expect(Buffer.isBuffer(result.value)).to.eql(true) - let firstTx = ethBlock.transactions[0] - expect(result.value.toString('hex')).to.eql(firstTx.value.toString('hex')) - done() - }) + it('"8" branch node resolves down to tx value', () => { + const branchNode = dagNodes[2] + const result = resolver.resolve(branchNode, '0/value') + const trieNode = result.value + expect(result.remainderPath).to.eql('') + expect(CID.isCID(trieNode)).to.be.false() + expect(Buffer.isBuffer(result.value)).to.be.true() + const firstTx = ethBlock.transactions[0] + expect(result.value.toString('hex')).to.eql(firstTx.value.toString('hex')) + }) + + it('resolves "0" to correct type', () => { + const result = resolver.resolve(dagNodes[0], '0') + expect(CID.isCID(result.value)).to.be.true() + }) + + it('resolves "8" to correct type', () => { + const result = resolver.resolve(dagNodes[0], '0') + expect(CID.isCID(result.value)).to.be.true() }) }) describe('resolver.tree', () => { - it('root has two children', (done) => { - let rootNode = dagNodes[0] - resolver.tree(rootNode, {}, (err, result) => { - expect(err).to.not.exist() - expect(result.length).to.eql(2) - done() - }) + it('root has two children', () => { + const rootNode = dagNodes[0] + const tree = resolver.tree(rootNode) + const paths = [...tree] + expect(paths).to.have.members([ + '0', + '8' + ]) }) }) }) -function dumpTrieDbNodes (trie, fullNodes, cb) { - trie._findDbNodes((root, node, key, next) => { - fullNodes.push(node) - next() - }, cb) +function dumpTrieDbNodes (trie) { + const fullNodes = [] + return new Promise((resolve, reject) => { + trie._findDbNodes((root, node, key, next) => { + fullNodes.push(node) + next() + }, (err) => { + if (err) { + return reject(err) + } + return resolve(fullNodes) + }) + }) } diff --git a/eth-tx/index.js b/eth-tx/index.js index 1ebf532..3718bac 100644 --- a/eth-tx/index.js +++ b/eth-tx/index.js @@ -1,72 +1,30 @@ 'use strict' const EthTx = require('ethereumjs-tx') const createResolver = require('../util/createResolver') - -module.exports = createResolver('eth-tx', EthTx, mapFromEthObj) - - -function mapFromEthObj (tx, options, callback) { - const paths = [] - - // external links (none) - - // external links as data (none) - - // internal data - - paths.push({ - path: 'nonce', - value: tx.nonce - }) - paths.push({ - path: 'gasPrice', - value: tx.gasPrice - }) - paths.push({ - path: 'gasLimit', - value: tx.gasLimit - }) - paths.push({ - path: 'toAddress', - value: tx.to - }) - paths.push({ - path: 'value', - value: tx.value - }) - paths.push({ - path: 'data', - value: tx.data - }) - paths.push({ - path: 'v', - value: tx.v - }) - paths.push({ - path: 'r', - value: tx.r - }) - paths.push({ - path: 's', - value: tx.s - }) - - // helpers - - paths.push({ - path: 'fromAddress', - value: tx.from - }) - - paths.push({ - path: 'signature', - value: [tx.v, tx.r, tx.s] - }) - - paths.push({ - path: 'isContractPublish', - value: tx.toCreationAddress() - }) - - callback(null, paths) +const multicodec = require('multicodec') + +const deserialize = (serialized) => { + const ethObj = new EthTx(serialized) + + const deserialized = { + data: ethObj.data, + fromAddress: ethObj.from, + gasLimit: ethObj.gasLimit, + gasPrice: ethObj.gasPrice, + isContractPublish: ethObj.toCreationAddress(), + nonce: ethObj.nonce, + r: ethObj.r, + s: ethObj.s, + signature: [ethObj.v, ethObj.r, ethObj.s], + toAddress: ethObj.to, + v: ethObj.v, + value: ethObj.value, + _ethObj: ethObj + } + + Object.defineProperty(deserialized, '_ethObj', { enumerable: false }) + + return deserialized } + +module.exports = createResolver(multicodec.ETH_TX, deserialize) diff --git a/eth-tx/test/resolver.spec.js b/eth-tx/test/resolver.spec.js index 930f733..0fd0164 100644 --- a/eth-tx/test/resolver.spec.js +++ b/eth-tx/test/resolver.spec.js @@ -1,102 +1,173 @@ /* eslint-env mocha */ 'use strict' -const expect = require('chai').expect +const chai = require('chai') +chai.use(require('dirty-chai')) +const expect = chai.expect const Transaction = require('ethereumjs-tx') -const dagEthBlock = require('../index') -const resolver = dagEthBlock.resolver -const util = dagEthBlock.util +const dagEthTx = require('../index') +const resolver = dagEthTx.resolver +const util = dagEthTx.util const multihash = require('multihashes') +const multicodec = require('multicodec') describe('IPLD format resolver (local)', () => { - let testIpfsBlob - let testData = { - nonce: new Buffer('01', 'hex'), - gasPrice: new Buffer('04a817c800', 'hex'), - gasLimit: new Buffer('061a80', 'hex'), - to: new Buffer('0731729bb6624343958d05be7b1d9257a8e802e7', 'hex'), - value: new Buffer('1234', 'hex'), + const testData = { + nonce: Buffer.from('01', 'hex'), + gasPrice: Buffer.from('04a817c800', 'hex'), + gasLimit: Buffer.from('061a80', 'hex'), + to: Buffer.from('0731729bb6624343958d05be7b1d9257a8e802e7', 'hex'), + value: Buffer.from('1234', 'hex'), // signature - v: new Buffer('1c', 'hex'), - r: new Buffer('33752a492fb77aca190ba9ba356bb8c9ad22d9aaa82c10bc8fc8ccca70da1985', 'hex'), - s: new Buffer('6ee2a50ec62e958fa2c9e214dae7de8ab4ab9a951b621a9deb04bb1bb37dd20f', 'hex') + v: Buffer.from('1c', 'hex'), + r: Buffer.from('33752a492fb77aca190ba9ba356bb8c9ad22d9aaa82c10bc8fc8ccca70da1985', 'hex'), + s: Buffer.from('6ee2a50ec62e958fa2c9e214dae7de8ab4ab9a951b621a9deb04bb1bb37dd20f', 'hex') } - - before((done) => { - const testTx = new Transaction(testData) - dagEthBlock.util.serialize(testTx, (err, result) => { - if (err) return done(err) - testIpfsBlob = result - done() - }) + const testTx = new Transaction(testData) + const testTxBlob = dagEthTx.util.serialize({ + _ethObj: testTx }) it('multicodec is eth-tx', () => { - expect(resolver.multicodec).to.equal('eth-tx') + expect(dagEthTx.codec).to.equal(multicodec.ETH_TX) }) it('defaultHashAlg is keccak-256', () => { - expect(resolver.defaultHashAlg).to.equal('keccak-256') + expect(dagEthTx.defaultHashAlg).to.equal(multicodec.KECCAK_256) }) describe('resolver.resolve', () => { it('path within scope', () => { - resolver.resolve(testIpfsBlob, 'nonce', (err, result) => { - expect(err).to.not.exist() - expect(result.value.toString('hex')).to.equal(testData.nonce.toString('hex')) - // expect(result.value).to.equal(testData.nonce.toString('hex')) - }) + const result = resolver.resolve(testTxBlob, 'nonce') + expect(result.value).to.eql(testData.nonce) }) - }) - describe('resolver.resolve', () => { - it('resolver.tree', () => { - resolver.tree(testIpfsBlob, (err, paths) => { - expect(err).to.not.exist() - expect(typeof paths).to.eql('object') - // expect(Array.isArray(paths)).to.eql(true) - }) + it('resolves "nonce" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'nonce') + expect(Buffer.isBuffer(result.value)).to.be.true() }) + + it('resolves "gasPrice" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'gasPrice') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "gasLimit" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'gasLimit') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "toAddress" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'toAddress') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "value" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'value') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "data" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'data') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "v" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'v') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "r" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'r') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "s" to correct type', () => { + const result = resolver.resolve(testTxBlob, 's') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "fromAddress" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'fromAddress') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "signature" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'signature') + expect(Array.isArray(result.value)).to.be.true() + }) + + it('resolves "signature/0" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'signature/0') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "signature/1" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'signature/1') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "signature/2" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'signature/2') + expect(Buffer.isBuffer(result.value)).to.be.true() + }) + + it('resolves "isContractPublish" to correct type', () => { + const result = resolver.resolve(testTxBlob, 'isContractPublish') + expect(typeof result.value).to.equal('boolean') + }) + }) + + it('resolver.tree', () => { + const tree = resolver.tree(testTxBlob) + const paths = [...tree] + expect(paths).to.have.members([ + 'nonce', + 'gasPrice', + 'gasLimit', + 'toAddress', + 'value', + 'data', + 'v', + 'r', + 's', + 'fromAddress', + 'signature', + 'signature/0', + 'signature/1', + 'signature/2', + 'isContractPublish' + ]) }) describe('util', () => { - it('create CID, no options', (done) => { - const testTx = new Transaction(testData) - util.cid(testTx, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-tx') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-256') - done() - }) + it('create CID, no options', async () => { + const cid = await util.cid(testTxBlob) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-tx') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-256') }) - it('create CID, empty options', (done) => { - const testTx = new Transaction(testData) - util.cid(testTx, {}, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-tx') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-256') - done() - }) - }) - - it('create CID, hashAlg', (done) => { - const testTx = new Transaction(testData) - util.cid(testTx, { hashAlg: 'keccak-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-tx') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-512') - done() + it('create CID, empty options', async () => { + const cid = await util.cid(testTxBlob, {}) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-tx') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-256') + }) + + it('create CID, hashAlg', async () => { + const cid = await util.cid(testTxBlob, { + hashAlg: multicodec.KECCAK_512 }) - }) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-tx') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-512') + }) }) }) diff --git a/package.json b/package.json index 98219d1..eed3b99 100644 --- a/package.json +++ b/package.json @@ -21,21 +21,21 @@ ], "license": "MIT", "dependencies": { - "async": "^2.6.0", "cids": "~0.6.0", "ethereumjs-account": "^2.0.4", "ethereumjs-block": "^2.1.0", "ethereumjs-tx": "^1.3.3", - "ipfs-block": "~0.8.0", "merkle-patricia-tree": "^3.0.0", + "multicodec": "~0.5.0", "multihashes": "~0.4.12", - "multihashing-async": "~0.6.0", + "multihashing-async": "~0.7.0", "rlp": "^2.0.0" }, "devDependencies": { "aegir": "^18.2.0", "chai": "^4.1.2", - "dirty-chai": "^2.0.1" + "dirty-chai": "^2.0.1", + "promisify-es6": "^1.0.3" }, "contributors": [ "Alan Shaw ", diff --git a/util/cidFromEthObj.js b/util/cidFromEthObj.js deleted file mode 100644 index 5423b84..0000000 --- a/util/cidFromEthObj.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' -const cidFromHash = require('./cidFromHash') - -module.exports = cidFromEthObj - -function cidFromEthObj (multicodec, ethObj, options) { - const hashBuffer = ethObj.hash() - const cid = cidFromHash(multicodec, hashBuffer, options) - return cid -} diff --git a/util/cidFromHash.js b/util/cidFromHash.js index e257b10..80c91d7 100644 --- a/util/cidFromHash.js +++ b/util/cidFromHash.js @@ -2,13 +2,16 @@ const CID = require('cids') const multihashes = require('multihashes') +const multicodec = require('multicodec') module.exports = cidFromHash function cidFromHash (codec, rawhash, options) { + // `CID` expects a string for the multicodec + const codecName = multicodec.print[codec] options = options || {} const hashAlg = options.hashAlg || 'keccak-256' const version = typeof options.version === 'undefined' ? 1 : options.version const multihash = multihashes.encode(rawhash, hashAlg) - return new CID(version, codec, multihash) + return new CID(version, codecName, multihash) } diff --git a/util/createIsLink.js b/util/createIsLink.js deleted file mode 100644 index 88d18ab..0000000 --- a/util/createIsLink.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = createIsLink - -function createIsLink (resolve) { - return function isLink (block, path, callback) { - resolve(block, path, (err, result) => { - if (err) { - return callback(err) - } - - if (result.remainderPath.length > 0) { - return callback(new Error('path out of scope')) - } - - if (typeof result.value === 'object' && result.value['/']) { - callback(null, result.value) - } else { - callback(null, false) - } - }) - } -} diff --git a/util/createResolver.js b/util/createResolver.js index 8c5e81e..287aac4 100644 --- a/util/createResolver.js +++ b/util/createResolver.js @@ -1,103 +1,85 @@ 'use strict' -const waterfall = require('async/waterfall') -const createIsLink = require('../util/createIsLink') +const CID = require('cids') +const multicodec = require('multicodec') const createUtil = require('../util/createUtil') -module.exports = createResolver - -function createResolver (multicodec, EthObjClass, mapFromEthObject) { - const util = createUtil(multicodec, EthObjClass) - const resolver = { - multicodec: multicodec, - defaultHashAlg: 'keccak-256', - resolve: resolve, - tree: tree, - isLink: createIsLink(resolve), - _resolveFromEthObject: resolveFromEthObject, - _treeFromEthObject: treeFromEthObject, - _mapFromEthObject: mapFromEthObject - } - - return { - resolver: resolver, - util: util, - } - - /* - * tree: returns a flattened array with paths: values of the project. options - * are option (i.e. nestness) +const createResolver = (codec, deserialize) => { + const util = createUtil(codec, deserialize) + + /** + * Resolves a path within a Ethereum block. + * + * Returns the value or a link and the partial mising path. This way the + * IPLD Resolver can fetch the link and continue to resolve. + * + * @param {Buffer} binaryBlob - Binary representation of a Ethereum block + * @param {string} [path='/'] - Path that should be resolved + * @returns {Object} result - Result of the path it it was resolved successfully + * @returns {*} result.value - Value the path resolves to + * @returns {string} result.remainderPath - If the path resolves half-way to a + * link, then the `remainderPath` is the part after the link that can be used + * for further resolving */ + const resolve = (binaryBlob, path) => { + let node = util.deserialize(binaryBlob) + + const parts = path.split('/').filter((x) => x) + while (parts.length) { + const key = parts.shift() + if (node[key] === undefined) { + throw new Error(`Object has no property '${key}'`) + } - function tree (binaryBlob, options, callback) { - // parse arguments - if (typeof options === 'function') { - callback = options - options = undefined - } - if (!options) { - options = {} + node = node[key] + if (CID.isCID(node)) { + return { + value: node, + remainderPath: parts.join('/') + } + } } - waterfall([ - (cb) => util.deserialize(binaryBlob, cb), - (ethObj, cb) => treeFromEthObject(ethObj, options, cb) - ], callback) + return { + value: node, + remainderPath: '' + } } - function treeFromEthObject (ethObj, options, callback) { - waterfall([ - (cb) => mapFromEthObject(ethObj, options, cb), - (tuples, cb) => cb(null, tuples.map((tuple) => tuple.path)) - ], callback) + const _traverse = function * (node, path) { + // Traverse only objects and arrays + if (Buffer.isBuffer(node) || CID.isCID(node) || typeof node === 'string' || + node === null) { + return + } + for (const item of Object.keys(node)) { + const nextpath = path === undefined ? item : path + '/' + item + yield nextpath + yield * _traverse(node[item], nextpath) + } } - /* - * resolve: receives a path and a binary blob and returns the value on path, - * throw if not possible. `binaryBlob`` is an Ethereum binary block. + /** + * Return all available paths of a block. + * + * @generator + * @param {Buffer} binaryBlob - Binary representation of a Bitcoin block + * @yields {string} - A single path */ + const tree = function * (binaryBlob) { + const node = util.deserialize(binaryBlob) - function resolve (binaryBlob, path, callback) { - waterfall([ - (cb) => util.deserialize(binaryBlob, cb), - (ethObj, cb) => resolveFromEthObject(ethObj, path, cb) - ], callback) + yield * _traverse(node) } - function resolveFromEthObject (ethObj, path, callback) { - // root - if (!path || path === '/') { - const result = { value: ethObj, remainderPath: '' } - return callback(null, result) - } - - // check tree results - mapFromEthObject(ethObj, {}, (err, paths) => { - if (err) return callback(err) - - // parse path - const pathParts = path.split('/') - // find potential matches - let matches = paths.filter((child) => child.path === path.slice(0, child.path.length)) - // only match whole path chunks - matches = matches.filter((child) => child.path.split('/').every((part, index) => part === pathParts[index])) - // take longest match - const sortedMatches = matches.sort((a, b) => b.path.length - a.path.length) - const treeResult = sortedMatches[0] - - if (!treeResult) { - let err = new Error('Path not found ("' + path + '").') - return callback(err) - } - - // slice off remaining path (after match and following slash) - const remainderPath = path.slice(treeResult.path.length + 1) - - const result = { - value: treeResult.value, - remainderPath: remainderPath - } - - return callback(null, result) - }) + return { + codec: codec, + defaultHashAlg: multicodec.KECCAK_256, + resolver: { + resolve: resolve, + tree: tree, + }, + util: util, } } + +module.exports = createResolver diff --git a/util/createTrieResolver.js b/util/createTrieResolver.js index 20bc7a6..8d48b67 100644 --- a/util/createTrieResolver.js +++ b/util/createTrieResolver.js @@ -1,108 +1,103 @@ 'use strict' -const each = require('async/each') -const waterfall = require('async/waterfall') -const asyncify = require('async/asyncify') const rlp = require('rlp') const EthTrieNode = require('merkle-patricia-tree/trieNode') const cidFromHash = require('./cidFromHash') -// const createBaseTrieResolver = require('./createBaseTrieResolver.js') const createResolver = require('./createResolver') -const isExternalLink = require('./isExternalLink') const createUtil = require('./createUtil') -const createIsLink = require('./createIsLink') -const cidFromEthObj = require('./cidFromEthObj') +// A `nibble` is an array of nested keys. So for example `[2, 1, 3]` would +// mean an item with value `"foo"` would be in an object like this: +// { +// "2": { +// "1": { +// "3": "foo" +// } +// } +// } +// This function converts such a nibble together with a `value` into such an +// object. As we want to combine multiple nibbles into a single object, we +// also pass in a `target` object where the value should be stored in. +const addNibbleToObject = (target, nibble, value) => { + // Make a reference to the target object that can be changed in the course + // of the algorithm + let current = target + for (const [ii, entry] of nibble.entries()) { + // Get the key the value should be stored in + const key = entry.toString(16) -module.exports = createTrieResolver - -function createTrieResolver(multicodec, leafResolver){ - const baseTrie = createResolver(multicodec, EthTrieNode, mapFromEthObj) - baseTrie.util.deserialize = asyncify((serialized) => { - const rawNode = rlp.decode(serialized) - const trieNode = new EthTrieNode(rawNode) - return trieNode - }) + if (ii + 1 < nibble.length) { + // We haven't reached the last item yet + // There is no item with that key yet + if (!(key in current)) { + current[key] = {} + } + // Keep traversing deeper + current = current[key] + } else { + // Else we've reached the last item, hence adding the actual value + current[key] = value + return + } + } +} - return baseTrie +const getLeafValue = (trieNode, leafResolver) => { + let value = trieNode.getValue() - // create map using both baseTrie and leafResolver - function mapFromEthObj (trieNode, options, callback) { - // expand from merkle-patricia-tree using leafResolver - mapFromBaseTrie(trieNode, options, (err, basePaths) => { - if (err) return callback(err) - if (!leafResolver) return callback(null, basePaths) - // expand children - let paths = basePaths.slice() - const leafTerminatingPaths = basePaths.filter(child => Buffer.isBuffer(child.value)) - each(leafTerminatingPaths, (child, cb) => { - return waterfall([ - (cb) => leafResolver.util.deserialize(child.value, cb), - (ethObj, cb) => leafResolver.resolver._mapFromEthObject(ethObj, options, cb) - ], (err, grandChildren) => { - if (err) return cb(err) - // add prefix to grandchildren - grandChildren.forEach((grandChild) => { - paths.push({ - path: child.path + '/' + grandChild.path, - value: grandChild.value, - }) - }) - cb() - }) - }, (err) => { - if (err) return callback(err) - callback(null, paths) - }) - }) + if (leafResolver !== undefined) { + value = leafResolver.util.deserialize(value) } - // create map from merkle-patricia-tree nodes - function mapFromBaseTrie (trieNode, options, callback) { - let paths = [] + return value +} - if (trieNode.type === 'leaf') { - // leaf nodes resolve to their actual value - paths.push({ - path: nibbleToPath(trieNode.getKey()), - value: trieNode.getValue() - }) - } +// create map from merkle-patricia-tree nodes +const mapFromBaseTrie = (codec, finalNode, trieNode, leafResolver) => { + if (trieNode.type === 'leaf') { + const value = getLeafValue(trieNode, leafResolver) + addNibbleToObject(finalNode, trieNode.getKey(), value) + return + } + + trieNode.getChildren().forEach(([nibble, value]) => { + let valueToAdd + if (EthTrieNode.isRawNode(value)) { + // inline child root + const childNode = new EthTrieNode(value) - each(trieNode.getChildren(), (childData, next) => { - const key = nibbleToPath(childData[0]) - const value = childData[1] - if (EthTrieNode.isRawNode(value)) { - // inline child root - const childNode = new EthTrieNode(value) - paths.push({ - path: key, - value: childNode - }) - // inline child non-leaf subpaths - mapFromBaseTrie(childNode, options, (err, subtree) => { - if (err) return next(err) - subtree.forEach((path) => { - path.path = key + '/' + path.path - }) - paths = paths.concat(subtree) - next() - }) + if (childNode.type === 'leaf') { + // Make sure the object is nested correctly + nibble.push(...childNode.getKey()) + valueToAdd = getLeafValue(childNode, leafResolver) } else { - // other nodes link by hash - let link = { '/': cidFromHash(multicodec, value).toBaseEncodedString() } - paths.push({ - path: key, - value: link - }) - next() + valueToAdd = childNode } - }, (err) => { - if (err) return callback(err) - callback(null, paths) - }) + } else { + // other nodes link by hash + valueToAdd = cidFromHash(codec, value) + } + addNibbleToObject(finalNode, nibble, valueToAdd) + }) +} + +// The `createUtilResolver` expects a constructor with a single parameter, +// hence wrap it in a creator function so that we can pass in the needed +// context +const createCustomEthTrieNode = function (codec, leafResolver) { + return function (serialized) { + const rawNode = rlp.decode(serialized) + const trieNode = new EthTrieNode(rawNode) + + const finalNode = {} + mapFromBaseTrie(codec, finalNode, trieNode, leafResolver) + return finalNode } } -function nibbleToPath (data) { - return data.map((num) => num.toString(16)).join('/') +const createTrieResolver = (codec, leafResolver) => { + const customEthTrieNode = createCustomEthTrieNode(codec, leafResolver) + const baseTrie = createResolver(codec, customEthTrieNode) + return baseTrie } + +module.exports = createTrieResolver diff --git a/util/createUtil.js b/util/createUtil.js index ad46001..b870538 100644 --- a/util/createUtil.js +++ b/util/createUtil.js @@ -1,12 +1,45 @@ -const cidFromEthObj = require('./cidFromEthObj') -const asyncify = require('async/asyncify') +const CID = require('cids') +const multicodec = require('multicodec') +const multihashing = require('multihashing-async') -module.exports = createUtil +const DEFAULT_HASH_ALG = multicodec.KECCAK_256 -function createUtil (multicodec, EthObjClass) { +const createUtil = (codec, deserialize) => { return { - deserialize: asyncify((serialized) => new EthObjClass(serialized)), - serialize: asyncify((ethObj) => ethObj.serialize()), - cid: asyncify((ethObj, options) => cidFromEthObj(multicodec, ethObj, options)) + /** + * Deserialize Ethereum block into the internal representation. + * + * @param {Buffer} serialized - Binary representation of a Ethereum block. + * @returns {Object} + */ + deserialize, + /** + * Serialize internal representation into a binary Ethereum block. + * + * @param {Object} deserialized - Internal representation of a Bitcoin block + * @returns {Buffer} + */ + serialize: (deserialized) => deserialized._ethObj.serialize(), + /** + * Calculate the CID of the binary blob. + * + * @param {Object} binaryBlob - Encoded IPLD Node + * @param {Object} [userOptions] - Options to create the CID + * @param {number} [userOptions.cidVersion=1] - CID version number + * @param {string} [UserOptions.hashAlg] - Defaults to the defaultHashAlg of the format + * @returns {Promise.} + */ + cid: async (binaryBlob, userOptions) => { + const defaultOptions = { cidVersion: 1, hashAlg: DEFAULT_HASH_ALG} + const options = Object.assign(defaultOptions, userOptions) + + const multihash = await multihashing(binaryBlob, options.hashAlg) + const codecName = multicodec.print[codec] + const cid = new CID(options.cidVersion, codecName, multihash) + + return cid + } } } + +module.exports = createUtil diff --git a/util/emptyCodeHash.js b/util/emptyCodeHash.js index 6f7f612..77f6339 100644 --- a/util/emptyCodeHash.js +++ b/util/emptyCodeHash.js @@ -1,2 +1,2 @@ // this is the hash of the empty code (SHA3_NULL) -module.exports = new Buffer('c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'hex') +module.exports = Buffer.from('c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'hex') diff --git a/util/emptyStateRoot.js b/util/emptyStateRoot.js deleted file mode 100644 index ddc3b49..0000000 --- a/util/emptyStateRoot.js +++ /dev/null @@ -1,2 +0,0 @@ -// this is the hash of the empty stateRoot (SHA3_RLP) -module.exports = new Buffer('56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', 'hex') diff --git a/util/isExternalLink.js b/util/isExternalLink.js deleted file mode 100644 index 8b42f0c..0000000 --- a/util/isExternalLink.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = isExternalLink - -function isExternalLink (obj) { - return Boolean(obj['/']) -} \ No newline at end of file