@relay-federation/bridge 0.3.0 → 0.3.2
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.
- package/cli.js +63 -14
- package/dashboard/index.html +2 -2
- package/lib/config.js +15 -2
- package/lib/peer-manager.js +1 -1
- package/lib/status-server.js +2 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -63,18 +63,19 @@ async function cmdInit () {
|
|
|
63
63
|
const config = await initConfig(dir)
|
|
64
64
|
|
|
65
65
|
console.log('Bridge initialized!\n')
|
|
66
|
-
console.log(`
|
|
67
|
-
console.log(`
|
|
68
|
-
console.log(`
|
|
69
|
-
console.log(`
|
|
66
|
+
console.log(` Name: ${config.name}`)
|
|
67
|
+
console.log(` Config: ${dir}/config.json`)
|
|
68
|
+
console.log(` Endpoint: ${config.endpoint}`)
|
|
69
|
+
console.log(` Pubkey: ${config.pubkeyHex}`)
|
|
70
|
+
console.log(` Address: ${config.address}`)
|
|
71
|
+
console.log(` Secret: ${config.statusSecret}`)
|
|
70
72
|
console.log('')
|
|
71
73
|
console.log(' Save your operator secret! You need it to log into the dashboard.')
|
|
72
74
|
console.log('')
|
|
73
75
|
console.log('Next steps:')
|
|
74
|
-
console.log(` 1.
|
|
75
|
-
console.log(
|
|
76
|
-
console.log(' 3.
|
|
77
|
-
console.log(' 4. Run: relay-bridge register')
|
|
76
|
+
console.log(` 1. Fund your bridge: send BSV to ${config.address}`)
|
|
77
|
+
console.log(' 2. Import the funding tx: relay-bridge fund <rawTxHex>')
|
|
78
|
+
console.log(' 3. Run: relay-bridge register')
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
async function cmdSecret () {
|
|
@@ -319,9 +320,10 @@ async function cmdStart () {
|
|
|
319
320
|
})
|
|
320
321
|
|
|
321
322
|
// Wire peer health tracking
|
|
322
|
-
peerManager.on('peer:connect', ({ pubkeyHex }) => {
|
|
323
|
+
peerManager.on('peer:connect', ({ pubkeyHex, endpoint }) => {
|
|
323
324
|
peerHealth.recordSeen(pubkeyHex)
|
|
324
325
|
scorer.setStakeAge(pubkeyHex, 7)
|
|
326
|
+
if (endpoint) gossipManager.addSeed({ pubkeyHex, endpoint, meshId: config.meshId })
|
|
325
327
|
})
|
|
326
328
|
|
|
327
329
|
peerManager.on('peer:disconnect', ({ pubkeyHex }) => {
|
|
@@ -429,6 +431,8 @@ async function cmdStart () {
|
|
|
429
431
|
const { extractOpReturnData, decodePayload, PROTOCOL_PREFIX } = await import('../registry/lib/cbor.js')
|
|
430
432
|
const { Transaction: BsvTx } = await import('@bsv/sdk')
|
|
431
433
|
|
|
434
|
+
// Registry bootstrapped via discoverNewPeers() after server start (no WoC dependency)
|
|
435
|
+
|
|
432
436
|
watcher.on('utxo:received', async ({ txid, hash160 }) => {
|
|
433
437
|
if (hash160 !== beaconHash160) return
|
|
434
438
|
|
|
@@ -640,14 +644,17 @@ async function cmdStart () {
|
|
|
640
644
|
let gossipStarted = false
|
|
641
645
|
|
|
642
646
|
// Start gossip after first peer connection completes.
|
|
643
|
-
//
|
|
644
|
-
//
|
|
647
|
+
// Delay 5s so all seed handshakes finish before gossip broadcasts
|
|
648
|
+
// (immediate broadcast would send announce/getpeers through connections
|
|
649
|
+
// whose inbound side is still waiting for verify — breaking the handshake).
|
|
645
650
|
peerManager.on('peer:connect', () => {
|
|
646
651
|
if (!gossipStarted) {
|
|
647
652
|
gossipStarted = true
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
653
|
+
setTimeout(() => {
|
|
654
|
+
gossipManager.start()
|
|
655
|
+
gossipManager.requestPeersFromAll()
|
|
656
|
+
console.log('Gossip started')
|
|
657
|
+
}, 5000)
|
|
651
658
|
|
|
652
659
|
// Periodic peer refresh — re-request peer lists every 10 minutes
|
|
653
660
|
// Catches registrations missed during downtime or initial gossip
|
|
@@ -702,6 +709,7 @@ async function cmdStart () {
|
|
|
702
709
|
// Connect to seed peers (accept both string URLs and {pubkeyHex, endpoint} objects)
|
|
703
710
|
console.log(`Connecting to ${seedPeers.length} seed peer(s)...`)
|
|
704
711
|
for (let i = 0; i < seedPeers.length; i++) {
|
|
712
|
+
if (i > 0) await new Promise(r => setTimeout(r, 2000)) // stagger to avoid handshake races
|
|
705
713
|
const seed = seedPeers[i]
|
|
706
714
|
const endpoint = typeof seed === 'string' ? seed : seed.endpoint
|
|
707
715
|
const pubkey = typeof seed === 'string' ? `seed_${i}` : seed.pubkeyHex
|
|
@@ -769,6 +777,47 @@ async function cmdStart () {
|
|
|
769
777
|
statusServer.startAppMonitoring()
|
|
770
778
|
console.log(` Status: http://127.0.0.1:${statusPort}/status`)
|
|
771
779
|
|
|
780
|
+
// ── Peer discovery — bootstrap registry from seed peers, then periodic refresh ──
|
|
781
|
+
const knownEndpoints = new Set()
|
|
782
|
+
for (const sp of (config.seedPeers || [])) knownEndpoints.add(sp.endpoint)
|
|
783
|
+
knownEndpoints.add(config.endpoint)
|
|
784
|
+
|
|
785
|
+
async function discoverNewPeers () {
|
|
786
|
+
const peersToQuery = [...(config.seedPeers || [])]
|
|
787
|
+
for (const [, conn] of peerManager.peers) {
|
|
788
|
+
if (conn.endpoint && conn.readyState === 1) peersToQuery.push({ endpoint: conn.endpoint })
|
|
789
|
+
}
|
|
790
|
+
for (const peer of peersToQuery) {
|
|
791
|
+
try {
|
|
792
|
+
const ep = peer.endpoint || ''
|
|
793
|
+
const u = new URL(ep)
|
|
794
|
+
const statusUrl = 'http://' + u.hostname + ':' + (parseInt(u.port, 10) + 1000) + '/discover'
|
|
795
|
+
const res = await fetch(statusUrl, { signal: AbortSignal.timeout(5000) })
|
|
796
|
+
if (!res.ok) continue
|
|
797
|
+
const data = await res.json()
|
|
798
|
+
if (!data.bridges) continue
|
|
799
|
+
for (const b of data.bridges) {
|
|
800
|
+
if (!b.endpoint) continue
|
|
801
|
+
if (b.pubkeyHex) registeredPubkeys.add(b.pubkeyHex)
|
|
802
|
+
seedEndpoints.add(b.endpoint)
|
|
803
|
+
if (knownEndpoints.has(b.endpoint)) continue
|
|
804
|
+
knownEndpoints.add(b.endpoint)
|
|
805
|
+
const conn = peerManager.connectToPeer({ endpoint: b.endpoint, pubkeyHex: b.pubkeyHex })
|
|
806
|
+
if (conn) {
|
|
807
|
+
conn.on('open', () => performOutboundHandshake(conn))
|
|
808
|
+
const msg = `Discovered new peer: ${b.name || b.pubkeyHex?.slice(0, 16) || b.endpoint}`
|
|
809
|
+
console.log(msg)
|
|
810
|
+
statusServer.addLog(msg)
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
} catch {}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
await discoverNewPeers()
|
|
817
|
+
console.log(` Registry: ${registeredPubkeys.size} trusted pubkeys after peer discovery`)
|
|
818
|
+
setTimeout(discoverNewPeers, 5000)
|
|
819
|
+
setInterval(discoverNewPeers, 300000)
|
|
820
|
+
|
|
772
821
|
// ── 9. Log events (dual: console + status server ring buffer) ──
|
|
773
822
|
peerManager.on('peer:connect', ({ pubkeyHex }) => {
|
|
774
823
|
const msg = `Peer connected: ${pubkeyHex.slice(0, 16)}...`
|
package/dashboard/index.html
CHANGED
|
@@ -978,7 +978,7 @@ async function fetchBridge(bridge) {
|
|
|
978
978
|
const r = await fetch(bridge.url + '/status' + getAuthParam(bridge.url), { signal: AbortSignal.timeout(5000) });
|
|
979
979
|
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
980
980
|
const data = await r.json();
|
|
981
|
-
data._name = bridge.name; data._url = bridge.url; data._error = null; data._lastSeen = Date.now();
|
|
981
|
+
data._name = (data.bridge && data.bridge.name) || bridge.name; data._url = bridge.url; data._error = null; data._lastSeen = Date.now();
|
|
982
982
|
return data;
|
|
983
983
|
} catch (e) {
|
|
984
984
|
return { _name: bridge.name, _url: bridge.url, _error: e.message,
|
|
@@ -1009,7 +1009,7 @@ async function discoverBridges() {
|
|
|
1009
1009
|
if (!b.statusUrl) continue;
|
|
1010
1010
|
const base = b.statusUrl.replace(/\/status$/, '');
|
|
1011
1011
|
if (!known.has(base)) {
|
|
1012
|
-
const name = 'bridge-' + (b.pubkeyHex ? b.pubkeyHex.slice(0, 8) : known.size);
|
|
1012
|
+
const name = b.name || 'bridge-' + (b.pubkeyHex ? b.pubkeyHex.slice(0, 8) : known.size);
|
|
1013
1013
|
known.set(base, name);
|
|
1014
1014
|
}
|
|
1015
1015
|
}
|
package/lib/config.js
CHANGED
|
@@ -21,16 +21,29 @@ export function defaultConfigDir () {
|
|
|
21
21
|
* @param {string} [dir] — Config directory (default: ~/.relay-bridge)
|
|
22
22
|
* @returns {Promise<object>} The generated config
|
|
23
23
|
*/
|
|
24
|
-
export async function initConfig (dir = DEFAULT_DIR) {
|
|
24
|
+
export async function initConfig (dir = DEFAULT_DIR, opts = {}) {
|
|
25
25
|
const privKey = PrivateKey.fromRandom()
|
|
26
26
|
|
|
27
27
|
const address = privKey.toPublicKey().toAddress()
|
|
28
28
|
|
|
29
|
+
// Auto-detect public IP
|
|
30
|
+
let publicIp = opts.ip || null
|
|
31
|
+
if (!publicIp) {
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch('https://api.ipify.org?format=json', { signal: AbortSignal.timeout(5000) })
|
|
34
|
+
if (res.ok) publicIp = (await res.json()).ip
|
|
35
|
+
} catch {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const name = opts.name || (publicIp ? 'bridge-' + publicIp.split('.').pop() : 'bridge-' + privKey.toPublicKey().toString().slice(0, 8))
|
|
39
|
+
const endpoint = publicIp ? 'ws://' + publicIp + ':8333' : 'ws://your-bridge-ip:8333'
|
|
40
|
+
|
|
29
41
|
const config = {
|
|
42
|
+
name,
|
|
30
43
|
wif: privKey.toWif(),
|
|
31
44
|
pubkeyHex: privKey.toPublicKey().toString(),
|
|
32
45
|
address,
|
|
33
|
-
endpoint
|
|
46
|
+
endpoint,
|
|
34
47
|
meshId: '70016',
|
|
35
48
|
capabilities: ['tx_relay', 'header_sync', 'broadcast', 'address_history'],
|
|
36
49
|
spvEndpoint: 'https://relay.indelible.one',
|
package/lib/peer-manager.js
CHANGED
|
@@ -169,7 +169,7 @@ export class PeerManager extends EventEmitter {
|
|
|
169
169
|
// If cryptographic handshake is available, use it
|
|
170
170
|
if (opts.handshake && msg.nonce && Array.isArray(msg.versions)) {
|
|
171
171
|
const isSeed = opts.seedEndpoints && opts.seedEndpoints.has(msg.endpoint)
|
|
172
|
-
const result = opts.handshake.handleHello(msg,
|
|
172
|
+
const result = opts.handshake.handleHello(msg, null) // cryptographic handshake is the security layer — no whitelist gate
|
|
173
173
|
if (result.error) {
|
|
174
174
|
ws.send(JSON.stringify({ type: 'error', error: result.error }))
|
|
175
175
|
ws.close()
|
package/lib/status-server.js
CHANGED
|
@@ -112,6 +112,7 @@ export class StatusServer {
|
|
|
112
112
|
|
|
113
113
|
const status = {
|
|
114
114
|
bridge: {
|
|
115
|
+
name: this._config.name || null,
|
|
115
116
|
pubkeyHex: this._config.pubkeyHex || null,
|
|
116
117
|
meshId: this._config.meshId || null,
|
|
117
118
|
uptimeSeconds: Math.floor((Date.now() - this._startedAt) / 1000)
|
|
@@ -425,6 +426,7 @@ export class StatusServer {
|
|
|
425
426
|
const bridges = []
|
|
426
427
|
// Add self
|
|
427
428
|
bridges.push({
|
|
429
|
+
name: this._config.name || null,
|
|
428
430
|
pubkeyHex: this._config.pubkeyHex || null,
|
|
429
431
|
endpoint: this._config.endpoint || null,
|
|
430
432
|
meshId: this._config.meshId || null,
|