ZEUS-3972: fix: Neutrino: invalid peer displays as good
This commit is contained in:
@@ -1370,6 +1370,7 @@
|
||||
"views.Settings.EmbeddedNode.NeutrinoPeers.allowingOtherPeers": "Allowing connections to other peers.",
|
||||
"views.Settings.EmbeddedNode.NeutrinoPeers.notAllowingOtherPeers": "Not allowing connections to other peers.",
|
||||
"views.Settings.EmbeddedNode.NeutrinoPeers.timedOut": "Ping timed out",
|
||||
"views.Settings.EmbeddedNode.NeutrinoPeers.unreachable": "Host could not be reached",
|
||||
"views.Settings.EmbeddedNode.NeutrinoPeers.invalidHost": "Please enter a hostname or IP address without http:// or https://",
|
||||
"views.Settings.EmbeddedNode.NeutrinoPeers.optimize": "Optimize peers selection",
|
||||
"views.Settings.EmbeddedNode.ZeroConfPeers.title": "Zero conf Peers",
|
||||
|
||||
@@ -84,11 +84,15 @@ export default class AlertStore {
|
||||
const peer = peers[i];
|
||||
await new Promise(async (resolve) => {
|
||||
try {
|
||||
const ms = await pingPeer(peer);
|
||||
console.log(`# ${peer} - ${ms}`);
|
||||
const result = await pingPeer(peer);
|
||||
console.log(`# ${peer} - ${result.ms}`);
|
||||
results.push({
|
||||
peer,
|
||||
ms
|
||||
ms: result.reachable
|
||||
? result.ms
|
||||
: localeString(
|
||||
'views.Settings.EmbeddedNode.NeutrinoPeers.unreachable'
|
||||
)
|
||||
});
|
||||
resolve(true);
|
||||
} catch (e) {
|
||||
|
||||
+43
-12
@@ -84,10 +84,15 @@ export const NEUTRINO_PING_THRESHOLD_MS = 1000;
|
||||
|
||||
// Fetch-based latency check that runs entirely on the JS thread,
|
||||
// avoiding the native thread race condition in react-native-ping.
|
||||
export interface PingResult {
|
||||
ms: number;
|
||||
reachable: boolean;
|
||||
}
|
||||
|
||||
export async function pingPeer(
|
||||
host: string,
|
||||
timeout: number = NEUTRINO_PING_TIMEOUT_MS
|
||||
): Promise<number> {
|
||||
): Promise<PingResult> {
|
||||
if (host.includes('://')) {
|
||||
throw new Error(
|
||||
localeString(
|
||||
@@ -98,15 +103,15 @@ export async function pingPeer(
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeout);
|
||||
const start = global.performance.now();
|
||||
let reachable = false;
|
||||
try {
|
||||
await fetch(`http://${host}:8333`, {
|
||||
method: 'HEAD',
|
||||
signal: controller.signal
|
||||
});
|
||||
} catch {
|
||||
// We only care about the round-trip time. A connection refused,
|
||||
// reset, or other network error still proves the host is reachable.
|
||||
// Only an abort (timeout) should be treated as unreachable.
|
||||
// Any HTTP response (even non-200) means the host is reachable.
|
||||
reachable = true;
|
||||
} catch (e: any) {
|
||||
if (controller.signal.aborted) {
|
||||
throw new Error(
|
||||
localeString(
|
||||
@@ -114,10 +119,36 @@ export async function pingPeer(
|
||||
)
|
||||
);
|
||||
}
|
||||
// Bitcoin peers don't speak HTTP so fetch always throws — even
|
||||
// for reachable peers (TCP connects, then the non-HTTP response
|
||||
// causes a parse error). We try to distinguish real peers from
|
||||
// nonexistent hosts via two independent signals:
|
||||
//
|
||||
// 1. Platform-specific DNS error messages.
|
||||
const msg = (e?.message || '').toLowerCase();
|
||||
const hasDnsHint =
|
||||
msg.includes('unable to resolve host') || // Android (OkHttp)
|
||||
msg.includes('no address associated') || // Android variant
|
||||
msg.includes('could not find the server') || // iOS
|
||||
msg.includes('cannot find host') || // iOS variant
|
||||
msg.includes('nodename nor servname') || // iOS/macOS getaddrinfo
|
||||
msg.includes('hostname could not be found') || // Windows
|
||||
msg.includes('not known'); // Linux getaddrinfo
|
||||
if (hasDnsHint) {
|
||||
reachable = false;
|
||||
} else {
|
||||
// 2. Timing heuristic for platforms where the error message
|
||||
// is generic (e.g. Android "Network request failed").
|
||||
// A real host needs at least one network round-trip for
|
||||
// DNS + TCP before the error, while a DNS NXDOMAIN from
|
||||
// the local resolver returns almost instantly.
|
||||
const elapsed = global.performance.now() - start;
|
||||
reachable = elapsed >= 100;
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
return Math.round(global.performance.now() - start);
|
||||
return { ms: Math.round(global.performance.now() - start), reachable };
|
||||
}
|
||||
|
||||
// ~4GB
|
||||
@@ -911,11 +942,11 @@ export async function optimizeNeutrinoPeers(
|
||||
const peer = peers[i];
|
||||
await new Promise(async (resolve) => {
|
||||
try {
|
||||
const ms = await pingPeer(peer);
|
||||
console.log(`# ${peer} - ${ms}`);
|
||||
const result = await pingPeer(peer);
|
||||
console.log(`# ${peer} - ${result.ms}`);
|
||||
results.push({
|
||||
peer,
|
||||
ms
|
||||
ms: result.reachable ? result.ms : 'Unreachable'
|
||||
});
|
||||
resolve(true);
|
||||
} catch (e) {
|
||||
@@ -1014,11 +1045,11 @@ export async function optimizeNeutrinoPeers(
|
||||
const peer = peers[i];
|
||||
await new Promise(async (resolve) => {
|
||||
try {
|
||||
const ms = await pingPeer(peer);
|
||||
console.log(`# ${peer} - ${ms}`);
|
||||
const result = await pingPeer(peer);
|
||||
console.log(`# ${peer} - ${result.ms}`);
|
||||
results.push({
|
||||
peer,
|
||||
ms
|
||||
ms: result.reachable ? result.ms : 'Unreachable'
|
||||
});
|
||||
resolve(true);
|
||||
} catch (e) {
|
||||
|
||||
@@ -84,8 +84,11 @@ export default class NodeChoice extends React.Component<
|
||||
let reachableCount = 0;
|
||||
for (const peer of DEFAULT_NEUTRINO_PEERS_MAINNET) {
|
||||
try {
|
||||
const ms = await pingPeer(peer);
|
||||
if (ms < NEUTRINO_PING_THRESHOLD_MS) {
|
||||
const result = await pingPeer(peer);
|
||||
if (
|
||||
result.reachable &&
|
||||
result.ms < NEUTRINO_PING_THRESHOLD_MS
|
||||
) {
|
||||
reachableCount++;
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -45,6 +45,7 @@ interface NeutrinoPeersState {
|
||||
addPeer: string;
|
||||
pingTime: number;
|
||||
pingTimeout: boolean;
|
||||
pingUnreachable: boolean;
|
||||
pingHost: string;
|
||||
loading: boolean;
|
||||
}
|
||||
@@ -70,6 +71,7 @@ export default class NeutrinoPeers extends React.Component<
|
||||
addPeer: '',
|
||||
pingTime: 0,
|
||||
pingTimeout: false,
|
||||
pingUnreachable: false,
|
||||
pingHost: '',
|
||||
loading: false
|
||||
};
|
||||
@@ -89,6 +91,7 @@ export default class NeutrinoPeers extends React.Component<
|
||||
addPeer,
|
||||
pingTime,
|
||||
pingTimeout,
|
||||
pingUnreachable,
|
||||
pingHost,
|
||||
loading
|
||||
} = this.state;
|
||||
@@ -125,6 +128,7 @@ export default class NeutrinoPeers extends React.Component<
|
||||
<View style={{ flex: 1 }}>
|
||||
{loading && <LoadingIndicator />}
|
||||
{!pingTimeout &&
|
||||
!pingUnreachable &&
|
||||
!loading &&
|
||||
pingHost &&
|
||||
pingTime <= 200 && (
|
||||
@@ -134,6 +138,7 @@ export default class NeutrinoPeers extends React.Component<
|
||||
/>
|
||||
)}
|
||||
{!pingTimeout &&
|
||||
!pingUnreachable &&
|
||||
!loading &&
|
||||
pingHost &&
|
||||
pingTime < NEUTRINO_PING_THRESHOLD_MS &&
|
||||
@@ -144,6 +149,7 @@ export default class NeutrinoPeers extends React.Component<
|
||||
/>
|
||||
)}
|
||||
{!pingTimeout &&
|
||||
!pingUnreachable &&
|
||||
!loading &&
|
||||
pingHost &&
|
||||
pingTime >= NEUTRINO_PING_THRESHOLD_MS && (
|
||||
@@ -160,6 +166,14 @@ export default class NeutrinoPeers extends React.Component<
|
||||
dismissable
|
||||
/>
|
||||
)}
|
||||
{!loading && pingHost && !!pingUnreachable && (
|
||||
<ErrorMessage
|
||||
message={`${pingHost}: ${localeString(
|
||||
'views.Settings.EmbeddedNode.NeutrinoPeers.unreachable'
|
||||
)}`}
|
||||
dismissable
|
||||
/>
|
||||
)}
|
||||
<ScrollView style={{ margin: 5 }}>
|
||||
<>
|
||||
<View
|
||||
@@ -266,18 +280,29 @@ export default class NeutrinoPeers extends React.Component<
|
||||
this.setState({
|
||||
pingTime: 0,
|
||||
pingTimeout: false,
|
||||
pingUnreachable:
|
||||
false,
|
||||
pingHost: addPeer,
|
||||
loading: true
|
||||
});
|
||||
|
||||
const ms =
|
||||
const result =
|
||||
await pingPeer(
|
||||
addPeer
|
||||
);
|
||||
this.setState({
|
||||
pingTime: ms,
|
||||
loading: false
|
||||
});
|
||||
if (result.reachable) {
|
||||
this.setState({
|
||||
pingTime:
|
||||
result.ms,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
pingUnreachable:
|
||||
true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
pingTimeout: true,
|
||||
@@ -375,6 +400,8 @@ export default class NeutrinoPeers extends React.Component<
|
||||
pingTime: 0,
|
||||
pingTimeout:
|
||||
false,
|
||||
pingUnreachable:
|
||||
false,
|
||||
pingHost:
|
||||
item,
|
||||
loading:
|
||||
@@ -382,18 +409,31 @@ export default class NeutrinoPeers extends React.Component<
|
||||
}
|
||||
);
|
||||
|
||||
const ms =
|
||||
const result =
|
||||
await pingPeer(
|
||||
item
|
||||
);
|
||||
this.setState(
|
||||
{
|
||||
pingTime:
|
||||
ms,
|
||||
loading:
|
||||
false
|
||||
}
|
||||
);
|
||||
if (
|
||||
result.reachable
|
||||
) {
|
||||
this.setState(
|
||||
{
|
||||
pingTime:
|
||||
result.ms,
|
||||
loading:
|
||||
false
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.setState(
|
||||
{
|
||||
pingUnreachable:
|
||||
true,
|
||||
loading:
|
||||
false
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.setState(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user