distributed-state-network 0.0.2__tar.gz → 0.0.3__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.2 → distributed_state_network-0.0.3}/PKG-INFO +11 -22
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/README.md +10 -21
- distributed_state_network-0.0.3/documentation/ds-node-config.md +47 -0
- distributed_state_network-0.0.3/documentation/ds-node-server.md +56 -0
- distributed_state_network-0.0.3/documentation/ds-node.md +64 -0
- distributed_state_network-0.0.3/documentation/protocol.md +37 -0
- distributed_state_network-0.0.3/documentation/usage.md +68 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/pyproject.toml +1 -1
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/dsnode.py +5 -1
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/handler.py +7 -8
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/tests/connections.py +18 -2
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/.github/workflows/publish.yml +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/.gitignore +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/LICENSE +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/coverage.sh +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/__init__.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/create_key.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/objects/config.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/objects/endpoint.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/objects/hello_packet.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/objects/peers_packet.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/objects/signed_packet.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/objects/state_packet.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/util/__init__.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/util/aes.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/util/byte_helper.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/util/ecdsa.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/util/https.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/src/distributed_state_network/util/key_manager.py +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/technical.md +0 -0
- {distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/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.3
|
|
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
|
|
@@ -79,26 +79,6 @@ DSN creates a peer-to-peer network where each node maintains its own state datab
|
|
|
79
79
|
- Any node can read any other node's state instantly
|
|
80
80
|
- All communication is encrypted with AES + ECDSA + HTTPS
|
|
81
81
|
|
|
82
|
-
## API Reference
|
|
83
|
-
|
|
84
|
-
### Node Methods
|
|
85
|
-
|
|
86
|
-
**update_data(key, value)** - Update a key in this node's state
|
|
87
|
-
```python
|
|
88
|
-
node.update_data("sensor_reading", "42.0")
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**read_data(node_id, key)** - Read a value from any node's state
|
|
92
|
-
|
|
93
|
-
```python
|
|
94
|
-
temperature = node.read_data("sensor_node", "temperature")
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**peers()** - List all connected nodes
|
|
98
|
-
```python
|
|
99
|
-
connected_nodes = node.peers()
|
|
100
|
-
```
|
|
101
|
-
|
|
102
82
|
## Example: Distributed Temperature Monitoring
|
|
103
83
|
|
|
104
84
|
Create a network of temperature sensors that share readings:
|
|
@@ -126,4 +106,13 @@ for node_id in monitor.node.peers():
|
|
|
126
106
|
if node_id.startswith("sensor_"):
|
|
127
107
|
temp = monitor.node.read_data(node_id, "temperature")
|
|
128
108
|
print(f"{node_id}: {temp}°F")
|
|
129
|
-
```
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Documentation
|
|
112
|
+
* [Usage Examples](./documentation/usage.md)
|
|
113
|
+
* [Protocol](./documentation/protocol.md)
|
|
114
|
+
|
|
115
|
+
#### API Reference
|
|
116
|
+
* [DSNodeServer](./documentation/ds-node-server.md)
|
|
117
|
+
* [DSNode](./documentation/ds-node.md)
|
|
118
|
+
* [DSNodeConfig](./documentation/ds-node-config.md)
|
|
@@ -54,26 +54,6 @@ DSN creates a peer-to-peer network where each node maintains its own state datab
|
|
|
54
54
|
- Any node can read any other node's state instantly
|
|
55
55
|
- All communication is encrypted with AES + ECDSA + HTTPS
|
|
56
56
|
|
|
57
|
-
## API Reference
|
|
58
|
-
|
|
59
|
-
### Node Methods
|
|
60
|
-
|
|
61
|
-
**update_data(key, value)** - Update a key in this node's state
|
|
62
|
-
```python
|
|
63
|
-
node.update_data("sensor_reading", "42.0")
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**read_data(node_id, key)** - Read a value from any node's state
|
|
67
|
-
|
|
68
|
-
```python
|
|
69
|
-
temperature = node.read_data("sensor_node", "temperature")
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**peers()** - List all connected nodes
|
|
73
|
-
```python
|
|
74
|
-
connected_nodes = node.peers()
|
|
75
|
-
```
|
|
76
|
-
|
|
77
57
|
## Example: Distributed Temperature Monitoring
|
|
78
58
|
|
|
79
59
|
Create a network of temperature sensors that share readings:
|
|
@@ -101,4 +81,13 @@ for node_id in monitor.node.peers():
|
|
|
101
81
|
if node_id.startswith("sensor_"):
|
|
102
82
|
temp = monitor.node.read_data(node_id, "temperature")
|
|
103
83
|
print(f"{node_id}: {temp}°F")
|
|
104
|
-
```
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Documentation
|
|
87
|
+
* [Usage Examples](./documentation/usage.md)
|
|
88
|
+
* [Protocol](./documentation/protocol.md)
|
|
89
|
+
|
|
90
|
+
#### API Reference
|
|
91
|
+
* [DSNodeServer](./documentation/ds-node-server.md)
|
|
92
|
+
* [DSNode](./documentation/ds-node.md)
|
|
93
|
+
* [DSNodeConfig](./documentation/ds-node-config.md)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
## DSNodeConfig
|
|
2
|
+
|
|
3
|
+
Configuration object for initializing a DSNode instance.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from distributed_state_network import DSNodeConfig
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Class Definition
|
|
10
|
+
```python
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class DSNodeConfig:
|
|
13
|
+
node_id: str
|
|
14
|
+
port: int
|
|
15
|
+
aes_key_file: str
|
|
16
|
+
bootstrap_nodes: List[Endpoint]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Attributes
|
|
20
|
+
- **node_id** (`str`): Unique identifier for the node
|
|
21
|
+
- **port** (`int`): Port number for the node to listen on
|
|
22
|
+
- **aes_key_file** (`str`): Path to the AES key file for encryption/decryption
|
|
23
|
+
- **bootstrap_nodes** (`List[Endpoint]`): List of initial nodes to connect to when joining the network
|
|
24
|
+
|
|
25
|
+
### Methods
|
|
26
|
+
|
|
27
|
+
### `from_dict(data: Dict) -> DSNodeConfig`
|
|
28
|
+
Creates a DSNodeConfig instance from a dictionary.
|
|
29
|
+
|
|
30
|
+
**Parameters:**
|
|
31
|
+
- `data` (`Dict`): Dictionary containing configuration parameters
|
|
32
|
+
|
|
33
|
+
**Returns:**
|
|
34
|
+
- `DSNodeConfig`: Configuration instance
|
|
35
|
+
|
|
36
|
+
**Example:**
|
|
37
|
+
```python
|
|
38
|
+
config_dict = {
|
|
39
|
+
"node_id": "node1",
|
|
40
|
+
"port": 8000,
|
|
41
|
+
"aes_key_file": "/path/to/key.aes",
|
|
42
|
+
"bootstrap_nodes": [
|
|
43
|
+
{"address": "127.0.0.1", "port": 8001}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
config = DSNodeConfig.from_dict(config_dict)
|
|
47
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## DSNodeServer
|
|
2
|
+
|
|
3
|
+
HTTPS server wrapper for DSNode that handles incoming network requests.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from distributed_state_network import DSNodeServer
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Class Definition
|
|
10
|
+
```python
|
|
11
|
+
class DSNodeServer(HTTPServer):
|
|
12
|
+
config: DSNodeConfig
|
|
13
|
+
node: DSNode
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Constructor
|
|
17
|
+
|
|
18
|
+
**Parameters:**
|
|
19
|
+
- `config` (`DSNodeConfig`): Node configuration
|
|
20
|
+
- `disconnect_callback` (`Optional[Callable]`): Callback for disconnect events
|
|
21
|
+
|
|
22
|
+
### Static Methods
|
|
23
|
+
|
|
24
|
+
### `start(config: DSNodeConfig, disconnect_callback: Optional[Callable] = None) -> DSNodeServer`
|
|
25
|
+
Creates and starts a new DSNodeServer instance with SSL configuration.
|
|
26
|
+
```python
|
|
27
|
+
server = DSNodeServer.start(config)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Parameters:**
|
|
31
|
+
- `config` (`DSNodeConfig`): Node configuration
|
|
32
|
+
- `disconnect_callback` (`Optional[Callable]`): Callback for disconnect events
|
|
33
|
+
|
|
34
|
+
**Returns:**
|
|
35
|
+
- `DSNodeServer`: Running server instance
|
|
36
|
+
|
|
37
|
+
### `generate_key(out_file_path: str) -> None`
|
|
38
|
+
Generates a new AES key file for network encryption.
|
|
39
|
+
|
|
40
|
+
**Parameters:**
|
|
41
|
+
- `out_file_path` (`str`): Path where the key file will be saved
|
|
42
|
+
|
|
43
|
+
**Example:**
|
|
44
|
+
```python
|
|
45
|
+
DSNodeServer.generate_key("/path/to/network.key")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Instance Methods
|
|
49
|
+
|
|
50
|
+
### `stop() -> None`
|
|
51
|
+
Gracefully shuts down the server and cleans up resources.
|
|
52
|
+
|
|
53
|
+
**Example:**
|
|
54
|
+
```python
|
|
55
|
+
server.stop()
|
|
56
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
## DSNode
|
|
2
|
+
|
|
3
|
+
Main node class that handles all distributed state network operations including peer management, state synchronization, and encrypted communication.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from distributed_state_network import DSNode
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Class Definition
|
|
10
|
+
```python
|
|
11
|
+
class DSNode:
|
|
12
|
+
config: DSNodeConfig
|
|
13
|
+
address_book: Dict[str, Endpoint]
|
|
14
|
+
node_states: Dict[str, StatePacket]
|
|
15
|
+
shutting_down: bool = False
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Constructor
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
node = DSNode(config, "0.0.3", disconnect_callback=on_disconnect)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Parameters:**
|
|
25
|
+
- `config` (`DSNodeConfig`): Node configuration
|
|
26
|
+
- `version` (`str`): Protocol version string for compatibility checking
|
|
27
|
+
- `disconnect_callback` (`Optional[Callable]`): Callback function invoked when a peer disconnects
|
|
28
|
+
|
|
29
|
+
### Key Methods
|
|
30
|
+
|
|
31
|
+
### `bootstrap(con: Endpoint) -> None`
|
|
32
|
+
Connects to a bootstrap node to join the network.
|
|
33
|
+
|
|
34
|
+
**Parameters:**
|
|
35
|
+
- `con` (`Endpoint`): Connection endpoint of the bootstrap node
|
|
36
|
+
|
|
37
|
+
**Raises:**
|
|
38
|
+
- `Exception`: If connection to bootstrap node fails
|
|
39
|
+
|
|
40
|
+
### `update_data(key: str, val: str) -> None`
|
|
41
|
+
Updates a key-value pair in the node's state and broadcasts the update to all peers.
|
|
42
|
+
```python
|
|
43
|
+
node.update_data("status", "active")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Parameters:**
|
|
47
|
+
- `key` (`str`): State key to update
|
|
48
|
+
- `val` (`str`): New value for the key
|
|
49
|
+
|
|
50
|
+
### `read_data(node_id: str, key: str) -> Optional[str]`
|
|
51
|
+
Reads a value from a specific node's state.
|
|
52
|
+
|
|
53
|
+
**Parameters:**
|
|
54
|
+
- `node_id` (`str`): ID of the node to read from
|
|
55
|
+
- `key` (`str`): Key to retrieve
|
|
56
|
+
|
|
57
|
+
**Returns:**
|
|
58
|
+
- `Optional[str]`: Value if exists, None otherwise
|
|
59
|
+
|
|
60
|
+
### `peers() -> List[str]`
|
|
61
|
+
Returns a list of all connected peer node IDs.
|
|
62
|
+
|
|
63
|
+
**Returns:**
|
|
64
|
+
- `List[str]`: List of node IDs
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
## Network Protocol
|
|
2
|
+
|
|
3
|
+
### Endpoints
|
|
4
|
+
The server exposes the following HTTPS endpoints:
|
|
5
|
+
|
|
6
|
+
- **POST /hello**: Exchange node information and certificates
|
|
7
|
+
- **POST /peers**: Request/share peer list
|
|
8
|
+
- **POST /update**: Send/receive state updates
|
|
9
|
+
- **POST /ping**: Connectivity check
|
|
10
|
+
|
|
11
|
+
### Security
|
|
12
|
+
- All communication is encrypted using AES with a shared key
|
|
13
|
+
- Messages are signed using ECDSA for authentication
|
|
14
|
+
- HTTPS with self-signed certificates for transport security
|
|
15
|
+
|
|
16
|
+
### State Synchronization
|
|
17
|
+
- Nodes maintain a copy of all peers' states
|
|
18
|
+
- Updates are broadcast to all connected peers
|
|
19
|
+
- Timestamps prevent older updates from overwriting newer ones
|
|
20
|
+
|
|
21
|
+
## Important Notes
|
|
22
|
+
|
|
23
|
+
1. **Shared AES Key**: All nodes in the network must use the same AES key file
|
|
24
|
+
2. **Unique Node IDs**: Each node must have a unique node_id
|
|
25
|
+
3. **Port Availability**: Ensure the specified port is available before starting
|
|
26
|
+
4. **Bootstrap Nodes**: At least one bootstrap node is required to join an existing network
|
|
27
|
+
5. **Network Tick**: The network performs maintenance checks every 3 seconds
|
|
28
|
+
6. **Certificate Management**: Certificates and keys are automatically generated and stored in `certs/` and `credentials/` directories
|
|
29
|
+
|
|
30
|
+
## Error Handling
|
|
31
|
+
|
|
32
|
+
Common HTTP status codes returned:
|
|
33
|
+
- **200**: Success
|
|
34
|
+
- **401**: Not Authorized (signature verification failed)
|
|
35
|
+
- **404**: Endpoint not found
|
|
36
|
+
- **406**: Not Acceptable (invalid data or version mismatch)
|
|
37
|
+
- **505**: Version not supported
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
## Usage Examples
|
|
2
|
+
|
|
3
|
+
### Basic Setup
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from distributed_state_network import DSNodeServer, DSNodeConfig, Endpoint
|
|
7
|
+
|
|
8
|
+
# Create configuration
|
|
9
|
+
config = DSNodeConfig(
|
|
10
|
+
node_id="node1",
|
|
11
|
+
port=8000,
|
|
12
|
+
aes_key_file="/path/to/shared.key",
|
|
13
|
+
bootstrap_nodes=[] # Empty for first node
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Start server
|
|
17
|
+
server = DSNodeServer.start(config)
|
|
18
|
+
|
|
19
|
+
# Update state
|
|
20
|
+
server.node.update_data("status", "online")
|
|
21
|
+
server.node.update_data("version", "1.0.0")
|
|
22
|
+
|
|
23
|
+
# Read own state
|
|
24
|
+
my_status = server.node.read_data("node1", "status")
|
|
25
|
+
|
|
26
|
+
# Get connected peers
|
|
27
|
+
peers = server.node.peers()
|
|
28
|
+
print(f"Connected peers: {peers}")
|
|
29
|
+
|
|
30
|
+
# Shutdown
|
|
31
|
+
server.stop()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Joining Existing Network
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# Node 2 configuration with bootstrap
|
|
38
|
+
config2 = DSNodeConfig(
|
|
39
|
+
node_id="node2",
|
|
40
|
+
port=8001,
|
|
41
|
+
aes_key_file="/path/to/shared.key", # Same key file as network
|
|
42
|
+
bootstrap_nodes=[
|
|
43
|
+
Endpoint("127.0.0.1", 8000) # Node 1's endpoint
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
server2 = DSNodeServer.start(config2)
|
|
48
|
+
|
|
49
|
+
# Read state from peer
|
|
50
|
+
peer_status = server2.node.read_data("node1", "status")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### With Disconnect Callback
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
def handle_disconnect():
|
|
57
|
+
print("A peer has disconnected!")
|
|
58
|
+
# Handle reconnection logic
|
|
59
|
+
|
|
60
|
+
config = DSNodeConfig(
|
|
61
|
+
node_id="node3",
|
|
62
|
+
port=8002,
|
|
63
|
+
aes_key_file="/path/to/shared.key",
|
|
64
|
+
bootstrap_nodes=[Endpoint("127.0.0.1", 8000)]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
server = DSNodeServer.start(config, disconnect_callback=handle_disconnect)
|
|
68
|
+
```
|
|
@@ -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.3"
|
|
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"]
|
|
@@ -5,7 +5,7 @@ import logging
|
|
|
5
5
|
import requests
|
|
6
6
|
import threading
|
|
7
7
|
from requests import RequestException
|
|
8
|
-
from typing import Dict, Tuple, List, Optional
|
|
8
|
+
from typing import Dict, Tuple, List, Optional, Callable
|
|
9
9
|
|
|
10
10
|
from distributed_state_network.objects.endpoint import Endpoint
|
|
11
11
|
from distributed_state_network.objects.hello_packet import HelloPacket
|
|
@@ -29,6 +29,7 @@ class DSNode:
|
|
|
29
29
|
self,
|
|
30
30
|
config: DSNodeConfig,
|
|
31
31
|
version: str,
|
|
32
|
+
disconnect_callback: Optional[Callable] = None
|
|
32
33
|
):
|
|
33
34
|
self.config = config
|
|
34
35
|
self.version = version
|
|
@@ -48,6 +49,7 @@ class DSNode:
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
self.logger = logging.getLogger("DSN: " + config.node_id)
|
|
52
|
+
self.disconnect_cb = disconnect_callback
|
|
51
53
|
if not os.path.exists(config.aes_key_file):
|
|
52
54
|
raise Exception(f"Could not find aes key file in {config.aes_key_file}")
|
|
53
55
|
threading.Thread(target=self.network_tick).start()
|
|
@@ -80,6 +82,8 @@ class DSNode:
|
|
|
80
82
|
except RequestException:
|
|
81
83
|
if node_id in self.node_states: # double check if something has changed since the ping request started
|
|
82
84
|
remove(node_id)
|
|
85
|
+
if self.disconnect_cb is not None:
|
|
86
|
+
self.disconnect_cb()
|
|
83
87
|
|
|
84
88
|
def send_request_to_node(self, node_id: str, path: str, payload: bytes, verify) -> Tuple[requests.Response, bytes]:
|
|
85
89
|
con = self.connection_from_node(node_id)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import ssl
|
|
2
2
|
import threading
|
|
3
|
-
import json
|
|
4
3
|
import logging
|
|
5
|
-
from typing import
|
|
6
|
-
from threading import Thread
|
|
4
|
+
from typing import Callable, Optional
|
|
7
5
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
8
6
|
|
|
9
7
|
from distributed_state_network.dsnode import DSNode
|
|
@@ -11,7 +9,7 @@ from distributed_state_network.objects.config import DSNodeConfig
|
|
|
11
9
|
from distributed_state_network.util.aes import generate_aes_key
|
|
12
10
|
from distributed_state_network.util import stop_thread
|
|
13
11
|
|
|
14
|
-
VERSION = "0.0.
|
|
12
|
+
VERSION = "0.0.3"
|
|
15
13
|
logging.basicConfig(level=logging.INFO)
|
|
16
14
|
|
|
17
15
|
def respond_bytes(handler: BaseHTTPRequestHandler, data: bytes):
|
|
@@ -71,11 +69,12 @@ def serve(httpd):
|
|
|
71
69
|
class DSNodeServer(HTTPServer):
|
|
72
70
|
def __init__(
|
|
73
71
|
self,
|
|
74
|
-
config: DSNodeConfig
|
|
72
|
+
config: DSNodeConfig,
|
|
73
|
+
disconnect_callback: Optional[Callable] = None
|
|
75
74
|
):
|
|
76
75
|
super().__init__(("127.0.0.1", config.port), DSNodeHandler)
|
|
77
76
|
self.config = config
|
|
78
|
-
self.node = DSNode(config, VERSION)
|
|
77
|
+
self.node = DSNode(config, VERSION, disconnect_callback)
|
|
79
78
|
self.node.logger.info(f'Started DSNode on port {config.port}')
|
|
80
79
|
|
|
81
80
|
def stop(self):
|
|
@@ -91,8 +90,8 @@ class DSNodeServer(HTTPServer):
|
|
|
91
90
|
f.write(key)
|
|
92
91
|
|
|
93
92
|
@staticmethod
|
|
94
|
-
def start(config: DSNodeConfig) -> 'NodeServer':
|
|
95
|
-
n = DSNodeServer(config)
|
|
93
|
+
def start(config: DSNodeConfig, disconnect_callback: Optional[Callable] = None) -> 'NodeServer':
|
|
94
|
+
n = DSNodeServer(config, disconnect_callback)
|
|
96
95
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
97
96
|
public_path = n.node.cert_manager.public_path(n.config.node_id)
|
|
98
97
|
ssl_context.load_cert_chain(
|
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
import sys
|
|
3
3
|
import time
|
|
4
4
|
import unittest
|
|
5
|
+
from typing import Optional, Callable
|
|
5
6
|
|
|
6
7
|
sys.path.append(os.path.join(os.path.dirname(__file__), '../src'))
|
|
7
8
|
|
|
@@ -12,9 +13,11 @@ KEY_FILE = "network.key"
|
|
|
12
13
|
if not os.path.exists(KEY_FILE):
|
|
13
14
|
DSNodeServer.generate_key("network.key")
|
|
14
15
|
|
|
16
|
+
d_cb_test = 0
|
|
17
|
+
|
|
15
18
|
current_port = 5000
|
|
16
19
|
|
|
17
|
-
def start_node(node_id: str, bootstrap_port: int = None):
|
|
20
|
+
def start_node(node_id: str, bootstrap_port: int = None, disconnect_cb: Optional[Callable] = None):
|
|
18
21
|
global current_port
|
|
19
22
|
args = {
|
|
20
23
|
"node_id": node_id,
|
|
@@ -30,7 +33,7 @@ def start_node(node_id: str, bootstrap_port: int = None):
|
|
|
30
33
|
else:
|
|
31
34
|
args["bootstrap_nodes"] = []
|
|
32
35
|
|
|
33
|
-
return DSNodeServer.start(DSNodeConfig(**args))
|
|
36
|
+
return DSNodeServer.start(DSNodeConfig(**args), disconnect_cb)
|
|
34
37
|
|
|
35
38
|
class ConnectionsTest(unittest.TestCase):
|
|
36
39
|
def test_one(self):
|
|
@@ -71,5 +74,18 @@ class ConnectionsTest(unittest.TestCase):
|
|
|
71
74
|
self.assertEqual(["node-1", "node-3", "node-4"], sorted(node3.node.peers()))
|
|
72
75
|
self.assertEqual(["node-1", "node-3", "node-4"], sorted(node4.node.peers()))
|
|
73
76
|
|
|
77
|
+
def test_disconnect_cb(self):
|
|
78
|
+
global d_cb_test
|
|
79
|
+
d_cb_test = 0
|
|
80
|
+
def inc():
|
|
81
|
+
global d_cb_test
|
|
82
|
+
d_cb_test = 1
|
|
83
|
+
node1 = start_node("node-1", None, inc)
|
|
84
|
+
node2 = start_node("node-2", node1.config.port)
|
|
85
|
+
time.sleep(1)
|
|
86
|
+
node2.stop()
|
|
87
|
+
time.sleep(5)
|
|
88
|
+
self.assertEqual(d_cb_test, 1)
|
|
89
|
+
|
|
74
90
|
if __name__ == '__main__':
|
|
75
91
|
unittest.main()
|
{distributed_state_network-0.0.2 → distributed_state_network-0.0.3}/.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
|