Skip to content

Commit db7fcde

Browse files
committed
Fix P2P: wait for TCP connection before sending version handshake
The version message was being sent immediately after calling socketProvider.connect(), but on react-native-tcp-socket the TCP connection is asynchronous. The version message was silently dropped because the socket wasn't connected yet. Added onConnect callback to SocketConnection interface. Peer now waits for onConnect before transitioning to 'connected' state and starting the version/verack handshake. This matches how Bitcoin Core handles the connection lifecycle.
1 parent f6fd225 commit db7fcde

2 files changed

Lines changed: 25 additions & 6 deletions

File tree

src/p2p/peer.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
// ---------------------------------------------------------------------------
2727

2828
export interface SocketConnection {
29+
onConnect(callback: () => void): void;
2930
onData(callback: (data: Uint8Array) => void): void;
3031
onClose(callback: () => void): void;
3132
onError(callback: (err: Error) => void): void;
@@ -148,6 +149,11 @@ export class Peer {
148149
return;
149150
}
150151

152+
this.socket.onConnect(() => {
153+
this._state = "connected";
154+
this.startHandshake();
155+
});
156+
151157
this.socket.onData((data: Uint8Array) => {
152158
this.onSocketData(data);
153159
});
@@ -160,9 +166,6 @@ export class Peer {
160166
this.events.onError(this, err);
161167
this.handleDisconnect(`error: ${err.message}`);
162168
});
163-
164-
this._state = "connected";
165-
this.startHandshake();
166169
}
167170

168171
disconnect(): void {

src/p2p/socket-provider.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,20 @@ function getElectronAPI(): ElectronAPI | undefined {
6767

6868
class NativeSocketProvider implements SocketProvider {
6969
connect(host: string, port: number): SocketConnection {
70-
// Dynamic require to avoid web bundling issues (same pattern as kv-store.ts)
71-
// eslint-disable-next-line @typescript-eslint/no-var-requires
7270
const TcpSocket = require("react-native-tcp-socket") as { default: NativeTcpModule };
71+
72+
let connectCallback: (() => void) | undefined;
73+
7374
const socket = TcpSocket.default.createConnection({ host, port }, () => {
74-
// Connection callback - no-op, events handle the lifecycle
75+
if (connectCallback) {
76+
connectCallback();
77+
}
7578
});
7679

7780
return {
81+
onConnect: (cb: () => void) => {
82+
connectCallback = cb;
83+
},
7884
onData: (cb: (data: Uint8Array) => void) => {
7985
socket.on("data", (data: Uint8Array) => {
8086
cb(new Uint8Array(data));
@@ -111,7 +117,17 @@ class ElectronSocketProvider implements SocketProvider {
111117

112118
const socket = electronAPI.p2p.connect(host, port);
113119

120+
// Electron's IPC p2p:connect resolves when the TCP connection is established,
121+
// so we fire onConnect immediately after setup.
122+
let connectCallback: (() => void) | undefined;
123+
setTimeout(() => {
124+
if (connectCallback) connectCallback();
125+
}, 0);
126+
114127
return {
128+
onConnect: (cb: () => void) => {
129+
connectCallback = cb;
130+
},
115131
onData: (cb: (data: Uint8Array) => void) => {
116132
socket.onData(cb);
117133
},

0 commit comments

Comments
 (0)