ipld-ethereum
Advanced tools
Comparing version 2.0.3 to 3.0.0
@@ -0,1 +1,34 @@ | ||
<a name="3.0.0"></a> | ||
# [3.0.0](https://github.com/ipld/js-ipld-ethereum/compare/v2.0.3...v3.0.0) (2019-05-08) | ||
### Bug Fixes | ||
* **package:** update cids to version 0.6.0 ([c38363a](https://github.com/ipld/js-ipld-ethereum/commit/c38363a)) | ||
* **package:** update multihashing-async to version 0.6.0 ([4eaa791](https://github.com/ipld/js-ipld-ethereum/commit/4eaa791)) | ||
### Features | ||
* new IPLD Format API ([dc19aa7](https://github.com/ipld/js-ipld-ethereum/commit/dc19aa7)) | ||
### BREAKING CHANGES | ||
* 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 | ||
<a name="2.0.3"></a> | ||
@@ -2,0 +35,0 @@ ## [2.0.3](https://github.com/ipld/js-ipld-ethereum/compare/v2.0.2...v2.0.3) (2019-01-18) |
'use strict' | ||
const EthAccount = require('ethereumjs-account') | ||
const multicodec = require('multicodec') | ||
const cidFromHash = require('../util/cidFromHash') | ||
@@ -7,65 +9,24 @@ const createResolver = require('../util/createResolver') | ||
module.exports = createResolver('eth-account-snapshot', EthAccount, mapFromEthObj) | ||
const deserialize = (serialized) => { | ||
const ethObj = new EthAccount(serialized) | ||
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 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 | ||
Object.defineProperty(deserialized, '_ethObj', { enumerable: false }) | ||
paths.push({ | ||
path: 'stateRoot', | ||
value: account.stateRoot | ||
}) | ||
return deserialized | ||
} | ||
paths.push({ | ||
path: 'codeHash', | ||
value: account.codeHash | ||
}) | ||
// 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) | ||
} | ||
module.exports = createResolver(multicodec.ETH_ACCOUNT_SNAPSHOT, deserialize) |
/* 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') | ||
@@ -9,27 +11,23 @@ const resolver = dagEthAccount.resolver | ||
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) | ||
}) | ||
@@ -39,26 +37,70 @@ | ||
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 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 "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 "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' | ||
]) | ||
}) | ||
}) | ||
}) |
'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 | ||
const deserialized = rawOmmers.map((rawBlock) => { | ||
return ipldEthBlock.util.deserialize(rawBlock) | ||
}) | ||
waterfall([ | ||
(cb) => util.serialize(blockList, cb), | ||
(data, cb) => multihash.digest(data, hashAlg, cb), | ||
asyncify((mhash) => cidFromHash('eth-block-list', mhash, options)) | ||
], callback) | ||
deserialized.count = deserialized.length | ||
return deserialized | ||
} | ||
module.exports = ethBlockListResolver | ||
const ethBlockListResolver = createResolver( | ||
multicodec.ETH_BLOCK_LIST, deserialize) | ||
ethBlockListResolver.util.serialize = (ethBlockList) => { | ||
const rawOmmers = ethBlockList.map((ethBlock) => ethBlock._ethObj.raw) | ||
return RLP.encode(rawOmmers) | ||
} | ||
function mapFromEthObj (ethBlockList, options, callback) { | ||
let paths = [] | ||
// external links (none) | ||
// external links as data (none) | ||
// helpers | ||
paths.push({ | ||
path: 'count', | ||
value: ethBlockList.length | ||
}) | ||
// internal data | ||
// 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) | ||
}) | ||
} | ||
module.exports = ethBlockListResolver |
/* 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') | ||
@@ -16,70 +19,117 @@ const resolver = dagEthBlockList.resolver | ||
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' | ||
]) | ||
}) | ||
@@ -89,51 +139,40 @@ }) | ||
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') | ||
}) | ||
}) | ||
}) |
'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() } | ||
}) | ||
return deserialized | ||
} | ||
// 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) | ||
} | ||
module.exports = createResolver(multicodec.ETH_BLOCK, deserialize) |
@@ -9,66 +9,47 @@ /* eslint-env mocha */ | ||
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')) | ||
}) | ||
@@ -78,55 +59,162 @@ | ||
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') | ||
}) | ||
@@ -137,101 +225,80 @@ }) | ||
describe('manual ancestor walking', () => { | ||
let cid1 | ||
let cid2 | ||
let ethBlock1 | ||
let ethBlock2 | ||
let ethBlock3 | ||
let cid1 | ||
let cid2 | ||
let cid3 | ||
let serialized1 | ||
let serialized2 | ||
let serialized3 | ||
before((done) => { | ||
before(async () => { | ||
ethBlock1 = { | ||
_ethObj: new EthBlockHeader({ | ||
number: 1 | ||
}) | ||
} | ||
serialized1 = ipldEthBlock.util.serialize(ethBlock1) | ||
cid1 = await ipldEthBlock.util.cid(serialized1) | ||
waterfall([ | ||
asyncify(() => { | ||
return ethBlock1 = new EthBlockHeader({ | ||
number: 1 | ||
}) | ||
}), | ||
ipldEthBlock.util.cid, | ||
asyncify((cid) => { cid1 = cid }), | ||
ethBlock2 = { | ||
_ethObj: new EthBlockHeader({ | ||
number: 2, | ||
parentHash: ethBlock1._ethObj.hash() | ||
}) | ||
} | ||
serialized2 = ipldEthBlock.util.serialize(ethBlock2) | ||
cid2 = await ipldEthBlock.util.cid(serialized2) | ||
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) | ||
ethBlock3 = { | ||
_ethObj: new EthBlockHeader({ | ||
number: 3, | ||
parentHash: ethBlock2._ethObj.hash() | ||
}) | ||
} | ||
serialized3 = ipldEthBlock.util.serialize(ethBlock3) | ||
await ipldEthBlock.util.cid(serialized3) | ||
}) | ||
it('root path (same as get)', (done) => { | ||
ipldEthBlock.resolver._resolveFromEthObject(ethBlock1, '/', (err, result) => { | ||
expect(err).to.not.exist() | ||
ipldEthBlock.util.cid(result.value, (err, cid) => { | ||
expect(err).to.not.exist() | ||
expect(cid).to.eql(cid1) | ||
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 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('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/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 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 (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 (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') | ||
}) | ||
}) | ||
'use strict' | ||
/* eslint max-nested-callbacks: ["error", 5] */ | ||
const multicodec = require('multicodec') | ||
@@ -7,4 +7,5 @@ const ethAccountSnapshotResolver = require('../eth-account-snapshot') | ||
const ethStateTrieResolver = createTrieResolver('eth-state-trie', ethAccountSnapshotResolver) | ||
const ethStateTrieResolver = createTrieResolver( | ||
multicodec.ETH_STATE_TRIE, ethAccountSnapshotResolver) | ||
module.exports = ethStateTrieResolver |
@@ -7,11 +7,8 @@ /* eslint-env mocha */ | ||
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 | ||
@@ -23,10 +20,6 @@ | ||
let testContractData = { | ||
balance: new Buffer('012345', 'hex'), | ||
codeHash: new Buffer('abcd04a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex'), | ||
stateRoot: new Buffer('012304a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex') | ||
balance: Buffer.from('012345', 'hex'), | ||
codeHash: Buffer.from('abcd04a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex'), | ||
stateRoot: Buffer.from('012304a817c80004a817c80004a817c80004a817c80004a817c80004a817c800', 'hex') | ||
} | ||
function prepareTestContract (done) { | ||
testContract = new Account(testContractData) | ||
done() | ||
} | ||
@@ -36,125 +29,121 @@ // setup external account test data | ||
let testExternalAccountData = { | ||
balance: new Buffer('abcdef', 'hex'), | ||
nonce: new Buffer('02', 'hex') | ||
balance: Buffer.from('abcdef', 'hex'), | ||
nonce: Buffer.from('02', 'hex') | ||
} | ||
function prepareTestExternalAccount (done) { | ||
testExternalAccount = new Account(testExternalAccountData) | ||
done() | ||
} | ||
// 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() | ||
}) | ||
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 | ||
} | ||
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) | ||
}) | ||
} | ||
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', (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('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('"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('"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('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' | ||
]) | ||
}) | ||
@@ -164,24 +153,15 @@ }) | ||
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) | ||
}) | ||
} | ||
@@ -192,5 +172,1 @@ | ||
} | ||
function contains (array, item) { | ||
return array.indexOf(item) !== -1 | ||
} |
'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 |
/* 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 | ||
@@ -14,112 +16,137 @@ | ||
// 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', (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('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', (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('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', (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('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('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('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('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() | ||
}) | ||
}) | ||
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 dumpTrieNonInlineNodes (trie, fullNodes, cb) { | ||
trie._findDbNodes((nodeRef, node, key, next) => { | ||
fullNodes.push(node) | ||
next() | ||
}, cb) | ||
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) | ||
}) | ||
}) | ||
} | ||
function contains (array, item) { | ||
return array.indexOf(item) !== -1 | ||
} |
'use strict' | ||
/* eslint max-nested-callbacks: ["error", 5] */ | ||
const multicodec = require('multicodec') | ||
@@ -7,4 +7,5 @@ const ethTxResolver = require('../eth-tx') | ||
const ethTxTrieResolver = createTrieResolver('eth-tx-trie', ethTxResolver) | ||
const ethTxTrieResolver = createTrieResolver( | ||
multicodec.ETH_TX_TRIE, ethTxResolver) | ||
module.exports = ethTxTrieResolver |
/* 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 | ||
@@ -16,19 +18,12 @@ describe('IPLD format resolver (local)', () => { | ||
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() | ||
@@ -38,100 +33,102 @@ // taken from block 0xc596cb892b649b4917da8c6b78611346d55daf7bcf4375da86a2d98810888e84 | ||
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' | ||
]) | ||
}) | ||
@@ -141,7 +138,15 @@ }) | ||
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) | ||
}) | ||
}) | ||
} |
'use strict' | ||
const EthTx = require('ethereumjs-tx') | ||
const createResolver = require('../util/createResolver') | ||
const multicodec = require('multicodec') | ||
module.exports = createResolver('eth-tx', EthTx, mapFromEthObj) | ||
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 | ||
} | ||
function mapFromEthObj (tx, options, callback) { | ||
const paths = [] | ||
Object.defineProperty(deserialized, '_ethObj', { enumerable: false }) | ||
// external links (none) | ||
return deserialized | ||
} | ||
// 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) | ||
} | ||
module.exports = createResolver(multicodec.ETH_TX, deserialize) |
/* 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) | ||
}) | ||
@@ -44,60 +41,134 @@ | ||
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, 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', (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, 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') | ||
}) | ||
}) | ||
}) |
{ | ||
"name": "ipld-ethereum", | ||
"version": "2.0.3", | ||
"version": "3.0.0", | ||
"description": "JavaScript Implementation of All Ethereum IPLD formats", | ||
@@ -24,17 +24,17 @@ "leadMaintainer": "Volker Mische <volker.mische@gmail.com>", | ||
"dependencies": { | ||
"async": "^2.6.0", | ||
"cids": "~0.5.2", | ||
"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.5.1", | ||
"multihashing-async": "~0.7.0", | ||
"rlp": "^2.0.0" | ||
}, | ||
"devDependencies": { | ||
"aegir": "^18.0.2", | ||
"aegir": "^18.2.0", | ||
"chai": "^4.1.2", | ||
"dirty-chai": "^2.0.1" | ||
"dirty-chai": "^2.0.1", | ||
"promisify-es6": "^1.0.3" | ||
}, | ||
@@ -41,0 +41,0 @@ "contributors": [ |
@@ -5,3 +5,5 @@ # js-ipld-ethereum | ||
[![](https://img.shields.io/badge/project-IPLD-blue.svg?style=flat-square)](http://github.com/ipld/ipld) | ||
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) [![Greenkeeper badge](https://badges.greenkeeper.io/ipld/js-ipld-ethereum.svg)](https://greenkeeper.io/) | ||
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) | ||
[![Travis CI](https://flat.badgen.net/travis/ipld/js-ipld-ethereum)](https://travis-ci.com/ipld/js-ipld-ethereum) | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/ipld/js-ipld-ethereum.svg)](https://greenkeeper.io/) | ||
@@ -8,0 +10,0 @@ > JavaScript Implementation of the IPLD format - Ethereum Block |
@@ -5,2 +5,3 @@ 'use strict' | ||
const multihashes = require('multihashes') | ||
const multicodec = require('multicodec') | ||
@@ -10,2 +11,4 @@ module.exports = cidFromHash | ||
function cidFromHash (codec, rawhash, options) { | ||
// `CID` expects a string for the multicodec | ||
const codecName = multicodec.print[codec] | ||
options = options || {} | ||
@@ -15,3 +18,3 @@ const hashAlg = options.hashAlg || 'keccak-256' | ||
const multihash = multihashes.encode(rawhash, hashAlg) | ||
return new CID(version, codec, multihash) | ||
return new CID(version, codecName, multihash) | ||
} |
'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 | ||
const createResolver = (codec, deserialize) => { | ||
const util = createUtil(codec, deserialize) | ||
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 | ||
} | ||
/** | ||
* 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) | ||
return { | ||
resolver: resolver, | ||
util: util, | ||
} | ||
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}'`) | ||
} | ||
/* | ||
* tree: returns a flattened array with paths: values of the project. options | ||
* are option (i.e. nestness) | ||
*/ | ||
node = node[key] | ||
if (CID.isCID(node)) { | ||
return { | ||
value: node, | ||
remainderPath: parts.join('/') | ||
} | ||
} | ||
} | ||
function tree (binaryBlob, options, callback) { | ||
// parse arguments | ||
if (typeof options === 'function') { | ||
callback = options | ||
options = undefined | ||
return { | ||
value: node, | ||
remainderPath: '' | ||
} | ||
if (!options) { | ||
options = {} | ||
} | ||
waterfall([ | ||
(cb) => util.deserialize(binaryBlob, cb), | ||
(ethObj, cb) => treeFromEthObject(ethObj, options, cb) | ||
], callback) | ||
} | ||
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 |
'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 | ||
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 | ||
} | ||
} | ||
} | ||
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 | ||
}) | ||
const getLeafValue = (trieNode, leafResolver) => { | ||
let value = trieNode.getValue() | ||
return baseTrie | ||
if (leafResolver !== undefined) { | ||
value = leafResolver.util.deserialize(value) | ||
} | ||
// 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) | ||
}) | ||
}) | ||
return value | ||
} | ||
// 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 | ||
} | ||
// create map from merkle-patricia-tree nodes | ||
function mapFromBaseTrie (trieNode, options, callback) { | ||
let paths = [] | ||
trieNode.getChildren().forEach(([nibble, value]) => { | ||
let valueToAdd | ||
if (EthTrieNode.isRawNode(value)) { | ||
// inline child root | ||
const childNode = new EthTrieNode(value) | ||
if (trieNode.type === 'leaf') { | ||
// leaf nodes resolve to their actual value | ||
paths.push({ | ||
path: nibbleToPath(trieNode.getKey()), | ||
value: trieNode.getValue() | ||
}) | ||
if (childNode.type === 'leaf') { | ||
// Make sure the object is nested correctly | ||
nibble.push(...childNode.getKey()) | ||
valueToAdd = getLeafValue(childNode, leafResolver) | ||
} else { | ||
valueToAdd = childNode | ||
} | ||
} else { | ||
// other nodes link by hash | ||
valueToAdd = cidFromHash(codec, value) | ||
} | ||
addNibbleToObject(finalNode, nibble, valueToAdd) | ||
}) | ||
} | ||
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() | ||
}) | ||
} else { | ||
// other nodes link by hash | ||
let link = { '/': cidFromHash(multicodec, value).toBaseEncodedString() } | ||
paths.push({ | ||
path: key, | ||
value: link | ||
}) | ||
next() | ||
} | ||
}, (err) => { | ||
if (err) return callback(err) | ||
callback(null, paths) | ||
}) | ||
// 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 |
@@ -1,12 +0,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>} | ||
*/ | ||
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 |
// this is the hash of the empty code (SHA3_NULL) | ||
module.exports = new Buffer('c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'hex') | ||
module.exports = Buffer.from('c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 'hex') |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
3673293
9
36
0
4
33
26652
+ Addedmulticodec@~0.5.0
+ Addedcids@0.6.0(transitive)
+ Addederr-code@1.1.2(transitive)
+ Addedmultihashing-async@0.7.0(transitive)
+ Addedmurmurhash3js-revisited@3.0.0(transitive)
- Removedasync@^2.6.0
- Removedipfs-block@~0.8.0
- Removedcids@0.5.80.7.5(transitive)
- Removedipfs-block@0.8.1(transitive)
- Removedis-promise@1.0.1(transitive)
- Removedmulticodec@1.0.4(transitive)
- Removedmultihashing-async@0.5.2(transitive)
- Removedmurmurhash3js@3.0.1(transitive)
- Removednodeify@1.0.1(transitive)
- Removedpromise@1.3.0(transitive)
Updatedcids@~0.6.0
Updatedmultihashing-async@~0.7.0