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

Commit 838bf6e

Browse files
committed
feat: new IPLD Format API
BREAKING CHANGE: The API is now async/await based There are numerous changes, the most significant one is that the API is no longer callback based, but it using async/await. For the full new API please see the [IPLD Formats spec]. [IPLD Formats spec]: https://github.com/ipld/interface-ipld-format
1 parent b7e3801 commit 838bf6e

8 files changed

+261
-432
lines changed

README.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,8 @@ Though it can also be used as a standalone module:
7070
const IpldZcash = require('ipld-zcash')
7171

7272
// `zcashBlock` is some binary Zcash block
73-
IpldZcash.util.deserialize(zcashBlock, (err, dagNode) => {
74-
if (err) {
75-
throw err
76-
}
77-
console.log(dagNode)
78-
})
73+
const dagNode = IpldZcash.util.deserialize(zcashBlock)
74+
console.log(dagNode)
7975
```
8076

8177
## Contribute

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,16 @@
3333
},
3434
"homepage": "https://github.com/ipld/js-ipld-zcash#readme",
3535
"dependencies": {
36-
"async": "^2.6.1",
3736
"cids": "~0.6.0",
37+
"multicodec": "~0.5.1",
3838
"multihashes": "~0.4.12",
39-
"multihashing-async": "~0.6.0",
39+
"multihashing-async": "~0.7.0",
4040
"zcash-bitcore-lib": "~0.13.20-rc3"
4141
},
4242
"devDependencies": {
4343
"aegir": "^18.2.0",
4444
"chai": "^4.1.2",
45+
"chai-as-promised": "^7.1.1",
4546
"dirty-chai": "^2.0.1"
4647
},
4748
"contributors": [

src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22

33
exports.resolver = require('./resolver.js')
44
exports.util = require('./util.js')
5+
exports.codec = exports.util.codec
6+
exports.defaultHashAlg = exports.util.defaultHashAlg

src/resolver.js

+43-116
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,70 @@
11
'use strict'
22

3+
const CID = require('cids')
4+
35
const util = require('./util')
46

57
/**
6-
* @callback ResolveCallback
7-
* @param {?Error} error - Error if path can't be resolved
8-
* @param {Object} result - Result of the path it it was resolved successfully
9-
* @param {*} result.value - Value the path resolves to
10-
* @param {string} result.remainderPath - If the path resolves half-way to a
11-
* link, then the `remainderPath` is the part after the link that can be used
12-
* for further resolving.
13-
*/
14-
/**
15-
* Resolves a path in a Zcash block.
8+
* Resolves a path within a Zcash block.
169
*
1710
* Returns the value or a link and the partial mising path. This way the
1811
* IPLD Resolver can fetch the link and continue to resolve.
1912
*
2013
* @param {Buffer} binaryBlob - Binary representation of a Zcash block
2114
* @param {string} [path='/'] - Path that should be resolved
22-
* @param {ResolveCallback} callback - Callback that handles the return value
23-
* @returns {void}
15+
* @returns {Object} result - Result of the path it it was resolved successfully
16+
* @returns {*} result.value - Value the path resolves to
17+
* @returns {string} result.remainderPath - If the path resolves half-way to a
18+
* link, then the `remainderPath` is the part after the link that can be used
19+
* for further resolving
2420
*/
25-
const resolve = (binaryBlob, path, callback) => {
26-
if (typeof path === 'function') {
27-
callback = path
28-
path = undefined
29-
}
21+
exports.resolve = (binaryBlob, path) => {
22+
let node = util.deserialize(binaryBlob)
3023

31-
util.deserialize(binaryBlob, (err, dagNode) => {
32-
if (err) {
33-
return callback(err)
24+
const parts = path.split('/').filter(Boolean)
25+
while (parts.length) {
26+
const key = parts.shift()
27+
if (node[key] === undefined) {
28+
throw new Error(`Object has no property '${key}'`)
3429
}
3530

36-
// Return the deserialized block if no path is given
37-
if (!path) {
38-
return callback(null, {
39-
value: dagNode,
40-
remainderPath: ''
41-
})
31+
node = node[key]
32+
if (CID.isCID(node)) {
33+
return {
34+
value: node,
35+
remainderPath: parts.join('/')
36+
}
4237
}
38+
}
4339

44-
const pathArray = path.split('/')
45-
const value = resolveField(dagNode, pathArray[0])
46-
if (value === null) {
47-
return callback(new Error('No such path'), null)
48-
}
40+
return {
41+
value: node,
42+
remainderPath: ''
43+
}
44+
}
4945

50-
let remainderPath = pathArray.slice(1).join('/')
51-
// It is a link, hence it may have a remainder
52-
if (value['/'] !== undefined) {
53-
return callback(null, {
54-
value: value,
55-
remainderPath: remainderPath
56-
})
57-
} else {
58-
if (remainderPath.length > 0) {
59-
return callback(new Error('No such path'), null)
60-
} else {
61-
return callback(null, {
62-
value: value,
63-
remainderPath: ''
64-
})
65-
}
66-
}
67-
})
46+
const traverse = function * (node, path) {
47+
// Traverse only objects and arrays
48+
if (Buffer.isBuffer(node) || CID.isCID(node) || typeof node === 'string' ||
49+
node === null) {
50+
return
51+
}
52+
for (const item of Object.keys(node)) {
53+
const nextpath = path === undefined ? item : path + '/' + item
54+
yield nextpath
55+
yield * traverse(node[item], nextpath)
56+
}
6857
}
6958

70-
/**
71-
* @callback TreeCallback
72-
* @param {?Error} error - Error if paths can't be retreived
73-
* @param {string[] | Object.<string, *>[]} result - The result depends on
74-
* `options.values`, whether it returns only the paths, or the paths with
75-
* the corresponding values
76-
*/
7759
/**
7860
* Return all available paths of a block.
7961
*
62+
* @generator
8063
* @param {Buffer} binaryBlob - Binary representation of a Zcash block
81-
* @param {Object} [options] - Possible options
82-
* @param {boolean} [options.values=false] - Retun only the paths by default.
83-
* If it is `true` also return the values
84-
* @param {TreeCallback} callback - Callback that handles the return value
85-
* @returns {void}
64+
* @yields {string} - A single path
8665
*/
87-
const tree = (binaryBlob, options, callback) => {
88-
if (typeof options === 'function') {
89-
callback = options
90-
options = undefined
91-
}
92-
options = options || {}
93-
94-
util.deserialize(binaryBlob, (err, dagNode) => {
95-
if (err) {
96-
return callback(err)
97-
}
98-
99-
const paths = ['version', 'timestamp', 'difficulty', 'nonce',
100-
'solution', 'reserved', 'parent', 'tx']
101-
102-
if (options.values === true) {
103-
const pathValues = {}
104-
for (let path of paths) {
105-
pathValues[path] = resolveField(dagNode, path)
106-
}
107-
return callback(null, pathValues)
108-
} else {
109-
return callback(null, paths)
110-
}
111-
})
112-
}
113-
114-
// Return top-level fields. Returns `null` if field doesn't exist
115-
const resolveField = (header, field) => {
116-
switch (field) {
117-
case 'version':
118-
return header.version
119-
case 'timestamp':
120-
return header.time
121-
case 'difficulty':
122-
return header.bits
123-
case 'nonce':
124-
return header.nonce
125-
case 'solution':
126-
return header.solution
127-
case 'reserved':
128-
return header.reserved
129-
case 'parent':
130-
return { '/': util.hashToCid(header.prevHash) }
131-
case 'tx':
132-
return { '/': util.hashToCid(header.merkleRoot) }
133-
default:
134-
return null
135-
}
136-
}
66+
exports.tree = function * (binaryBlob) {
67+
const node = util.deserialize(binaryBlob)
13768

138-
module.exports = {
139-
multicodec: 'zcash-block',
140-
defaultHashAlg: 'dbl-sha2-256',
141-
resolve: resolve,
142-
tree: tree
69+
yield * traverse(node)
14370
}

src/util.js

+61-69
Original file line numberDiff line numberDiff line change
@@ -2,104 +2,94 @@
22

33
const ZcashBitcoreBlockHeader = require('zcash-bitcore-lib').BlockHeader
44
const CID = require('cids')
5+
const multicodec = require('multicodec')
56
const multihashes = require('multihashes')
67
const multihashing = require('multihashing-async')
7-
const waterfall = require('async/waterfall')
88

99
const ZCASH_BLOCK_HEADER_SIZE = 1487
10+
const CODEC = multicodec.ZCASH_BLOCK
11+
const DEFAULT_HASH_ALG = multicodec.DBL_SHA2_256
1012

11-
/**
12-
* @callback SerializeCallback
13-
* @param {?Error} error - Error if serialization failed
14-
* @param {?Buffer} binaryBlob - Binary Zcash block if serialization was
15-
* successful
16-
*/
1713
/**
1814
* Serialize internal representation into a binary Zcash block.
1915
*
2016
* @param {ZcashBlock} dagNode - Internal representation of a Zcash block
21-
* @param {SerializeCallback} callback - Callback that handles the
22-
* return value
23-
* @returns {void}
17+
* @returns {Buffer}
2418
*/
25-
const serialize = (dagNode, callback) => {
26-
let err = null
27-
let binaryBlob
28-
try {
29-
binaryBlob = dagNode.toBuffer()
30-
} catch (serializeError) {
31-
err = serializeError
32-
} finally {
33-
callback(err, binaryBlob)
34-
}
19+
const serialize = (dagNode) => {
20+
return dagNode.toBuffer()
3521
}
3622

3723
/**
38-
* @callback DeserializeCallback
39-
* @param {?Error} error - Error if deserialization failed
40-
* @param {?ZcashBlock} dagNode - Internal representation of a Zcash block
41-
* if deserialization was successful
42-
*/
43-
/**
44-
* Deserialize Zcash block into the internal representation,
24+
* Deserialize Zcash block into the internal representation.
4525
*
4626
* @param {Buffer} binaryBlob - Binary representation of a Zcash block
47-
* @param {DeserializeCallback} callback - Callback that handles the
48-
* return value
49-
* @returns {void}
27+
* @returns {ZcashBlock}
5028
*/
51-
const deserialize = (binaryBlob, callback) => {
29+
const deserialize = (binaryBlob) => {
5230
if (binaryBlob.length !== ZCASH_BLOCK_HEADER_SIZE) {
53-
const err = new Error(
31+
throw new Error(
5432
`Zcash block header needs to be ${ZCASH_BLOCK_HEADER_SIZE} bytes`)
55-
return callback(err)
5633
}
5734

58-
const dagNode = ZcashBitcoreBlockHeader.fromBuffer(binaryBlob)
59-
callback(null, dagNode)
35+
const deserialized = ZcashBitcoreBlockHeader.fromBuffer(binaryBlob)
36+
37+
const getters = {
38+
difficulty: function () {
39+
return this.bits
40+
},
41+
parent: function () {
42+
return hashToCid(this.prevHash)
43+
},
44+
tx: function () {
45+
return hashToCid(this.merkleRoot)
46+
}
47+
}
48+
Object.entries(getters).forEach(([name, fun]) => {
49+
Object.defineProperty(deserialized, name, {
50+
enumerable: true,
51+
get: fun
52+
})
53+
})
54+
55+
const removeEnumberables = [
56+
'bits',
57+
'merkleRoot',
58+
'prevHash',
59+
'time'
60+
]
61+
removeEnumberables.forEach((field) => {
62+
if (field in deserialized) {
63+
Object.defineProperty(deserialized, field, { enumerable: false })
64+
}
65+
})
66+
67+
return deserialized
6068
}
6169

6270
/**
63-
* @callback CidCallback
64-
* @param {?Error} error - Error if getting the CID failed
65-
* @param {?CID} cid - CID if call was successful
66-
*/
67-
/**
68-
* Get the CID of the DAG-Node.
71+
* Calculate the CID of the binary blob.
6972
*
70-
* @param {ZcashBlock} dagNode - Internal representation of a Zcash block
71-
* @param {Object} [options] - Options to create the CID
72-
* @param {number} [options.version=1] - CID version number
73-
* @param {string} [options.hashAlg='dbl-sha2-256'] - Hashing algorithm
74-
* @param {CidCallback} callback - Callback that handles the return value
75-
* @returns {void}
73+
* @param {Object} binaryBlob - Encoded IPLD Node
74+
* @param {Object} [userOptions] - Options to create the CID
75+
* @param {number} [userOptions.cidVersion=1] - CID version number
76+
* @param {string} [UserOptions.hashAlg] - Defaults to the defaultHashAlg of the format
77+
* @returns {Promise.<CID>}
7678
*/
77-
const cid = (dagNode, options, callback) => {
78-
if (typeof options === 'function') {
79-
callback = options
80-
options = {}
81-
}
82-
options = options || {}
83-
// avoid deadly embrace between resolver and util
84-
const hashAlg = options.hashAlg || require('./resolver').defaultHashAlg
85-
const version = typeof options.version === 'undefined' ? 1 : options.version
86-
waterfall([
87-
(cb) => {
88-
try {
89-
multihashing(dagNode.toBuffer(), hashAlg, cb)
90-
} catch (err) {
91-
cb(err)
92-
}
93-
},
94-
(mh, cb) => cb(null, new CID(version, 'zcash-block', mh))
95-
], callback)
79+
const cid = async (binaryBlob, userOptions) => {
80+
const defaultOptions = { cidVersion: 1, hashAlg: DEFAULT_HASH_ALG }
81+
const options = Object.assign(defaultOptions, userOptions)
82+
83+
const multihash = await multihashing(binaryBlob, options.hashAlg)
84+
const codecName = multicodec.print[CODEC]
85+
const cid = new CID(options.cidVersion, codecName, multihash)
86+
87+
return cid
9688
}
9789

9890
// Convert a Zcash hash (as Buffer) to a CID
9991
const hashToCid = (hash) => {
100-
// avoid deadly embrace between resolver and util
101-
const defaultHashAlg = require('./resolver').defaultHashAlg
102-
const multihash = multihashes.encode(hash, defaultHashAlg)
92+
const multihash = multihashes.encode(hash, DEFAULT_HASH_ALG)
10393
const cidVersion = 1
10494
const cid = new CID(cidVersion, 'zcash-block', multihash)
10595
return cid
@@ -108,6 +98,8 @@ const hashToCid = (hash) => {
10898
module.exports = {
10999
hashToCid: hashToCid,
110100
ZCASH_BLOCK_HEADER_SIZE: ZCASH_BLOCK_HEADER_SIZE,
101+
codec: CODEC,
102+
defaultHashAlg: DEFAULT_HASH_ALG,
111103

112104
// Public API
113105
cid: cid,

0 commit comments

Comments
 (0)