Skip to content

Commit aa996c5

Browse files
committed
updated endpoints for sending kubo peerid separate than ipfscluster
1 parent 0159eb3 commit aa996c5

File tree

4 files changed

+240
-29
lines changed

4 files changed

+240
-29
lines changed

README.md

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ By default, Libp2p connections are established through Functionland's libp2p rel
4444
| --- | --- |
4545
| [blox](blox) | Blox provides the backend to receive the DAG created by fulamobile and store it |
4646
| [mobile](mobile) | Initiates a libp2p instance and interacts with WNFS (as its datastore) to encrypt the data and Send and receive files in a browser or an Android or iOS app. Available for [React-Native here](https://github.com/functionland/react-native-fula) and for [Android here](https://github.com/functionland/fula-build-aar) |
47-
| [exchange](exchange) | Fula exchange protocol is responsible for the ctual transfer of data |
47+
| [exchange](exchange) | Fula exchange protocol is responsible for the actual transfer of data |
48+
| [blockchain](blockchain) | On-chain interactions for pool management, manifests, and account operations |
49+
| [wap](wap) | Wireless Access Point server — provides HTTP endpoints (`/properties`, `/readiness`, `/wifi/*`, `/peer/*`) on port 3500 and mDNS service discovery |
4850

4951
## Other related libraries
5052

@@ -53,6 +55,145 @@ By default, Libp2p connections are established through Functionland's libp2p rel
5355
| [WNFS for Android](https://github.com/functionland/wnfs-android) | Android build for WNFS rust version |
5456
| [WNFS for iOS](https://github.com/functionland/wnfs-ios) | iOS build for WNFS rust version |
5557

58+
## PeerID Architecture
59+
60+
Blox devices maintain two separate libp2p identities:
61+
62+
- **Kubo peerID** — Derived via HMAC-SHA256 (domain `"fula-kubo-identity-v1"`) from the main identity key. Used by the embedded IPFS (kubo) node. Always available.
63+
- **ipfs-cluster peerID** — The original identity from `config.yaml`. Used by ipfs-cluster when installed.
64+
65+
Kubo is always running on the device; ipfs-cluster may not be installed. The `wifi.GetKuboPeerID()` utility function provides reliable access to the kubo peerID (via config file or kubo API fallback) without depending on ipfs-cluster.
66+
67+
### Getting ipfs-cluster and kubo PeerIDs
68+
69+
There are several ways to retrieve peerIDs depending on which part of the system you are working with.
70+
71+
#### 1. WAP HTTP Endpoints (port 3500)
72+
73+
These endpoints are served by the WAP server on the device. Mobile apps connect to them over the local network.
74+
75+
**GET `/properties`**
76+
77+
Returns device properties including the kubo peerID.
78+
79+
```bash
80+
curl http://<device-ip>:3500/properties
81+
```
82+
83+
Response (relevant fields):
84+
```json
85+
{
86+
"kubo_peer_id": "12D3KooWAbCdEf...",
87+
"hardwareID": "a1b2c3...",
88+
"bloxFreeSpace": { "device_count": 1, "size": 500000000000, "used": 120000000000, "avail": 380000000000, "used_percentage": 24 },
89+
"ota_version": "1.2.3",
90+
"...": "..."
91+
}
92+
```
93+
94+
- `kubo_peer_id` — The kubo (IPFS) peerID. Always present when kubo is running.
95+
96+
**GET `/readiness`**
97+
98+
Returns readiness status and properties, also including the kubo peerID.
99+
100+
```bash
101+
curl http://<device-ip>:3500/readiness
102+
```
103+
104+
Response (relevant fields):
105+
```json
106+
{
107+
"name": "fula_go",
108+
"kubo_peer_id": "12D3KooWAbCdEf...",
109+
"...": "..."
110+
}
111+
```
112+
113+
#### 2. Mobile SDK (`GetClusterInfo`)
114+
115+
From the mobile app, call `GetClusterInfo()` on the Fula client. This uses libp2p to call the blox node directly (no HTTP needed).
116+
117+
```go
118+
result, err := client.GetClusterInfo()
119+
```
120+
121+
Response JSON:
122+
```json
123+
{
124+
"cluster_peer_id": "12D3KooWXyZaBc...",
125+
"cluster_peer_name": "12D3KooWAbCdEf..."
126+
}
127+
```
128+
129+
| Field | Description | When empty |
130+
|-------|-------------|------------|
131+
| `cluster_peer_id` | ipfs-cluster peerID (from `/uniondrive/ipfs-cluster/identity.json`) | ipfs-cluster is not installed |
132+
| `cluster_peer_name` | kubo peerID (from kubo config or API) | Only if kubo is also unreachable |
133+
134+
When ipfs-cluster is not installed, the response will have an empty `cluster_peer_id` but `cluster_peer_name` (kubo peerID) will still be populated:
135+
```json
136+
{
137+
"cluster_peer_id": "",
138+
"cluster_peer_name": "12D3KooWAbCdEf..."
139+
}
140+
```
141+
142+
#### 3. Kubo API Directly (port 5001)
143+
144+
If you have direct access to the device, you can query kubo's identity endpoint:
145+
146+
```bash
147+
curl -X POST http://127.0.0.1:5001/api/v0/id
148+
```
149+
150+
Response:
151+
```json
152+
{
153+
"ID": "12D3KooWAbCdEf...",
154+
"PublicKey": "base64encodedkey...",
155+
"Addresses": [],
156+
"AgentVersion": "0.0.1",
157+
"ProtocolVersion": "fx_exchange/0.0.1",
158+
"Protocols": ["fx_exchange"]
159+
}
160+
```
161+
162+
- `ID` — The kubo peerID.
163+
164+
#### 4. mDNS Service Discovery
165+
166+
Blox devices advertise both peerIDs via mDNS TXT records on the local network (service type `_fulatower._tcp`).
167+
168+
| TXT Key | Value | Description |
169+
|---------|-------|-------------|
170+
| `bloxPeerIdString` | `12D3KooWAbCdEf...` | Kubo peerID |
171+
| `ipfsClusterID` | `12D3KooWXyZaBc...` | ipfs-cluster peerID |
172+
| `poolName` | `my-pool` | Pool name from config |
173+
| `authorizer` | `...` | Authorizer address |
174+
| `hardwareID` | `a1b2c3...` | Device hardware ID |
175+
176+
If the config file is missing or identity derivation fails, `bloxPeerIdString` falls back to reading from kubo's config file via `GetKuboPeerID()`. Fields default to `"NA"` when unavailable.
177+
178+
#### 5. Go Code (`wifi.GetKuboPeerID()`)
179+
180+
For internal Go code that needs the kubo peerID, use the standalone utility function:
181+
182+
```go
183+
import wifi "github.com/functionland/go-fula/wap/pkg/wifi"
184+
185+
kuboPeerID, err := wifi.GetKuboPeerID()
186+
if err != nil {
187+
// both kubo config file and kubo API are unavailable
188+
}
189+
```
190+
191+
This function tries two sources in order:
192+
1. Read `/internal/ipfs_data/config` and parse `Identity.PeerID`
193+
2. Call kubo API at `http://127.0.0.1:5001/api/v0/id` and parse the `ID` field
194+
195+
It does not depend on ipfs-cluster being installed.
196+
56197
## Run
57198

58199
```go

wap/cmd/mdns/mdns.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package mdns
22

33
import (
4+
"bytes"
45
"context"
6+
"crypto/hmac"
7+
"crypto/sha256"
58
"encoding/base64"
69
"net"
710
"os"
@@ -34,6 +37,7 @@ type Config struct {
3437

3538
type Meta struct {
3639
BloxPeerIdString string
40+
IpfsClusterID string
3741
PoolName string
3842
Authorizer string
3943
HardwareID string
@@ -48,6 +52,7 @@ func LoadConfig() {
4852
// Initialize with default values
4953
globalConfig = &Meta{
5054
BloxPeerIdString: defaultValue,
55+
IpfsClusterID: defaultValue,
5156
PoolName: defaultValue,
5257
Authorizer: defaultValue,
5358
HardwareID: defaultValue,
@@ -90,12 +95,42 @@ func LoadConfig() {
9095
if err != nil {
9196
log.Errorw("UnmarshalPrivateKey failed", "err", err)
9297
} else {
93-
bloxPeerId, err := peer.IDFromPrivateKey(key)
98+
// ipfs-cluster peer ID (direct from identity)
99+
clusterPeerId, err := peer.IDFromPrivateKey(key)
94100
if err != nil {
95-
log.Errorw("IDFromPrivateKey failed", "err", err)
101+
log.Errorw("IDFromPrivateKey failed for cluster", "err", err)
96102
} else {
97-
globalConfig.BloxPeerIdString = bloxPeerId.String() // Successfully decoded BloxPeerId
103+
globalConfig.IpfsClusterID = clusterPeerId.String()
98104
}
105+
106+
// kubo peer ID (derived via HMAC, same as deriveKuboKey in cmd/blox/main.go)
107+
rawKey, err := key.Raw()
108+
if err != nil {
109+
log.Errorw("Raw key extraction failed", "err", err)
110+
} else {
111+
mac := hmac.New(sha256.New, []byte("fula-kubo-identity-v1"))
112+
mac.Write(rawKey[:32])
113+
derivedSeed := mac.Sum(nil)
114+
115+
kuboPriv, _, err := crypto.GenerateEd25519Key(bytes.NewReader(derivedSeed))
116+
if err != nil {
117+
log.Errorw("GenerateEd25519Key for kubo failed", "err", err)
118+
} else {
119+
kuboPeerId, err := peer.IDFromPublicKey(kuboPriv.GetPublic())
120+
if err != nil {
121+
log.Errorw("IDFromPublicKey for kubo failed", "err", err)
122+
} else {
123+
globalConfig.BloxPeerIdString = kuboPeerId.String()
124+
}
125+
}
126+
}
127+
}
128+
}
129+
130+
// Fallback: if HMAC derivation failed, try reading kubo config directly
131+
if globalConfig.BloxPeerIdString == defaultValue {
132+
if kuboPeerID, err := wifi.GetKuboPeerID(); err == nil {
133+
globalConfig.BloxPeerIdString = kuboPeerID
99134
}
100135
}
101136

@@ -120,10 +155,11 @@ func createInfo() []string {
120155
// Use the loaded globalConfig here to create your metadata
121156
// Example:
122157
infoSlice := []string{
123-
"bloxPeerIdString=" + globalConfig.BloxPeerIdString, // Just an example, adjust according to actual data structure
158+
"bloxPeerIdString=" + globalConfig.BloxPeerIdString,
159+
"ipfsClusterID=" + globalConfig.IpfsClusterID,
124160
"poolName=" + globalConfig.PoolName,
125161
"authorizer=" + globalConfig.Authorizer,
126-
"hardwareID=" + globalConfig.HardwareID, // Assuming you handle hardwareID differently
162+
"hardwareID=" + globalConfig.HardwareID,
127163
}
128164

129165
return infoSlice

wap/pkg/server/server.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ func propertiesHandler(w http.ResponseWriter, r *http.Request) {
162162
response["restartNeeded"] = restartNeeded
163163
response["ota_version"] = config.OTA_VERSION
164164

165+
kuboPeerID, err := wifi.GetKuboPeerID()
166+
if err == nil {
167+
response["kubo_peer_id"] = kuboPeerID
168+
}
169+
165170
w.Header().Set("Content-Type", "application/json")
166171
w.WriteHeader(http.StatusOK)
167172
jsonErr := json.NewEncoder(w).Encode(response)
@@ -281,6 +286,12 @@ func readinessHandler(w http.ResponseWriter, r *http.Request) {
281286
return
282287
}
283288
p["name"] = config.PROJECT_NAME
289+
290+
kuboPeerID, err := wifi.GetKuboPeerID()
291+
if err == nil {
292+
p["kubo_peer_id"] = kuboPeerID
293+
}
294+
284295
w.Header().Set("Content-Type", "application/json")
285296
w.WriteHeader(http.StatusOK)
286297
jsonErr := json.NewEncoder(w).Encode(p)

wap/pkg/wifi/properties.go

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -195,37 +195,60 @@ func GetDockerImageBuildDates() (GetDockerImageBuildDatesResponse, error) {
195195
return GetDockerImageBuildDatesResponse{Images: images}, nil
196196
}
197197

198-
func GetClusterInfo() (GetClusterInfoResponse, error) {
199-
// Read cluster peer ID from identity.json
200-
clusterIdentityData, err := os.ReadFile("/uniondrive/ipfs-cluster/identity.json")
198+
// GetKuboPeerID reliably returns kubo's peerID without depending on ipfs-cluster.
199+
// It first tries reading from the kubo config file, then falls back to the kubo API.
200+
func GetKuboPeerID() (string, error) {
201+
// Try reading from kubo config file first
202+
kuboConfigData, err := os.ReadFile("/internal/ipfs_data/config")
203+
if err == nil {
204+
var kuboConfig struct {
205+
Identity struct {
206+
PeerID string `json:"PeerID"`
207+
} `json:"Identity"`
208+
}
209+
if err := json.Unmarshal(kuboConfigData, &kuboConfig); err == nil && kuboConfig.Identity.PeerID != "" {
210+
return kuboConfig.Identity.PeerID, nil
211+
}
212+
}
213+
214+
// Fallback: query kubo API
215+
resp, err := http.Post("http://127.0.0.1:5001/api/v0/id", "", nil)
201216
if err != nil {
202-
return GetClusterInfoResponse{}, fmt.Errorf("reading cluster identity: %w", err)
217+
return "", fmt.Errorf("both config file and kubo API failed: %w", err)
203218
}
204-
var clusterIdentity struct {
205-
ID string `json:"id"`
219+
defer resp.Body.Close()
220+
var idResp struct {
221+
ID string `json:"ID"`
206222
}
207-
if err := json.Unmarshal(clusterIdentityData, &clusterIdentity); err != nil {
208-
return GetClusterInfoResponse{}, fmt.Errorf("parsing cluster identity: %w", err)
223+
if err := json.NewDecoder(resp.Body).Decode(&idResp); err != nil {
224+
return "", fmt.Errorf("failed to parse kubo API response: %w", err)
209225
}
226+
return idResp.ID, nil
227+
}
210228

211-
// Read kubo peer ID (used as CLUSTER_PEERNAME) from kubo config
212-
kuboConfigData, err := os.ReadFile("/internal/ipfs_data/config")
213-
if err != nil {
214-
return GetClusterInfoResponse{}, fmt.Errorf("reading kubo config: %w", err)
215-
}
216-
var kuboConfig struct {
217-
Identity struct {
218-
PeerID string `json:"PeerID"`
219-
} `json:"Identity"`
229+
func GetClusterInfo() (GetClusterInfoResponse, error) {
230+
resp := GetClusterInfoResponse{}
231+
232+
// Best-effort: read cluster peer ID from identity.json (file may not exist)
233+
clusterIdentityData, err := os.ReadFile("/uniondrive/ipfs-cluster/identity.json")
234+
if err == nil {
235+
var clusterIdentity struct {
236+
ID string `json:"id"`
237+
}
238+
if err := json.Unmarshal(clusterIdentityData, &clusterIdentity); err == nil {
239+
resp.ClusterPeerID = clusterIdentity.ID
240+
}
220241
}
221-
if err := json.Unmarshal(kuboConfigData, &kuboConfig); err != nil {
222-
return GetClusterInfoResponse{}, fmt.Errorf("parsing kubo config: %w", err)
242+
// No error return here — cluster being unavailable is expected
243+
244+
// Always get kubo peer ID reliably
245+
kuboPeerID, err := GetKuboPeerID()
246+
if err != nil {
247+
return resp, fmt.Errorf("getting kubo peer ID: %w", err)
223248
}
249+
resp.ClusterPeerName = kuboPeerID
224250

225-
return GetClusterInfoResponse{
226-
ClusterPeerID: clusterIdentity.ID,
227-
ClusterPeerName: kuboConfig.Identity.PeerID,
228-
}, nil
251+
return resp, nil
229252
}
230253

231254
func GetHardwareID() (string, error) {

0 commit comments

Comments
 (0)