Skip to content

Commit ec72aea

Browse files
committed
uploadInputDataSystemTest
1 parent e76cfbd commit ec72aea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1742
-337
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ go.work.sum
2525
# env file
2626
.env
2727
/data
28+
/tests/system/data

.vscode/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
"type": "go",
77
"request": "launch",
88
"mode": "debug",
9-
"program": "${workspaceFolder}/main.go",
9+
"program": "${workspaceFolder}/supernode/main.go",
1010
"env": {},
11-
"args": [],
11+
"args": ["start"],
1212
"showLog": true
1313
}
1414
]

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ module github.com/LumeraProtocol/supernode
33
go 1.24.0
44

55
require (
6-
cosmossdk.io/api v0.7.6
7-
github.com/LumeraProtocol/lumera v0.4.2
6+
github.com/LumeraProtocol/lumera v0.4.3
87
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
98
github.com/cenkalti/backoff v2.2.1+incompatible
109
github.com/cenkalti/backoff/v4 v4.3.0
@@ -25,6 +24,7 @@ require (
2524
github.com/pkg/errors v0.9.1
2625
github.com/sirupsen/logrus v1.9.3
2726
github.com/spf13/viper v1.19.0
27+
github.com/spf13/cobra v1.8.1
2828
github.com/stretchr/testify v1.10.0
2929
github.com/x-cray/logrus-prefixed-formatter v0.5.2
3030
go.uber.org/ratelimit v0.3.1
@@ -34,9 +34,11 @@ require (
3434
google.golang.org/grpc v1.70.0
3535
google.golang.org/protobuf v1.36.5
3636
gopkg.in/natefinch/lumberjack.v2 v2.2.1
37+
gopkg.in/yaml.v3 v3.0.1
3738
)
3839

3940
require (
41+
cosmossdk.io/api v0.7.6 // indirect
4042
cosmossdk.io/collections v0.4.0 // indirect
4143
cosmossdk.io/core v0.11.1 // indirect
4244
cosmossdk.io/depinject v1.1.0 // indirect
@@ -147,7 +149,6 @@ require (
147149
github.com/sourcegraph/conc v0.3.0 // indirect
148150
github.com/spf13/afero v1.11.0 // indirect
149151
github.com/spf13/cast v1.7.1 // indirect
150-
github.com/spf13/cobra v1.8.1 // indirect
151152
github.com/spf13/pflag v1.0.5 // indirect
152153
github.com/stretchr/objx v0.5.2 // indirect
153154
github.com/subosito/gotenv v1.6.0 // indirect
@@ -167,7 +168,6 @@ require (
167168
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect
168169
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
169170
gopkg.in/ini.v1 v1.67.0 // indirect
170-
gopkg.in/yaml.v3 v3.0.1 // indirect
171171
gotest.tools/v3 v3.5.1 // indirect
172172
lukechampine.com/uint128 v1.3.0 // indirect
173173
nhooyr.io/websocket v1.8.6 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3
6161
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
6262
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
6363
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
64-
github.com/LumeraProtocol/lumera v0.4.2 h1:yW7mwoYiBCcFLFNs9AgmaLc0DVkir95NGFtR2j/VYsw=
65-
github.com/LumeraProtocol/lumera v0.4.2/go.mod h1:MRqVY+f8edEBkDvpr4z2nJpglp3Qj1OUvjeWvrvIUSM=
64+
github.com/LumeraProtocol/lumera v0.4.3 h1:q/FuT+JOLIpYdlunczRUr6K85r9Sn0lKvGltSrj4r6s=
65+
github.com/LumeraProtocol/lumera v0.4.3/go.mod h1:MRqVY+f8edEBkDvpr4z2nJpglp3Qj1OUvjeWvrvIUSM=
6666
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
6767
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
6868
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=

p2p/DEVDOCS.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Lumera P2P Service
2+
3+
A Kademlia-based distributed hash table (DHT) implementation that provides decentralized storage and retrieval capabilities for the Lumera network.
4+
5+
## Overview
6+
7+
The P2P service enables supernodes to:
8+
- Store and retrieve data in a distributed network
9+
- Auto-discover other nodes via the Lumera blockchain
10+
- Securely communicate using ALTS (Application Layer Transport Security)
11+
- Replicate data across the network for redundancy
12+
13+
## Architecture
14+
15+
```
16+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
17+
│ P2P API │────▶│ DHT │────▶│ Network │
18+
└─────────────┘ └─────────────┘ └─────────────┘
19+
│ │ │
20+
│ │ │
21+
▼ ▼ ▼
22+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
23+
│ Local Store │ │ Hash Table │ │ Conn Pool │
24+
└─────────────┘ └─────────────┘ └─────────────┘
25+
```
26+
27+
- **P2P API**: Public interface for store/retrieve operations
28+
- **DHT**: Core DHT implementation using Kademlia algorithm
29+
- **Network**: Handles peer connections, messaging, and encryption
30+
- **Local Store**: SQLite database for persistent storage
31+
- **Hash Table**: Manages routing table of known peers
32+
- **Conn Pool**: Manages network connections to peers
33+
34+
## Configuration
35+
36+
Key configuration parameters in the YAML config:
37+
38+
```yaml
39+
p2p:
40+
listen_address: "0.0.0.0" # Network interface to listen on
41+
port: 4445 # Port for P2P communication
42+
data_dir: "~/.lumera/p2p" # Directory for DHT data storage
43+
bootstrap_nodes: "" # Optional comma-separated list of bootstrap nodes
44+
external_ip: "" # Optional override for auto-detected external IP
45+
```
46+
47+
### Configuration Field Details
48+
49+
| Field | Description | Default | Required |
50+
|-------|-------------|---------|----------|
51+
| `listen_address` | Network interface to bind to | `0.0.0.0` | Yes |
52+
| `port` | Port to listen for P2P connections | `4445` | Yes |
53+
| `data_dir` | Storage directory for P2P data | N/A | Yes |
54+
| `bootstrap_nodes` | Format: `identity@host:port,identity2@host2:port2` | Auto-fetched from blockchain | No |
55+
| `external_ip` | Public IP address for the node | Auto-detected | No |
56+
57+
The **Node ID** is derived from the Lumera account in your keyring specified by the `key_name` in the supernode config.
58+
59+
## Usage Example
60+
61+
Initializing the P2P service:
62+
63+
```go
64+
// Create P2P configuration
65+
p2pConfig := &p2p.Config{
66+
ListenAddress: "0.0.0.0",
67+
Port: 4445,
68+
DataDir: "/path/to/data",
69+
ID: supernodeAddress, // Lumera account address
70+
}
71+
72+
// Initialize P2P service
73+
p2pService, err := p2p.New(ctx, p2pConfig, lumeraClient, keyring, rqStore, nil, nil)
74+
if err != nil {
75+
return err
76+
}
77+
78+
// Start P2P service
79+
if err := p2pService.Run(ctx); err != nil {
80+
return err
81+
}
82+
```
83+
84+
Storing and retrieving data:
85+
86+
```go
87+
// Store data - returns base58-encoded key
88+
key, err := p2pService.Store(ctx, []byte("Hello, world!"), 0)
89+
if err != nil {
90+
return err
91+
}
92+
93+
// Retrieve data
94+
data, err := p2pService.Retrieve(ctx, key)
95+
if err != nil {
96+
return err
97+
}
98+
```
99+
100+
## Key Components
101+
102+
### Keyring Integration
103+
104+
The P2P service uses the Cosmos SDK keyring for:
105+
- Secure node identity (derived from your Lumera account)
106+
- Cryptographic signatures for secure communication
107+
- Authentication between peers
108+
109+
### Bootstrap Process
110+
111+
When a node starts:
112+
1. It checks for configured bootstrap nodes
113+
2. If none provided, queries the Lumera blockchain for active supernodes
114+
3. Connects to bootstrap nodes and performs iterative `FIND_NODE` queries
115+
4. Builds its routing table based on responses
116+
5. Becomes a full participant in the network
117+
118+
### Data Replication
119+
120+
Data stored in the network is:
121+
1. Stored locally in SQLite
122+
2. Replicated to the closest `Alpha` (6) nodes in the DHT
123+
3. Periodically checked and re-replicated as nodes come and go
124+
125+
## Troubleshooting
126+
127+
- **Can't connect to network**: Verify your `external_ip` is correct or remove it to use auto-detection
128+
- **Bootstrap fails**: Ensure the Lumera client is connected or specify manual bootstrap nodes
129+
- **Storage issues**: Check `data_dir` path and permissions
130+
131+
## Development Notes
132+
133+
- Use `localOnly: true` with `Retrieve()` to only check local storage
134+
- DHT operations use a modified Kademlia with `Alpha=6` for parallelism
135+
- Key format is base58-encoded SHA-256 hash of the data

p2p/kademlia/bootstrap.go

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/LumeraProtocol/supernode/pkg/errors"
1212

1313
"github.com/LumeraProtocol/supernode/pkg/log"
14-
"github.com/LumeraProtocol/supernode/pkg/lumera"
1514
ltc "github.com/LumeraProtocol/supernode/pkg/net/credentials"
1615
)
1716

@@ -77,7 +76,7 @@ func (s *DHT) setBootstrapNodesFromConfigVar(ctx context.Context, bootstrapNodes
7776
}
7877

7978
nodes = append(nodes, &Node{
80-
ID: []byte(lumeraAddress.Identity),
79+
ID: []byte(lumeraAddress.Identity),
8180
IP: lumeraAddress.Host,
8281
Port: lumeraAddress.Port,
8382
})
@@ -88,6 +87,7 @@ func (s *DHT) setBootstrapNodesFromConfigVar(ctx context.Context, bootstrapNodes
8887
return nil
8988
}
9089

90+
// ConfigureBootstrapNodes connects with lumera client & gets p2p boostrap ip & port
9191
// ConfigureBootstrapNodes connects with lumera client & gets p2p boostrap ip & port
9292
func (s *DHT) ConfigureBootstrapNodes(ctx context.Context, bootstrapNodes string) error {
9393
if bootstrapNodes != "" {
@@ -100,53 +100,77 @@ func (s *DHT) ConfigureBootstrapNodes(ctx context.Context, bootstrapNodes string
100100
}
101101
selfAddress = fmt.Sprintf("%s:%d", selfAddress, s.options.Port)
102102

103-
get := func(ctx context.Context, f func(context.Context) (lumera.SuperNodeAddressInfos, error)) ([]*Node, error) {
104-
mns, err := f(ctx)
103+
var boostrapNodes []*Node
104+
105+
if s.options.LumeraClient != nil {
106+
// Get the latest block to determine height
107+
latestBlockResp, err := s.options.LumeraClient.Node().GetLatestBlock(ctx)
108+
if err != nil {
109+
return fmt.Errorf("failed to get latest block: %w", err)
110+
}
111+
112+
// Get the block height
113+
blockHeight := uint64(latestBlockResp.SdkBlock.Header.Height)
114+
115+
// Get top supernodes for this block
116+
supernodeResp, err := s.options.LumeraClient.SuperNode().GetTopSuperNodesForBlock(ctx, blockHeight)
105117
if err != nil {
106-
return []*Node{}, err
118+
return fmt.Errorf("failed to get top supernodes: %w", err)
107119
}
108120

109121
mapNodes := map[string]*Node{}
110-
for _, mn := range mns {
111-
node, err := s.parseNode(mn.ExtP2P, selfAddress)
122+
123+
for _, supernode := range supernodeResp.Supernodes {
124+
// Find the latest IP address (with highest block height)
125+
var latestIP string
126+
var maxHeight int64 = -1
127+
128+
for _, ipHistory := range supernode.PrevIpAddresses {
129+
if ipHistory.Height > maxHeight {
130+
maxHeight = ipHistory.Height
131+
latestIP = ipHistory.Address
132+
}
133+
}
134+
135+
if latestIP == "" {
136+
log.P2P().WithContext(ctx).
137+
WithField("supernode", supernode.SupernodeAccount).
138+
Warn("No valid IP address found for supernode")
139+
continue
140+
}
141+
142+
// Parse the node from the IP address
143+
node, err := s.parseNode(latestIP, selfAddress)
112144
if err != nil {
113-
log.P2P().WithContext(ctx).WithError(err).WithField("extP2P", mn.ExtP2P).Warn("Skip Bad Boostrap Address")
145+
log.P2P().WithContext(ctx).WithError(err).
146+
WithField("address", latestIP).
147+
WithField("supernode", supernode.SupernodeAccount).
148+
Warn("Skip Bad Bootstrap Address")
114149
continue
115150
}
116151

117-
mapNodes[mn.ExtP2P] = node
152+
// Store the supernode account as the node ID
153+
node.ID = []byte(supernode.SupernodeAccount)
154+
mapNodes[latestIP] = node
118155
}
119156

120-
nodes := []*Node{}
157+
// Convert the map to a slice
121158
for _, node := range mapNodes {
122-
nodes = append(nodes, node)
159+
boostrapNodes = append(boostrapNodes, node)
123160
}
124-
125-
return nodes, nil
126161
}
127162

128-
var boostrapNodes []*Node
129-
if s.options.LumeraNetwork != nil {
130-
boostrapNodes, err := get(ctx, s.options.LumeraNetwork.MasterNodesExtra)
131-
if err != nil {
132-
return fmt.Errorf("masternodesTop failed: %s", err)
133-
} else if len(boostrapNodes) == 0 {
134-
boostrapNodes, err = get(ctx, s.options.LumeraNetwork.MasterNodesTop)
135-
if err != nil {
136-
return fmt.Errorf("masternodesExtra failed: %s", err)
137-
} else if len(boostrapNodes) == 0 {
138-
log.P2P().WithContext(ctx).Error("unable to fetch bootstrap ip. Missing extP2P")
139-
140-
return nil
141-
}
142-
}
163+
if len(boostrapNodes) == 0 {
164+
log.P2P().WithContext(ctx).Error("unable to fetch bootstrap IP addresses. No valid supernodes found.")
165+
return nil
143166
}
144167

145168
for _, node := range boostrapNodes {
146169
log.P2P().WithContext(ctx).WithFields(log.Fields{
147170
"bootstap_ip": node.IP,
148171
"bootstrap_port": node.Port,
149-
}).Info("adding p2p bootstap node")
172+
"node_id": string(node.ID),
173+
}).Info("adding p2p bootstrap node")
150174
}
151175

152176
s.options.BootstrapNodes = append(s.options.BootstrapNodes, boostrapNodes...)

0 commit comments

Comments
 (0)