Skip to content

Commit 0f14947

Browse files
authored
Merge pull request #236 from dozyio/feat-globaldb-updates
feat: update globaldb example
2 parents cbaacc1 + 431e8f3 commit 0f14947

File tree

5 files changed

+593
-221
lines changed

5 files changed

+593
-221
lines changed

examples/globaldb/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data/

examples/globaldb/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Example GlobalDB CLI
2+
3+
This repository contains an example command-line interface (CLI) tool for joining a global, permissionless, CRDT-based database using CRDTs, IPFS & libp2p.
4+
5+
## Features
6+
7+
- Join a global CRDT-based database with IPFS.
8+
- Store and retrieve key-value pairs in a distributed datastore.
9+
- Subscribe to a pubsub topic to receive updates in real-time.
10+
- Bootstrap and connect to other peers in the network.
11+
- Operate in daemon mode for continuous operation.
12+
- Simple CLI commands to interact with the database.
13+
14+
## Building
15+
16+
To build the example GlobalDB CLI, clone this repository and build the binary:
17+
18+
```bash
19+
git clone https://github.com/ipfs/go-ds-crdt
20+
cd examples/globaldb
21+
go build -o globaldb
22+
```
23+
24+
Ensure that you have Go installed and set up in your environment.
25+
26+
## Usage
27+
28+
Run the CLI with:
29+
30+
```bash
31+
./globaldb [options]
32+
```
33+
34+
### Options
35+
36+
- `-daemon`: Run in daemon mode.
37+
- `-datadir`: Specify a directory for storing the local database and keys.
38+
39+
### Commands
40+
41+
Once running, the CLI provides the following interactive commands:
42+
43+
- `list`: List all items in the store.
44+
- `get <key>`: Retrieve the value for a specified key.
45+
- `put <key> <value>`: Store a value with a specified key.
46+
- `connect <multiaddr>`: Connect to a peer using its multiaddress.
47+
- `debug <on/off/peers/subs>`: Enable or disable debug logging, list connected peers, show pubsub subscribers
48+
- `exit`: Quit the CLI.
49+
50+
### Example
51+
52+
Starting the CLI:
53+
54+
```bash
55+
./globaldb -datadir /path/to/data
56+
```
57+
58+
Interacting with the database:
59+
60+
```plaintext
61+
> put exampleKey exampleValue
62+
> get exampleKey
63+
[exampleKey] -> exampleValue
64+
> list
65+
[exampleKey] -> exampleValue
66+
> connect /ip4/192.168.1.3/tcp/33123/p2p/12D3KooWEkgRTTXGsmFLBembMHxVPDcidJyqFcrqbm9iBE1xhdXq
67+
```
68+
69+
### Daemon Mode
70+
71+
To run in daemon mode, use:
72+
73+
```bash
74+
./globaldb -daemon -datadir /path/to/data
75+
```
76+
77+
The CLI will keep running, periodically reporting the number of connected peers and those subscribed to the crdt topic.
78+
79+
## Technical Details
80+
81+
The GlobalDB CLI leverages the following components:
82+
83+
- **IPFS Lite**: Provides a lightweight IPFS node for peer-to-peer networking.
84+
- **Libp2p PubSub**: Enables decentralized communication using the GossipSub protocol.
85+
- **CRDTs**: Ensure conflict-free synchronization of data across distributed peers.
86+
- **Badger Datastore**: A high-performance datastore for storing key-value pairs.

examples/globaldb/globaldb.go

