Craig Raw
4 years ago
3 changed files with 171 additions and 9 deletions
@ -0,0 +1,137 @@ |
|||
package com.sparrowwallet.sparrow.net; |
|||
|
|||
import com.sparrowwallet.drongo.Network; |
|||
import com.sparrowwallet.drongo.Utils; |
|||
import com.sparrowwallet.drongo.protocol.Sha256Hash; |
|||
import com.sparrowwallet.drongo.protocol.Transaction; |
|||
import com.sparrowwallet.sparrow.AppServices; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.io.*; |
|||
import java.net.HttpURLConnection; |
|||
import java.net.MalformedURLException; |
|||
import java.net.Proxy; |
|||
import java.net.URL; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.SecureRandom; |
|||
|
|||
public enum BroadcastSource { |
|||
BLOCKSTREAM_INFO("blockstream.info", "https://blockstream.info", "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion") { |
|||
@Override |
|||
public Sha256Hash broadcastTransaction(Transaction transaction) throws BroadcastException { |
|||
String data = Utils.bytesToHex(transaction.bitcoinSerialize()); |
|||
return postTransactionData(data); |
|||
} |
|||
|
|||
protected URL getURL(Proxy proxy) throws MalformedURLException { |
|||
if(Network.get() == Network.MAINNET) { |
|||
return new URL(getBaseUrl(proxy) + "/api/tx"); |
|||
} else if(Network.get() == Network.TESTNET) { |
|||
return new URL(getBaseUrl(proxy) + "/testnet/api/tx"); |
|||
} else { |
|||
throw new IllegalStateException("Cannot broadcast transaction to " + getName() + " on network " + Network.get()); |
|||
} |
|||
} |
|||
}, |
|||
MEMPOOL_SPACE("mempool.space", "https://mempool.space", "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion") { |
|||
public Sha256Hash broadcastTransaction(Transaction transaction) throws BroadcastException { |
|||
String data = Utils.bytesToHex(transaction.bitcoinSerialize()); |
|||
return postTransactionData(data); |
|||
} |
|||
|
|||
protected URL getURL(Proxy proxy) throws MalformedURLException { |
|||
if(Network.get() == Network.MAINNET) { |
|||
return new URL(getBaseUrl(proxy) + "/api/tx"); |
|||
} else if(Network.get() == Network.TESTNET) { |
|||
return new URL(getBaseUrl(proxy) + "/testnet/api/tx"); |
|||
} else { |
|||
throw new IllegalStateException("Cannot broadcast transaction to " + getName() + " on network " + Network.get()); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
private final String name; |
|||
private final String tlsUrl; |
|||
private final String onionUrl; |
|||
|
|||
private static final Logger log = LoggerFactory.getLogger(BroadcastSource.class); |
|||
private static final SecureRandom secureRandom = new SecureRandom(); |
|||
|
|||
BroadcastSource(String name, String tlsUrl, String onionUrl) { |
|||
this.name = name; |
|||
this.tlsUrl = tlsUrl; |
|||
this.onionUrl = onionUrl; |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public String getTlsUrl() { |
|||
return tlsUrl; |
|||
} |
|||
|
|||
public String getOnionUrl() { |
|||
return onionUrl; |
|||
} |
|||
|
|||
public String getBaseUrl(Proxy proxy) { |
|||
return (proxy == null ? getTlsUrl() : getOnionUrl()); |
|||
} |
|||
|
|||
public abstract Sha256Hash broadcastTransaction(Transaction transaction) throws BroadcastException; |
|||
|
|||
protected abstract URL getURL(Proxy proxy) throws MalformedURLException; |
|||
|
|||
public Sha256Hash postTransactionData(String data) throws BroadcastException { |
|||
//If a Tor proxy is configured, ensure we use a new circuit by configuring a random proxy password
|
|||
Proxy proxy = AppServices.getProxy(Integer.toString(secureRandom.nextInt())); |
|||
|
|||
try { |
|||
URL url = getURL(proxy); |
|||
|
|||
HttpURLConnection connection = proxy == null ? (HttpURLConnection)url.openConnection() : (HttpURLConnection)url.openConnection(proxy); |
|||
connection.setRequestMethod("POST"); |
|||
connection.setRequestProperty("Content-Type", "text/plain"); |
|||
connection.setDoOutput(true); |
|||
|
|||
try(OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream())) { |
|||
writer.write(data); |
|||
writer.flush(); |
|||
} |
|||
|
|||
StringBuilder response = new StringBuilder(); |
|||
try(BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { |
|||
String responseLine; |
|||
while((responseLine = br.readLine()) != null) { |
|||
response.append(responseLine.trim()); |
|||
} |
|||
} |
|||
|
|||
int statusCode = connection.getResponseCode(); |
|||
if(statusCode < 200 || statusCode >= 300) { |
|||
throw new BroadcastException("Could not broadcast transaction, server returned " + statusCode + ": " + response); |
|||
} |
|||
|
|||
try { |
|||
return Sha256Hash.wrap(response.toString().trim()); |
|||
} catch(Exception e) { |
|||
throw new BroadcastException("Could not retrieve txid from broadcast, server returned " + statusCode + ": " + response); |
|||
} |
|||
} catch(IOException e) { |
|||
log.error("Could not post transaction via " + getName(), e); |
|||
throw new BroadcastException("Could not broadcast transaction via " + getName(), e); |
|||
} |
|||
} |
|||
|
|||
public static final class BroadcastException extends Exception { |
|||
public BroadcastException(String message) { |
|||
super(message); |
|||
} |
|||
|
|||
public BroadcastException(String message, Throwable cause) { |
|||
super(message, cause); |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue