distributed-state-network 0.0.1__tar.gz → 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/.gitignore +2 -1
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/PKG-INFO +3 -23
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/README.md +2 -22
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/pyproject.toml +1 -1
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/dsnode.py +4 -4
- distributed_state_network-0.0.2/tests/connections.py +75 -0
- distributed_state_network-0.0.1/security.md +0 -14
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/.github/workflows/publish.yml +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/LICENSE +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/coverage.sh +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/__init__.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/create_key.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/handler.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/config.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/endpoint.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/hello_packet.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/peers_packet.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/signed_packet.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/state_packet.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/__init__.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/aes.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/byte_helper.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/ecdsa.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/https.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/key_manager.py +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/technical.md +0 -0
- {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: distributed-state-network
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: A tool to distribute the state of a network device to other devices on the network
|
|
5
5
|
Project-URL: Homepage, https://github.com/erinclemmer/distributed_state_network
|
|
6
6
|
Project-URL: Issues, https://github.com/erinclemmer/distributed_state_network/issues
|
|
@@ -99,9 +99,7 @@ temperature = node.read_data("sensor_node", "temperature")
|
|
|
99
99
|
connected_nodes = node.peers()
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
##
|
|
103
|
-
|
|
104
|
-
### Distributed Temperature Monitoring
|
|
102
|
+
## Example: Distributed Temperature Monitoring
|
|
105
103
|
|
|
106
104
|
Create a network of temperature sensors that share readings:
|
|
107
105
|
|
|
@@ -128,22 +126,4 @@ for node_id in monitor.node.peers():
|
|
|
128
126
|
if node_id.startswith("sensor_"):
|
|
129
127
|
temp = monitor.node.read_data(node_id, "temperature")
|
|
130
128
|
print(f"{node_id}: {temp}°F")
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## Troubleshooting
|
|
134
|
-
|
|
135
|
-
### Node Can't Connect
|
|
136
|
-
- Verify the AES key file is identical on all nodes
|
|
137
|
-
- Check firewall rules allow traffic on the configured port
|
|
138
|
-
- Ensure bootstrap node address is reachable
|
|
139
|
-
|
|
140
|
-
### State Not Updating
|
|
141
|
-
- Confirm nodes show as connected with `node.peers()`
|
|
142
|
-
- Check network latency between nodes
|
|
143
|
-
- Verify no duplicate node IDs (each must be unique)
|
|
144
|
-
|
|
145
|
-
## Performance Characteristics
|
|
146
|
-
- State updates typically propagate in <100ms on local networks
|
|
147
|
-
- Each node stores the complete state of all other nodes
|
|
148
|
-
- Suitable for networks up to ~100 nodes
|
|
149
|
-
- State values should be kept reasonably small (< 1MB per key)
|
|
129
|
+
```
|
|
@@ -74,9 +74,7 @@ temperature = node.read_data("sensor_node", "temperature")
|
|
|
74
74
|
connected_nodes = node.peers()
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
##
|
|
78
|
-
|
|
79
|
-
### Distributed Temperature Monitoring
|
|
77
|
+
## Example: Distributed Temperature Monitoring
|
|
80
78
|
|
|
81
79
|
Create a network of temperature sensors that share readings:
|
|
82
80
|
|
|
@@ -103,22 +101,4 @@ for node_id in monitor.node.peers():
|
|
|
103
101
|
if node_id.startswith("sensor_"):
|
|
104
102
|
temp = monitor.node.read_data(node_id, "temperature")
|
|
105
103
|
print(f"{node_id}: {temp}°F")
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Troubleshooting
|
|
109
|
-
|
|
110
|
-
### Node Can't Connect
|
|
111
|
-
- Verify the AES key file is identical on all nodes
|
|
112
|
-
- Check firewall rules allow traffic on the configured port
|
|
113
|
-
- Ensure bootstrap node address is reachable
|
|
114
|
-
|
|
115
|
-
### State Not Updating
|
|
116
|
-
- Confirm nodes show as connected with `node.peers()`
|
|
117
|
-
- Check network latency between nodes
|
|
118
|
-
- Verify no duplicate node IDs (each must be unique)
|
|
119
|
-
|
|
120
|
-
## Performance Characteristics
|
|
121
|
-
- State updates typically propagate in <100ms on local networks
|
|
122
|
-
- Each node stores the complete state of all other nodes
|
|
123
|
-
- Suitable for networks up to ~100 nodes
|
|
124
|
-
- State values should be kept reasonably small (< 1MB per key)
|
|
104
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "distributed-state-network"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.2"
|
|
8
8
|
authors = [{name="Erin Clemmer", email="erin.c.clemmer@gmail.com"}]
|
|
9
9
|
description = "A tool to distribute the state of a network device to other devices on the network"
|
|
10
10
|
keywords = ["distributed", "networking"]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import time
|
|
3
|
-
import json
|
|
4
3
|
import logging
|
|
5
4
|
|
|
6
5
|
import requests
|
|
@@ -16,7 +15,7 @@ from distributed_state_network.objects.config import DSNodeConfig
|
|
|
16
15
|
|
|
17
16
|
from distributed_state_network.util import get_dict_hash
|
|
18
17
|
from distributed_state_network.util.key_manager import CertManager, CredentialManager
|
|
19
|
-
from distributed_state_network.util.aes import aes_encrypt, aes_decrypt
|
|
18
|
+
from distributed_state_network.util.aes import aes_encrypt, aes_decrypt
|
|
20
19
|
|
|
21
20
|
TICK_INTERVAL = 3
|
|
22
21
|
|
|
@@ -60,7 +59,7 @@ class DSNode:
|
|
|
60
59
|
def network_tick(self):
|
|
61
60
|
time.sleep(TICK_INTERVAL)
|
|
62
61
|
if self.shutting_down:
|
|
63
|
-
self.logger.info(
|
|
62
|
+
self.logger.info("Shutting down node")
|
|
64
63
|
return
|
|
65
64
|
self.test_connections()
|
|
66
65
|
threading.Thread(target=self.network_tick).start()
|
|
@@ -69,9 +68,10 @@ class DSNode:
|
|
|
69
68
|
def remove(node_id: str):
|
|
70
69
|
if node_id in self.node_states:
|
|
71
70
|
del self.node_states[node_id]
|
|
71
|
+
del self.address_book[node_id]
|
|
72
72
|
self.logger.info(f"PING failed for {node_id}, disconnecting...")
|
|
73
73
|
for node_id in self.node_states.copy().keys():
|
|
74
|
-
if not
|
|
74
|
+
if node_id not in self.node_states or node_id == self.config.node_id:
|
|
75
75
|
continue
|
|
76
76
|
try:
|
|
77
77
|
if self.shutting_down:
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
sys.path.append(os.path.join(os.path.dirname(__file__), '../src'))
|
|
7
|
+
|
|
8
|
+
from distributed_state_network import DSNodeServer, DSNodeConfig, Endpoint
|
|
9
|
+
|
|
10
|
+
KEY_FILE = "network.key"
|
|
11
|
+
|
|
12
|
+
if not os.path.exists(KEY_FILE):
|
|
13
|
+
DSNodeServer.generate_key("network.key")
|
|
14
|
+
|
|
15
|
+
current_port = 5000
|
|
16
|
+
|
|
17
|
+
def start_node(node_id: str, bootstrap_port: int = None):
|
|
18
|
+
global current_port
|
|
19
|
+
args = {
|
|
20
|
+
"node_id": node_id,
|
|
21
|
+
"port": current_port,
|
|
22
|
+
"aes_key_file": KEY_FILE
|
|
23
|
+
}
|
|
24
|
+
current_port += 1
|
|
25
|
+
if bootstrap_port is not None:
|
|
26
|
+
args["bootstrap_nodes"] = [Endpoint(
|
|
27
|
+
address="127.0.0.1",
|
|
28
|
+
port=bootstrap_port
|
|
29
|
+
)]
|
|
30
|
+
else:
|
|
31
|
+
args["bootstrap_nodes"] = []
|
|
32
|
+
|
|
33
|
+
return DSNodeServer.start(DSNodeConfig(**args))
|
|
34
|
+
|
|
35
|
+
class ConnectionsTest(unittest.TestCase):
|
|
36
|
+
def test_one(self):
|
|
37
|
+
node = start_node("node-1")
|
|
38
|
+
self.assertEqual(["node-1"], node.node.peers())
|
|
39
|
+
|
|
40
|
+
def test_two(self):
|
|
41
|
+
node1 = start_node("node-1")
|
|
42
|
+
node2 = start_node("node-2", node1.config.port)
|
|
43
|
+
self.assertEqual(["node-1", "node-2"], sorted(node1.node.peers()))
|
|
44
|
+
self.assertEqual(["node-1", "node-2"], sorted(node2.node.peers()))
|
|
45
|
+
|
|
46
|
+
def test_many(self):
|
|
47
|
+
bootstrap_node = start_node("bootstrap")
|
|
48
|
+
nodes = []
|
|
49
|
+
for i in range(10):
|
|
50
|
+
nodes.append(start_node(f"node-{i}", bootstrap_node.config.port))
|
|
51
|
+
|
|
52
|
+
node_list = ["bootstrap"]
|
|
53
|
+
node_list.extend([f"node-{i}" for i in range(10)])
|
|
54
|
+
|
|
55
|
+
time.sleep(5)
|
|
56
|
+
self.assertEqual(node_list, sorted(bootstrap_node.node.peers()))
|
|
57
|
+
for i in range(10):
|
|
58
|
+
self.assertEqual(node_list, sorted(nodes[i].node.peers()))
|
|
59
|
+
|
|
60
|
+
def test_disconnect(self):
|
|
61
|
+
node1 = start_node("node-1")
|
|
62
|
+
node2 = start_node("node-2", node1.config.port)
|
|
63
|
+
node3 = start_node("node-3", node1.config.port)
|
|
64
|
+
|
|
65
|
+
time.sleep(1)
|
|
66
|
+
node2.stop()
|
|
67
|
+
time.sleep(10)
|
|
68
|
+
node4 = start_node("node-4", node1.config.port)
|
|
69
|
+
time.sleep(10)
|
|
70
|
+
self.assertEqual(["node-1", "node-3", "node-4"], sorted(node1.node.peers()))
|
|
71
|
+
self.assertEqual(["node-1", "node-3", "node-4"], sorted(node3.node.peers()))
|
|
72
|
+
self.assertEqual(["node-1", "node-3", "node-4"], sorted(node4.node.peers()))
|
|
73
|
+
|
|
74
|
+
if __name__ == '__main__':
|
|
75
|
+
unittest.main()
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
## Security Architecture
|
|
2
|
-
We use a three-layer security model where each layer addresses a specific vulnerability. Understanding this design helps explain why certain setup steps are necessary and how your data stays protected.
|
|
3
|
-
The Three-Layer Defense
|
|
4
|
-
Think of our security like protecting a secret club where members share sensitive information. You need multiple types of protection because different attacks require different defenses.
|
|
5
|
-
### Layer 1: AES
|
|
6
|
-
AES Encryption acts as the network's membership card. Every message between nodes is encrypted with a shared AES key, making the network invisible to outsiders. If someone intercepts your network traffic without this key, they see only meaningless encrypted bytes. This is why you must carefully share the key file with trusted nodes - it's the fundamental gatekeeper of your network.
|
|
7
|
-
### Layer 2: ECDSA
|
|
8
|
-
ECDSA Signatures solve a critical problem that AES alone cannot address. Once someone has the network key, how do you prevent them from impersonating other nodes? Each node has its own unforgeable cryptographic identity. When node "sensor_1" shares its temperature reading, it signs that data with its private key. Other nodes verify this signature to ensure the data truly came from sensor_1, not from an attacker pretending to be sensor_1. This is why node IDs must be unique - each ID is permanently bound to a specific cryptographic identity.
|
|
9
|
-
### Layer 3: HTTPS
|
|
10
|
-
HTTPS/TLS prevents sophisticated network-level attacks. Even with encryption and signatures, an attacker positioned between two nodes could intercept messages, block updates, or redirect traffic to malicious servers. HTTPS certificates ensure you're communicating with the exact node you intend to reach. During the initial handshake, nodes exchange certificates that get verified on every subsequent connection, creating authenticated channels that can't be hijacked.
|
|
11
|
-
|
|
12
|
-
## Why This Matters
|
|
13
|
-
Each layer compensates for what the others cannot protect. AES keeps outsiders out but can't distinguish between insiders. ECDSA proves identity but doesn't hide data. HTTPS secures the transport but doesn't control network access. Together, they create a system where only authorized nodes can join, every piece of state data is cryptographically guaranteed to come from its claimed source, and all communications flow through verified channels. This isn't over-engineering - it's the minimum required for nodes to trust each other in a truly distributed system without any central authority.
|
|
14
|
-
When you see the various key files and certificates being generated during setup, you're watching these three defensive layers being constructed. Each one is essential for maintaining the integrity and privacy of your distributed application.
|
{distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/.github/workflows/publish.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|