Lines changed: 125 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ package main
66
import (
77
"bufio"
88
"context"
9+
"flag"
910
"fmt"
10-
"io/ioutil"
1111
"os"
1212
"os/signal"
1313
"path/filepath"
14+
"strconv"
1415
"strings"
1516
"syscall"
1617
"time"
@@ -27,20 +28,31 @@ import (
2728
"github.com/libp2p/go-libp2p/core/peer"
2829

2930
ipfslite "github.com/hsanjuan/ipfs-lite"
30-
"github.com/mitchellh/go-homedir"
3131

3232
multiaddr "github.com/multiformats/go-multiaddr"
3333
)
3434

3535
var (
3636
logger = logging.Logger("globaldb")
37-
listen, _ = multiaddr.NewMultiaddr("/ip4/0.0.0.0/tcp/33123")
3837
topicName = "globaldb-example"
3938
netTopic = "globaldb-example-net"
4039
config = "globaldb-example"
4140
)
4241

4342
func main() {
43+
daemonMode := flag.Bool("daemon", false, "Run in daemon mode")
44+
dataDir := flag.String("datadir", "", "Use a custom data directory")
45+
port := flag.String("port", "0", "Specify the TCP port to listen on")
46+
47+
flag.Parse()
48+
49+
if *port != "" {
50+
parsedPort, err := strconv.ParseUint(*port, 10, 32)
51+
if err != nil || parsedPort > 65535 {
52+
logger.Fatal("Specify a valid TCP port")
53+
}
54+
}
55+
4456
// Bootstrappers are using 1024 keys. See:
4557
// https://github.com/ipfs/infra/issues/378
4658
crypto.MinRsaKeyBits = 1024
@@ -49,11 +61,26 @@ func main() {
4961
ctx, cancel := context.WithCancel(context.Background())
5062
defer cancel()
5163

52-
dir, err := homedir.Dir()
53-
if err != nil {
54-
logger.Fatal(err)
64+
data := ""
65+
66+
if dataDir == nil || *dataDir == "" {
67+
dir, err := os.MkdirTemp("", "globaldb-example")
68+
if err != nil {
69+
logger.Fatal(err)
70+
}
71+
defer os.RemoveAll(dir)
72+
data = dir + "/" + config
73+
} else {
74+
// check if the directory exists or create it
75+
_, err := os.Stat(*dataDir)
76+
if os.IsNotExist(err) {
77+
err = os.Mkdir(*dataDir, 0755)
78+
if err != nil {
79+
logger.Fatal(err)
80+
}
81+
}
82+
data = *dataDir + "/" + config
5583
}
56-
data := filepath.Join(dir, config)
5784

5885
store, err := badger.NewDatastore(data, &badger.DefaultOptions)
5986
if err != nil {
@@ -73,14 +100,14 @@ func main() {
73100
if err != nil {
74101
logger.Fatal(err)
75102
}
76-
err = ioutil.WriteFile(keyPath, data, 0400)
103+
err = os.WriteFile(keyPath, data, 0400)
77104
if err != nil {
78105
logger.Fatal(err)
79106
}
80107
} else if err != nil {
81108
logger.Fatal(err)
82109
} else {
83-
key, err := ioutil.ReadFile(keyPath)
110+
key, err := os.ReadFile(keyPath)
84111
if err != nil {
85112
logger.Fatal(err)
86113
}
@@ -95,6 +122,8 @@ func main() {
95122
logger.Fatal(err)
96123
}
97124

125+
listen, _ := multiaddr.NewMultiaddr("/ip4/0.0.0.0/tcp/" + *port)
126+
98127
h, dht, err := ipfslite.SetupLibp2p(
99128
ctx,
100129
priv,
@@ -188,29 +217,26 @@ func main() {
188217

189218
fmt.Printf(`
190219
Peer ID: %s
191-
Listen address: %s
192220
Topic: %s
193221
Data Folder: %s
222+
Listen addresses:
223+
%s
194224
195225
Ready!
196-
197-
Commands:
198-
199-
> list -> list items in the store
200-
> get <key> -> get value for a key
201-
> put <key> <value> -> store value on a key
202-
> exit -> quit
203-
204-
205226
`,
206-
pid, listen, topicName, data,
227+
pid, topicName, data, listenAddrs(h),
207228
)
208229

209-
if len(os.Args) > 1 && os.Args[1] == "daemon" {
230+
if *daemonMode {
210231
fmt.Println("Running in daemon mode")
211232
go func() {
212233
for {
213-
fmt.Printf("%s - %d connected peers\n", time.Now().Format(time.Stamp), len(connectedPeers(h)))
234+
fmt.Printf(
235+
"%s - %d connected peers - %d peers in topic\n",
236+
time.Now().Format(time.Stamp),
237+
len(connectedPeers(h)),
238+
len(topic.ListPeers()),
239+
)
214240
time.Sleep(10 * time.Second)
215241
}
216242
}()
@@ -225,6 +251,22 @@ Commands:
225251
return
226252
}
227253

254+
commands := `
255+
> (l)ist -> list items in the store
256+
> (g)get <key> -> get value for a key
257+
> (p)ut <key> <value> -> store value on a key
258+
> (d)elete <key> -> delete a key
259+
> (c)onnect <multiaddr> -> connect a multiaddr
260+
> print -> Print DAG
261+
> debug <on/off/peers/subs> -> enable/disable debug logging
262+
show connected peers
263+
show pubsub subscribers
264+
> exit -> quit
265+
266+
267+
`
268+
fmt.Printf("%s", commands)
269+
228270
fmt.Printf("> ")
229271
scanner := bufio.NewScanner(os.Stdin)
230272
for scanner.Scan() {
@@ -240,9 +282,15 @@ Commands:
240282
switch cmd {
241283
case "exit", "quit":
242284
return
285+
case "?", "help", "h":
286+
fmt.Printf("%s", commands)
287+
fmt.Printf("> ")
288+
continue
243289
case "debug":
244290
if len(fields) < 2 {
245-
fmt.Println("debug <on/off/peers>")
291+
fmt.Println("debug <on/off/peers/subs>")
292+
fmt.Println("> ")
293+
continue
246294
}
247295
st := fields[1]
248296
switch st {
@@ -261,8 +309,12 @@ Commands:
261309
fmt.Println(a)
262310
}
263311
}
312+
case "subs":
313+
for _, p := range topic.ListPeers() {
314+
fmt.Println(p.String())
315+
}
264316
}
265-
case "list":
317+
case "l", "list":
266318
q := query.Query{}
267319
results, err := crdt.Query(ctx, q)
268320
if err != nil {
@@ -275,10 +327,10 @@ Commands:
275327
}
276328
fmt.Printf("[%s] -> %s\n", r.Key, string(r.Value))
277329
}
278-
case "get":
330+
case "g", "get":
279331
if len(fields) < 2 {
280332
fmt.Println("get <key>")
281-
fmt.Println("> ")
333+
fmt.Printf("> ")
282334
continue
283335
}
284336
k := ds.NewKey(fields[1])
@@ -288,10 +340,10 @@ Commands:
288340
continue
289341
}
290342
fmt.Printf("[%s] -> %s\n", k, string(v))
291-
case "put":
343+
case "p", "put":
292344
if len(fields) < 3 {
293345
fmt.Println("put <key> <value>")
294-
fmt.Println("> ")
346+
fmt.Printf("> ")
295347
continue
296348
}
297349
k := ds.NewKey(fields[1])
@@ -301,6 +353,42 @@ Commands:
301353
printErr(err)
302354
continue
303355
}
356+
case "d", "delete":
357+
if len(fields) < 2 {
358+
fmt.Println("delete <key>")
359+
fmt.Printf("> ")
360+
continue
361+
}
362+
k := ds.NewKey(fields[1])
363+
err := crdt.Delete(ctx, k)
364+
if err != nil {
365+
printErr(err)
366+
continue
367+
}
368+
case "c", "connect":
369+
if len(fields) < 2 {
370+
fmt.Println("connect <mulitaddr>")
371+
fmt.Printf("> ")
372+
continue
373+
}
374+
ma, err := multiaddr.NewMultiaddr(fields[1])
375+
if err != nil {
376+
printErr(err)
377+
continue
378+
}
379+
peerInfo, err := peer.AddrInfoFromP2pAddr(ma)
380+
if err != nil {
381+
printErr(err)
382+
continue
383+
}
384+
h.Peerstore().AddAddr(peerInfo.ID, peerInfo.Addrs[0], 300)
385+
err = h.Connect(ctx, *peerInfo)
386+
if err != nil {
387+
printErr(err)
388+
continue
389+
}
390+
case "print":
391+
crdt.PrintDAG()
304392
}
305393
fmt.Printf("> ")
306394
}
@@ -321,3 +409,12 @@ func connectedPeers(h host.Host) []*peer.AddrInfo {
321409
}
322410
return pinfos
323411
}
412+
413+
func listenAddrs(h host.Host) string {
414+
var addrs []string
415+
for _, c := range h.Addrs() {
416+
ma, _ := multiaddr.NewMultiaddr(c.String() + "/p2p/" + h.ID().String())
417+
addrs = append(addrs, ma.String())
418+
}
419+
return strings.Join(addrs, "\n")
420+
}

0 commit comments

Comments
 (0)