Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

feat: preload object.new object.put object.patch.* #1471

Closed
wants to merge 15 commits into from
Closed
31 changes: 27 additions & 4 deletions .aegir.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
'use strict'

const createServer = require('ipfsd-ctl').createServer
const IPFSFactory = require('ipfsd-ctl')
const parallel = require('async/parallel')
const MockPreloadNode = require('./test/utils/mock-preload-node')

const server = createServer()
const ipfsdServer = IPFSFactory.createServer()
const preloadNode = MockPreloadNode.createNode()

module.exports = {
webpack: {
Expand All @@ -21,9 +24,29 @@ module.exports = {
singleRun: true
},
hooks: {
node: {
pre: (cb) => preloadNode.start(cb),
post: (cb) => preloadNode.stop(cb)
},
browser: {
pre: server.start.bind(server),
post: server.stop.bind(server)
pre: (cb) => {
parallel([
(cb) => {
ipfsdServer.start()
cb()
},
(cb) => preloadNode.start(cb)
], cb)
},
post: (cb) => {
parallel([
(cb) => {
ipfsdServer.stop()
cb()
},
(cb) => preloadNode.stop(cb)
], cb)
}
}
}
}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ Creates and returns an instance of an IPFS node. Use the `options` argument to s
- `enabled` (boolean): Make this node a relay (other nodes can connect *through* it). (Default: `false`)
- `active` (boolean): Make this an *active* relay node. Active relay nodes will attempt to dial a destination peer even if that peer is not yet connected to the relay. (Default: `false`)

- `preload` (object): Configure external nodes that will preload content added to this node
- `enabled` (boolean): Enable content preloading (Default: `true`)
- `addresses` (array): Multiaddr API addresses of nodes that should preload content. NOTE: nodes specified here should also be added to your node's bootstrap address list at `config.Boostrap`
- `EXPERIMENTAL` (object): Enable and configure experimental features.
- `pubsub` (boolean): Enable libp2p pub-sub. (Default: `false`)
- `sharding` (boolean): Enable directory sharding. Directories that have many child objects will be represented by multiple DAG nodes instead of just one. It can improve lookup performance when a directory has several thousand files or more. (Default: `false`)
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"./src/core/components/init-assets.js": false,
"./src/core/runtime/config-nodejs.js": "./src/core/runtime/config-browser.js",
"./src/core/runtime/libp2p-nodejs.js": "./src/core/runtime/libp2p-browser.js",
"./src/core/runtime/preload-nodejs.js": "./src/core/runtime/preload-browser.js",
"./src/core/runtime/repo-nodejs.js": "./src/core/runtime/repo-browser.js",
"./src/core/runtime/dns-nodejs.js": "./src/core/runtime/dns-browser.js",
"./test/utils/create-repo-nodejs.js": "./test/utils/create-repo-browser.js",
Expand Down Expand Up @@ -140,6 +141,7 @@
"mime-types": "^2.1.18",
"mkdirp": "~0.5.1",
"multiaddr": "^5.0.0",
"multiaddr-to-uri": "^4.0.0",
"multibase": "~0.4.0",
"multihashes": "~0.4.13",
"once": "^1.4.0",
Expand Down
5 changes: 5 additions & 0 deletions src/core/components/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ module.exports = function block (self) {
if (err) {
return cb(err)
}

if (options.preload !== false) {
self._preload(block.cid)
}

cb(null, block)
})
], callback)
Expand Down
15 changes: 15 additions & 0 deletions src/core/components/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ function normalizeContent (opts, content) {
})
}

function preloadFile (self, opts, file) {
const isRootFile = opts.wrapWithDirectory
? file.path === ''
: !file.path.includes('/')

const shouldPreload = isRootFile && !opts.onlyHash && opts.preload !== false

if (shouldPreload) {
self._preload(file.hash)
}

return file
}

function pinFile (self, opts, file, cb) {
// Pin a file if it is the root dir of a recursive add or the single file
// of a direct add.
Expand Down Expand Up @@ -158,6 +172,7 @@ module.exports = function files (self) {
pull.flatten(),
importer(self._ipld, opts),
pull.asyncMap(prepareFile.bind(null, self, opts)),
pull.map(preloadFile.bind(null, self, opts)),
pull.asyncMap(pinFile.bind(null, self, opts))
)
}
Expand Down
48 changes: 37 additions & 11 deletions src/core/components/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,17 @@ module.exports = function object (self) {
if (err) {
return cb(err)
}
self._ipld.put(node, {
cid: new CID(node.multihash)
}, (err) => {
cb(err, node)

const cid = new CID(node.multihash)

self._ipld.put(node, { cid }, (err) => {
if (err) return cb(err)

if (options.preload !== false) {
self._preload(cid)
}

cb(null, node)
})
})
}
Expand All @@ -92,12 +99,20 @@ module.exports = function object (self) {
}

