Merge pull request #1108 from orbitdb/helia

Integrate Helia
This commit is contained in:
Hayden Young 2024-01-20 00:00:31 +08:00 committed by GitHub
commit 64e36df5a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 11808 additions and 19307 deletions

View File

@ -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
View File

@ -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/

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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`)

View File

@ -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`)

View File

@ -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')

View File

@ -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: {

View File

@ -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 })

View File

@ -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

View File

@ -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).

View File

@ -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

View File

@ -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' })

View File

@ -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
})

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"
]

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}
}

View File

@ -5,5 +5,6 @@
"exit": true,
"bail": false,
"slow": 1000,
"exclude": ["test/browser/**/*.js"]
"exclude": ["test/browser/**/*.js", "test/utils/relay.js"],
"timeout": 30000
}

View File

@ -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' })

View File

@ -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' })

View File

@ -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
}
}
}
}
}

View File

@ -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' })

View File

@ -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 })

View File

@ -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 })

View File

@ -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 })

View File

@ -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 })

View File

@ -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 })

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 })
})

View File

@ -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)

View File

@ -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 })
})

View File

@ -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 })
})

View File

@ -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 () => {

View File

@ -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 () => {

View File

@ -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 })

View File

@ -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)
})

View File

@ -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)
})
})
})

View File

@ -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)

View File

@ -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)
})

View File

@ -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 })

View 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')
})
})

View File

@ -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')
})
})
})

View File

@ -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)
}
}

View 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 })
}

View File

@ -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
View 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

View File

@ -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())