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

Commit b5470d4

Browse files
authored
fix: repo auto-migration regression (#3718)
The recent js-ipfs-repo release changed how options are merged which resulted in the wrong `autoMigrate` value being used. Pass it in explicitly and add a test to prevent future regressions. Fixes #3712
1 parent d4a1bf0 commit b5470d4

File tree

8 files changed

+172
-30
lines changed

8 files changed

+172
-30
lines changed

packages/ipfs-core/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
"ipld-git": "^0.6.1",
133133
"iso-url": "^1.0.0",
134134
"nanoid": "^3.1.12",
135+
"p-defer": "^3.0.0",
135136
"rimraf": "^3.0.2",
136137
"sinon": "^10.0.1"
137138
}

packages/ipfs-core/src/components/storage.js

+11-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const uint8ArrayToString = require('uint8arrays/to-string')
99
const PeerId = require('peer-id')
1010
const { mergeOptions } = require('../utils')
1111
const configService = require('./config')
12-
const { NotEnabledError } = require('../errors')
12+
const { NotEnabledError, NotInitializedError } = require('../errors')
1313
const createLibP2P = require('./libp2p')
1414

1515
/**
@@ -45,10 +45,14 @@ class Storage {
4545
* @param {IPFSOptions} options
4646
*/
4747
static async start (print, options) {
48-
const { repoAutoMigrate, repo: inputRepo } = options
48+
const { repoAutoMigrate, repo: inputRepo, onMigrationProgress } = options
4949

5050
const repo = (typeof inputRepo === 'string' || inputRepo == null)
51-
? createRepo(print, { path: inputRepo, autoMigrate: Boolean(repoAutoMigrate) })
51+
? createRepo(print, {
52+
path: inputRepo,
53+
autoMigrate: repoAutoMigrate,
54+
onMigrationProgress: onMigrationProgress
55+
})
5256
: inputRepo
5357

5458
const { peerId, keychain, isNew } = await loadRepo(print, repo, options)
@@ -198,14 +202,16 @@ const configureRepo = async (repo, options) => {
198202
const profiles = (options.init && options.init.profiles) || []
199203
const pass = options.pass
200204
const original = await repo.config.getAll()
201-
// @ts-ignore TODO: move config types to repo
202205
const changed = mergeConfigs(applyProfiles(original, profiles), config)
203206

204207
if (original !== changed) {
205208
await repo.config.replace(changed)
206209
}
207210

208-
// @ts-ignore - Identity may not be present
211+
if (!changed.Identity || !changed.Identity.PrivKey) {
212+
throw new NotInitializedError('No private key was found in the config, please intialize the repo')
213+
}
214+
209215
const peerId = await PeerId.createFromPrivKey(changed.Identity.PrivKey)
210216
const libp2p = await createLibP2P({
211217
options: undefined,

packages/ipfs-core/src/runtime/repo-browser.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22

33
const IPFSRepo = require('ipfs-repo')
44

5+
/**
6+
* @typedef {import('ipfs-repo-migrations').ProgressCallback} MigrationProgressCallback
7+
*/
8+
59
/**
610
* @param {import('../types').Print} print
711
* @param {object} options
812
* @param {string} [options.path]
9-
* @param {boolean} options.autoMigrate
13+
* @param {boolean} [options.autoMigrate]
14+
* @param {MigrationProgressCallback} [options.onMigrationProgress]
1015
*/
1116
module.exports = (print, options) => {
1217
const repoPath = options.path || 'ipfs'
13-
return new IPFSRepo(repoPath, { autoMigrate: options.autoMigrate })
18+
return new IPFSRepo(repoPath, {
19+
autoMigrate: options.autoMigrate,
20+
onMigrationProgress: options.onMigrationProgress || print
21+
})
1422
}

packages/ipfs-core/src/runtime/repo-nodejs.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,28 @@ const os = require('os')
44
const IPFSRepo = require('ipfs-repo')
55
const path = require('path')
66

7+
/**
8+
* @typedef {import('ipfs-repo-migrations').ProgressCallback} MigrationProgressCallback
9+
*/
10+
711
/**
812
* @param {import('../types').Print} print
913
* @param {object} options
1014
* @param {string} [options.path]
11-
* @param {boolean} options.autoMigrate
15+
* @param {boolean} [options.autoMigrate]
16+
* @param {MigrationProgressCallback} [options.onMigrationProgress]
1217
*/
13-
module.exports = (print, options = { autoMigrate: true }) => {
18+
module.exports = (print, options = {}) => {
1419
const repoPath = options.path || path.join(os.homedir(), '.jsipfs')
1520
/**
1621
* @type {number}
1722
*/
1823
let lastMigration
1924

2025
/**
21-
* @param {number} version
22-
* @param {string} percentComplete
23-
* @param {string} message
26+
* @type {MigrationProgressCallback}
2427
*/
25-
const onMigrationProgress = (version, percentComplete, message) => {
28+
const onMigrationProgress = options.onMigrationProgress || function (version, percentComplete, message) {
2629
if (version !== lastMigration) {
2730
lastMigration = version
2831

@@ -33,7 +36,7 @@ module.exports = (print, options = { autoMigrate: true }) => {
3336
}
3437

3538
return new IPFSRepo(repoPath, {
36-
autoMigrate: options.autoMigrate,
39+
autoMigrate: options.autoMigrate != null ? options.autoMigrate : true,
3740
onMigrationProgress: onMigrationProgress
3841
})
3942
}

packages/ipfs-core/test/create-node.spec.js

+44-6
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,23 @@ const { isNode } = require('ipfs-utils/src/env')
88
const tmpDir = require('ipfs-utils/src/temp-dir')
99
const PeerId = require('peer-id')
1010
const { supportedKeys } = require('libp2p-crypto/src/keys')
11-
const IPFS = require('../')
11+
const IPFS = require('../src')
12+
const defer = require('p-defer')
13+
const uint8ArrayToString = require('uint8arrays/to-string')
1214

1315
// This gets replaced by `create-repo-browser.js` in the browser
1416
const createTempRepo = require('./utils/create-repo-nodejs.js')
1517

1618
describe('create node', function () {
1719
let tempRepo
1820

19-
beforeEach(() => {
20-
tempRepo = createTempRepo()
21+
beforeEach(async () => {
22+
tempRepo = await createTempRepo()
2123
})
2224

23-
afterEach(() => tempRepo.teardown())
25+
afterEach(() => {
26+
tempRepo.teardown()
27+
})
2428

2529
it('should create a node with a custom repo path', async function () {
2630
this.timeout(80 * 1000)
@@ -240,8 +244,8 @@ describe('create node', function () {
240244
})
241245
}
242246

243-
const repoA = createTempRepo()
244-
const repoB = createTempRepo()
247+
const repoA = await createTempRepo()
248+
const repoB = await createTempRepo()
245249
const [nodeA, nodeB] = await Promise.all([createNode(repoA), createNode(repoB)])
246250
const [idA, idB] = await Promise.all([nodeA.id(), nodeB.id()])
247251

@@ -285,4 +289,38 @@ describe('create node', function () {
285289

286290
await expect(node.start()).to.eventually.be.rejected().with.property('code', 'ERR_WEBSOCKET_STAR_SWARM_ADDR_NOT_SUPPORTED')
287291
})
292+
293+
it('should auto-migrate repos by default', async function () {
294+
this.timeout(80 * 1000)
295+
296+
const deferred = defer()
297+
const id = await PeerId.create({
298+
bits: 512
299+
})
300+
301+
// create an old-looking repo
302+
const repo = await createTempRepo({
303+
version: 1,
304+
spec: 1,
305+
config: {
306+
Identity: {
307+
PeerId: id.toString(),
308+
PrivKey: uint8ArrayToString(id.marshalPrivKey(), 'base64pad')
309+
}
310+
}
311+
})
312+
313+
const node = await IPFS.create({
314+
repo: repo.path,
315+
onMigrationProgress: () => {
316+
// migrations are happening
317+
deferred.resolve()
318+
}
319+
})
320+
321+
await deferred.promise
322+
323+
await node.stop()
324+
await repo.teardown()
325+
})
288326
})

packages/ipfs-core/test/utils/create-node.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const IPFS = require('../../')
55
const createTempRepo = require('./create-repo-nodejs')
66

77
module.exports = async (config = {}) => {
8-
const repo = createTempRepo()
8+
const repo = await createTempRepo()
99
const ipfs = await IPFS.create(mergeOptions({
1010
silent: true,
1111
repo,

packages/ipfs-core/test/utils/create-repo-browser.js

+69-5
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,33 @@ const idb = self.indexedDB ||
99
self.webkitIndexedDB ||
1010
self.msIndexedDB
1111

12-
module.exports = function createTempRepo (repoPath) {
13-
repoPath = repoPath || '/ipfs-' + nanoid()
12+
/**
13+
* @param {object} options
14+
* @param {string} [options.path]
15+
* @param {number} [options.version]
16+
* @param {number} [options.spec]
17+
* @param {import('ipfs-core-types/src/config').Config} [options.config]
18+
*/
19+
module.exports = async function createTempRepo (options = {}) {
20+
options.path = options.path || `ipfs-${nanoid()}`
1421

15-
const repo = new IPFSRepo(repoPath)
22+
await createDB(options.path, (objectStore) => {
23+
const encoder = new TextEncoder()
24+
25+
if (options.version) {
26+
objectStore.put(encoder.encode(`${options.version}`), '/version')
27+
}
28+
29+
if (options.spec) {
30+
objectStore.put(encoder.encode(`${options.spec}`), '/datastore_spec')
31+
}
32+
33+
if (options.config) {
34+
objectStore.put(encoder.encode(JSON.stringify(options.config)), '/config')
35+
}
36+
})
37+
38+
const repo = new IPFSRepo(options.path)
1639

1740
repo.teardown = async () => {
1841
try {
@@ -23,9 +46,50 @@ module.exports = function createTempRepo (repoPath) {
2346
}
2447
}
2548

26-
idb.deleteDatabase(repoPath)
27-
idb.deleteDatabase(repoPath + '/blocks')
49+
idb.deleteDatabase(options.path)
50+
idb.deleteDatabase(options.path + '/blocks')
2851
}
2952

3053
return repo
3154
}
55+
56+
/**
57+
* Allows pre-filling the root IndexedDB object store with data
58+
*
59+
* @param {string} path
60+
* @param {(objectStore: IDBObjectStore) => void} fn
61+
*/
62+
function createDB (path, fn) {
63+
return new Promise((resolve, reject) => {
64+
const request = idb.open(path, 1)
65+
66+
request.onupgradeneeded = () => {
67+
const db = request.result
68+
69+
db.onerror = () => {
70+
reject(new Error('Could not create database'))
71+
}
72+
73+
db.createObjectStore(path)
74+
}
75+
76+
request.onsuccess = () => {
77+
const db = request.result
78+
79+
const transaction = db.transaction(path, 'readwrite')
80+
transaction.onerror = () => {
81+
reject(new Error('Could not add data to database'))
82+
}
83+
transaction.oncomplete = () => {
84+
db.close()
85+
resolve()
86+
}
87+
88+
const objectStore = transaction.objectStore(path)
89+
90+
fn(objectStore)
91+
92+
transaction.commit()
93+
}
94+
})
95+
}

packages/ipfs-core/test/utils/create-repo-nodejs.js

+26-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,33 @@ const clean = require('./clean')
55
const os = require('os')
66
const path = require('path')
77
const { nanoid } = require('nanoid')
8+
const fs = require('fs').promises
89

9-
module.exports = function createTempRepo (repoPath) {
10-
repoPath = repoPath || path.join(os.tmpdir(), '/ipfs-test-' + nanoid())
10+
/**
11+
* @param {object} options
12+
* @param {string} [options.path]
13+
* @param {number} [options.version]
14+
* @param {number} [options.spec]
15+
* @param {import('ipfs-core-types/src/config').Config} [options.config]
16+
*/
17+
module.exports = async function createTempRepo (options = {}) {
18+
options.path = options.path || path.join(os.tmpdir(), '/ipfs-test-' + nanoid())
1119

12-
const repo = new IPFSRepo(repoPath)
20+
await fs.mkdir(options.path)
21+
22+
if (options.version) {
23+
await fs.writeFile(path.join(options.path, 'version'), `${options.version}`)
24+
}
25+
26+
if (options.spec) {
27+
await fs.writeFile(path.join(options.path, 'spec'), `${options.spec}`)
28+
}
29+
30+
if (options.config) {
31+
await fs.writeFile(path.join(options.path, 'config'), JSON.stringify(options.config))
32+
}
33+
34+
const repo = new IPFSRepo(options.path)
1335

1436
repo.teardown = async () => {
1537
try {
@@ -20,7 +42,7 @@ module.exports = function createTempRepo (repoPath) {
2042
}
2143
}
2244

23-
await clean(repoPath)
45+
await clean(options.path)
2446
}
2547

2648
return repo

0 commit comments

Comments
 (0)