swarm-nl 0.2.0

A library to build custom networking layers for decentralized and distributed applications.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
<!-- <img src="https://github.com/algorealmInc/SwarmNL/blob/c3fe530350ec37755c64b47cba06361d39b3b095/SwarmNL.png" alt="SwarmNL" style="border-radius: 15px !important;"> -->

# SwarmNL

**A library to build custom networking layers for decentralized and distributed applications**

SwarmNL is a library designed for P2P networking in distributed systems. It's lightweight, scalable, and easy to configure, making it perfect for decentralized applications. Powered by [libp2p](https://docs.libp2p.io/), SwarmNL simplifies networking so developers can focus on building.

Visit the deployed Rust docs [here](https://algorealminc.github.io/SwarmNL/swarm-nl/index.html).

<!-- TOC start -->

- [Why SwarmNL?]#why-swarmnl
- [Tutorials and examples]#tutorials-and-examples
- [Research and technicalities]#research-and-technicalities
- [Node configuration]#node-configuration
  - [Example]#example-node-configuration-with-default-settings
  - [Event handling]#event-handling
- [Node communication]#node-communication
  - [Example]#example-communicating-with-the-network-layer
- [Replication]#replication
  - [Key features]#key-features
  - [Example]#example-configuring-and-using-replication
  - [Why use SwarmNL for replication]#why-use-swarmnl-for-replication
- [Sharding]#sharding
  - [Key features]#key-features-1
  - [Example]#example-configuring-and-operating-a-sharded-network
  - [Why use SwarmNL for sharding]#why-use-swarmnl-for-sharding
<!-- TOC end -->

## Why SwarmNL?

SwarmNL makes buiding a peer-to-peer decentralized and distributed networking stack for your application a breeze. With SwarmNL, you can effortlessly configure nodes, tailor network conditions, and fine-tune behaviors specific to your project's needs, allowing you to dive into networking without any hassle.

Say goodbye to the complexities of networking and hello to simplicity. With SwarmNL, all the hard work is done for you, leaving you to focus on simple configurations and your application logic.

## Tutorials and examples

Have a look at these step-by-step examples that demonstrate the use of SwarmNL in various contexts:

- [Echo server tutorial]https://github.com/algorealmInc/SwarmNL/tree/main/examples/echo-server: demonstrates a simple use case of setting up a node and querying the network layer.
- [File sharing application tutorial]https://github.com/algorealmInc/SwarmNL/tree/main/examples/file-sharing-app: demonstrates interacting with the DHT and sending/recieving RPCs from peers.
- [Simple game tutorial]https://github.com/algorealmInc/SwarmNL/tree/main/examples/simple-game: demonstrates communicating with peers over the network through gossiping.
- [Sharding tutorial]https://github.com/algorealmInc/SwarmNL/tree/main/examples/sharding: demonstrates splitting a network into shards for scaling and handling communication between various nodes in a shard and across the network.
- [Replication tutorials]https://github.com/algorealmInc/SwarmNL/tree/main/examples/replication: demonstrates the replication of data across nodes specially configured to provide redundancy to the network.

Visit the examples folder [here](https://github.com/algorealmInc/SwarmNL/tree/dev/examples/) to gain a fuller understanding on ways to use the library, including how to integrate SwarmNL with IPFS and HTTP servers.

## Research and technicalities

Have a look at [this document](https://github.com/algorealmInc/SwarmNL/blob/main/research.md) for a technical overview of SwarmNL and it's design choices.

## Node configuration

SwarmNL provides a simple interface to configure a node and specify parameters to dictate its behaviour. This includes:

- Selection and configuration of the transport layers to be supported by the node
- Selection of cryptographic keypairs (ed25519, RSA, secp256k1, ecdsa)
- Storage and retrieval of keypair locally
- PeerID and multiaddress generation
- Protocol specification and handlers
- Event handlers for network events and logging

### Example: node configuration with default settings

```rust
      #![cfg_attr(not(doctest))]
      //! Using the default node setup configuration

      // Default config
      let config = BootstrapConfig::default();
      // Build node or network core
      let node = CoreBuilder::with_config(config)
          .build()
          .await
          .unwrap();

      //! Using a custom node setup configuration

      // Custom configuration
      // a. Using config from an `.ini` file
      let config = BootstrapConfig::from_file("bootstrap_config.ini");

      // b. Using config methods
      let mut bootnode = HashMap::new();  // Bootnodes
      let ports = (1509, 2710);  // TCP, UDP ports

      bootnode.insert(
          PeerId::random(),
          "/ip4/x.x.x.x/tcp/1509".to_string()
      );

      let config = BootstrapConfig::new()
          .with_bootnodes(bootnode)
          .with_tcp(ports.0)
          .with_udp(ports.1);

      // Build node or network core
      let node = CoreBuilder::with_config(config)
          .build()
          .await
          .unwrap();

```

Please look at a template `.ini` file [here](https://github.com/algorealmInc/SwarmNL/blob/dev/swarm-nl/bootstrap_config.ini) for configuring a node in the network.<br><br>

### Event handling

During network operations, various events are generated. These events help us track the activities in the network layer. When generated, they are stored in an internal buffer until they are explicitly polled and consumed, or until the queue is full. It is important to consume critical events promptly to prevent loss if the buffer becomes full.

```rust
    #![cfg_attr(not(doctest))]
    //! Consuming the events by retrieving it as a iterator

   // Default config
    let config = BootstrapConfig::default();
    // Build node or network core
    let node = CoreBuilder::with_config(config)
        .build()
        .await
        .unwrap();

	// Read all currently buffered network events
	let events = node.events().await;

	let _ = events
		.map(|e| {
			match e {
				NetworkEvent::NewListenAddr {
					local_peer_id,
					listener_id: _,
					address,
				} => {
					// Announce interfaces we're listening on
					println!("Peer id: {}", local_peer_id);
					println!("We're listening on the {}", address);
				},
				NetworkEvent::ConnectionEstablished {
					peer_id,
					connection_id: _,
					endpoint: _,
					num_established: _,
					established_in: _,
				} => {
					println!("Connection established with peer: {:?}", peer_id);
				},
				_ => {},
			}
		})
		.collect::<Vec<_>>();


    //! Consume the immediate next events in the internal event buffer

    // Read events generated at setup
	while let Some(event) = node.next_event().await {
		match event {
			NetworkEvent::NewListenAddr {
				local_peer_id,
				listener_id: _,
				address,
			} => {
				// announce interfaces we're listening on
				println!("Peer id: {}", local_peer_id);
				println!("We're listening on the {}", address);
			},
			NetworkEvent::ConnectionEstablished {
				peer_id,
				connection_id: _,
				endpoint: _,
				num_established: _,
				established_in: _,
			} => {
				println!("Connection established with peer: {:?}", peer_id);
			},
			_ => {},
		}
	}
```

## Node communication

For communication, SwarmNL leverages the powerful capabilities of libp2p. These includes:

- The Kadmlia DHT: Developers can use the DHT to store infomation and leverage the capabilities of the DHT to build powerful applications, easily.
- A simple RPC mechanism to exchange data quickly between peers.
- Gossiping: SwarmNL uses the Gossipsub 1.1 protocol, specified by the [libp2p spec]https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md.

### Example: communicating with the network layer

```rust
      #![cfg_attr(not(doctest))]
      //! Communicate with remote nodes using the simple and familiar async-await paradigm.

      // Build node or network core
      let node = CoreBuilder::with_config(config, state)
          .build()
          .await
          .unwrap();

      // Communication interfaces
      // a. Kademlia DHT e.g

      // Prepare an kademlia `store_record` request to send to the network layer
      let (key, value, expiration_time, explicit_peers) = (
          KADEMLIA_TEST_KEY.as_bytes().to_vec(),
          KADEMLIA_TEST_VALUE.as_bytes().to_vec(),
          None,
          None,
      );

      let kad_request = AppData::KademliaStoreRecord {
          key: key.clone(),
          value,
          expiration_time,
          explicit_peers,
      };

      // Send request
      if let Ok(result) = node.query_network(kad_request).await {
          assert_eq!(KademliaStoreRecordSuccess,result);
	    }

      // b. RPC (request-response) e.g

      // Prepare a RPC fetch request
      let fetch_key = vec!["SomeFetchKey".as_bytes().to_vec()];

      let fetch_request = AppData::SendRpc {
          keys: fetch_key.clone(),
          peer: node4_peer_id,
      };

      // Get a stream id to track the request
      let stream_id = node.send_to_network(fetch_request).await.unwrap();

      // Poll for the result
      if let Ok(result) = node.recv_from_network(stream_id).await {
          // Here, the request data was simply echoed by the remote peer
          assert_eq!(AppResponse::SendRpc(fetch_key), result);
      }

      // c. Gossiping e.g

      // Prepare gossip request
      let gossip_request = AppData::GossipsubBroadcastMessage {
          topic: GOSSIP_NETWORK.to_string(),
          message: vec!["Daniel".to_string(), "Deborah".to_string()],
      };

      if let Ok(result) = node.query_network(gossip_request).await {
          assert_eq!(AppResponse::GossipsubBroadcastSuccess, result);
      }
```

## Replication

**SwarmNL** makes fault tolerance through redundancy simple and easy to integrate into your application. With replication built into SwarmNL, you can achieve robust and scalable systems effortlessly.

### Key features

- **Consistency models**: Choose from a variety of consistency models, including strong consistency with customizable parameters.
- **Dynamic node management**: Nodes can seamlessly join and leave replica networks without disrupting operations. Events are quickly propagated to all nodes.
- **Ease of use**: Minimal setup is required to add replication to your system, ensuring quick integration and deployment.
- **Node cloning**: Complete instant cloning of data in the buffer of a replica peer.

### Example: configuring and using replication

Here’s how you can set up and use SwarmNL's replication capabilities:

#### Configuring a node for replication

```rust
    #![cfg_attr(not(doctest))]
    //! Configure the node for replication with a strong consistency model

    // Define the replica network ID
    const REPL_NETWORK_ID: &str = "replica_xx";

    // Configure replication settings
    let repl_config = ReplNetworkConfig::Custom {
        queue_length: 150,
        expiry_time: Some(10),
        sync_wait_time: 5,
        consistency_model: ConsistencyModel::Strong(ConsensusModel::All),
        data_aging_period: 2,
    };

    // Build the node with replication enabled
    let node = builder.with_replication(repl_config).build().await.unwrap();

    // Join a replica network
    node.join_repl_network(REPL_NETWORK_ID.into()).await;

    // Replicate data across the network
    node.replicate(payload, REPL_NETWORK_ID).await;
```

#### Handling replication events

SwarmNL exposes network events to your application, allowing you to process incoming replica data effectively.

```rust
    //! Listen for replication events
    
    loop {
        // Check for incoming data events
        if let Some(event) = node.next_event().await {
            if let NetworkEvent::ReplicaDataIncoming { source, .. } = event {
                println!("Received incoming replica data from {}", source.to_base58());
            }
        }

        // Try to consume data from the replication buffer
        if let Some(repl_data) = node.consume_repl_data(REPL_NETWORK_ID).await {
            println!(
                "Data received from replica: {} ({} confirmations)",
                repl_data.data[0],
                repl_data.confirmations.unwrap()
            );
        }
    }

```

### Why use SwarmNL for replication?

- **Reliability**: Ensures data integrity across multiple nodes with customizable consistency guarantees.
- **Scalability**: Handles dynamic node changes with ease, making it suitable for large distributed systems.
- **Flexibility**: Provides a range of replication configurations to meet diverse application needs.

## Sharding

Sharding is a capability in distributed systems that enables networks to scale efficiently. SwarmNL provides a generic sharding functionality, allowing applications to easily partition their network and configure it for sharding.

### Key features

- **Customizable Sharding Algorithms**: SwarmNL supports generic interfaces that let you specify your own sharding algorithm, such as hash-based or range-based, while leveraging the full capabilities of the network.
- **Replication-Driven Sharding**: Sharding in SwarmNL is built on its replication capabilities, ensuring the library remains lightweight and highly functional.
- **Data Forwarding**: SwarmNL implements data-forwarding, allowing any node to handle requests for data stored on other nodes within any shard. Data is forwarded to the appropriate node for storage, and a network search algorithm enables retrieval from any node in any shard.
- **Integrated Application Layer Traps**: To maintain flexibility, SwarmNL permits nodes storing data to `trap` into the application layer when handling data requests. This ensures practicality and usability in real-world scenarios.

### Example: configuring and operating a sharded network

Here’s how you can set up and use SwarmNL's sharding capabilities:

#### Configuring a node for sharding

```rust
    #![cfg_attr(not(doctest))]
    //! Configure a node for sharding operations

    /// The constant id of the sharded network. Should be kept as a secret.
    pub const NETWORK_SHARDING_ID: &'static str = "sharding_xx";

    /// The shard local storage which is a directory in the local filesystem.
    #[derive(Debug)]
    struct LocalStorage;

    impl LocalStorage {
        /// Reads a file's content from the working directory.
        fn read_file(&self, key: &str) -> Option<ByteVector> {
            let mut file = fs::File::open(key).ok()?;
            let mut content = Vec::new();
            file.read_to_end(&mut content).ok()?;
            // Wrap the content in an outer Vec
            Some(vec![content])
        }
    }

    // Implement the `ShardStorage` trait for our local storage
    impl ShardStorage for LocalStorage {
        fn fetch_data(&mut self, key: ByteVector) -> ByteVector {
            // Process each key in the ByteVector
            for sub_key in key.iter() {
                let key_str = String::from_utf8_lossy(sub_key);
                // Attempt to read the file corresponding to the key
                if let Some(data) = self.read_file(&format!("storage/{}", key_str.as_ref())) {
                    return data;
                }
            }
            // If no match is found, return an empty ByteVector
            Default::default()
        }
    }

    /// Hash-based sharding implementation.
    pub struct HashSharding;

    impl HashSharding {
        /// Compute a simple hash for the key.
        fn hash_key(&self, key: &str) -> u64 {
            // Convert the key to bytes
            let key_bytes = key.as_bytes();

            // Generate a hash from the first byte
            if let Some(&first_byte) = key_bytes.get(0) {
                key_bytes.iter().fold(first_byte as u64, |acc, &byte| {
                    acc.wrapping_add(byte as u64)
                })
            } else {
                0
            }
        }
    }

    /// Implement the `Sharding` trait.
    impl Sharding for HashSharding {
        type Key = str;
        type ShardId = String;

        /// Locate the shard corresponding to the given key.
        fn locate_shard(&self, key: &Self::Key) -> Option<Self::ShardId> {
            // Calculate and return hash
            Some(self.hash_key(key).to_string())
        }
    }

    // Local shard storage
    let local_storage = Arc::new(Mutex::new(LocalStorage));

    // Configure node for replication, we will be using an eventual consistency model here.
    let repl_config = ReplNetworkConfig::Custom {
        queue_length: 150,
        expiry_time: Some(10),
        sync_wait_time: 5,
        consistency_model: ConsistencyModel::Eventual,
        data_aging_period: 2,
    };

    let node = builder
        .with_replication(repl_config)
        .with_sharding(NETWORK_SHARDING_ID.into(), shard_storage)
        .build()
        .await
        .unwrap();
```

#### Choosing a sharding algorithm and storing data on the network

```rust
    #![cfg_attr(not(doctest))]
    //! Select a sharding algorithm and assign nodes to their respective shards

    // Initialize the hash-based sharding policy
    let shard_executor = HashSharding;

    // Get shard IDs using the configured location algorithm.
    let shard_id_1 = shard_executor.locate_shard("earth").unwrap();
    let shard_id_2 = shard_executor.locate_shard("mars").unwrap();

    // Nodes join their respective shards
    // Node 2 and Node 3 will join the same shard, enabling replication to maintain
    // a consistent shard network state across nodes.
    match name {
        "Node 1" => {
            if shard_executor
                .join_network(node.clone(), &shard_id_1)
                .await
                .is_ok()
            {
                println!("Successfully joined shard: {}", shard_id_1);
            }
        },
        "Node 2" => {
            if shard_executor
                .join_network(node.clone(), &shard_id_2)
                .await
                .is_ok()
            {
                println!("Successfully joined shard: {}", shard_id_2);
            }
        },
        "Node 3" => {
            if shard_executor
                .join_network(node.clone(), &shard_id_2)
                .await
                .is_ok()
            {
                println!("Successfully joined shard: {}", shard_id_2);
            }
        },
        _ => {}
    }

    let shard_key = "mars".to_string();

    // Store data across the network in the shard pointed to by the key
    match shard_executor
        .shard(
            node.clone(),
            &shard_key,
            payload,
        )
        .await;
```

#### Handling sharding events

A node can receive data either through forwarding from a node in another shard or via replication from a peer node in the same shard. Below is an example demonstrating how to listen for and handle both types of events.

```rust
    //! Listen for and consume data from a sharded network.
    loop {
        // Check for incoming data events
        if let Some(event) = node.next_event().await {
            // Handle incoming data events
            match event {
                NetworkEvent::IncomingForwardedData { data, source } => {
                    println!(
                        "Received forwarded data: {:?} from peer: {}",
                        data,
                        source.to_base58()
                    );

                    // Split the contents of the incoming data
                    let data_vec = data[0].split(" ").collect::<Vec<_>>();

                    // Extract file name and content
                    if let [file_name, content] = &data_vec[..] {
                        let _ = append_to_file(file_name, content).await;
                    }
                },
                NetworkEvent::ReplicaDataIncoming {
                    data,
                    network,
                    source,
                    ..
                } => {
                    println!(
                        "Received replica data: {:?} from shard peer: {}",
                        data,
                        source.to_base58()
                    );

                    if let Some(repl_data) = node.consume_repl_data(&network).await {
                        // Split the contents of the incoming data
                        let data = repl_data.data[0].split(" ").collect::<Vec<_>>();

                        // Extract file name and content
                        if let [file_name, content] = &data[..] {
                            let _ = append_to_file(file_name, content).await;
                        }
                    } else {
                        println!("Error: No message in replica buffer");
                    }
                },
                _ => {}
            }
        }
    }
```

### Why use SwarmNL for sharding?

SwarmNL integrates the networking and storage layers to deliver a seamless sharding experience. This approach enables nodes to interact directly with the application layer and local environment, providing a robust and flexible solution for scalable distributed systems.

### _Moving forward 👷🏼_
_In future iterations, we will be working on:_
- _Extending support for more transport layers._
- _Providing further optimizating of network algorithms for various network scenerios._

<br>
In essence, SwarmNL is designed to simplify networking so you can focus on building that world-changing application of yours! Cheers! 🥂

With ❤️ from [Deji](https://github.com/thewoodfish) and [Sacha](https://github.com/sacha-l).

<p align="center">
<img src="https://github.com/algorealmInc/SwarmNL/blob/9c00c692857c6981faae2680cc785a430e378ecf/web3%20foundation_grants_badge_black.png"  alt="Web3 Foundation Grants Badge" width="300"> 
</p>