Implement BitcoinCoreSeedListDiscovery method

- Update bitcoin core seed list to upstream version
- Use IpV4 and onion addresses from it

Our users are still protected because we connect to IpV4 addresses
through Tor. This improves connectivity because Bitcoin Core creates an
ephemeral onion service on each startup.
This commit is contained in:
Alva Swanson 2024-11-05 00:54:37 +00:00
parent cbe3ead0d5
commit c959f7b52c
No known key found for this signature in database
GPG Key ID: 004760E77F753090
3 changed files with 1934 additions and 0 deletions

View File

@ -0,0 +1,124 @@
package bisq.network;
import org.bitcoinj.net.discovery.PeerDiscovery;
import org.bitcoinj.net.discovery.PeerDiscoveryException;
import java.net.InetSocketAddress;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static bisq.network.Socks5MultiDiscovery.SOCKS5_DISCOVER_ADDR;
import static bisq.network.Socks5MultiDiscovery.SOCKS5_DISCOVER_ONION;
/**
* BitcoinCoreSeedListDiscovery delivers the list of known Bitcoin .onion seeds shipped
* in Bitcoin Core.
*/
public class BitcoinCoreSeedListDiscovery implements PeerDiscovery {
private final int discoveryMode;
public BitcoinCoreSeedListDiscovery(int discoveryMode) {
this.discoveryMode = discoveryMode;
}
@Override
public InetSocketAddress[] getPeers(long services,
long timeoutValue,
TimeUnit timeoutUnit) throws PeerDiscoveryException {
try (BufferedReader bufferedReader = openStreamToBitcoinCoreSeedList()) {
return extractSeedNodes(bufferedReader.lines())
.filter(address -> !address.isEmpty())
.map(this::mapToInetSocketAddress)
.toArray(InetSocketAddress[]::new);
} catch (IOException e) {
throw new PeerDiscoveryException(e);
}
}
@Override
public void shutdown() {
}
private BufferedReader openStreamToBitcoinCoreSeedList() {
ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("bitcoin_core_nodes_main.txt");
if (inputStream == null) {
throw new IllegalStateException("Bitcoin Core seed nodes list missing in resources.");
}
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
return new BufferedReader(inputStreamReader);
}
private Stream<String> extractSeedNodes(Stream<String> lines) {
return lines.filter(this::isSupportedAddress)
.map(this::removeComment);
}
private InetSocketAddress mapToInetSocketAddress(String address) {
// '<hostname>:port'
String[] hostnameAndPort = address.split(":");
if (hostnameAndPort.length != 2) {
throw new IllegalStateException("Invalid address: " + address);
}
try {
int port = Integer.parseInt(hostnameAndPort[1]);
return InetSocketAddress.createUnresolved(hostnameAndPort[0], port);
} catch (NumberFormatException e) {
throw new IllegalStateException("Invalid port number in address: " + address, e);
}
}
private String removeComment(String line) {
// '<ip>:port # AS<no>'
return line.contains("#") ? line.split("#")[0].trim() : line;
}
private boolean isSupportedAddress(String line) {
if (!includeIpV4Addresses() && isIpV4Address(line)) {
return false;
}
if (!includeOnionAddresses() && isOnionAddress(line)) {
return false;
}
return !isIpV6Address(line) && !isI2PAddress(line);
}
private boolean includeIpV4Addresses() {
return (discoveryMode & SOCKS5_DISCOVER_ADDR) != 0;
}
private boolean includeOnionAddresses() {
return (discoveryMode & SOCKS5_DISCOVER_ONION) != 0;
}
private boolean isIpV4Address(String line) {
// '123.456.789.012:1234 # AS1234'
return Pattern.matches("^\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+.+", line);
}
private boolean isIpV6Address(String line) {
return line.startsWith("[") && line.contains(":");
}
private boolean isOnionAddress(String line) {
return line.endsWith(".onion:8333");
}
private boolean isI2PAddress(String line) {
return line.endsWith(".b32.i2p:0");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
package bisq.network;
import org.bitcoinj.net.discovery.PeerDiscoveryException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BitcoinCoreSeedListDiscoveryTest {
@Test
void allTest() throws PeerDiscoveryException {
int mode = Socks5MultiDiscovery.SOCKS5_DISCOVER_ALL;
BitcoinCoreSeedListDiscovery bitcoinCoreSeedListDiscovery = new BitcoinCoreSeedListDiscovery(mode);
InetSocketAddress[] peers = bitcoinCoreSeedListDiscovery
.getPeers(0L, 0L, TimeUnit.MILLISECONDS);
assertTrue(peers.length > 0);
}
@Test
void onlyIpV4Test() throws PeerDiscoveryException {
int mode = Socks5MultiDiscovery.SOCKS5_DISCOVER_ADDR;
BitcoinCoreSeedListDiscovery bitcoinCoreSeedListDiscovery = new BitcoinCoreSeedListDiscovery(mode);
InetSocketAddress[] peers = bitcoinCoreSeedListDiscovery
.getPeers(0L, 0L, TimeUnit.MILLISECONDS);
assertTrue(peers.length > 0);
assertTrue(
Arrays.stream(peers)
.allMatch(this::isIpV4Address)
);
}
@Test
void onlyOnionTest() throws PeerDiscoveryException {
int mode = Socks5MultiDiscovery.SOCKS5_DISCOVER_ONION;
BitcoinCoreSeedListDiscovery bitcoinCoreSeedListDiscovery = new BitcoinCoreSeedListDiscovery(mode);
InetSocketAddress[] peers = bitcoinCoreSeedListDiscovery
.getPeers(0L, 0L, TimeUnit.MILLISECONDS);
assertTrue(peers.length > 0);
assertTrue(
Arrays.stream(peers)
.allMatch(socketAddress -> socketAddress.getHostName().endsWith(".onion"))
);
}
private boolean isIpV4Address(InetSocketAddress socketAddress) {
try {
return InetAddress.getByName(socketAddress.getHostName()) instanceof Inet4Address;
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
}