use of org.bitcoinj.core.SegwitAddress in project bitcoin-wallet by bitcoin-wallet.
the class RequestWalletBalanceTask method requestWalletBalance.
public void requestWalletBalance(final AssetManager assets, final ECKey key) {
backgroundHandler.post(new Runnable() {
@Override
public void run() {
org.bitcoinj.core.Context.propagate(Constants.CONTEXT);
final Address legacyAddress = LegacyAddress.fromKey(Constants.NETWORK_PARAMETERS, key);
final Script[] outputScripts;
final String addressesStr;
if (key.isCompressed()) {
final Address segwitAddress = SegwitAddress.fromKey(Constants.NETWORK_PARAMETERS, key);
outputScripts = new Script[] { ScriptBuilder.createP2PKHOutputScript(legacyAddress.getHash()), ScriptBuilder.createP2WPKHOutputScript(segwitAddress.getHash()) };
addressesStr = legacyAddress.toString() + "," + segwitAddress.toString();
} else {
outputScripts = new Script[] { ScriptBuilder.createP2PKHOutputScript(legacyAddress.getHash()) };
addressesStr = legacyAddress.toString();
}
final List<ElectrumServer> servers = loadElectrumServers(Assets.open(assets, Constants.Files.ELECTRUM_SERVERS_ASSET));
final List<Callable<Set<UTXO>>> tasks = new ArrayList<>(servers.size());
for (final ElectrumServer server : servers) {
tasks.add(() -> {
log.info("{} - trying to request wallet balance for {}", server.socketAddress, addressesStr);
try (final Socket socket = connect(server)) {
final BufferedSink sink = Okio.buffer(Okio.sink(socket));
sink.timeout().timeout(5000, TimeUnit.MILLISECONDS);
final BufferedSource source = Okio.buffer(Okio.source(socket));
source.timeout().timeout(5000, TimeUnit.MILLISECONDS);
final Moshi moshi = new Moshi.Builder().build();
final JsonAdapter<ElectrumRequest> requestAdapter = moshi.adapter(ElectrumRequest.class);
final JsonAdapter<ListunspentResponse> listunspentResponseAdapter = moshi.adapter(ListunspentResponse.class);
final JsonAdapter<TransactionResponse> transactionResponseAdapter = moshi.adapter(TransactionResponse.class);
final Set<UTXO> utxos = new HashSet<>();
for (final Script outputScript : outputScripts) {
requestAdapter.toJson(sink, new ElectrumRequest(outputScript.getScriptType().ordinal(), "blockchain.scripthash.listunspent", new String[] { Constants.HEX.encode(Sha256Hash.of(outputScript.getProgram()).getReversedBytes()) }));
sink.writeUtf8("\n").flush();
final ListunspentResponse listunspentResponse = listunspentResponseAdapter.fromJson(source);
final int expectedResponseId = outputScript.getScriptType().ordinal();
if (listunspentResponse.id != expectedResponseId) {
log.warn("{} - id mismatch listunspentResponse:{} vs request:{}", server.socketAddress, listunspentResponse.id, expectedResponseId);
return null;
}
if (listunspentResponse.error != null) {
log.info("{} - server error {}: {}", server.socketAddress, listunspentResponse.error.code, listunspentResponse.error.message);
return null;
}
if (listunspentResponse.result == null) {
log.info("{} - missing result", server.socketAddress);
return null;
}
for (final ListunspentResponse.Utxo responseUtxo : listunspentResponse.result) {
final Sha256Hash utxoHash = Sha256Hash.wrap(responseUtxo.tx_hash);
final int utxoIndex = responseUtxo.tx_pos;
// the value cannot be trusted; will be validated below
final Coin utxoValue = Coin.valueOf(responseUtxo.value);
final UTXO utxo = new UTXO(utxoHash, utxoIndex, utxoValue, responseUtxo.height, false, outputScript);
// validation of value and some sanity checks
requestAdapter.toJson(sink, new ElectrumRequest("blockchain.transaction.get", new String[] { Constants.HEX.encode(utxo.getHash().getBytes()) }));
sink.writeUtf8("\n").flush();
final TransactionResponse transactionResponse = transactionResponseAdapter.fromJson(source);
if (transactionResponse.error != null) {
log.info("{} - server error {}: {}", server.socketAddress, transactionResponse.error.code, transactionResponse.error.message);
return null;
}
if (transactionResponse.result == null) {
log.info("{} - missing result", server.socketAddress);
return null;
}
final Transaction tx = new Transaction(Constants.NETWORK_PARAMETERS, Constants.HEX.decode(transactionResponse.result));
if (!tx.getTxId().equals(utxo.getHash()))
log.warn("{} - lied about txid", server.socketAddress);
else if (!tx.getOutput(utxo.getIndex()).getValue().equals(utxo.getValue()))
log.warn("{} - lied about amount", server.socketAddress);
else if (!tx.getOutput(utxo.getIndex()).getScriptPubKey().equals(outputScript))
log.warn("{} - lied about output script", server.socketAddress);
else
// use valid UTXO
utxos.add(utxo);
}
}
log.info("{} - got {} UTXOs {}", server.socketAddress, utxos.size(), utxos);
return utxos;
} catch (final ConnectException | SSLPeerUnverifiedException | JsonDataException x) {
log.warn("{} - {}", server.socketAddress, x.getMessage());
return null;
} catch (final IOException x) {
log.info(server.socketAddress.toString(), x);
return null;
} catch (final RuntimeException x) {
log.error(server.socketAddress.toString(), x);
throw x;
}
});
}
final ExecutorService threadPool = Executors.newFixedThreadPool(servers.size(), new ContextPropagatingThreadFactory("request"));
final List<Future<Set<UTXO>>> futures;
try {
futures = threadPool.invokeAll(tasks, 10, TimeUnit.SECONDS);
} catch (final InterruptedException x) {
throw new RuntimeException(x);
} finally {
threadPool.shutdown();
}
final Multiset<UTXO> countedUtxos = HashMultiset.create();
int numSuccess = 0, numFail = 0, numTimeOuts = 0;
for (Future<Set<UTXO>> future : futures) {
if (!future.isCancelled()) {
try {
final Set<UTXO> utxos = future.get();
if (utxos != null) {
countedUtxos.addAll(utxos);
numSuccess++;
} else {
numFail++;
}
} catch (InterruptedException | ExecutionException x) {
throw new RuntimeException(x);
}
} else {
numTimeOuts++;
}
}
final int trustThreshold = servers.size() / 2;
for (final Iterator<Multiset.Entry<UTXO>> i = countedUtxos.entrySet().iterator(); i.hasNext(); ) {
final Multiset.Entry<UTXO> entry = i.next();
if (entry.getCount() < trustThreshold)
i.remove();
}
final Set<UTXO> utxos = countedUtxos.elementSet();
log.info("{} successes, {} fails, {} time-outs, {} UTXOs {}", numSuccess, numFail, numTimeOuts, utxos.size(), utxos);
if (numSuccess < trustThreshold)
onFail(R.string.sweep_wallet_fragment_request_wallet_balance_failed_connection);
else if (utxos.isEmpty())
onFail(R.string.sweep_wallet_fragment_request_wallet_balance_empty);
else
onResult(utxos);
}
private Socket connect(final ElectrumServer server) throws IOException {
final Socket socket;
if (server.type == ElectrumServer.Type.TLS) {
final SocketFactory sf = sslTrustAllCertificates();
socket = sf.createSocket(server.socketAddress.getHostName(), server.socketAddress.getPort());
final SSLSession sslSession = ((SSLSocket) socket).getSession();
final Certificate certificate = sslSession.getPeerCertificates()[0];
final String certificateFingerprint = sslCertificateFingerprint(certificate);
if (server.certificateFingerprint == null) {
// signed by CA
if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(server.socketAddress.getHostName(), sslSession))
throw new SSLPeerUnverifiedException("Expected " + server.socketAddress.getHostName() + ", got " + sslSession.getPeerPrincipal());
} else {
// self-signed
if (!certificateFingerprint.equals(server.certificateFingerprint))
throw new SSLPeerUnverifiedException("Expected " + server.certificateFingerprint + " for " + server.socketAddress.getHostName() + ", got " + certificateFingerprint);
}
} else if (server.type == ElectrumServer.Type.TCP) {
socket = new Socket();
socket.connect(server.socketAddress, 5000);
} else {
throw new IllegalStateException("Cannot handle: " + server.type);
}
return socket;
}
});
}
Aggregations