return {
new: promisify((template, callback) => {
new: promisify((template, options, callback) => {
if (typeof template === 'function') {
callback = template
template = undefined
options = {}
}

if (typeof options === 'function') {
callback = options
options = {}
}

options = options || {}

let data

if (template) {
Expand All @@ -111,13 +126,18 @@ module.exports = function object (self) {
if (err) {
return callback(err)
}
self._ipld.put(node, {
cid: new CID(node.multihash)
}, (err) => {

const cid = new CID(node.multihash)

self._ipld.put(node, { cid }, (err) => {
if (err) {
return callback(err)
}

if (options.preload !== false) {
self._preload(cid)
}

callback(null, node)
})
})
Expand Down Expand Up @@ -166,13 +186,17 @@ module.exports = function object (self) {
}

function next () {
self._ipld.put(node, {
cid: new CID(node.multihash)
}, (err) => {
const cid = new CID(node.multihash)

self._ipld.put(node, { cid }, (err) => {
if (err) {
return callback(err)
}

if (options.preload !== false) {
self._preload(cid)
}

self.object.get(node.multihash, callback)
})
}
Expand Down Expand Up @@ -282,6 +306,8 @@ module.exports = function object (self) {
editAndSave((node, cb) => {
if (DAGLink.isDAGLink(linkRef)) {
linkRef = linkRef._name
} else if (linkRef && linkRef.name) {
linkRef = linkRef.name
}
DAGNode.rmLink(node, linkRef, cb)
})(multihash, options, callback)
Expand Down
2 changes: 2 additions & 0 deletions src/core/components/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ module.exports = (self) => {
},
(cb) => self.libp2p.start(cb),
(cb) => {
self._preload.start()

self._bitswap = new Bitswap(
self._libp2pNode,
self._repo.blocks,
Expand Down
1 change: 1 addition & 0 deletions src/core/components/stop.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = (self) => {
self.state.stop()
self._blockService.unsetExchange()
self._bitswap.stop()
self._preload.stop()

series([
(cb) => self.libp2p.stop(cb),
Expand Down
4 changes: 4 additions & 0 deletions src/core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const schema = Joi.object().keys({
Joi.string()
).allow(null),
repoOwner: Joi.boolean().default(true),
preload: Joi.object().keys({
enabled: Joi.boolean().default(true),
addresses: Joi.array().items(Joi.multiaddr().options({ convert: false }))
}).allow(null),
init: Joi.alternatives().try(
Joi.boolean(),
Joi.object().keys({ bits: Joi.number().integer() })
Expand Down
11 changes: 10 additions & 1 deletion src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const boot = require('./boot')
const components = require('./components')
// replaced by repo-browser when running in the browser
const defaultRepo = require('./runtime/repo-nodejs')
const preload = require('./preload')

class IPFS extends EventEmitter {
constructor (options) {
Expand All @@ -30,7 +31,14 @@ class IPFS extends EventEmitter {
this._options = {
init: true,
start: true,
EXPERIMENTAL: {}
EXPERIMENTAL: {},
preload: {
enabled: true,
addresses: [
'/dnsaddr/node0.preload.ipfs.io/https',
'/dnsaddr/node1.preload.ipfs.io/https'
]
}
}

options = config.validate(options || {})
Expand Down Expand Up @@ -78,6 +86,7 @@ class IPFS extends EventEmitter {
this._blockService = new BlockService(this._repo)
this._ipld = new Ipld(this._blockService)
this._pubsub = undefined
this._preload = preload(this)

// IPFS Core exposed components
// - for booting up a node
Expand Down
88 changes: 88 additions & 0 deletions src/core/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use strict'

const setImmediate = require('async/setImmediate')
const retry = require('async/retry')
const toUri = require('multiaddr-to-uri')
const debug = require('debug')
const CID = require('cids')
const preload = require('./runtime/preload-nodejs')

const log = debug('jsipfs:preload')
log.error = debug('jsipfs:preload:error')

const noop = (err) => { if (err) log.error(err) }

module.exports = self => {
const options = self._options.preload || {}
options.enabled = Boolean(options.enabled)
options.addresses = options.addresses || []

if (!options.enabled || !options.addresses.length) {
return (_, callback) => {
if (callback) {
setImmediate(() => callback())
}
}
}

let stopped = true
let requests = []
const apiUris = options.addresses.map(apiAddrToUri)

const api = (cid, callback) => {
callback = callback || noop

if (typeof cid !== 'string') {
try {
cid = new CID(cid).toBaseEncodedString()
} catch (err) {
return setImmediate(() => callback(err))
}
}

const fallbackApiUris = Array.from(apiUris)
let request
const now = Date.now()

retry({ times: fallbackApiUris.length }, (cb) => {
if (stopped) return cb(new Error(`preload aborted for ${cid}`))

// Remove failed request from a previous attempt
requests = requests.filter(r => r !== request)

const apiUri = fallbackApiUris.shift()

request = preload(`${apiUri}/api/v0/refs?r=true&arg=${cid}`, cb)
requests = requests.concat(request)
}, (err) => {
requests = requests.filter(r => r !== request)

if (err) {
return callback(err)
}

log(`preloaded ${cid} in ${Date.now() - now}ms`)
callback()
})
}

api.start = () => {
stopped = false
}

api.stop = () => {
stopped = true
log(`canceling ${requests.length} pending preload request(s)`)
requests.forEach(r => r.cancel())
requests = []
}

return api
}

function apiAddrToUri (addr) {
if (!(addr.endsWith('http') || addr.endsWith('https'))) {
addr = addr + '/http'
}
return toUri(addr)
}
4 changes: 3 additions & 1 deletion src/core/runtime/config-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ module.exports = () => ({
'/dns4/nyc-1.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm',
'/dns4/nyc-2.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64',
'/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic',
'/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6'
'/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6',
'/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic',
'/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6'
]
})
4 changes: 3 additions & 1 deletion src/core/runtime/config-nodejs.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ module.exports = () => ({
'/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3',
'/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx',
'/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic',
'/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6'
'/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6',
'/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic',
'/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6'
]
})
Loading