mirror of
https://github.com/orbitdb/orbitdb.git
synced 2025-03-30 15:08:28 +00:00
commit
64e36df5a1
2
.github/workflows/run-test.yml
vendored
2
.github/workflows/run-test.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
run: npm ci
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
- name: Run webrtc-star-signalling-server in the background
|
||||
- name: Run a webrtc relay in the background
|
||||
run: npm run webrtc:background
|
||||
- name: Run browser tests
|
||||
run: npm run test:browser
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,9 +1,6 @@
|
||||
# Don't distribute the dependencies
|
||||
node_modules/
|
||||
|
||||
# Don't track code coverage reports
|
||||
coverage/
|
||||
|
||||
#Don't track ipfs files
|
||||
test/ipfs/
|
||||
test/browser/ipfs/
|
||||
|
13
.npmignore
13
.npmignore
@ -10,19 +10,10 @@ dist/*.map
|
||||
# Don't distribute the debug build
|
||||
dist/orbitdb.js
|
||||
|
||||
# Don't distribute screenshot
|
||||
# See examples at https://github.com/orbitdb/orbit-db
|
||||
images/
|
||||
|
||||
.git
|
||||
.circleci
|
||||
benchmarks
|
||||
test
|
||||
ipfs
|
||||
docker
|
||||
conf
|
||||
Makefile
|
||||
API.md
|
||||
CHANGELOG.md
|
||||
FAQ.md
|
||||
GUIDE.md
|
||||
CONTRIBUTING.md
|
||||
CODE_OF_CONDUCT.md
|
||||
|
3
Makefile
3
Makefile
@ -7,14 +7,13 @@ test: deps
|
||||
npm run test -- --exit
|
||||
|
||||
build: test
|
||||
mkdir -p examples/browser/lib/
|
||||
npm run build
|
||||
@echo "Build success!"
|
||||
|
||||
clean:
|
||||
rm -rf node_modules/
|
||||
rm -rf coverage/
|
||||
rm -rf docs/api/
|
||||
rm -rf dist/
|
||||
rm -f test/browser/bundle.js*
|
||||
|
||||
clean-dependencies: clean
|
||||
|
29
README.md
29
README.md
@ -19,14 +19,13 @@ All databases are [implemented](https://github.com/orbitdb/orbitdb/tree/main/src
|
||||
|
||||
This is the Javascript implementation and it works both in **Browsers** and **Node.js** with support for Linux, OS X, and Windows.
|
||||
|
||||
***NOTE!*** *[js-ipfs](https://github.com/ipfs/js-ipfs) and related packages are now superseded by IPFS's [Helia](https://github.com/ipfs/helia) project and are no longer being maintained. As part of this migration, OrbitDB will be soon [switching to Helia](https://github.com/ipfs/helia).*
|
||||
|
||||
A Go implementation is developed and maintained by the [Berty](https://github.com/berty) project at [berty/go-orbit-db](https://github.com/berty/go-orbit-db).
|
||||
|
||||
## Installation
|
||||
|
||||
Install OrbitDB and its dependencies:
|
||||
```
|
||||
npm install @orbitdb/core
|
||||
npm install @orbitdb/core helia
|
||||
```
|
||||
|
||||
### Browser <script> tag
|
||||
@ -35,16 +34,20 @@ OrbitDB can be loaded in the browser using the distributed js file with the `<sc
|
||||
|
||||
`<script>/path/to/orbitdb.min.js</script>`
|
||||
|
||||
## Quick Start
|
||||
|
||||
If you want to get up and running with OrbitDB quickly, install and follow the instructions in the [@orbitdb/quickstart](https://github.com/orbitdb/quickstart) module.
|
||||
|
||||
## Usage
|
||||
|
||||
If you're using `@orbitdb/core` to develop **browser** or **Node.js** applications, use it as a module with the javascript instance of IPFS.
|
||||
|
||||
```javascript
|
||||
import IPFS from 'ipfs-core'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB } from '@orbitdb/core'
|
||||
|
||||
;(async function () {
|
||||
const ipfs = await IPFS.create()
|
||||
const ipfs = await createHelia()
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
|
||||
// Create / Open a database. Defaults to db type "events".
|
||||
@ -73,12 +76,24 @@ import { createOrbitDB } from '@orbitdb/core'
|
||||
|
||||
await db.close()
|
||||
await orbitdb.stop()
|
||||
await ipfs.stop()
|
||||
})()
|
||||
```
|
||||
|
||||
To configure your [IPFS instance](https://github.com/ipfs/helia) for persistency and [Libp2p](https://github.com/libp2p/js-libp2p) to connect to peers, see [Creating a Helia instance](https://github.com/orbitdb/quickstart/blob/main/src/index.js) and the [Default Libp2p Configurations](https://github.com/orbitdb/quickstart/blob/main/src/config/libp2p/index.js) in [@orbitdb/quickstart](https://github.com/orbitdb/quickstart/blob/main/src/config/libp2p/index.js) for examples.
|
||||
|
||||
## Documentation
|
||||
|
||||
Use the **[Getting Started](https://github.com/orbitdb/orbitdb/blob/main/docs/GETTING_STARTED.md)** guide for an initial introduction to OrbitDB and you can find more advanced topics covered in our [docs](https://github.com/orbitdb/orbitdb/blob/main/docs).
|
||||
Use the **[Getting Started](https://github.com/orbitdb/orbitdb/blob/main/docs/GETTING_STARTED.md)** guide for an initial introduction to OrbitDB.
|
||||
|
||||
You can find more advanced topics in our [docs](https://github.com/orbitdb/orbitdb/blob/main/docs) covering:
|
||||
- [Databases](https://github.com/orbitdb/orbitdb/blob/main/docs/DATABASES.md)
|
||||
- [Storage](https://github.com/orbitdb/orbitdb/blob/main/docs/STORAGE.md)
|
||||
- [Identities](https://github.com/orbitdb/orbitdb/blob/main/docs/IDENTITIES.md)
|
||||
- [Access Controllers](https://github.com/orbitdb/orbitdb/blob/main/docs/ACCESS_CONTROLLERS.md)
|
||||
- [Connecting Peers](https://github.com/orbitdb/orbitdb/blob/main/docs/CONNECTING_PEERS.md)
|
||||
- [Replication](https://github.com/orbitdb/orbitdb/blob/main/docs/REPLICATION.md)
|
||||
- [Oplog](https://github.com/orbitdb/orbitdb/blob/main/docs/OPLOG.md)
|
||||
|
||||
### API
|
||||
|
||||
@ -98,7 +113,7 @@ npm run build
|
||||
|
||||
### Benchmark
|
||||
```sh
|
||||
node benchmarks/benchmark-add.js
|
||||
node benchmarks/orbitdb-events.js
|
||||
```
|
||||
|
||||
See [benchmarks/](https://github.com/orbitdb/orbitdb/tree/master/benchmarks) for more benchmarks.
|
||||
|
@ -1,33 +1,10 @@
|
||||
import { createOrbitDB } from '../src/index.js'
|
||||
import { rimraf as rmrf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import createHelia from '../test/utils/create-helia.js'
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
EventEmitter.defaultMaxListeners = 10000
|
||||
|
||||
const ipfsConfig = {
|
||||
preload: {
|
||||
enabled: false
|
||||
},
|
||||
config: {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: ['/ip4/0.0.0.0/tcp/0'],
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
},
|
||||
Bootstrap: [],
|
||||
Discovery: {
|
||||
MDNS: {
|
||||
Enabled: false,
|
||||
Interval: 0
|
||||
},
|
||||
webRTCStar: {
|
||||
Enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
console.log('Starting benchmark...')
|
||||
|
||||
@ -36,7 +13,7 @@ const ipfsConfig = {
|
||||
await rmrf('./ipfs')
|
||||
await rmrf('./orbitdb')
|
||||
|
||||
const ipfs = await IPFS.create({ ...ipfsConfig, repo: './ipfs' })
|
||||
const ipfs = await createHelia()
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
|
||||
console.log(`Create ${entryCount} events`)
|
||||
|
@ -1,33 +1,10 @@
|
||||
import { createOrbitDB } from '../src/index.js'
|
||||
import { rimraf as rmrf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import createHelia from '../test/utils/create-helia.js'
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
EventEmitter.defaultMaxListeners = 10000
|
||||
|
||||
const ipfsConfig = {
|
||||
preload: {
|
||||
enabled: false
|
||||
},
|
||||
config: {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: ['/ip4/0.0.0.0/tcp/0'],
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
},
|
||||
Bootstrap: [],
|
||||
Discovery: {
|
||||
MDNS: {
|
||||
Enabled: false,
|
||||
Interval: 0
|
||||
},
|
||||
webRTCStar: {
|
||||
Enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
console.log('Starting benchmark...')
|
||||
|
||||
@ -36,7 +13,7 @@ const ipfsConfig = {
|
||||
await rmrf('./ipfs')
|
||||
await rmrf('./orbitdb')
|
||||
|
||||
const ipfs = await IPFS.create({ ...ipfsConfig, repo: './ipfs' })
|
||||
const ipfs = await createHelia()
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
|
||||
console.log(`Set ${entryCount} keys/values`)
|
||||
|
@ -1,47 +1,22 @@
|
||||
import { createOrbitDB } from '../src/index.js'
|
||||
import { rimraf as rmrf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import createHelia from '../test/utils/create-helia.js'
|
||||
import connectPeers from '../test/utils/connect-nodes.js'
|
||||
import waitFor from '../test/utils/wait-for.js'
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
EventEmitter.defaultMaxListeners = 10000
|
||||
|
||||
const ipfsConfig = {
|
||||
preload: {
|
||||
enabled: false
|
||||
},
|
||||
config: {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: ['/ip4/0.0.0.0/tcp/0'],
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
},
|
||||
Bootstrap: [],
|
||||
Discovery: {
|
||||
MDNS: {
|
||||
Enabled: true,
|
||||
Interval: 0
|
||||
},
|
||||
webRTCStar: {
|
||||
Enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
console.log('Starting benchmark...')
|
||||
|
||||
const entryCount = 1000
|
||||
|
||||
await rmrf('./ipfs1')
|
||||
await rmrf('./ipfs2')
|
||||
await rmrf('./orbitdb1')
|
||||
await rmrf('./orbitdb2')
|
||||
|
||||
const ipfs1 = await IPFS.create({ ...ipfsConfig, repo: './ipfs1' })
|
||||
const ipfs2 = await IPFS.create({ ...ipfsConfig, repo: './ipfs2' })
|
||||
const [ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
|
||||
const orbitdb1 = await createOrbitDB({ ipfs: ipfs1, directory: './orbitdb1' })
|
||||
const orbitdb2 = await createOrbitDB({ ipfs: ipfs2, directory: './orbitdb2' })
|
||||
|
||||
@ -75,19 +50,12 @@ const ipfsConfig = {
|
||||
|
||||
await waitFor(() => connected, () => true)
|
||||
|
||||
console.log(`Iterate ${entryCount} events to replicate them`)
|
||||
|
||||
const all = []
|
||||
for await (const { value } of db2.iterator()) {
|
||||
all.unshift(value)
|
||||
}
|
||||
|
||||
const endTime2 = new Date().getTime()
|
||||
const duration2 = endTime2 - startTime2
|
||||
const operationsPerSecond2 = Math.floor(entryCount / (duration2 / 1000))
|
||||
const millisecondsPerOp2 = duration2 / entryCount
|
||||
|
||||
console.log(`Replicating ${all.length} events took ${duration2} ms, ${operationsPerSecond2} ops/s, ${millisecondsPerOp2} ms/op`)
|
||||
console.log(`Replicating ${entryCount} events took ${duration2} ms, ${operationsPerSecond2} ops/s, ${millisecondsPerOp2} ms/op`)
|
||||
|
||||
await db1.drop()
|
||||
await db1.close()
|
||||
@ -99,8 +67,6 @@ const ipfsConfig = {
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
|
||||
await rmrf('./ipfs1')
|
||||
await rmrf('./ipfs2')
|
||||
await rmrf('./orbitdb1')
|
||||
await rmrf('./orbitdb2')
|
||||
|
||||
|
@ -10,7 +10,7 @@ export default (env, argv) => {
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
return {
|
||||
entry: glob.sync('./test/**/*.js', { ignore: [] }),
|
||||
entry: glob.sync('./test/**/*.js', { ignore: ['./test/utils/relay.js'] }),
|
||||
output: {
|
||||
filename: '../test/browser/bundle.js'
|
||||
},
|
||||
@ -26,6 +26,9 @@ export default (env, argv) => {
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
Buffer: ['buffer', 'Buffer']
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG)
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
|
@ -7,11 +7,14 @@ An access controller is passed when a database is opened for the first time. Onc
|
||||
Different access controllers can be assigned to the database using the `AccessController` param and passing it to OrbitDB's `open` function.
|
||||
|
||||
```js
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB } from '@orbitdb/core'
|
||||
import * as SomeAccessController from 'some-access-controller.js'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs = create({ options })
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
|
||||
@ -34,10 +37,13 @@ await db.add('hello world') // only orbitdb.identity.id can write to the db.
|
||||
To change write access, pass the IPFSAccessController with the `write` parameter and an array of one or more Identity ids:
|
||||
|
||||
```js
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB, Identities, IPFSAccessController } from '@orbitdb/core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs = create({ options })
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
const identities = await Identities()
|
||||
const anotherIdentity = identities.createIdentity('userB')
|
||||
@ -52,10 +58,13 @@ const db = orbitdb.open('my-db', { AccessController: IPFSAccessController({ writ
|
||||
To allow anyone to write to the database, specify the wildcard '*':
|
||||
|
||||
```js
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB, Identities, IPFSAccessController } from '@orbitdb/core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs = create({ options })
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
|
||||
@ -69,10 +78,13 @@ const db = orbitdb.open('my-db', { AccessController: IPFSAccessController({ writ
|
||||
The OrbitDB access controller provides configurable write access using grant and revoke.
|
||||
|
||||
```js
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB, Identities, OrbitDBAccessController } from '@orbitdb/core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs = create({ options })
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
|
||||
@ -155,8 +167,13 @@ In the above example, the `entry.identity` will be the hash of the identity. Usi
|
||||
Before passing the custom access controller to the `open` function, it must be added to OrbitDB's AccessControllers:
|
||||
|
||||
```js
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB, useAccessController } from '@orbitdb/core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
useAccessController(CustomAccessController)
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
|
@ -7,10 +7,17 @@ OrbitDB peers connect to one another using js-libp2p. Connection settings will v
|
||||
Node.js allows libp2p to open connections with other Node.js daemons.
|
||||
|
||||
```javascript
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs1 = await create({ repo: './ipfs1' })
|
||||
const ipfs2 = await create({ repo: './ipfs2' })
|
||||
const initIPFSInstance = () => {
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
return createHelia({ libp2p })
|
||||
}
|
||||
|
||||
const ipfs1 = await initIPFSInstance()
|
||||
const ipfs2 = await initIPFSInstance()
|
||||
|
||||
const cid = await ipfs1.block.put('here is some data')
|
||||
const block = await ipfs2.block.get(cid)
|
||||
@ -18,15 +25,23 @@ const block = await ipfs2.block.get(cid)
|
||||
|
||||
On localhost or a local network, both ipfs nodes should discover each other quickly enough so that ipfs2 will retrieve the block added to ipfs1.
|
||||
|
||||
In remote networks, retrieval of content across peers may take significantly longer. To speed up communication between the two peers, one peer can be directly connected to another using the swarm API and a peer's publicly accessible address. For example, assuming ipfs1 is listening on the address /ip4/1.2.3.4/tcp/12345/p2p/ipfs1-peer-hash:
|
||||
In remote networks, retrieval of content across peers may take significantly longer. To speed up communication between the two peers, one peer can be directly connected to another using libp2p's dial function:
|
||||
|
||||
```javascript
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs1 = await create({ repo: './ipfs1' })
|
||||
const ipfs2 = await create({ repo: './ipfs2' })
|
||||
const initIPFSInstance = () => {
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
return createHelia({ libp2p })
|
||||
}
|
||||
|
||||
await ipfs2.swarm.connect('/ip4/1.2.3.4/tcp/12345/p2p/ipfs1-peer-hash')
|
||||
const ipfs1 = await initIPFSInstance()
|
||||
const ipfs2 = await initIPFSInstance()
|
||||
|
||||
await ipfs2.libp2p.save(ipfs1.libp2p.peerId, { multiaddr: ipfs1.libp2p.getMultiaddrs() })
|
||||
await ipfs2.libp2p.dial(ipfs1.libp2p.peerId)
|
||||
|
||||
const cid = await ipfs1.block.put('here is some data')
|
||||
const block = await ipfs2.block.get(cid)
|
||||
@ -39,40 +54,83 @@ For various security reasons, a browser cannot dial another peer over a raw TCP
|
||||
On the server, listen for incoming websocket connections:
|
||||
|
||||
```javascript
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { identify } from '@libp2p/identify'
|
||||
import { circuitRelayServer } from '@libp2p/circuit-relay-v2'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { create } from 'ipfs-core'
|
||||
import * as filters from '@libp2p/websockets/filters'
|
||||
|
||||
ipfs1 = await create({
|
||||
libp2p: {
|
||||
addresses: {
|
||||
listen: [
|
||||
'/ip4/0.0.0.0/tcp/0/wss'
|
||||
]
|
||||
},
|
||||
transports: [webSockets()]
|
||||
const options = {
|
||||
addresses: {
|
||||
listen: ['/ip4/0.0.0.0/tcp/12345/ws']
|
||||
},
|
||||
repo: './ipfs1'
|
||||
})
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: filters.all
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
services: {
|
||||
identify: identify(),
|
||||
relay: circuitRelayServer()
|
||||
}
|
||||
}
|
||||
|
||||
const libp2p = createLibp2p(options)
|
||||
const ipfs1 = await createHelia({ libp2p })
|
||||
```
|
||||
|
||||
Within the browser, dial into the server using the server's exposed web socket:
|
||||
|
||||
```javascript
|
||||
// import the following libraries if using a build environment such as vite.
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
import { identify } from '@libp2p/identify'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { create } from 'ipfs-core'
|
||||
import { webRTC } from '@libp2p/webrtc'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||
|
||||
const ws = new webSockets()
|
||||
|
||||
ipfs1 = await create({
|
||||
libp2p: {
|
||||
transports: [ws]
|
||||
}},
|
||||
repo: './ipfs1'
|
||||
})
|
||||
const options = {
|
||||
addresses: {
|
||||
listen: [
|
||||
'/webrtc'
|
||||
]
|
||||
},
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: all
|
||||
}),
|
||||
webRTC(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 1
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
connectionGater: {
|
||||
denyDialMultiaddr: () => {
|
||||
return false
|
||||
}
|
||||
},
|
||||
services: {
|
||||
identify: identify()
|
||||
}
|
||||
}
|
||||
|
||||
const libp2p = createLibp2p(options)
|
||||
const ipfs1 = await createHelia({ libp2p })
|
||||
```
|
||||
|
||||
You may find IPFS is unable to connect to a local WebRTC Star server. This will likely to due to the local WebSockets transport being insecure (ws instead of wss). To solve this issue, pass the `all` filter to the `webSockets` function:
|
||||
You may find IPFS is unable to connect to a local WebRTC relay. This is likely due to the local WebSockets transport being insecure (ws instead of wss). This issue should be solvable by passing the `all` filter to the `webSockets` function (remove this in production environments):
|
||||
|
||||
```
|
||||
import { all } from '@libp2p/websockets/filters'
|
||||
@ -84,42 +142,142 @@ const ws = webSockets({ filter: all })
|
||||
|
||||
A connection cannot be made directly to a browser node. Browsers do not listen for incoming connections, they operate in a server/client environment where the server address is known and the browser connects to the server using the known address. Therefore, for a browser to respond to an incoming connection a relay is required to "listen" on the browser's behalf. The relay assigns and advertises multi addresses on behalf of the browser nodes, allowing the nodes to create a direct connection between each other.
|
||||
|
||||
Peer to peer connections where another peer connects to a browser node can use WebRTC as the transport protocol. A signalling server is required to facilitate the discovery of a node and establish a direct connection to another node.
|
||||
Peer to peer connections where another peer connects to a browser node can use WebRTC as the transport protocol. A relay server is required to facilitate the discovery of a node and establish a direct connection to another node.
|
||||
|
||||
Details on how to [deploy a WebRTC signalling server](https://github.com/libp2p/js-libp2p-webrtc-star/tree/master/packages/webrtc-star-signalling-server) are provided by the libp2p project.
|
||||
Details on how to [deploy a relay server](https://github.com/libp2p/js-libp2p-examples/tree/main/examples/js-libp2p-example-circuit-relay) are provided by the libp2p-examples project.
|
||||
|
||||
To connect two nodes via a relay, the IPFS swarm address should match the address of the signalling server.
|
||||
To connect two nodes via a relay, dial the relay from the first browser node. Once the first browser node's address is known, use this to dial the first browser node from the second browser node.
|
||||
|
||||
In the first browser peer, configure
|
||||
In the first browser peer, dial the relay to discover the browser peer's address:
|
||||
|
||||
```javascript
|
||||
import { create } from 'ipfs-core'
|
||||
import { multiaddr } from 'multiaddr'
|
||||
// import the following libraries if using a build environment such as vite.
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
import { identify } from '@libp2p/identify'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { webRTC } from '@libp2p/webrtc'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||
import { multiaddr } from '@multiformats/multiaddr'
|
||||
import { WebRTC as WebRTCMatcher } from '@multiformats/multiaddr-matcher'
|
||||
import pRetry from 'p-retry'
|
||||
import delay from 'delay'
|
||||
|
||||
ipfs = await create({
|
||||
config: {
|
||||
Addresses: {
|
||||
Swarm: ['/ip4/0.0.0.0/tcp/12345/ws/p2p-webrtc-star']
|
||||
const options = {
|
||||
addresses: {
|
||||
listen: [
|
||||
'/webrtc'
|
||||
]
|
||||
},
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: all
|
||||
}),
|
||||
webRTC(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 1
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
connectionGater: {
|
||||
denyDialMultiaddr: () => {
|
||||
return false
|
||||
}
|
||||
},
|
||||
services: {
|
||||
identify: identify()
|
||||
}
|
||||
}
|
||||
|
||||
const libp2p = createLibp2p(options)
|
||||
const ipfs1 = await createHelia({ libp2p })
|
||||
|
||||
/*The creation and deployment of a circuit relay is not covered in this documentation. However, you can use the one bundled with the OrbitDB unit tests by cloning the OrbitDB repository, installing the dependencies and then running `npm run webrtc` from the OrbitDB project's root dir. Once running, the webrtc relay server will print a number of addresses it is listening on. Use the address /ip4/127.0.0.1/tcp/12345/ws/p2p when specifying the relay for browser 1.
|
||||
*/
|
||||
const relay = '/ip4/127.0.0.1/tcp/12345/ws/p2p' // the address of the relay server. Change this if you are not using the OrbitDB-bundled webrtc relay.
|
||||
|
||||
await ipfs1.libp2p.dial(multiaddr(relay))
|
||||
|
||||
const a1 = await pRetry(async () => {
|
||||
const addr = ipfs1.libp2p.getMultiaddrs().filter(ma => WebRTCMatcher.matches(ma)).pop()
|
||||
|
||||
if (addr == null) {
|
||||
await delay(10)
|
||||
throw new Error('No WebRTC address found')
|
||||
}
|
||||
|
||||
return addr
|
||||
})
|
||||
|
||||
console.log('ipfs1 address discovered: ', a1)
|
||||
```
|
||||
|
||||
Configure the second browser node in the same way as the first, then dial in to the first browser peer using its multiaddress:
|
||||
Configure the second browser node in the same way as the first, then dial in to the first browser peer using the "ipfs1 address discovered":
|
||||
|
||||
```javascript
|
||||
import { create } from 'ipfs-core'
|
||||
import { multiaddr } from 'multiaddr'
|
||||
// import the following libraries if using a build environment such as vite.
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
import { identify } from '@libp2p/identify'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { webRTC } from '@libp2p/webrtc'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||
import { multiaddr } from '@multiformats/multiaddr'
|
||||
import { WebRTC as WebRTCMatcher } from '@multiformats/multiaddr-matcher'
|
||||
import pRetry from 'p-retry'
|
||||
import delay from 'delay'
|
||||
|
||||
ipfs = await create({
|
||||
config: {
|
||||
Addresses: {
|
||||
Swarm: ['/ip4/0.0.0.0/tcp/12345/ws/p2p-webrtc-star']
|
||||
const options = {
|
||||
addresses: {
|
||||
listen: [
|
||||
'/webrtc'
|
||||
]
|
||||
},
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: all
|
||||
}),
|
||||
webRTC(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 1
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
connectionGater: {
|
||||
denyDialMultiaddr: () => {
|
||||
return false
|
||||
}
|
||||
},
|
||||
services: {
|
||||
identify: identify()
|
||||
}
|
||||
}
|
||||
|
||||
const libp2p = createLibp2p(options)
|
||||
const ipfs1 = await createHelia({ libp2p })
|
||||
|
||||
const ipfs1Address = '' // paste the "ipfs1 address discovered:" value here.
|
||||
|
||||
await ipfs1.libp2p.dial(multiaddr(ipfs1Address))
|
||||
|
||||
const a2 = await pRetry(async () => {
|
||||
const addr = ipfs2.libp2p.getMultiaddrs().filter(ma => WebRTCMatcher.matches(ma)).pop()
|
||||
|
||||
if (addr == null) {
|
||||
await delay(10)
|
||||
throw new Error('No WebRTC address found')
|
||||
}
|
||||
|
||||
return addr
|
||||
})
|
||||
|
||||
await ipfs.swarm.connect('/multiaddr/of/first-peer')
|
||||
console.log('ipfs2 address discovered: ', a2)
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
|
@ -35,10 +35,14 @@ The second part, an IPFS multihash `zdpuAmrcSRUhkQcnRQ6p4bphs7DJWGBkqczSGFYynX6m
|
||||
In order to replicate the database with peers, the address is what you need to give to other peers in order for them to start replicating the database.
|
||||
|
||||
```js
|
||||
import IPFS from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB } from '@orbitdb/core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
const ipfs = await IPFS.create()
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
const db = await orbitdb.open('my-db')
|
||||
console.log(db.address)
|
||||
@ -62,15 +66,18 @@ An example of a manifest is given below:
|
||||
The manifest is an [IPLD data structure](https://ipld.io/docs/) which can be retrived from IPFS using the manifest's hash:
|
||||
|
||||
```js
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import * as Block from 'multiformats/block'
|
||||
import { createOrbitDB, OrbitDBAddress } from '@orbitdb/core'
|
||||
import * as dagCbor from '@ipld/dag-cbor'
|
||||
import { sha256 } from 'multiformats/hashes/sha2'
|
||||
import { base58btc } from 'multiformats/bases/base58'
|
||||
import { CID } from 'multiformats/cid'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs = await create()
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
// Create the db then close.
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
@ -169,14 +176,21 @@ await db.del(hash)
|
||||
|
||||
The power of OrbitDB lies in its ability to replicate databases across distributed systems that may not always be connected.
|
||||
|
||||
A simple way to replicate a database between peers can be accomplished by opening a database, listening for updates and iterating over the records as those updates occur.
|
||||
A simple way to replicate a database between peers can be accomplished by connecting one peer to another and then opening the database by its address:
|
||||
|
||||
```js
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB } from '@orbitdb/core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs1 = await create({ config1, repo: './ipfs1' })
|
||||
const ipfs2 = await create({ config2, repo: './ipfs2' })
|
||||
const initIPFSInstance = () => {
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
return createHelia({ libp2p })
|
||||
}
|
||||
|
||||
const ipfs1 = await initIPFSInstance()
|
||||
const ipfs2 = await initIPFSInstance()
|
||||
|
||||
orbitdb1 = await createOrbitDB({ ipfs: ipfs1, id: 'user1', directory: './orbitdb1' })
|
||||
orbitdb2 = await createOrbitDB({ ipfs: ipfs2, id: 'user2', directory: './orbitdb2' })
|
||||
@ -189,18 +203,9 @@ await db1.add('hello world')
|
||||
// database heads will be synchronized.
|
||||
const db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
// We only have the latest record of db1. To replicate all of db1's records, we will
|
||||
// need to iterate over db1's entire record set.
|
||||
// We can determine when heads have been synchronized from db1 to db2 by
|
||||
// listening for the "update" event and iterating over the record set.
|
||||
db2.events.on('update', async (entry) => {
|
||||
for await (const record of db2.iterator()) {
|
||||
console.log(record)
|
||||
}
|
||||
// we can use the convenience function db.all() instead of iterating over
|
||||
// db2's records.
|
||||
// await db2.all()
|
||||
})
|
||||
for await (const record of db2.iterator()) {
|
||||
console.log(record)
|
||||
}
|
||||
```
|
||||
|
||||
To learn more, check out [OrbitDB's sychronization protocol](https://orbitdb.org/api/module-Sync.html) and the [OrbitDB replication documentation](./REPLICATION.md).
|
||||
|
@ -10,10 +10,99 @@ Install OrbitDB:
|
||||
npm i @orbitdb/core
|
||||
```
|
||||
|
||||
You will also need IPFS for replication:
|
||||
You will also need Helia for replication:
|
||||
|
||||
```sh
|
||||
npm i ipfs-core
|
||||
npm i helia
|
||||
```
|
||||
|
||||
## Prerequisites: Helia and Libp2p
|
||||
|
||||
OrbitDB uses Helia for block storage and Libp2p for database synchronization. However, you need to configure Helia and pass it to OrbitDB when creating a peer.
|
||||
|
||||
### Block Storage
|
||||
|
||||
Helia uses memory block storage by default. This means that storage is destroyed every time your application ends and OrbitDB will no longer be able to retrieve blocks from Helia. Therefore, it is necessary to configure Helia with permanent block storage. Helia comes with [a variety of storage solutions](https://github.com/ipfs-examples/helia-101#blockstore) including filesystem storage, IndexDB and Level. To add one of these storage mechanisms to Helia, install the relevant package:
|
||||
|
||||
```
|
||||
npm i blockstore-level
|
||||
```
|
||||
|
||||
then instantiate and pass to Helia:
|
||||
|
||||
```
|
||||
import { LevelBlockstore } from 'blockstore-level'
|
||||
|
||||
const blockstore = new LevelBlockstore('./ipfs')
|
||||
const ipfs = createHelia({ blockstore })
|
||||
```
|
||||
|
||||
### Libp2p
|
||||
|
||||
OrbitDB synchronizes databases between peers using a p2p networking stack called [Libp2p](https://github.com/libp2p/js-libp2p/).
|
||||
|
||||
An instance of Libp2p is required by Helia which is then used by OrbitDB to synchronize database data across various networks.
|
||||
|
||||
A simple Node.js example might look something like:
|
||||
|
||||
```json
|
||||
{
|
||||
addresses: {
|
||||
listen: ['/ip4/0.0.0.0/tcp/0/ws']
|
||||
},
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: all
|
||||
}),
|
||||
webRTC(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 1
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
connectionGater: {
|
||||
denyDialMultiaddr: () => false
|
||||
},
|
||||
services: {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroPeers: true })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can export the above configuration from a file:
|
||||
|
||||
```js
|
||||
export const Libp2pOptions = {
|
||||
addresses: {
|
||||
listen: ['/ip4/0.0.0.0/tcp/0/ws']
|
||||
},
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: all
|
||||
}),
|
||||
webRTC(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 1
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
connectionGater: {
|
||||
denyDialMultiaddr: () => false
|
||||
},
|
||||
services: {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroPeers: true })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Throughout this documentation, you will see the above Libp2p configuration imported from a file called **./config/libp2p.js**, for example:
|
||||
|
||||
```js
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
```
|
||||
|
||||
## Creating a standalone database
|
||||
@ -31,11 +120,14 @@ npm init
|
||||
Create a file in your project called index.js and add the following code to it:
|
||||
|
||||
```js
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB } from '@orbitdb/core'
|
||||
import { create } from 'ipfs-core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
// Create an IPFS instance with defaults.
|
||||
const ipfs = await create()
|
||||
// Create an IPFS instance.
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
const orbitdb = await createOrbitDB({ ipfs })
|
||||
|
||||
@ -117,27 +209,19 @@ npm init
|
||||
Create a new file called index.js and paste in the following code:
|
||||
|
||||
```js
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { OrbitDB, IPFSAccessController } from '@orbitdb/core'
|
||||
import { create } from 'ipfs-core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const main = async () => {
|
||||
// create a random directory to avoid IPFS and OrbitDB conflicts.
|
||||
const main = async () => {
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
// create a random directory to avoid OrbitDB conflicts.
|
||||
let randDir = (Math.random() + 1).toString(36).substring(2)
|
||||
|
||||
const config = {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: ['/ip4/0.0.0.0/tcp/0'],
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
}
|
||||
}
|
||||
|
||||
// This will create an IPFS repo in ./[randDir]/ipfs.
|
||||
const ipfs = await create({ config, repo: './' + randDir + '/ipfs'})
|
||||
|
||||
// This will create all OrbitDB-related databases (keystore, my-db, etc) in
|
||||
// ./[randDir]/ipfs.
|
||||
const orbitdb = await createOrbitDB({ ipfs, directory: './' + randDir + '/orbitdb' })
|
||||
const orbitdb = await createOrbitDB({ ipfs, directory: `./${randDir}/orbitdb` })
|
||||
|
||||
let db
|
||||
|
||||
|
@ -88,15 +88,18 @@ const identities = await Identities({ path })
|
||||
The identity object is stored like any other [IPLD data structure](https://ipld.io/docs/) and can therefore be retrieved from IPFS using the identity's hash:
|
||||
|
||||
```js
|
||||
import { create } from 'ipfs-core'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia' from 'ipfs-core'
|
||||
import * as Block from 'multiformats/block'
|
||||
import { Identities } from '@orbitdb/core'
|
||||
import * as dagCbor from '@ipld/dag-cbor'
|
||||
import { sha256 } from 'multiformats/hashes/sha2'
|
||||
import { base58btc } from 'multiformats/bases/base58'
|
||||
import { CID } from 'multiformats/cid'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
const ipfs = await create()
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
const ipfs = await createHelia({ libp2p })
|
||||
|
||||
const identities = await Identities({ ipfs })
|
||||
const identity = await identities.createIdentity({ id: 'me' })
|
||||
|
@ -3,49 +3,28 @@
|
||||
Below is a simple replication example. Both peers run within the same Nodejs process.
|
||||
|
||||
```js
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { createHelia } from 'helia'
|
||||
import { createOrbitDB } from '@orbitdb/core'
|
||||
import { create } from 'ipfs-core'
|
||||
import { Libp2pOptions } from './config/libp2p.js'
|
||||
|
||||
// The config will set up a TCP connection when dialling other node.js peers.
|
||||
// You can find out more about peer connectivity at https://connectivity.libp2p.io/.
|
||||
const config1 = {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: ['/ip4/0.0.0.0/tcp/0'],
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
},
|
||||
Bootstrap: [],
|
||||
Discovery: {
|
||||
MDNS: {
|
||||
Enabled: true,
|
||||
Interval: 0
|
||||
}
|
||||
}
|
||||
// Our ipfs instances will be connecting over websockets. However, you could achieve the same here using tcp. You can find out more about peer connectivity at https://connectivity.libp2p.io/.
|
||||
|
||||
const initIPFSInstance = () => {
|
||||
const libp2p = await createLibp2p(Libp2pOptions)
|
||||
return createHelia({ libp2p })
|
||||
}
|
||||
|
||||
const config2 = {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: ['/ip4/0.0.0.0/tcp/0'],
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
},
|
||||
Bootstrap: [],
|
||||
Discovery: {
|
||||
MDNS: {
|
||||
Enabled: true,
|
||||
Interval: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ipfs1 = await create({ config: config1, repo: './ipfs/1' })
|
||||
const ipfs2 = await create({ config: config2, repo: './ipfs/2' })
|
||||
const ipfs1 = await initIPFSInstance()
|
||||
const ipfs2 = await initIPFSInstance()
|
||||
|
||||
// The decentralized nature if IPFS can make it slow for peers to find one
|
||||
// another. You can speed up a connection between two peers by "dialling-in"
|
||||
// to one peer from another.
|
||||
// const ipfs1PeerId = await ipfs1.id()
|
||||
// await ipfs2.swarm.connect(ipfs1PeerId.id)
|
||||
/*
|
||||
await ipfs2.libp2p.save(ipfs1.libp2p.peerId, { multiaddr: ipfs1.libp2p.getMultiaddrs() })
|
||||
await ipfs2.libp2p.dial(ipfs1.libp2p.peerId)
|
||||
*/
|
||||
|
||||
const orbitdb1 = await createOrbitDB({ ipfs: ipfs1, id: 'userA', directory: './orbitdb/1' })
|
||||
const orbitdb2 = await createOrbitDB({ ipfs: ipfs2, id: 'userB', directory: './orbitdb/2' })
|
||||
@ -75,12 +54,9 @@ db2.events.on('join', async (peerId, heads) => {
|
||||
console.log(peerId, (await ipfs1.id()).id)
|
||||
})
|
||||
|
||||
// Listen for any updates to db2. This is especially useful when listening for
|
||||
// new heads that are available on db1.
|
||||
// Listen for any updates to db2.
|
||||
// If we want to listen for new data on db2, add an "update" listener to db1.
|
||||
db2.events.on('update', async (entry) => {
|
||||
// Full replication is achieved by explicitly retrieving all records from db1.
|
||||
console.log(await db2.all())
|
||||
db2Updated = true
|
||||
})
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
## OrbitDB API - v1.0
|
||||
## OrbitDB API - v2.0
|
||||
|
||||
OrbitDB is a serverless, distributed, peer-to-peer database. OrbitDB uses IPFS
|
||||
as its data storage and Libp2p Pubsub to automatically sync databases with peers. It's an eventually consistent database that uses Merkle-CRDTs for conflict-free database writes and merges making OrbitDB an excellent choice for p2p and decentralized apps, blockchain applications and local first web applications.
|
||||
|
29517
package-lock.json
generated
29517
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@orbitdb/core",
|
||||
"version": "1.0.2",
|
||||
"version": "2.0.0",
|
||||
"description": "Distributed p2p database on IPFS",
|
||||
"author": "Haad",
|
||||
"license": "MIT",
|
||||
@ -9,7 +9,7 @@
|
||||
"url": "https://github.com/orbitdb/orbitdb"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
@ -18,42 +18,40 @@
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"dependencies": {
|
||||
"@ipld/dag-cbor": "^9.0.5",
|
||||
"@libp2p/crypto": "^2.0.4",
|
||||
"@ipld/dag-cbor": "^9.0.6",
|
||||
"@libp2p/crypto": "^3.0.2",
|
||||
"it-pipe": "^3.0.1",
|
||||
"level": "^8.0.0",
|
||||
"lru": "^3.1.0",
|
||||
"multiformats": "^12.1.1",
|
||||
"p-queue": "^7.4.1",
|
||||
"multiformats": "^12.1.3",
|
||||
"p-queue": "^8.0.1",
|
||||
"timeout-abort-controller": "^3.0.0",
|
||||
"uint8arrays": "^4.0.6"
|
||||
"uint8arrays": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@libp2p/webrtc-star-signalling-server": "^4.0.0",
|
||||
"assert": "^2.1.0",
|
||||
"babel-loader": "^9.1.3",
|
||||
"@chainsafe/libp2p-yamux": "^6.0.1",
|
||||
"@helia/block-brokers": "^1.0.0",
|
||||
"@libp2p/circuit-relay-v2": "^1.0.10",
|
||||
"blockstore-level": "^1.1.7",
|
||||
"c8": "^8.0.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"fs-extra": "^11.1.1",
|
||||
"ipfs-core": "^0.18.0",
|
||||
"it-all": "^3.0.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
"helia": "^3.0.1",
|
||||
"it-all": "^3.0.4",
|
||||
"jsdoc": "^4.0.2",
|
||||
"mocha": "^10.2.0",
|
||||
"mocha-headless-chrome": "^4.0.0",
|
||||
"open-cli": "^7.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"rimraf": "^5.0.1",
|
||||
"playwright-test": "^14.0.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"standard": "^17.1.0",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:docs": "remark -qf -u validate-links .",
|
||||
"test:all": "npm run test:browser-multiple-tabs && npm run test",
|
||||
"test": "cross-env mocha --config test/.mocharc.json",
|
||||
"test:ci": "cross-env c8 mocha --config test/.mocharc.json",
|
||||
"test:browser": "npm run build:tests && mocha-headless-chrome -t 360000 -f ./test/browser/index.html -a no-sandbox",
|
||||
"build": "npm run build:dist && npm run build:debug",
|
||||
"test:browser": "npm run build:tests && ./node_modules/.bin/playwright-test test/browser/bundle.js --runner mocha",
|
||||
"build": "npm run build:docs && npm run build:dist && npm run build:debug",
|
||||
"build:dist": "webpack --config conf/webpack.config.js",
|
||||
"build:debug": "webpack --config conf/webpack.debug.config.js",
|
||||
"build:docs": "jsdoc -c ./conf/jsdoc/jsdoc.json -r src/** -d ./docs/api -R ./docs/jsdoc/README.md",
|
||||
@ -61,8 +59,8 @@
|
||||
"prepublishOnly": "npm run build",
|
||||
"lint": "standard --env=mocha",
|
||||
"lint:fix": "standard --fix",
|
||||
"webrtc": "webrtc-star --port=12345",
|
||||
"webrtc:background": "webrtc-star --port=12345 &"
|
||||
"webrtc": "node ./test/utils/relay.js",
|
||||
"webrtc:background": "node ./test/utils/relay.js &"
|
||||
},
|
||||
"standard": {
|
||||
"env": [
|
||||
@ -82,6 +80,7 @@
|
||||
"decentralised",
|
||||
"distributed",
|
||||
"ipfs",
|
||||
"libp2p",
|
||||
"p2p",
|
||||
"peer-to-peer"
|
||||
]
|
||||
|
@ -41,10 +41,15 @@ const DefaultAccessController = async () => {
|
||||
* @param {Object} identity Identity.
|
||||
* @param {Object} options
|
||||
* @param {string} options.logId ID of the log
|
||||
* @param {Array<Entry>} options.logHeads Set the heads of the log
|
||||
* @param {Object} options.access AccessController (./default-access-controller)
|
||||
* @param {Array<Entry>} options.entries An Array of Entries from which to create the log
|
||||
* @param {Array<Entry>} options.heads Set the heads of the log
|
||||
* @param {module:Clock} options.clock Set the clock of the log
|
||||
* @param {module:Storage} [options.entryStorage] A compatible storage instance
|
||||
* for storing log entries. Defaults to MemoryStorage.
|
||||
* @param {module:Storage} [options.headsStorage] A compatible storage
|
||||
* instance for storing log heads. Defaults to MemoryStorage.
|
||||
* @param {module:Storage} [options.indexStorage] A compatible storage
|
||||
* instance for storing an index of log entries. Defaults to MemoryStorage.
|
||||
* @param {Function} options.sortFn The sort function - by default LastWriteWins
|
||||
* @return {module:Log~Log} sync An instance of Log
|
||||
* @memberof module:Log
|
||||
|
@ -41,7 +41,7 @@ const OrbitDB = async ({ ipfs, id, identity, identities, directory } = {}) => {
|
||||
}
|
||||
|
||||
id = id || await createId()
|
||||
const { id: peerId } = await ipfs.id()
|
||||
const peerId = ipfs.libp2p.peerId
|
||||
directory = directory || './orbitdb'
|
||||
|
||||
let keystore
|
||||
|
@ -6,8 +6,9 @@
|
||||
*/
|
||||
import { CID } from 'multiformats/cid'
|
||||
import { base58btc } from 'multiformats/bases/base58'
|
||||
import { TimeoutController } from 'timeout-abort-controller'
|
||||
|
||||
const defaultTimeout = 30000
|
||||
const DefaultTimeout = 30000 // 30 seconds
|
||||
|
||||
/**
|
||||
* Creates an instance of IPFSBlockStorage.
|
||||
@ -15,19 +16,17 @@ const defaultTimeout = 30000
|
||||
* @param {Object} params One or more parameters for configuring
|
||||
* IPFSBlockStorage.
|
||||
* @param {IPFS} params.ipfs An IPFS instance.
|
||||
* @param {number} [params.timeout=defaultTimeout] A timeout in ms.
|
||||
* @param {boolean} [params.pin=false] True, if the block should be pinned,
|
||||
* false otherwise.
|
||||
* @param {number} [params.timeout=defaultTimeout] A timeout in ms.
|
||||
* @return {module:Storage.Storage-IPFS} An instance of IPFSBlockStorage.
|
||||
* @memberof module:Storage
|
||||
* @throw An instance of ipfs is required if params.ipfs is not specified.
|
||||
* @instance
|
||||
*/
|
||||
const IPFSBlockStorage = async ({ ipfs, timeout, pin } = {}) => {
|
||||
const IPFSBlockStorage = async ({ ipfs, pin, timeout } = {}) => {
|
||||
if (!ipfs) throw new Error('An instance of ipfs is required.')
|
||||
|
||||
timeout = timeout || defaultTimeout
|
||||
|
||||
/**
|
||||
* Puts data to an IPFS block.
|
||||
* @function
|
||||
@ -38,14 +37,12 @@ const IPFSBlockStorage = async ({ ipfs, timeout, pin } = {}) => {
|
||||
*/
|
||||
const put = async (hash, data) => {
|
||||
const cid = CID.parse(hash, base58btc)
|
||||
await ipfs.block.put(data, {
|
||||
cid: cid.bytes,
|
||||
version: cid.version,
|
||||
format: 'dag-cbor',
|
||||
mhtype: 'sha2-256',
|
||||
pin,
|
||||
timeout
|
||||
})
|
||||
const { signal } = new TimeoutController(timeout || DefaultTimeout)
|
||||
await ipfs.blockstore.put(cid, data, { signal })
|
||||
|
||||
if (pin && !(await ipfs.pins.isPinned(cid))) {
|
||||
await ipfs.pins.add(cid)
|
||||
}
|
||||
}
|
||||
|
||||
const del = async (hash) => {}
|
||||
@ -54,12 +51,14 @@ const IPFSBlockStorage = async ({ ipfs, timeout, pin } = {}) => {
|
||||
* Gets data from an IPFS block.
|
||||
* @function
|
||||
* @param {string} hash The hash of the block to get.
|
||||
* @return {Uint8Array} The block.
|
||||
* @memberof module:Storage.Storage-IPFS
|
||||
* @instance
|
||||
*/
|
||||
const get = async (hash) => {
|
||||
const cid = CID.parse(hash, base58btc)
|
||||
const block = await ipfs.block.get(cid, { timeout })
|
||||
const { signal } = new TimeoutController(timeout || DefaultTimeout)
|
||||
const block = await ipfs.blockstore.get(cid, { signal })
|
||||
if (block) {
|
||||
return block
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ const defaultSize = 1000000
|
||||
* Creates an instance of LRUStorage.
|
||||
* @function
|
||||
* @param {Object} [params={}] One or more parameters for configuring
|
||||
* IPFSBlockStorage.
|
||||
* LRUStorage.
|
||||
* @param {string} [params.size=defaultSize] The number of elements to store.
|
||||
* @return {module:Storage.Storage-LRU} An instance of LRUStorage.
|
||||
* @memberof module:Storage
|
||||
|
66
src/sync.js
66
src/sync.js
@ -23,12 +23,10 @@ const DefaultTimeout = 30000 // 30 seconds
|
||||
*
|
||||
* Once the initial sync has completed, peers notify one another of updates to
|
||||
* the log, ie. updates to the database, using the initially opened pubsub
|
||||
* topic subscription.
|
||||
* A peer with new heads broadcasts changes to other peers by publishing the
|
||||
* updated heads
|
||||
* to the pubsub topic. Peers subscribed to the same topic will then receive
|
||||
* the update and
|
||||
* will update their log's state, the heads, accordingly.
|
||||
* topic subscription. A peer with new heads broadcasts changes to other peers
|
||||
* by publishing the updated heads to the pubsub topic. Peers subscribed to the
|
||||
* same topic will then receive the update and will update their log's state,
|
||||
* the heads, accordingly.
|
||||
*
|
||||
* The Sync Protocol is eventually consistent. It guarantees that once all
|
||||
* messages have been sent and received, peers will observe the same log state
|
||||
@ -36,12 +34,6 @@ const DefaultTimeout = 30000 // 30 seconds
|
||||
* are received or even that a message is recieved at all, nor any timing on
|
||||
* when messages are received.
|
||||
*
|
||||
* Note that the Sync Protocol does not retrieve the full log when
|
||||
* synchronizing the heads. Rather only the "latest entries" in the log, the
|
||||
* heads, are exchanged. In order to retrieve the full log and each entry, the
|
||||
* user would call the log.traverse() or log.iterator() functions, which go
|
||||
* through the log and retrieve each missing log entry from IPFS.
|
||||
*
|
||||
* @example
|
||||
* // Using defaults
|
||||
* const sync = await Sync({ ipfs, log, onSynced: (peerId, heads) => ... })
|
||||
@ -115,6 +107,9 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => {
|
||||
if (!ipfs) throw new Error('An instance of ipfs is required.')
|
||||
if (!log) throw new Error('An instance of log is required.')
|
||||
|
||||
const libp2p = ipfs.libp2p
|
||||
const pubsub = ipfs.libp2p.services.pubsub
|
||||
|
||||
const address = log.id
|
||||
const headsSyncAddress = pathJoin('/orbitdb/heads/', address)
|
||||
|
||||
@ -146,7 +141,7 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => {
|
||||
events.emit('join', peerId, heads)
|
||||
}
|
||||
|
||||
const sendHeads = async (source) => {
|
||||
const sendHeads = (source) => {
|
||||
return (async function * () {
|
||||
const heads = await log.heads()
|
||||
for await (const { bytes } of heads) {
|
||||
@ -162,7 +157,9 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => {
|
||||
await onSynced(headBytes)
|
||||
}
|
||||
}
|
||||
await onPeerJoined(peerId)
|
||||
if (started) {
|
||||
await onPeerJoined(peerId)
|
||||
}
|
||||
}
|
||||
|
||||
const handleReceiveHeads = async ({ connection, stream }) => {
|
||||
@ -192,13 +189,14 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => {
|
||||
const { signal } = timeoutController
|
||||
try {
|
||||
peers.add(peerId)
|
||||
const stream = await ipfs.libp2p.dialProtocol(remotePeer, headsSyncAddress, { signal })
|
||||
const stream = await libp2p.dialProtocol(remotePeer, headsSyncAddress, { signal })
|
||||
await pipe(sendHeads, stream, receiveHeads(peerId))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
peers.delete(peerId)
|
||||
if (e.code === 'ERR_UNSUPPORTED_PROTOCOL') {
|
||||
// Skip peer, they don't have this database currently
|
||||
} else {
|
||||
peers.delete(peerId)
|
||||
events.emit('error', e)
|
||||
}
|
||||
} finally {
|
||||
@ -214,20 +212,22 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => {
|
||||
queue.add(task)
|
||||
}
|
||||
|
||||
const handleUpdateMessage = async (message) => {
|
||||
const handleUpdateMessage = async message => {
|
||||
const { topic, data } = message.detail
|
||||
|
||||
const task = async () => {
|
||||
const { id: peerId } = await ipfs.id()
|
||||
const messageIsNotFromMe = (message) => String(peerId) !== String(message.from)
|
||||
const messageHasData = (message) => message.data !== undefined
|
||||
try {
|
||||
if (messageIsNotFromMe(message) && messageHasData(message) && onSynced) {
|
||||
await onSynced(message.data)
|
||||
if (data && onSynced) {
|
||||
await onSynced(data)
|
||||
}
|
||||
} catch (e) {
|
||||
events.emit('error', e)
|
||||
}
|
||||
}
|
||||
queue.add(task)
|
||||
|
||||
if (topic === address) {
|
||||
queue.add(task)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -239,7 +239,7 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => {
|
||||
*/
|
||||
const add = async (entry) => {
|
||||
if (started) {
|
||||
await ipfs.pubsub.publish(address, entry.bytes)
|
||||
await pubsub.publish(address, entry.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,12 +251,13 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => {
|
||||
*/
|
||||
const stopSync = async () => {
|
||||
if (started) {
|
||||
await queue.onIdle()
|
||||
ipfs.libp2p.pubsub.removeEventListener('subscription-change', handlePeerSubscribed)
|
||||
await ipfs.libp2p.unhandle(headsSyncAddress)
|
||||
await ipfs.pubsub.unsubscribe(address, handleUpdateMessage)
|
||||
peers.clear()
|
||||
started = false
|
||||
await queue.onIdle()
|
||||
pubsub.removeEventListener('subscription-change', handlePeerSubscribed)
|
||||
pubsub.removeEventListener('message', handleUpdateMessage)
|
||||
await libp2p.unhandle(headsSyncAddress)
|
||||
await pubsub.unsubscribe(address)
|
||||
peers.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,10 +270,11 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => {
|
||||
const startSync = async () => {
|
||||
if (!started) {
|
||||
// Exchange head entries with peers when connected
|
||||
await ipfs.libp2p.handle(headsSyncAddress, handleReceiveHeads)
|
||||
ipfs.libp2p.pubsub.addEventListener('subscription-change', handlePeerSubscribed)
|
||||
await libp2p.handle(headsSyncAddress, handleReceiveHeads)
|
||||
pubsub.addEventListener('subscription-change', handlePeerSubscribed)
|
||||
pubsub.addEventListener('message', handleUpdateMessage)
|
||||
// Subscribe to the pubsub channel for this database through which updates are sent
|
||||
await ipfs.pubsub.subscribe(address, handleUpdateMessage)
|
||||
await pubsub.subscribe(address)
|
||||
started = true
|
||||
}
|
||||
}
|
||||
|
@ -5,5 +5,6 @@
|
||||
"exit": true,
|
||||
"bail": false,
|
||||
"slow": 1000,
|
||||
"exclude": ["test/browser/**/*.js"]
|
||||
"exclude": ["test/browser/**/*.js", "test/utils/relay.js"],
|
||||
"timeout": 30000
|
||||
}
|
@ -1,18 +1,15 @@
|
||||
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import Keystore from '../../src/key-store.js'
|
||||
import Identities from '../../src/identities/identities.js'
|
||||
import IPFSAccessController from '../../src/access-controllers/ipfs.js'
|
||||
import config from '../config.js'
|
||||
import connectPeers from '../utils/connect-nodes.js'
|
||||
import createHelia from '../utils/create-helia.js'
|
||||
|
||||
describe('IPFSAccessController', function () {
|
||||
const dbPath1 = './orbitdb/tests/ipfs-access-controller/1'
|
||||
const dbPath2 = './orbitdb/tests/ipfs-access-controller/2'
|
||||
|
||||
this.timeout(config.timeout)
|
||||
|
||||
let ipfs1, ipfs2
|
||||
let keystore1, keystore2
|
||||
let identities1, identities2
|
||||
@ -20,8 +17,7 @@ describe('IPFSAccessController', function () {
|
||||
let orbitdb1, orbitdb2
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
keystore1 = await Keystore({ path: dbPath1 + '/keys' })
|
||||
|
@ -1,26 +1,22 @@
|
||||
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import OrbitDB from '../../src/orbitdb.js'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import Keystore from '../../src/key-store.js'
|
||||
import Identities from '../../src/identities/identities.js'
|
||||
import OrbitDBAccessController from '../../src/access-controllers/orbitdb.js'
|
||||
import config from '../config.js'
|
||||
import connectPeers from '../utils/connect-nodes.js'
|
||||
import createHelia from '../utils/create-helia.js'
|
||||
|
||||
const dbPath1 = './orbitdb/tests/orbitdb-access-controller/1'
|
||||
const dbPath2 = './orbitdb/tests/orbitdb-access-controller/2'
|
||||
|
||||
describe('OrbitDBAccessController', function () {
|
||||
this.timeout(config.timeout)
|
||||
|
||||
let ipfs1, ipfs2
|
||||
let orbitdb1, orbitdb2
|
||||
let identities1, identities2, testIdentity1, testIdentity2
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
const keystore1 = await Keystore({ path: dbPath1 + '/keys' })
|
||||
|
@ -1,80 +0,0 @@
|
||||
const isBrowser = () => typeof window !== 'undefined'
|
||||
|
||||
const swarmAddress = isBrowser()
|
||||
? ['/ip4/0.0.0.0/tcp/12345/ws/p2p-webrtc-star']
|
||||
: ['/ip4/0.0.0.0/tcp/0']
|
||||
|
||||
export default {
|
||||
timeout: 30000,
|
||||
defaultIpfsConfig: {
|
||||
preload: {
|
||||
enabled: false
|
||||
},
|
||||
EXPERIMENTAL: {
|
||||
pubsub: true
|
||||
},
|
||||
config: {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: swarmAddress,
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
},
|
||||
Bootstrap: [],
|
||||
Discovery: {
|
||||
MDNS: {
|
||||
Enabled: true,
|
||||
Interval: 0
|
||||
},
|
||||
webRTCStar: {
|
||||
Enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
daemon1: {
|
||||
silent: true,
|
||||
EXPERIMENTAL: {
|
||||
pubsub: true
|
||||
},
|
||||
config: {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: isBrowser() ? ['/ip4/0.0.0.0/tcp/12345/ws/p2p-webrtc-star'] : ['/ip4/0.0.0.0/tcp/0'],
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
},
|
||||
Bootstrap: [],
|
||||
Discovery: {
|
||||
MDNS: {
|
||||
Enabled: true,
|
||||
Interval: 0
|
||||
},
|
||||
webRTCStar: {
|
||||
Enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
daemon2: {
|
||||
silent: true,
|
||||
EXPERIMENTAL: {
|
||||
pubsub: true
|
||||
},
|
||||
config: {
|
||||
Addresses: {
|
||||
API: '/ip4/127.0.0.1/tcp/0',
|
||||
Swarm: isBrowser() ? ['/ip4/0.0.0.0/tcp/12345/ws/p2p-webrtc-star'] : ['/ip4/0.0.0.0/tcp/0'],
|
||||
Gateway: '/ip4/0.0.0.0/tcp/0'
|
||||
},
|
||||
Bootstrap: [],
|
||||
Discovery: {
|
||||
MDNS: {
|
||||
Enabled: true,
|
||||
Interval: 0
|
||||
},
|
||||
webRTCStar: {
|
||||
Enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,18 @@
|
||||
import { strictEqual, deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { Database, KeyStore, Identities } from '../src/index.js'
|
||||
import config from './config.js'
|
||||
import testKeysPath from './fixtures/test-keys-path.js'
|
||||
import connectPeers from './utils/connect-nodes.js'
|
||||
import waitFor from './utils/wait-for.js'
|
||||
import ComposedStorage from '../src/storage/composed.js'
|
||||
import IPFSBlockStorage from '../src/storage/ipfs-block.js'
|
||||
import MemoryStorage from '../src/storage/memory.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
describe('Database - Replication', function () {
|
||||
this.timeout(60000)
|
||||
|
||||
let ipfs1, ipfs2
|
||||
let keystore
|
||||
let identities
|
||||
@ -33,8 +30,7 @@ describe('Database - Replication', function () {
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
@ -78,7 +74,6 @@ describe('Database - Replication', function () {
|
||||
describe('Replicate across peers', () => {
|
||||
beforeEach(async () => {
|
||||
db1 = await Database({ ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' })
|
||||
db2 = await Database({ ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' })
|
||||
})
|
||||
|
||||
it('replicates databases across two peers', async () => {
|
||||
@ -93,6 +88,8 @@ describe('Database - Replication', function () {
|
||||
replicated = expectedEntryHash !== null && entry.hash === expectedEntryHash
|
||||
}
|
||||
|
||||
db2 = await Database({ ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' })
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
|
||||
@ -128,6 +125,8 @@ describe('Database - Replication', function () {
|
||||
replicated = expectedEntryHash && entry.hash === expectedEntryHash
|
||||
}
|
||||
|
||||
db2 = await Database({ ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' })
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
|
||||
@ -168,11 +167,6 @@ describe('Database - Replication', function () {
|
||||
connected = true
|
||||
}
|
||||
|
||||
await db2.drop()
|
||||
await db2.close()
|
||||
|
||||
await rimraf('./orbitdb2')
|
||||
|
||||
await db1.addOperation({ op: 'PUT', key: 1, value: 'record 1 on db 1' })
|
||||
|
||||
db2 = await Database({ ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' })
|
||||
|
@ -2,13 +2,12 @@ import { strictEqual, deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { existsSync } from 'fs'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import Path from 'path'
|
||||
import { Database, Entry, KeyStore, Identities } from '../src/index.js'
|
||||
import LevelStorage from '../src/storage/level.js'
|
||||
import MemoryStorage from '../src/storage/memory.js'
|
||||
import config from './config.js'
|
||||
import testKeysPath from './fixtures/test-keys-path.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -31,8 +30,7 @@ describe('Database', function () {
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
|
||||
ipfs = await createHelia()
|
||||
await copy(testKeysPath, keysPath)
|
||||
keystore = await KeyStore({ path: keysPath })
|
||||
identities = await Identities({ keystore })
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { deepStrictEqual, strictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { KeyStore, Identities } from '../../src/index.js'
|
||||
import Documents from '../../src/databases/documents.js'
|
||||
import config from '../config.js'
|
||||
import testKeysPath from '../fixtures/test-keys-path.js'
|
||||
import createHelia from '../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -20,7 +19,7 @@ describe('Documents Database', function () {
|
||||
const databaseId = 'documents-AAA'
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs = await createHelia()
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
keystore = await KeyStore({ path: keysPath })
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { deepStrictEqual, strictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { KeyStore, Identities } from '../../src/index.js'
|
||||
import Events from '../../src/databases/events.js'
|
||||
import config from '../config.js'
|
||||
import testKeysPath from '../fixtures/test-keys-path.js'
|
||||
import createHelia from '../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -20,7 +19,7 @@ describe('Events Database', function () {
|
||||
const databaseId = 'events-AAA'
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs = await createHelia()
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
keystore = await KeyStore({ path: keysPath })
|
||||
|
@ -3,11 +3,10 @@ import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { KeyStore, Identities, MemoryStorage } from '../../src/index.js'
|
||||
import KeyValueIndexed from '../../src/databases/keyvalue-indexed.js'
|
||||
import config from '../config.js'
|
||||
import testKeysPath from '../fixtures/test-keys-path.js'
|
||||
import createHelia from '../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -22,7 +21,7 @@ describe('KeyValueIndexed Database', function () {
|
||||
const databaseId = 'keyvalue-AAA'
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs = await createHelia()
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
keystore = await KeyStore({ path: keysPath })
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { deepStrictEqual, strictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { KeyStore, Identities } from '../../src/index.js'
|
||||
import KeyValue from '../../src/databases/keyvalue.js'
|
||||
import config from '../config.js'
|
||||
import testKeysPath from '../fixtures/test-keys-path.js'
|
||||
import createHelia from '../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -20,7 +19,7 @@ describe('KeyValue Database', function () {
|
||||
const databaseId = 'keyvalue-AAA'
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs = await createHelia()
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
keystore = await KeyStore({ path: keysPath })
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { KeyStore, Identities } from '../../../src/index.js'
|
||||
import Documents from '../../../src/databases/documents.js'
|
||||
import config from '../../config.js'
|
||||
import testKeysPath from '../../fixtures/test-keys-path.js'
|
||||
import connectPeers from '../../utils/connect-nodes.js'
|
||||
import waitFor from '../../utils/wait-for.js'
|
||||
import createHelia from '../../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -31,8 +30,7 @@ describe('Documents Database Replication', function () {
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { KeyStore, Identities } from '../../../src/index.js'
|
||||
import Events from '../../../src/databases/events.js'
|
||||
import config from '../../config.js'
|
||||
import testKeysPath from '../../fixtures/test-keys-path.js'
|
||||
import connectPeers from '../../utils/connect-nodes.js'
|
||||
import waitFor from '../../utils/wait-for.js'
|
||||
import createHelia from '../../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -41,8 +40,7 @@ describe('Events Database Replication', function () {
|
||||
]
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
@ -76,10 +74,12 @@ describe('Events Database Replication', function () {
|
||||
if (db1) {
|
||||
await db1.drop()
|
||||
await db1.close()
|
||||
db1 = null
|
||||
}
|
||||
if (db2) {
|
||||
await db2.drop()
|
||||
await db2.close()
|
||||
db2 = null
|
||||
}
|
||||
})
|
||||
|
||||
@ -130,9 +130,6 @@ describe('Events Database Replication', function () {
|
||||
})
|
||||
|
||||
it('loads the database after replication', async () => {
|
||||
db1 = await Events()({ ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' })
|
||||
db2 = await Events()({ ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' })
|
||||
|
||||
let replicated = false
|
||||
let expectedEntryHash = null
|
||||
|
||||
@ -148,6 +145,9 @@ describe('Events Database Replication', function () {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
db1 = await Events()({ ipfs: ipfs1, identity: testIdentity1, address: databaseId, accessController, directory: './orbitdb1' })
|
||||
db2 = await Events()({ ipfs: ipfs2, identity: testIdentity2, address: databaseId, accessController, directory: './orbitdb2' })
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { KeyStore, Identities } from '../../../src/index.js'
|
||||
import KeyValueIndexed from '../../../src/databases/keyvalue-indexed.js'
|
||||
import config from '../../config.js'
|
||||
import testKeysPath from '../../fixtures/test-keys-path.js'
|
||||
import connectPeers from '../../utils/connect-nodes.js'
|
||||
import waitFor from '../../utils/wait-for.js'
|
||||
import createHelia from '../../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -30,8 +29,7 @@ describe('KeyValueIndexed Database Replication', function () {
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { KeyStore, Identities } from '../../../src/index.js'
|
||||
import KeyValue from '../../../src/databases/keyvalue.js'
|
||||
import config from '../../config.js'
|
||||
import testKeysPath from '../../fixtures/test-keys-path.js'
|
||||
import connectPeers from '../../utils/connect-nodes.js'
|
||||
import waitFor from '../../utils/wait-for.js'
|
||||
import createHelia from '../../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -30,8 +29,7 @@ describe('KeyValue Database Replication', function () {
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { strictEqual, deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import ManifestStore from '../src/manifest-store.js'
|
||||
import config from './config.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
describe('Manifest', () => {
|
||||
const repo = './ipfs'
|
||||
@ -10,7 +9,7 @@ describe('Manifest', () => {
|
||||
let manifestStore
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo })
|
||||
ipfs = await createHelia()
|
||||
manifestStore = await ManifestStore({ ipfs })
|
||||
})
|
||||
|
||||
|
@ -1,19 +1,15 @@
|
||||
import { strictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { Log, Entry, Identities, KeyStore, IPFSBlockStorage } from '../../src/index.js'
|
||||
import config from '../config.js'
|
||||
import testKeysPath from '../fixtures/test-keys-path.js'
|
||||
import connectPeers from '../utils/connect-nodes.js'
|
||||
import getIpfsPeerId from '../utils/get-ipfs-peer-id.js'
|
||||
import waitForPeers from '../utils/wait-for-peers.js'
|
||||
import createHelia from '../utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
describe('Log - Replication', function () {
|
||||
this.timeout(60000)
|
||||
|
||||
let ipfs1, ipfs2
|
||||
let id1, id2
|
||||
let keystore
|
||||
@ -22,12 +18,11 @@ describe('Log - Replication', function () {
|
||||
let storage1, storage2
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
id1 = await getIpfsPeerId(ipfs1)
|
||||
id2 = await getIpfsPeerId(ipfs2)
|
||||
id1 = ipfs1.libp2p.peerId
|
||||
id2 = ipfs2.libp2p.peerId
|
||||
|
||||
await copy(testKeysPath, keysPath)
|
||||
keystore = await KeyStore({ path: keysPath })
|
||||
@ -69,11 +64,11 @@ describe('Log - Replication', function () {
|
||||
let log1, log2, input1, input2
|
||||
|
||||
const handleMessage1 = async (message) => {
|
||||
const { id: peerId } = await ipfs1.id()
|
||||
const peerId = ipfs1.libp2p.peerId
|
||||
const messageIsFromMe = (message) => String(peerId) === String(message.from)
|
||||
try {
|
||||
if (!messageIsFromMe(message)) {
|
||||
const entry = await Entry.decode(message.data)
|
||||
const entry = await Entry.decode(message.detail.data)
|
||||
await storage1.put(entry.hash, entry.bytes)
|
||||
await log1.joinEntry(entry)
|
||||
}
|
||||
@ -83,11 +78,11 @@ describe('Log - Replication', function () {
|
||||
}
|
||||
|
||||
const handleMessage2 = async (message) => {
|
||||
const { id: peerId } = await ipfs2.id()
|
||||
const peerId = ipfs2.libp2p.peerId
|
||||
const messageIsFromMe = (message) => String(peerId) === String(message.from)
|
||||
try {
|
||||
if (!messageIsFromMe(message)) {
|
||||
const entry = await Entry.decode(message.data)
|
||||
const entry = await Entry.decode(message.detail.data)
|
||||
await storage2.put(entry.hash, entry.bytes)
|
||||
await log2.joinEntry(entry)
|
||||
}
|
||||
@ -101,13 +96,15 @@ describe('Log - Replication', function () {
|
||||
log2 = await Log(testIdentity2, { logId, entryStorage: storage2 })
|
||||
input1 = await Log(testIdentity1, { logId, entryStorage: storage1 })
|
||||
input2 = await Log(testIdentity2, { logId, entryStorage: storage2 })
|
||||
await ipfs1.pubsub.subscribe(logId, handleMessage1)
|
||||
await ipfs2.pubsub.subscribe(logId, handleMessage2)
|
||||
ipfs1.libp2p.services.pubsub.addEventListener('message', handleMessage1)
|
||||
ipfs2.libp2p.services.pubsub.addEventListener('message', handleMessage2)
|
||||
await ipfs1.libp2p.services.pubsub.subscribe(logId)
|
||||
await ipfs2.libp2p.services.pubsub.subscribe(logId)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await ipfs1.pubsub.unsubscribe(logId, handleMessage1)
|
||||
await ipfs2.pubsub.unsubscribe(logId, handleMessage2)
|
||||
await ipfs1.libp2p.services.pubsub.unsubscribe(logId)
|
||||
await ipfs2.libp2p.services.pubsub.unsubscribe(logId)
|
||||
})
|
||||
|
||||
it('replicates logs', async () => {
|
||||
@ -117,8 +114,8 @@ describe('Log - Replication', function () {
|
||||
for (let i = 1; i <= amount; i++) {
|
||||
const entry1 = await input1.append('A' + i)
|
||||
const entry2 = await input2.append('B' + i)
|
||||
await ipfs1.pubsub.publish(logId, entry1.bytes)
|
||||
await ipfs2.pubsub.publish(logId, entry2.bytes)
|
||||
await ipfs1.libp2p.services.pubsub.publish(logId, entry1.bytes)
|
||||
await ipfs2.libp2p.services.pubsub.publish(logId, entry2.bytes)
|
||||
}
|
||||
|
||||
console.log('Messages sent')
|
||||
@ -139,7 +136,7 @@ describe('Log - Replication', function () {
|
||||
})
|
||||
}
|
||||
|
||||
await whileProcessingMessages(config.timeout)
|
||||
await whileProcessingMessages(this.timeout())
|
||||
|
||||
const result = await Log(testIdentity1, { logId, entryStorage: storage1 })
|
||||
await result.join(log1)
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import OrbitDB from '../src/orbitdb.js'
|
||||
import { IPFSAccessController, OrbitDBAccessController, useAccessController, getAccessController } from '../src/access-controllers/index.js'
|
||||
import config from './config.js'
|
||||
import pathJoin from '../src/utils/path-join.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const type = 'custom!'
|
||||
|
||||
@ -25,7 +24,7 @@ describe('Add a custom access controller', function () {
|
||||
let orbitdb
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs = await createHelia()
|
||||
orbitdb = await OrbitDB({ ipfs })
|
||||
})
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { existsSync } from 'fs'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { getDatabaseType } from '../src/databases/index.js'
|
||||
import { createOrbitDB, useDatabaseType, Database, KeyValueIndexed } from '../src/index.js'
|
||||
import pathJoin from '../src/utils/path-join.js'
|
||||
import config from './config.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const type = 'custom!'
|
||||
|
||||
@ -27,7 +26,7 @@ describe('Add a custom database type', function () {
|
||||
let orbitdb
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs = await createHelia()
|
||||
orbitdb = await createOrbitDB({ ipfs })
|
||||
})
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { createOrbitDB, Identities, useIdentityProvider } from '../src/index.js'
|
||||
import config from './config.js'
|
||||
// import pathJoin from '../src/utils/path-join.js'
|
||||
import CustomIdentityProvider from './fixtures/providers/custom.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
describe('Add a custom identity provider', function () {
|
||||
this.timeout(5000)
|
||||
@ -12,7 +11,7 @@ describe('Add a custom identity provider', function () {
|
||||
let ipfs
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs = await createHelia()
|
||||
})
|
||||
|
||||
it('creates an identity using an id and default pubkey provider', async () => {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { strictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { createOrbitDB } from '../src/index.js'
|
||||
import config from './config.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
describe('Drop databases', function () {
|
||||
this.timeout(5000)
|
||||
@ -12,7 +11,7 @@ describe('Drop databases', function () {
|
||||
let db
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs' })
|
||||
ipfs = await createHelia()
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { strictEqual } from 'assert'
|
||||
// import mapSeries from 'p-each-series'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { rimraf } from 'rimraf'
|
||||
import OrbitDB from '../src/orbitdb.js'
|
||||
import config from './config.js'
|
||||
import connectPeers from './utils/connect-nodes.js'
|
||||
import waitFor from './utils/wait-for.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const dbPath1 = './orbitdb/tests/multiple-databases/1'
|
||||
const dbPath2 = './orbitdb/tests/multiple-databases/2'
|
||||
@ -50,8 +49,7 @@ describe('orbitdb - Multiple Databases', function () {
|
||||
|
||||
// Create two IPFS instances and two OrbitDB instances (2 nodes/peers)
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
console.log('Peers connected')
|
||||
orbitdb1 = await OrbitDB({ ipfs: ipfs1, id: 'user1', directory: dbPath1 })
|
||||
|
@ -2,12 +2,11 @@ import { deepStrictEqual, strictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { createOrbitDB, isValidAddress, LevelStorage } from '../src/index.js'
|
||||
import KeyValueIndexed from '../src/databases/keyvalue-indexed.js'
|
||||
import config from './config.js'
|
||||
import connectPeers from './utils/connect-nodes.js'
|
||||
import waitFor from './utils/wait-for.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
describe('Open databases', function () {
|
||||
this.timeout(5000)
|
||||
@ -16,8 +15,7 @@ describe('Open databases', function () {
|
||||
let orbitdb1, orbitdb2
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
})
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { deepStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { createOrbitDB } from '../src/index.js'
|
||||
import config from './config.js'
|
||||
import connectPeers from './utils/connect-nodes.js'
|
||||
import waitFor from './utils/wait-for.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
describe('Replicating databases', function () {
|
||||
this.timeout(10000)
|
||||
@ -13,22 +12,26 @@ describe('Replicating databases', function () {
|
||||
let orbitdb1, orbitdb2
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
ipfs1 = await createHelia({ directory: './ipfs1' })
|
||||
ipfs2 = await createHelia({ directory: './ipfs2' })
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
orbitdb1 = await createOrbitDB({ ipfs: ipfs1, id: 'user1', directory: './orbitdb1' })
|
||||
orbitdb2 = await createOrbitDB({ ipfs: ipfs2, id: 'user2', directory: './orbitdb2' })
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
await rimraf('./ipfs1')
|
||||
await rimraf('./ipfs2')
|
||||
await orbitdb1.stop()
|
||||
await orbitdb2.stop()
|
||||
await ipfs1.blockstore.child.child.close()
|
||||
await ipfs2.blockstore.child.child.close()
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
|
||||
await rimraf('./orbitdb1')
|
||||
await rimraf('./orbitdb2')
|
||||
await rimraf('./ipfs1')
|
||||
await rimraf('./ipfs2')
|
||||
})
|
||||
|
||||
describe('replicating a database', () => {
|
||||
@ -42,7 +45,7 @@ describe('Replicating databases', function () {
|
||||
let db1, db2
|
||||
|
||||
before(async () => {
|
||||
db1 = await orbitdb1.open('helloworld')
|
||||
db1 = await orbitdb1.open('helloworld', { referencesCount: 0 })
|
||||
|
||||
console.time('write')
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
@ -51,8 +54,7 @@ describe('Replicating databases', function () {
|
||||
console.timeEnd('write')
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await db1.close()
|
||||
afterEach(async () => {
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
@ -99,5 +101,67 @@ describe('Replicating databases', function () {
|
||||
|
||||
console.log('events:', amount)
|
||||
})
|
||||
|
||||
it('returns all entries in the replicated database after recreating orbitdb/ipfs instances', async () => {
|
||||
console.time('replicate')
|
||||
|
||||
let replicated = false
|
||||
|
||||
const onJoin = async (peerId, heads) => {
|
||||
replicated = true
|
||||
}
|
||||
|
||||
const onError = (err) => {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
db2.events.on('join', onJoin)
|
||||
db2.events.on('error', onError)
|
||||
db1.events.on('error', onError)
|
||||
|
||||
await waitFor(() => replicated, () => true)
|
||||
|
||||
console.time('query 1')
|
||||
const eventsFromDb2 = []
|
||||
for await (const event of db2.iterator()) {
|
||||
eventsFromDb2.unshift(event)
|
||||
}
|
||||
console.timeEnd('query 1')
|
||||
|
||||
console.timeEnd('replicate')
|
||||
|
||||
deepStrictEqual(eventsFromDb2.map(e => e.value), expected)
|
||||
|
||||
await orbitdb1.stop()
|
||||
await orbitdb2.stop()
|
||||
await ipfs1.blockstore.child.child.close()
|
||||
await ipfs2.blockstore.child.child.close()
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
|
||||
ipfs1 = await createHelia({ directory: './ipfs1' })
|
||||
ipfs2 = await createHelia({ directory: './ipfs2' })
|
||||
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
orbitdb1 = await createOrbitDB({ ipfs: ipfs1, id: 'user1', directory: './orbitdb1' })
|
||||
orbitdb2 = await createOrbitDB({ ipfs: ipfs2, id: 'user2', directory: './orbitdb2' })
|
||||
|
||||
db1 = await orbitdb1.open('helloworld', { referencesCount: 0 })
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
console.time('query 2')
|
||||
const eventsFromDb1 = []
|
||||
for await (const event of db1.iterator()) {
|
||||
eventsFromDb1.unshift(event)
|
||||
}
|
||||
console.timeEnd('query 2')
|
||||
|
||||
deepStrictEqual(eventsFromDb1.map(e => e.value), expected)
|
||||
|
||||
console.log('events:', amount)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { strictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import path from 'path'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import OrbitDB from '../src/orbitdb.js'
|
||||
import config from './config.js'
|
||||
import waitFor from './utils/wait-for.js'
|
||||
import connectPeers from './utils/connect-nodes.js'
|
||||
import IPFSAccessController from '../src/access-controllers/ipfs.js'
|
||||
import OrbitDBAccessController from '../src/access-controllers/orbitdb.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const dbPath = './orbitdb/tests/write-permissions'
|
||||
|
||||
@ -16,10 +15,10 @@ describe('Write Permissions', function () {
|
||||
|
||||
let ipfs1, ipfs2
|
||||
let orbitdb1, orbitdb2
|
||||
let db1, db2
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
orbitdb1 = await OrbitDB({ ipfs: ipfs1, id: 'user1', directory: path.join(dbPath, '1') })
|
||||
@ -48,6 +47,14 @@ describe('Write Permissions', function () {
|
||||
await rimraf('./ipfs2')
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await db1.drop()
|
||||
await db1.close()
|
||||
|
||||
await db2.drop()
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
it('throws an error if another peer writes to a log with default write access', async () => {
|
||||
let err
|
||||
let connected = false
|
||||
@ -56,8 +63,8 @@ describe('Write Permissions', function () {
|
||||
connected = true
|
||||
}
|
||||
|
||||
const db1 = await orbitdb1.open('write-test')
|
||||
const db2 = await orbitdb2.open(db1.address)
|
||||
db1 = await orbitdb1.open('write-test-1')
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
|
||||
@ -72,9 +79,6 @@ describe('Write Permissions', function () {
|
||||
}
|
||||
|
||||
strictEqual(err, `Error: Could not append entry:\nKey "${db2.identity.hash}" is not allowed to write to the log`)
|
||||
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
it('allows anyone to write to the log', async () => {
|
||||
@ -89,8 +93,8 @@ describe('Write Permissions', function () {
|
||||
++updateCount
|
||||
}
|
||||
|
||||
const db1 = await orbitdb1.open('write-test', { AccessController: IPFSAccessController({ write: ['*'] }) })
|
||||
const db2 = await orbitdb2.open(db1.address)
|
||||
db1 = await orbitdb1.open('write-test-2', { AccessController: IPFSAccessController({ write: ['*'] }) })
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
@ -103,9 +107,6 @@ describe('Write Permissions', function () {
|
||||
await waitFor(() => updateCount === 2, () => true)
|
||||
|
||||
strictEqual((await db1.all()).length, (await db2.all()).length)
|
||||
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
it('allows specific peers to write to the log', async () => {
|
||||
@ -129,8 +130,8 @@ describe('Write Permissions', function () {
|
||||
++updateCount
|
||||
}
|
||||
|
||||
const db1 = await orbitdb1.open('write-test', options)
|
||||
const db2 = await orbitdb2.open(db1.address)
|
||||
db1 = await orbitdb1.open('write-test-3', options)
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
@ -143,9 +144,6 @@ describe('Write Permissions', function () {
|
||||
await waitFor(() => updateCount === 2, () => true)
|
||||
|
||||
strictEqual((await db1.all()).length, (await db2.all()).length)
|
||||
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
it('throws an error if peer does not have write access', async () => {
|
||||
@ -164,8 +162,8 @@ describe('Write Permissions', function () {
|
||||
connected = true
|
||||
}
|
||||
|
||||
const db1 = await orbitdb1.open('write-test', options)
|
||||
const db2 = await orbitdb2.open(db1.address)
|
||||
db1 = await orbitdb1.open('write-test-4', options)
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
|
||||
@ -180,9 +178,6 @@ describe('Write Permissions', function () {
|
||||
}
|
||||
|
||||
strictEqual(err, `Error: Could not append entry:\nKey "${db2.identity.hash}" is not allowed to write to the log`)
|
||||
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
it('uses an OrbitDB access controller to manage access - one writer', async () => {
|
||||
@ -197,8 +192,8 @@ describe('Write Permissions', function () {
|
||||
++updateCount
|
||||
}
|
||||
|
||||
const db1 = await orbitdb1.open('write-test', { AccessController: OrbitDBAccessController() })
|
||||
const db2 = await orbitdb2.open(db1.address)
|
||||
db1 = await orbitdb1.open('write-test-5', { AccessController: OrbitDBAccessController() })
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
@ -221,9 +216,6 @@ describe('Write Permissions', function () {
|
||||
|
||||
notStrictEqual(err, undefined)
|
||||
strictEqual(err.toString().endsWith('is not allowed to write to the log'), true)
|
||||
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
it('uses an OrbitDB access controller to manage access - two writers', async () => {
|
||||
@ -243,8 +235,8 @@ describe('Write Permissions', function () {
|
||||
accessUpdated = true
|
||||
}
|
||||
|
||||
const db1 = await orbitdb1.open('write-test', { AccessController: OrbitDBAccessController() })
|
||||
const db2 = await orbitdb2.open(db1.address)
|
||||
db1 = await orbitdb1.open('write-test', { AccessController: OrbitDBAccessController() })
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
@ -263,14 +255,12 @@ describe('Write Permissions', function () {
|
||||
await waitFor(() => updateCount === 2, () => true)
|
||||
|
||||
strictEqual((await db1.all()).length, (await db2.all()).length)
|
||||
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
it('OrbitDB access controller address is deterministic', async () => {
|
||||
let connected = false
|
||||
let updateCount = 0
|
||||
let closed = false
|
||||
|
||||
const onConnected = async (peerId, heads) => {
|
||||
connected = true
|
||||
@ -280,13 +270,20 @@ describe('Write Permissions', function () {
|
||||
++updateCount
|
||||
}
|
||||
|
||||
let db1 = await orbitdb1.open('write-test', { AccessController: OrbitDBAccessController() })
|
||||
let db2 = await orbitdb2.open(db1.address)
|
||||
const onClose = async () => {
|
||||
closed = true
|
||||
}
|
||||
|
||||
const dbName = 'write-test-7'
|
||||
|
||||
db1 = await orbitdb1.open(dbName, { AccessController: OrbitDBAccessController() })
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
const addr = db1.address
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
db2.events.on('close', onClose)
|
||||
|
||||
await waitFor(() => connected, () => true)
|
||||
|
||||
@ -299,7 +296,9 @@ describe('Write Permissions', function () {
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
|
||||
db1 = await orbitdb1.open('write-test', { AccessController: OrbitDBAccessController() })
|
||||
await waitFor(() => closed, () => true)
|
||||
|
||||
db1 = await orbitdb1.open(dbName, { AccessController: OrbitDBAccessController() })
|
||||
db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
strictEqual(db1.address, addr)
|
||||
|
@ -2,10 +2,9 @@ import { strictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { createOrbitDB, isIdentity } from '../src/index.js'
|
||||
import config from './config.js'
|
||||
import connectPeers from './utils/connect-nodes.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const isBrowser = () => typeof window !== 'undefined'
|
||||
|
||||
@ -16,8 +15,7 @@ describe('OrbitDB', function () {
|
||||
let orbitdb1
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
})
|
||||
|
||||
@ -51,8 +49,8 @@ describe('OrbitDB', function () {
|
||||
})
|
||||
|
||||
it('has the IPFS instance given as a parameter', async () => {
|
||||
const { id: expectedId } = await ipfs1.id()
|
||||
const { id: resultId } = await orbitdb1.ipfs.id()
|
||||
const { id: expectedId } = ipfs1.libp2p.peerId
|
||||
const { id: resultId } = orbitdb1.ipfs.libp2p.peerId
|
||||
strictEqual(expectedId, resultId)
|
||||
})
|
||||
|
||||
@ -109,7 +107,7 @@ describe('OrbitDB', function () {
|
||||
})
|
||||
|
||||
it('has a peerId that matches the IPFS id', async () => {
|
||||
const { id } = await ipfs1.id()
|
||||
const id = ipfs1.libp2p.peerId
|
||||
strictEqual(orbitdb1.peerId, id)
|
||||
})
|
||||
|
||||
@ -143,8 +141,8 @@ describe('OrbitDB', function () {
|
||||
})
|
||||
|
||||
it('has the IPFS instance given as a parameter', async () => {
|
||||
const { id: expectedId } = await ipfs1.id()
|
||||
const { id: resultId } = await orbitdb1.ipfs.id()
|
||||
const { id: expectedId } = ipfs1.libp2p.peerId
|
||||
const { id: resultId } = orbitdb1.ipfs.libp2p.peerId
|
||||
strictEqual(expectedId, resultId)
|
||||
})
|
||||
|
||||
@ -201,7 +199,7 @@ describe('OrbitDB', function () {
|
||||
})
|
||||
|
||||
it('has a peerId that matches the IPFS id', async () => {
|
||||
const { id } = await ipfs1.id()
|
||||
const id = ipfs1.libp2p.peerId
|
||||
strictEqual(orbitdb1.peerId, id)
|
||||
})
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { strictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import { Log, Identities, KeyStore } from '../src/index.js'
|
||||
import { IPFSBlockStorage, MemoryStorage, LRUStorage, ComposedStorage } from '../src/storage/index.js'
|
||||
import config from './config.js'
|
||||
import testKeysPath from './fixtures/test-keys-path.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -17,8 +16,7 @@ describe('Storages', function () {
|
||||
let testIdentity
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
|
||||
ipfs = await createHelia()
|
||||
await copy(testKeysPath, keysPath)
|
||||
keystore = await KeyStore({ path: keysPath })
|
||||
|
||||
|
54
test/storage/ipfs-block.test.js
Normal file
54
test/storage/ipfs-block.test.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { strictEqual, notStrictEqual } from 'assert'
|
||||
import * as Block from 'multiformats/block'
|
||||
import * as dagCbor from '@ipld/dag-cbor'
|
||||
import { sha256 } from 'multiformats/hashes/sha2'
|
||||
import { base58btc } from 'multiformats/bases/base58'
|
||||
import createHelia from '../utils/create-helia.js'
|
||||
import IPFSBlockStorage from '../../src/storage/ipfs-block.js'
|
||||
|
||||
describe('IPFSBlockStorage', function () {
|
||||
const codec = dagCbor
|
||||
const hasher = sha256
|
||||
|
||||
let ipfs, storage
|
||||
|
||||
beforeEach(async () => {
|
||||
ipfs = await createHelia()
|
||||
const pin = true
|
||||
const timeout = 1000
|
||||
|
||||
storage = await IPFSBlockStorage({ ipfs, pin, timeout })
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await ipfs.stop()
|
||||
})
|
||||
|
||||
it('gets a block', async () => {
|
||||
const expected = 'hello world'
|
||||
const block = await Block.encode({ value: expected, codec, hasher })
|
||||
const cid = block.cid.toString(base58btc)
|
||||
|
||||
await storage.put(cid, 'hello world')
|
||||
const actual = await storage.get(cid)
|
||||
|
||||
strictEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('throws an error if a block does not exist', async () => {
|
||||
const value = 'i don\'t exist'
|
||||
const block = await Block.encode({ value, codec, hasher })
|
||||
const cid = block.cid.toString(base58btc)
|
||||
|
||||
let err = null
|
||||
|
||||
try {
|
||||
await storage.get(cid)
|
||||
} catch (error) {
|
||||
err = error
|
||||
}
|
||||
|
||||
notStrictEqual(err, null)
|
||||
strictEqual(err.message, 'All promises were rejected')
|
||||
})
|
||||
})
|
@ -1,16 +1,15 @@
|
||||
import { deepStrictEqual, strictEqual, notStrictEqual } from 'assert'
|
||||
import { rimraf } from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import Sync from '../src/sync.js'
|
||||
import { Log, Entry, Identities, KeyStore } from '../src/index.js'
|
||||
import config from './config.js'
|
||||
import connectPeers from './utils/connect-nodes.js'
|
||||
import waitFor from './utils/wait-for.js'
|
||||
import testKeysPath from './fixtures/test-keys-path.js'
|
||||
import LRUStorage from '../src/storage/lru.js'
|
||||
import IPFSBlockStorage from '../src/storage/ipfs-block.js'
|
||||
import ComposedStorage from '../src/storage/composed.js'
|
||||
import createHelia from './utils/create-helia.js'
|
||||
|
||||
const keysPath = './testkeys'
|
||||
|
||||
@ -24,14 +23,10 @@ describe('Sync protocol', function () {
|
||||
let peerId1, peerId2
|
||||
|
||||
before(async () => {
|
||||
await rimraf('./ipfs1')
|
||||
await rimraf('./ipfs2')
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
|
||||
peerId1 = (await ipfs1.id()).id
|
||||
peerId2 = (await ipfs2.id()).id
|
||||
peerId1 = ipfs1.libp2p.peerId
|
||||
peerId2 = ipfs2.libp2p.peerId
|
||||
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
@ -55,9 +50,10 @@ describe('Sync protocol', function () {
|
||||
|
||||
describe('Creating an instance', () => {
|
||||
let sync
|
||||
let log
|
||||
|
||||
before(async () => {
|
||||
const log = await Log(testIdentity1)
|
||||
log = await Log(testIdentity1)
|
||||
sync = await Sync({ ipfs: ipfs1, log })
|
||||
})
|
||||
|
||||
@ -65,6 +61,9 @@ describe('Sync protocol', function () {
|
||||
if (sync) {
|
||||
await sync.stop()
|
||||
}
|
||||
if (log) {
|
||||
await log.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('creates an instance', async () => {
|
||||
@ -137,8 +136,8 @@ describe('Sync protocol', function () {
|
||||
await IPFSBlockStorage({ ipfs: ipfs2, pin: true })
|
||||
)
|
||||
|
||||
log1 = await Log(testIdentity1, { logId: 'synclog1', entryStorage: entryStorage1 })
|
||||
log2 = await Log(testIdentity2, { logId: 'synclog1', entryStorage: entryStorage2 })
|
||||
log1 = await Log(testIdentity1, { logId: 'synclog111', entryStorage: entryStorage1 })
|
||||
log2 = await Log(testIdentity2, { logId: 'synclog111', entryStorage: entryStorage2 })
|
||||
|
||||
const onSynced = async (bytes) => {
|
||||
const entry = await Entry.decode(bytes)
|
||||
@ -160,8 +159,7 @@ describe('Sync protocol', function () {
|
||||
sync1.events.on('join', onJoin)
|
||||
sync2.events.on('join', onJoin)
|
||||
|
||||
await waitFor(() => joinEventFired, () => true)
|
||||
await waitFor(() => syncedEventFired, () => true)
|
||||
await waitFor(() => joinEventFired && syncedEventFired, () => true)
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
@ -171,6 +169,12 @@ describe('Sync protocol', function () {
|
||||
if (sync2) {
|
||||
await sync2.stop()
|
||||
}
|
||||
if (log1) {
|
||||
await log1.close()
|
||||
}
|
||||
if (log2) {
|
||||
await log2.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('syncs the head', async () => {
|
||||
@ -230,6 +234,12 @@ describe('Sync protocol', function () {
|
||||
if (sync2) {
|
||||
await sync2.stop()
|
||||
}
|
||||
if (log1) {
|
||||
await log1.close()
|
||||
}
|
||||
if (log2) {
|
||||
await log2.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('is eventually consistent', async () => {
|
||||
@ -299,6 +309,12 @@ describe('Sync protocol', function () {
|
||||
if (sync2) {
|
||||
await sync2.stop()
|
||||
}
|
||||
if (log1) {
|
||||
await log1.close()
|
||||
}
|
||||
if (log2) {
|
||||
await log2.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('starts syncing', async () => {
|
||||
@ -364,6 +380,12 @@ describe('Sync protocol', function () {
|
||||
if (sync2) {
|
||||
await sync2.stop()
|
||||
}
|
||||
if (log1) {
|
||||
await log1.close()
|
||||
}
|
||||
if (log2) {
|
||||
await log2.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('starts syncing', async () => {
|
||||
@ -454,6 +476,12 @@ describe('Sync protocol', function () {
|
||||
if (sync2) {
|
||||
await sync2.stop()
|
||||
}
|
||||
if (log1) {
|
||||
await log1.close()
|
||||
}
|
||||
if (log2) {
|
||||
await log2.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('restarts syncing', async () => {
|
||||
@ -493,7 +521,7 @@ describe('Sync protocol', function () {
|
||||
const onSynced = async (bytes) => {
|
||||
syncedHead = await Entry.decode(bytes)
|
||||
if (expectedEntry) {
|
||||
syncedEventFired = expectedEntry.hash === syncedHead.hash
|
||||
syncedEventFired = expectedEntry ? expectedEntry.hash === syncedHead.hash : false
|
||||
}
|
||||
}
|
||||
|
||||
@ -520,6 +548,12 @@ describe('Sync protocol', function () {
|
||||
if (sync2) {
|
||||
await sync2.stop()
|
||||
}
|
||||
if (log1) {
|
||||
await log1.close()
|
||||
}
|
||||
if (log2) {
|
||||
await log2.close()
|
||||
}
|
||||
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
@ -547,6 +581,7 @@ describe('Sync protocol', function () {
|
||||
|
||||
describe('Events', () => {
|
||||
let sync1, sync2
|
||||
let log1, log2
|
||||
let joinEventFired = false
|
||||
let leaveEventFired = false
|
||||
let receivedHeads = []
|
||||
@ -554,19 +589,15 @@ describe('Sync protocol', function () {
|
||||
let leavingPeerId
|
||||
|
||||
before(async () => {
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
|
||||
peerId1 = (await ipfs1.id()).id
|
||||
peerId2 = (await ipfs2.id()).id
|
||||
peerId1 = ipfs1.libp2p.peerId
|
||||
peerId2 = ipfs2.libp2p.peerId
|
||||
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
const log1 = await Log(testIdentity1, { logId: 'synclog3' })
|
||||
const log2 = await Log(testIdentity2, { logId: 'synclog3' })
|
||||
log1 = await Log(testIdentity1, { logId: 'synclog3' })
|
||||
log2 = await Log(testIdentity2, { logId: 'synclog3' })
|
||||
|
||||
const onJoin = (peerId, heads) => {
|
||||
joinEventFired = true
|
||||
@ -600,6 +631,14 @@ describe('Sync protocol', function () {
|
||||
if (sync2) {
|
||||
await sync2.stop()
|
||||
}
|
||||
if (log1) {
|
||||
await log1.close()
|
||||
}
|
||||
if (log2) {
|
||||
await log2.close()
|
||||
}
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
})
|
||||
|
||||
it('emits \'join\' event when a peer starts syncing', () => {
|
||||
@ -612,12 +651,12 @@ describe('Sync protocol', function () {
|
||||
})
|
||||
|
||||
it('the peerId passed by the \'join\' event is the expected peer ID', async () => {
|
||||
const { id } = await ipfs2.id()
|
||||
const id = ipfs2.libp2p.peerId
|
||||
strictEqual(String(joiningPeerId), String(id))
|
||||
})
|
||||
|
||||
it('the peerId passed by the \'leave\' event is the expected peer ID', async () => {
|
||||
const { id } = await ipfs2.id()
|
||||
const id = ipfs2.libp2p.peerId
|
||||
strictEqual(String(leavingPeerId), String(id))
|
||||
})
|
||||
})
|
||||
@ -629,14 +668,10 @@ describe('Sync protocol', function () {
|
||||
const timeoutTime = 1 // 1 millisecond
|
||||
|
||||
before(async () => {
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
|
||||
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
|
||||
peerId1 = (await ipfs1.id()).id
|
||||
peerId2 = (await ipfs2.id()).id
|
||||
peerId1 = ipfs1.libp2p.peerId
|
||||
peerId2 = ipfs2.libp2p.peerId
|
||||
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
@ -651,13 +686,22 @@ describe('Sync protocol', function () {
|
||||
if (sync2) {
|
||||
await sync2.stop()
|
||||
}
|
||||
if (log1) {
|
||||
await log1.close()
|
||||
}
|
||||
if (log2) {
|
||||
await log2.close()
|
||||
}
|
||||
|
||||
await ipfs1.stop()
|
||||
await ipfs2.stop()
|
||||
})
|
||||
|
||||
it('emits an error when connecting to peer was cancelled due to timeout', async () => {
|
||||
let err = null
|
||||
|
||||
const onError = (error) => {
|
||||
err = error
|
||||
(!err) && (err = error)
|
||||
}
|
||||
|
||||
sync1 = await Sync({ ipfs: ipfs1, log: log1, timeout: timeoutTime })
|
||||
@ -674,7 +718,7 @@ describe('Sync protocol', function () {
|
||||
|
||||
notStrictEqual(err, null)
|
||||
strictEqual(err.type, 'aborted')
|
||||
strictEqual(err.message, 'The operation was aborted')
|
||||
strictEqual(err.message, 'Read aborted')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,21 +1,30 @@
|
||||
'use strict'
|
||||
import { multiaddr } from '@multiformats/multiaddr'
|
||||
import { WebRTC } from '@multiformats/multiaddr-matcher'
|
||||
import waitFor from './wait-for.js'
|
||||
|
||||
const defaultFilter = () => true
|
||||
|
||||
const isBrowser = () => typeof window !== 'undefined'
|
||||
|
||||
const connectIpfsNodes = async (ipfs1, ipfs2, options = {
|
||||
filter: defaultFilter
|
||||
}) => {
|
||||
const id1 = await ipfs1.id()
|
||||
const id2 = await ipfs2.id()
|
||||
if (isBrowser()) {
|
||||
const relayId = '12D3KooWAJjbRkp8FPF5MKgMU53aUTxWkqvDrs4zc1VMbwRwfsbE'
|
||||
|
||||
const addresses1 = id1.addresses.filter(options.filter)
|
||||
const addresses2 = id2.addresses.filter(options.filter)
|
||||
await ipfs1.libp2p.dial(multiaddr(`/ip4/127.0.0.1/tcp/12345/ws/p2p/${relayId}`))
|
||||
|
||||
for (const a2 of addresses2) {
|
||||
await ipfs1.swarm.connect(a2)
|
||||
}
|
||||
for (const a1 of addresses1) {
|
||||
await ipfs2.swarm.connect(a1)
|
||||
let address1
|
||||
|
||||
await waitFor(() => {
|
||||
address1 = ipfs1.libp2p.getMultiaddrs().filter(ma => WebRTC.matches(ma)).pop()
|
||||
return address1 != null
|
||||
}, () => true)
|
||||
|
||||
await ipfs2.libp2p.dial(address1)
|
||||
} else {
|
||||
await ipfs2.libp2p.peerStore.save(ipfs1.libp2p.peerId, { multiaddrs: ipfs1.libp2p.getMultiaddrs().filter(options.filter) })
|
||||
await ipfs2.libp2p.dial(ipfs1.libp2p.peerId)
|
||||
}
|
||||
}
|
||||
|
||||
|
82
test/utils/create-helia.js
Normal file
82
test/utils/create-helia.js
Normal file
@ -0,0 +1,82 @@
|
||||
import { createHelia } from 'helia'
|
||||
import { bitswap } from '@helia/block-brokers'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { MemoryBlockstore } from 'blockstore-core'
|
||||
import { LevelBlockstore } from 'blockstore-level'
|
||||
import { identify } from '@libp2p/identify'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { webRTC } from '@libp2p/webrtc'
|
||||
import { all } from '@libp2p/websockets/filters'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
||||
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||
|
||||
const isBrowser = () => typeof window !== 'undefined'
|
||||
|
||||
const Libp2pOptions = {
|
||||
addresses: {
|
||||
listen: ['/ip4/0.0.0.0/tcp/0/ws']
|
||||
},
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: all
|
||||
}),
|
||||
webRTC(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 1
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
connectionGater: {
|
||||
denyDialMultiaddr: () => false
|
||||
},
|
||||
services: {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroPeers: true })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A basic Libp2p configuration for browser nodes.
|
||||
*/
|
||||
const Libp2pBrowserOptions = {
|
||||
addresses: {
|
||||
listen: ['/webrtc']
|
||||
},
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: all
|
||||
}),
|
||||
webRTC(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 1
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
connectionGater: {
|
||||
denyDialMultiaddr: () => false
|
||||
},
|
||||
services: {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroPeers: true })
|
||||
}
|
||||
}
|
||||
|
||||
export default async ({ directory } = {}) => {
|
||||
const options = isBrowser() ? Libp2pBrowserOptions : Libp2pOptions
|
||||
|
||||
const libp2p = await createLibp2p({ ...options })
|
||||
|
||||
const blockstore = directory ? new LevelBlockstore(`${directory}/blocks`) : new MemoryBlockstore()
|
||||
|
||||
const heliaOptions = {
|
||||
blockstore,
|
||||
libp2p,
|
||||
blockBrokers: [bitswap()]
|
||||
}
|
||||
|
||||
return createHelia({ ...heliaOptions })
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const getIpfsPeerId = async (ipfs) => {
|
||||
const peerId = await ipfs.id()
|
||||
return peerId.id
|
||||
}
|
||||
|
||||
export default getIpfsPeerId
|
56
test/utils/relay.js
Normal file
56
test/utils/relay.js
Normal file
@ -0,0 +1,56 @@
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { circuitRelayServer } from '@libp2p/circuit-relay-v2'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import * as filters from '@libp2p/websockets/filters'
|
||||
import { identify } from '@libp2p/identify'
|
||||
import { createFromPrivKey } from '@libp2p/peer-id-factory'
|
||||
import { unmarshalPrivateKey } from '@libp2p/crypto/keys'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
|
||||
// output of: console.log(server.peerId.privateKey.toString('hex'))
|
||||
const relayPrivKey = '08011240821cb6bc3d4547fcccb513e82e4d718089f8a166b23ffcd4a436754b6b0774cf07447d1693cd10ce11ef950d7517bad6e9472b41a927cd17fc3fb23f8c70cd99'
|
||||
// the peer id of the above key
|
||||
// const relayId = '12D3KooWAJjbRkp8FPF5MKgMU53aUTxWkqvDrs4zc1VMbwRwfsbE'
|
||||
|
||||
const encoded = uint8ArrayFromString(relayPrivKey, 'hex')
|
||||
const privateKey = await unmarshalPrivateKey(encoded)
|
||||
const peerId = await createFromPrivKey(privateKey)
|
||||
|
||||
const server = await createLibp2p({
|
||||
peerId,
|
||||
addresses: {
|
||||
listen: ['/ip4/0.0.0.0/tcp/12345/ws']
|
||||
},
|
||||
transports: [
|
||||
webSockets({
|
||||
filter: filters.all
|
||||
})
|
||||
],
|
||||
connectionEncryption: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
services: {
|
||||
identify: identify(),
|
||||
relay: circuitRelayServer({
|
||||
reservations: {
|
||||
maxReservations: 5000,
|
||||
reservationTtl: 1000,
|
||||
defaultDataLimit: BigInt(1024 * 1024 * 1024)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
server.addEventListener('peer:connect', async event => {
|
||||
console.log('peer:connect', event.detail)
|
||||
})
|
||||
|
||||
server.addEventListener('peer:disconnect', async event => {
|
||||
console.log('peer:disconnect', event.detail)
|
||||
server.peerStore.delete(event.detail)
|
||||
})
|
||||
|
||||
console.log(server.peerId.toString())
|
||||
console.log('p2p addr: ', server.getMultiaddrs().map((ma) => ma.toString()))
|
||||
// generates a deterministic address: /ip4/127.0.0.1/tcp/33519/ws/p2p/12D3KooWAJjbRkp8FPF5MKgMU53aUTxWkqvDrs4zc1VMbwRwfsbE
|
@ -4,7 +4,7 @@ const waitForPeers = (ipfs, peersToWait, topic) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const peers = await ipfs.pubsub.peers(topic)
|
||||
const peers = await ipfs.libp2p.services.pubsub.getPeers(topic)
|
||||
const peerIds = peers.map(peer => peer.toString())
|
||||
const peerIdsToWait = peersToWait.map(peer => peer.toString())
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user